OSDN Git Service

Don't attempt to resolve dependencies for unidentified packages.
[mingw/mingw-get.git] / src / pkgdata.cpp
index 1ee54a1..841e07b 100644 (file)
@@ -4,7 +4,7 @@
  * $Id$
  *
  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
- * Copyright (C) 2012, MinGW Project
+ * Copyright (C) 2012, 2013, MinGW.org Project
  *
  *
  * Implementation of the classes and methods required to support the
@@ -25,6 +25,8 @@
  * arising from the use of this software.
  *
  */
+#define _WIN32_IE 0x0300
+
 #include <stdlib.h>
 #include <string.h>
 
 #include "pkgbase.h"
 #include "pkgdata.h"
 #include "pkgkeys.h"
+#include "pkginfo.h"
 #include "pkglist.h"
+#include "pkgproc.h"
 #include "pkgtask.h"
 
+#include <windowsx.h>
+
 using WTK::StringResource;
 using WTK::WindowClassMaker;
 using WTK::ChildWindowMaker;
@@ -288,6 +294,19 @@ bool pkgTroffLayoutEngine::WriteLn( HDC canvas, RECT *bounds )
        */
       TextOutW( canvas, bounds->left, bounds->top - offset, linebuf, filled );
     }
+    else if( (fold == 0) && (new_width > max_width) )
+      /*
+       * The output line which we've just processed lies outside
+       * the viewport.  We note that it's initial (non-breakable)
+       * "word" would require more display width than the viewport
+       * can accommodate, if it were to be moved into the visible
+       * region; thus, this "word" will continue to be presented
+       * to the formatting engine, as the next input "word" to be
+       * processed.  This would result in an infinite loop, so we
+       * MUST discard this "word" from the input queue.
+       */
+      curr = (pkgTroffLayoutEngine *)(curr->next);
+
     /* Finally, adjust the top boundary of the viewport, to indicate
      * where the NEXT output line, if any, is to be positioned, and
      * return TRUE, to indicate that an output line was processed.
@@ -332,6 +351,7 @@ class DataSheetMaker: public ChildWindowMaker
     static int DisplayLicenceURL( const char * );
     static int DisplayPackageURL( const char * );
     inline void DisplayDescription( pkgXmlNode * );
+    inline void DisplayFilesManifest( pkgXmlNode * );
     void ComposeDescription( pkgXmlNode *, pkgXmlNode * );
     int FormatRecord( int, const char *, const char * );
     inline void FormatText( const char * );
@@ -454,37 +474,16 @@ void DataSheetMaker::DisplayGeneralData( pkgXmlNode *ref )
       );
 
   /* Using a temporary action item, collect information on the
-   * available and installed (if any) versions of the selected
-   * package.
+   * latest available version, and the installed version if any,
+   * of the selected package; print the applicable information,
+   * noting that "none" may be appropriate in the case of the
+   * installed version.
    */
   pkgActionItem avail;
-  ref = ref->FindFirstAssociate( release_key );
-  while( ref != NULL )
-  {
-    /* ...to scan all associated release keys, and select the
-     * most recent recorded in the database as available...
-     */
-    avail.SelectIfMostRecentFit( ref );
-    if( ref->GetInstallationRecord( ref->GetPropVal( tarname_key, NULL )) != NULL )
-      /*
-       * ...also noting if any is marked as installed...
-       */
-      avail.SelectPackage( ref, to_remove );
-
-    /* ...until all release keys have been inspected...
-     */
-    ref = ref->FindNextAssociate( release_key );
-  }
-  /* ...and performing a final check on installation status.
-   */
-  avail.ConfirmInstallationStatus();
-
-  /* Now print the applicable version information, noting that
-   * "none" may be appropriate for the installed version.
-   */
   FormatRecord( 0, "Installed Version",
-      ((ref = avail.Selection( to_remove )) != NULL)
-       ? ref->GetPropVal( tarname_key, value_unknown ) : value_none
+      ((ref = pkgGetStatus( ref, &avail )) != NULL)
+       ? ref->GetPropVal( tarname_key, value_unknown )
+       : value_none
     );
   FormatRecord( 1, "Repository Version",
       (ref = avail.Selection())->GetPropVal( tarname_key, value_unknown )
@@ -585,6 +584,81 @@ void DataSheetMaker::ComposeDescription( pkgXmlNode *ref, pkgXmlNode *root )
   }
 }
 
+void DataSheetMaker::DisplayFilesManifest( pkgXmlNode *ref )
+{
+  /* Helper method to compile the list of files installed by a package,
+   * for display on the "Installed Files" tab of the data sheet panel.
+   */
+  pkgActionItem avail;
+  if( (ref = pkgGetStatus( ref, &avail )) == NULL )
+  {
+    /* This represents a package which is available, but has not been
+     * installed; we simply decline to compile the files list.
+     */
+    FormatRecord( 0, "Package",
+       avail.Selection()->GetPropVal( tarname_key, value_unknown )
+      );
+    bounding_box.top += PARAGRAPH_MARGIN;
+    FormatText(
+               "This package has not been installed; "
+       "the list of installed files is not available for packages "
+       "which have not been installed."
+      );
+  }
+  else
+  { /* This represents a package which has been installed; begin
+     * compilation of the list of installed files.
+     */
+    const char *tarname;
+    FormatRecord( 0, "Package",
+       tarname = ref->GetPropVal( tarname_key, value_unknown )
+      );
+    if( match_if_explicit( ref->ArchiveName(), value_none ) )
+    {
+      /* This is a meta-package; there are no files to list.
+       */
+      bounding_box.top += PARAGRAPH_MARGIN;
+      FormatText(
+         "This meta-package facilitates the installation of a collection "
+         "of other logically related packages; it provides no files, and "
+         "may be safely removed."
+       );
+    }
+    else
+    { /* This is a real package; retrieve the manifest of installed files,
+       * which was created during the installation process.
+       */
+      pkgXmlNode *index;
+      pkgManifest inventory( package_key, tarname );
+      if( (index = inventory.GetRoot()->FindFirstAssociate( manifest_key )) != NULL )
+      {
+       /* We've located a files list within the manifest; process it...
+        */
+       FormatRecord( 0, "This package provides the following files", "" );
+       bounding_box.left += LEFT_MARGIN; bounding_box.top += PARAGRAPH_MARGIN;
+       do { if( (ref = index->FindFirstAssociate( filename_key )) != NULL )
+              /*
+               * We found at least one file name within the list; emit it
+               * to the display, followed by any additional names present.
+               */
+              do { FormatText( ref->GetPropVal( pathname_key, value_unknown ) );
+                 } while( (ref = ref->FindNextAssociate( filename_key )) != NULL );
+
+            /* There should be no more than one, but check for, and process
+             * any additional files lists which may be present.
+             */
+          } while( (index = index->FindNextAssociate( manifest_key )) != NULL );
+      }
+      else
+      { /* The manifest appears to be lacking any files list; diagnose.
+        */
+       bounding_box.top += PARAGRAPH_MARGIN;
+       FormatText( "This package appears to provide no files." );
+      }
+    }
+  }
+}
+
 static pkgXmlNode *pkgListSelection( HWND package_ref, LVITEM *lookup )
 {
   /* Helper function, to retrieve the active selection from the
@@ -684,6 +758,14 @@ long DataSheetMaker::OnPaint()
        DisplayDescription( pkgListSelection( PackageRef, &lookup ) );
        break;
 
+      case PKG_DATASHEET_INSTALLED_FILES:
+       /* Available only for packages which have been installed,
+        * this comprises the files list content from the manifest
+        * which was created during the installation process.
+        */
+       DisplayFilesManifest( pkgListSelection( PackageRef, &lookup ) );
+       break;
+
       default:
        /* Handle requests for data sheets for which we have yet
         * to provide a compiling routine.
@@ -865,7 +947,7 @@ void AppWindowMaker::InitPackageTabControl()
    */
   TCITEM tab;
   tab.mask = TCIF_TEXT;
-  char *TabLegend[] =
+  const char *TabLegend[] =
   { "General", "Description", "Dependencies", "Installed Files", "Versions",
 
     /* ...with a NULL sentinel marking the preceding label as
@@ -877,7 +959,7 @@ void AppWindowMaker::InitPackageTabControl()
   {
     /* This loop assumes responsibility for actual tab creation...
      */
-    tab.pszText = TabLegend[i];
+    tab.pszText = (char *)(TabLegend[i]);
     if( TabCtrl_InsertItem( PackageTabControl, i, &tab ) == -1 )
     {
       /* ...bailing out, and deleting the container window,
@@ -904,6 +986,302 @@ void AppWindowMaker::InitPackageTabControl()
   }
 }
 
+void AppWindowMaker::UpdatePackageMenuBindings()
+# define PKGSTATE_FLAG( ID )  (1 << PKGSTATE( ID ))
+{
+  /* Helper method to enable or disable the set of options
+   * which may be chosen from the package menu; (this varies
+   * according to the installation status of the package, if
+   * any, which has been selected in the package list view).
+   */
+  HMENU menu;
+  if( (menu = GetMenu( AppWindow )) != NULL )
+  {
+    /* We got a valid handle for the menubar; identify the
+     * list view selection, which controls the available set
+     * of menu options...
+     */
+    LVITEM lookup;
+    lookup.iItem = (PackageListView != NULL)
+      ? ListView_GetNextItem( PackageListView, (WPARAM)(-1), LVIS_SELECTED )
+      : -1;
+
+    /* ...and identify its state of the associated package,
+     * as indicated by the assigned icon.
+     */
+    lookup.iSubItem = 0;
+    lookup.mask = LVIF_IMAGE;
+    ListView_GetItem( PackageListView, &lookup );
+
+    /* Convert the indicated state to a selector bit-flag.
+     */
+    int state = ((lookup.iItem >= 0) && (lookup.iImage <= PKGSTATE( PURGE )))
+      ? 1 << lookup.iImage : 0;
+
+    /* Walk over all state-conditional menu items...
+     */
+    for( int item = IDM_PACKAGE_UNMARK; item <= IDM_PACKAGE_REMOVE; item++ )
+    {
+      /* ...evaluating an independent state flag for each,
+       * setting it as non-zero for menu items which may be
+       * made "selectable", or zero otherwise...
+       */
+      int state_flag = state;
+      switch( item )
+      { /* ...testing against item specific flag groups, to
+        * determine which menu items should be enabled for
+        * the currently selected list view item...
+        */
+       case IDM_PACKAGE_INSTALL:
+         /* "Mark for Installation" is available for packages
+          * which exist in the repository, (long-term or new),
+          * but which are not yet identified as "installed".
+          */
+         state_flag &= PKGSTATE_FLAG( AVAILABLE )
+           | PKGSTATE_FLAG( AVAILABLE_NEW );
+         break;
+
+       case IDM_PACKAGE_REMOVE:
+       //case IDM_PACKAGE_REINSTALL: // FIXME: for now, we don't consider this!
+         /* "Mark for Removal" and "Mark for Reinstallation"
+          * are viable selections only for packages identified
+          * as "installed", (current or upgradeable).
+          */
+         state_flag &= PKGSTATE_FLAG( INSTALLED_CURRENT )
+           | PKGSTATE_FLAG( INSTALLED_OLD );
+         break;
+
+       case IDM_PACKAGE_UPGRADE:
+         /* "Mark for Upgrade" is viable only for packages
+          * identified as "installed", and then only when an
+          * upgrade has been published.
+          */
+         state_flag &= PKGSTATE_FLAG( INSTALLED_OLD );
+         break;
+
+       case IDM_PACKAGE_UNMARK:
+         /* The "Unmark" facility is available only for packages
+          * which have been marked, (perhaps inadvertently), for
+          * any of the preceding actions.
+          */
+         state_flag &= PKGSTATE_FLAG( AVAILABLE_INSTALL )
+           | PKGSTATE_FLAG( UPGRADE ) | PKGSTATE_FLAG( DOWNGRADE )
+           | PKGSTATE_FLAG( REMOVE ) | PKGSTATE_FLAG( PURGE )
+           | PKGSTATE_FLAG( REINSTALL );
+         break;
+
+       default:
+         /* When none of the preceding is applicable, the menu
+          * item should not be selectable.
+          */
+         state_flag = 0;
+      }
+      /* ...and set the menu item enabled state accordingly.
+       */
+      EnableMenuItem( menu, item, (state_flag == 0) ? MF_GRAYED : MF_ENABLED );
+    }
+  }
+  /* Although it is listed under the "Installation" drop-down menu,
+   * this is also a convenient point to consider activation of the
+   * "Apply Changes" capability.
+   */
+  EnableMenuItem( menu, IDM_REPO_APPLY,
+      (pkgData->Schedule()->EnumeratePendingActions() > 0) ? MF_ENABLED
+       : MF_GRAYED
+    );
+}
+
+inline void AppWindowMaker::MarkSchedule( pkgActionItem *pending_actions )
+{
+  /* Helper routine to update the status icons within the package list view,
+   * reflecting any scheduled action in respect of the package associated with
+   * each, and updating the menu bindings to match.
+   */
+  if( pending_actions != NULL )
+  {
+    pkgListViewMaker pkglist( PackageListView );
+    pkglist.MarkScheduledActions( pending_actions );
+  }
+  UpdatePackageMenuBindings();
+}
+
+void AppWindowMaker::Schedule
+( unsigned long action, const char *bounds, const char *pkgname )
+{
+  /* GUI menu driven interface to the pkgActionItem task scheduler;
+   * it constructs a pseudo-argument string, emulating the effect of
+   * parsing a CLI argument, then passes this to the CLI scheduler
+   * API class method.
+   */
+  if( pkgname == NULL )
+  {
+    /* Initial entry on menu item selection; package name has not
+     * yet been identified, so find the selected list view item...
+     */
+    LVITEM lookup;
+    lookup.iItem = (PackageListView != NULL)
+      ? ListView_GetNextItem( PackageListView, (WPARAM)(-1), LVIS_SELECTED )
+      : -1;
+
+    /* ...and look up the package name identified within it.
+     */
+    const char *pkg, *fmt = "%s-%s";
+    pkgXmlNode *ref = pkgListSelection( PackageListView, &lookup );
+    if( (pkg = ref->GetContainerAttribute( name_key, NULL )) != NULL )
+    {
+      /* We now have a valid package name; check for a
+       * component package association.
+       */
+      const char *cpt;
+      if( (cpt = ref->GetPropVal( class_key, NULL )) == NULL )
+      {
+       /* Current list view selection represents a
+        * non-component package; encode its name only
+        * as a string argument, using only the final
+        * string field of the format specification.
+        */
+       char pkgspec[ 1 + snprintf( NULL, 0, fmt + 3, pkg ) ];
+       snprintf( pkgspec, sizeof( pkgspec ), fmt + 3, pkg );
+
+       /* Recurse, to capture any supplied version bounds
+        * specification, and ultimately schedule the action.
+        */
+       Schedule( action, bounds, pkgspec );
+      }
+      else
+      { /* Current list view selection represents a
+        * package name qualified by a component name;
+        * use the full format specification to encode
+        * the fully qualified package name.
+        */
+       char pkgspec[ 1 + snprintf( NULL, 0, fmt, pkg, cpt ) ];
+       snprintf( pkgspec, sizeof( pkgspec ), fmt, pkg, cpt );
+
+       /* Again, recurse to capture any supplied version
+        * bounds specification, before ultimately scheduling
+        * the selected action.
+        */
+       Schedule( action, bounds, pkgspec );
+      }
+    }
+  }
+  else if( bounds != NULL )
+  {
+    /* Recursive entry, after package name identification,
+     * but with supplied version bounds specification yet
+     * to be resolved; append the bounds specification to
+     * the package name, as it would be in a CLI argument...
+     */
+    const char *fmt = "%s=%s";
+    char pkgspec[ 1 + snprintf( NULL, 0, fmt, pkgname, bounds ) ];
+    snprintf( pkgspec, sizeof( pkgspec ), fmt, pkgname, bounds );
+    /*
+     * ...then recurse a final time, to schedule the action.
+     */
+    Schedule( action, NULL, pkgspec );
+  }
+  else
+  { /* Final recursive entry, with pkgname argument in the
+     * same form as a CLI package name/bounds specification
+     * argument; hand it off to the CLI scheduler, capturing
+     * the resultant schedule of actions, and update the list
+     * view state icons to reflect the pending actions.
+     */
+    MarkSchedule( pkgData->Schedule( action, pkgname ) );
+  }
+}
+
+inline unsigned long pkgActionItem::CancelScheduledAction( void )
+{
+  /* Helper method to mark a scheduled action as "cancelled".
+   */
+  return (this != NULL) ? (flags &= ~ACTION_MASK) : 0UL;
+}
+
+void AppWindowMaker::UnmarkSelectedPackage( void )
+{
+  /* Method to clear any request for an action in respect of
+   * the currently selected package entry in the list view; we
+   * implement this as a cancellation of any pending scheduled
+   * action, in respect of the selected package.
+   *
+   * First, obtain a reference for the list view selection...
+   */
+  LVITEM lookup;
+  lookup.iItem = (PackageListView != NULL)
+    ? ListView_GetNextItem( PackageListView, (WPARAM)(-1), LVIS_SELECTED )
+    : -1;
+
+  /* ...and when it represents a valid selection...
+   */
+  if( lookup.iItem >= 0 )
+  {
+    /* ...retrieve its associated XML database package reference...
+     */
+    pkgXmlNode *pkg = pkgListSelection( PackageListView, &lookup );
+    /*
+     * ...search the action schedule, for an action associated with
+     * this package, if any, and cancel it.
+     */
+    pkgData->Schedule()->GetReference( pkg )->CancelScheduledAction();
+
+    /* The scheduling state for packages shown in the list view
+     * may have changed, so refresh the icon associations and the
+     * package menu bindings accordingly.
+     */
+    MarkSchedule( pkgData->Schedule() );
+  }
+}
+
+void AppWindowMaker::SelectPackageAction( unsigned mode )
+{
+  /* Helper method to present the package menu as a floating pop-up.
+   */
+  HMENU popup;
+  LVHITTESTINFO whence;
+
+  /* Before presenting the menu, ensure that its selection bindings
+   * are current, as determined for the selected package; note that
+   * we do this unconditionally, to ensure that the bindings remain
+   * current, when the user accesses the menu from the menu bar.
+   */
+  UpdatePackageMenuBindings();
+  
+  /* Locate the cursor position, mapping it into the co-ordinate
+   * system of the list view client window.
+   */
+  whence.pt.y = GetMessagePos();
+  whence.pt.x = GET_X_LPARAM( whence.pt.y );
+  whence.pt.y = GET_Y_LPARAM( whence.pt.y );
+  ScreenToClient( PackageListView, &whence.pt );
+
+  /* Perform a hit-test, to confirm that either the left mouse
+   * button was clicked on the package status icon, or the right
+   * button was clicked anywhere on the package list entry; only
+   * if one of these is detected, do we then proceed to retrieve
+   * a handle for the pop-up menu itself...
+   */
+  if(  (ListView_SubItemHitTest( PackageListView, &whence ) >= 0)
+  &&  ((whence.flags & mode) != 0) && ((popup = GetMenu( AppWindow )) != NULL)
+  &&  ((popup = GetSubMenu( popup, 1 )) != NULL)   )
+  {
+    /* ...and provided it is valid, we remap the cursor position 
+     * back into the screen co-ordinate system, and present the
+     * menu at the resultant position.
+     */
+    ClientToScreen( PackageListView, &whence.pt );
+    TrackPopupMenu( popup, 0, whence.pt.x, whence.pt.y, 0, AppWindow, NULL );
+  }
+}
+
+void AppWindowMaker::UpdateDataSheet( void )
+{
+  /* Helper method, called when we wish to update the data sheet
+   * panel, to match the current list view and tab selection.
+   */
+  DataSheet->DisplayData( PackageTabControl, PackageListView );
+}
+
 long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
 {
   /* Handler for notifiable events to be processed in the context
@@ -919,14 +1297,97 @@ long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
      * package list view and data sheet tab control panes...
      */
     case ID_PACKAGE_LISTVIEW:
+      if( ((NMHDR *)(data))->code == NM_RCLICK )
+      {
+       /* A right mouse button click within the package list view
+        * selects the package under the cursor, refreshing the tab
+        * pane to display its associated data sheet, and offers a
+        * pop-up menu of actions which may be performed on it.
+        */
+       UpdateDataSheet();
+       SelectPackageAction( LVHT_ONITEMICON | LVHT_ONITEMLABEL );
+       break;
+      }
+    /* Any other notification from the list view control is handled
+     * in common with similar notifications from the tab control, so
+     * we do not break here, but simply fall through.
+     */
     case ID_PACKAGE_TABCONTROL:
       if( ((NMHDR *)(data))->code == NM_CLICK )
-       /*
-        * ...each of which may require the data sheet content
+      {
+       /* ...each of which may require the data sheet content
         * to be updated, (to reflect a changed selection).
         */
-       DataSheet->DisplayData( PackageTabControl, PackageListView );
+       UpdateDataSheet();
+
+       /* Additionally, for a left click on the package status
+        * icon within the list view, we present a pop-up menu
+        * offering a selection of available actions.
+        */
+       if( client_id == ID_PACKAGE_LISTVIEW )
+         SelectPackageAction( LVHT_ONITEMICON );
+      }
       break;
+
+    /* We also need to consider notifications from the tree view...
+     */
+    case ID_PACKAGE_TREEVIEW:
+      if( ((NMHDR *)(data))->code == TVN_SELCHANGED )
+      {
+       /* ...from which we are interested only in notifications
+        * that the user has changed the package group selection.
+        *
+        * First, we ensure that any children of the selected
+        * package group are made visible.
+        */
+       TreeView_Expand( PackageTreeView,
+           ((NMTREEVIEW *)(data))->itemNew.hItem, TVE_EXPAND
+         );
+
+       /* We then clear out the previous content of the list view
+        * pane, and reconstruct it with new content, as determined
+        * by the new package group selection...
+        */
+       ClearPackageList();
+       UpdatePackageList();
+
+       /* ...and reapply any scheduled action markers, which may
+        * be applicable.
+        */
+       MarkSchedule( pkgData->Schedule() );
+
+       /* Finally, provided the previous selection is not an
+        * ancestor of the current, we may collapse any visible
+        * subtree descending from the previous.
+        *
+        * FIXME: We may wish to avoid collapsing any subtree
+        * which was designated as "expanded", in the original
+        * group hierarchy specification.  We may also wish to
+        * provide a user option, to disable this feature.
+        */
+       bool may_fold = true;
+       HTREEITEM prev = ((NMTREEVIEW *)(data))->itemNew.hItem;
+       while( may_fold && (prev != NULL) )
+       { if( prev == ((NMTREEVIEW *)(data))->itemOld.hItem )
+           /*
+            * Previous selection IS an ancestor of current;
+            * we must not collapse it.
+            */
+           may_fold = false;
+
+         else
+           /* Continue tracing ancestry, back to the root.
+            */
+           prev = TreeView_GetParent( PackageTreeView, prev );
+       }
+       if( may_fold )
+         /*
+          * Previous selection may be collapsed; do so.
+          */
+         TreeView_Expand( PackageTreeView,
+             ((NMTREEVIEW *)(data))->itemOld.hItem, TVE_COLLAPSE
+           );
+      }
   }
   /* Otherwise, this return causes any other notifiable events
    * to be simply ignored, (as they were by the original stub).
@@ -934,4 +1395,95 @@ long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
   return EXIT_SUCCESS;
 }
 
+unsigned long pkgActionItem::EnumeratePendingActions( int classified )
+{
+  /* Helper method to count the pending actions in a
+   * scheduled action list.
+   */
+  unsigned long count = 0;
+  if( this != NULL )
+  {
+    /* Regardless of the position of the 'this' pointer,
+     * within the list of scheduled actions...
+     */
+    pkgActionItem *item = this;
+    while( item->prev != NULL )
+      /*
+       * ...we want to get a reference to the first
+       * item in the list.
+       */
+      item = item->prev;
+
+    /* Now, working through the list...
+     */
+    while( item != NULL )
+    {
+      /* ...note items with any scheduled action...
+       */
+      int action;
+      if( (action = item->flags & ACTION_MASK) != 0 )
+      {
+       /* ...and, when one is found, (noting that ACTION_UPGRADE may
+        * also be considered as a special case of ACTION_INSTALL)...
+        */
+       if(  (action == classified)
+       ||  ((action == ACTION_UPGRADE) && (classified == ACTION_INSTALL))  )
+       {
+         /* ...and it matches the classification in which
+          * we are interested, then we retrieve the tarname
+          * for the related package...
+          */
+         pkgXmlNode *selected = (classified & ACTION_REMOVE)
+           ? item->Selection( to_remove )
+           : item->Selection();
+         const char *notification = (selected != NULL)
+           ? selected->GetPropVal( tarname_key, NULL )
+           : NULL;
+         if( notification != NULL )
+         {
+           /* ...and, provided it is valid, we append it to
+            * the DMH driven dialogue in which the enumeration
+            * is being reported...
+            */
+           dmh_printf( "%s\n", notification );
+           /*
+            * ...and include it in the accumulated count...
+            */
+           ++count;
+         }
+       }
+       else if( (classified == 0)
+         /*
+          * ...otherwise, when we aren't interested in any particular
+          * class of action regardless of classification...
+          */
+       || ((classified == ACTION_UNSUCCESSFUL) && ((flags & classified) != 0)) )
+         /*
+          * ...or when we are checking for unsuccessful actions, we
+          * count all those which are found, either unclassified, or
+          * marked as unsuccessful, respectively.
+          */
+         ++count;
+      }
+      /* ...then move on, to consider the next entry, if any.
+       */
+      item = item->next;
+    }
+  }
+  /* Ultimately, return the count of pending actions,
+   * as noted while processing the above loop.
+   */
+  return count;
+}
+
+long AppWindowMaker::OnClose()
+{
+  /* Intercept application termination requests; check for
+   * outstanding pending actions, and offer a cancellation
+   * option for the termination request, so that the user
+   * has an opportunity to complete such actions.
+   */
+  return ConfirmActionRequest( "quit" ) ? -1 : 0;
+}
+
 /* $RCSfile$: end of file */