OSDN Git Service

Delete an unnecessary duplicate function call.
[mingw/mingw-get.git] / src / guiexec.cpp
1 /*
2  * guiexec.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keith@users.osdn.me>
7  * Copyright (C) 2012, 2013, 2020, MinGW.org Project
8  *
9  *
10  * Implementation of XML data loading services for the mingw-get GUI.
11  *
12  *
13  * This is free software.  Permission is granted to copy, modify and
14  * redistribute this software, under the provisions of the GNU General
15  * Public License, Version 3, (or, at your option, any later version),
16  * as published by the Free Software Foundation; see the file COPYING
17  * for licensing details.
18  *
19  * Note, in particular, that this software is provided "as is", in the
20  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
23  * MinGW Project, accept liability for any damages, however caused,
24  * arising from the use of this software.
25  *
26  */
27 #include "guimain.h"
28 #include "pkgbase.h"
29 #include "pkginet.h"
30 #include "pkgkeys.h"
31 #include "pkglist.h"
32 #include "pkgstat.h"
33 #include "pkgtask.h"
34
35 #include <unistd.h>
36 #include <wtkexcept.h>
37 #include <wtkalign.h>
38
39 /* The DMH sub-system provides the following function, but it does not
40  * declare the prototype; (this is to obviate any automatic requirement
41  * for every DMH client to include windows.h).  We have already included
42  * windows.h indirectly, via guimain.h, so it is convenient for us to
43  * declare the requisite prototype here.
44  */
45 EXTERN_C void dmh_setpty( HWND );
46
47 #define PROGRESS_METER_CLASS    ProgressMeterMaker
48
49 class PROGRESS_METER_CLASS: public pkgProgressMeter
50 {
51   /* A locally defined class, supporting progress metering
52    * for package catalogue update and load operations.
53    */
54   public:
55     PROGRESS_METER_CLASS( HWND, AppWindowMaker * );
56     ~PROGRESS_METER_CLASS(){ referrer->DetachProgressMeter( this ); }
57
58     virtual int Annotate( const char *, ... );
59     virtual void SetRange( int, int );
60     virtual void SetValue( int );
61
62   private:
63     AppWindowMaker *referrer;
64     HWND annotation, count, lim, frac, progress_bar;
65     void PutVal( HWND, const char *, ... );
66     int total;
67 };
68
69 inline
70 pkgProgressMeter *AppWindowMaker::AttachProgressMeter( pkgProgressMeter *meter )
71 {
72   /* A local helper method for attaching a progress meter to the
73    * controlling class instance for the main application window.
74    */
75   if( AttachedProgressMeter == NULL )
76     AttachedProgressMeter = meter;
77   return AttachedProgressMeter;
78 }
79
80 inline void AppWindowMaker::DetachProgressMeter( pkgProgressMeter *meter )
81 {
82   /* A local helper method for detaching a progress meter from the
83    * controlling class instance for the main application window.
84    */
85   if( meter == AttachedProgressMeter )
86   {
87     pkgData->DetachProgressMeter( meter );
88     AttachedProgressMeter = NULL;
89   }
90 }
91
92 /* We must also provide the implementation of our local progress meter class.
93  */
94 #define IDD( DLG, ITEM ) GetDlgItem( DLG, IDD_PROGRESS_##ITEM )
95 PROGRESS_METER_CLASS::PROGRESS_METER_CLASS( HWND dlg, AppWindowMaker *owner ):
96 referrer( owner ), annotation( IDD( dlg, MSG ) ), progress_bar( IDD( dlg, BAR ) ),
97 count( IDD( dlg, VAL ) ), lim( IDD( dlg, MAX ) ), frac( IDD( dlg, PCT ) )
98 {
99   /* Constructor creates an instance of the progress meter class, attaching it
100    * to the main application window, with an initial metering range of 0..100%,
101    * and a starting indicated completion state of 0%.
102    */
103   owner->AttachProgressMeter( this );
104   SetRange( 0, 1 ); SetValue( 0 );
105 }
106
107 #include "pmihook.cpp"
108
109 /* Implementation of service routines, for loading the package catalogue
110  * from its defining collection of XML files.
111  */
112 void AppWindowMaker::LoadPackageData( bool force_update )
113 {
114   /* Helper method to load the package database from its
115    * defining collection of XML catalogue files.
116    */
117   const char *dfile;
118   if( pkgData == NULL )
119   {
120     /* This is the first request to load the database;
121      * establish the load starting point as "profile.xml",
122      * if available...
123      */
124     if( access( dfile = xmlfile( profile_key ), R_OK ) != 0 )
125     {
126       /* ...or as "defaults.xml" otherwise.
127        */
128       free( (void *)(dfile) );
129       dfile = xmlfile( defaults_key );
130     }
131   }
132   else
133   { /* This is a reload request; in this case we adopt the
134      * starting point as established for the initial load...
135      */
136     dfile = strdup( pkgData->Value() );
137     /*
138      * ...and clear out all stale data from the previous
139      * time of loading.
140      */
141     delete pkgData;
142   }
143   /* Commence loading...
144    */
145   pkgData = new pkgXmlDocument( dfile );
146   if( (pkgData == NULL) || ! pkgData->IsOk() )
147     /*
148      * ...bailing out on failure to access the initial file.
149      */
150     throw WTK::runtime_error( WTK::error_text(
151         "%s: cannot open package database", dfile
152       ));
153
154   /* Once the initial file has been loaded, its name is
155    * recorded within the XML data image itself; thus, we
156    * may release the heap memory used to establish it
157    * prior to opening the file.
158    */
159   free( (void *)(dfile) );
160
161   /* Create a graft point for attachment of the package
162    * group hierarchy tree to the loaded XML data image.
163    */
164   pkgInitCategoryTreeGraft( pkgData->GetRoot() );
165
166   /* Establish the repository URI references, for retrieval
167    * of the downloadable catalogue files, and load them...
168    */
169   pkgData->AttachProgressMeter( AttachedProgressMeter );
170   if( pkgData->BindRepositories( force_update ) == NULL )
171     /*
172      * ...once again, bailing out on failure.
173      */
174     throw WTK::runtime_error( "Cannot read package catalogue" );
175
176   /* Finally, load the installation records pertaining to
177    * the active system map...
178    */
179   pkgData->LoadSystemMap();
180
181   /* ...and establish any preferences which the user may have
182    * specified within profile.xml
183    */
184   pkgData->EstablishPreferences( "gui" );
185 }
186
187 static void pkgInvokeInitDataLoad( void *window )
188 {
189   /* Thread procedure for performing the initial catalogue load, on
190    * application start-up.  This will load from locally cached data
191    * files, when available; however, it will also initiate a download
192    * from the remote repository, for any file which is missing from
193    * the local cache.  Since this may be a time consuming process,
194    * we subject it to progress metering, to ensure that the user is
195    * not left staring at an apparently hung, blank window.
196    */
197   AppWindowMaker *app = GetAppWindow( GetParent( (HWND)(window) ));
198   SendMessage( (HWND)(window),
199       WM_SETTEXT, 0, (LPARAM)("Loading Package Catalogue")
200     );
201   ProgressMeterMaker ui( (HWND)(window), app );
202
203   /* For this activity, we request automatic dismissal of the dialogue,
204    * when loading has been completed; the user will have an opportunity
205    * to countermand this choice, if loading is delayed by the required
206    * download of any missing local catalogue file.
207    */
208   HWND dlg = GetDlgItem( (HWND)(window), IDD_AUTO_CLOSE_OPTION );
209   SendMessage( dlg, WM_SETTEXT, 0,
210       (LPARAM)("Close dialogue automatically, when loading is complete.")
211     );
212   CheckDlgButton( (HWND)(window), IDD_AUTO_CLOSE_OPTION, BST_CHECKED );
213
214   /* We've now set up the initial state for the progress meter dialogue;
215    * proceed to load, (and perhaps download), the XML data files.
216    */
217   app->LoadPackageData( false );
218
219   /* When loading has been completed, automatically dismiss the dialogue...
220    */
221   if( IsDlgButtonChecked( (HWND)(window), IDD_AUTO_CLOSE_OPTION ) )
222     SendMessage( (HWND)(window), WM_COMMAND, (WPARAM)(IDOK), 0 );
223
224   /* ...unless the user has countermanded the automatic dismissal request...
225    */
226   else
227   { /* ...in which case, we activate the manual dismissal button...
228      */
229     if( (dlg = GetDlgItem( (HWND)(window), IDOK )) != NULL )
230       EnableWindow( dlg, TRUE );
231
232     /* ...and notify the user that it must be clicked to continue.
233      */
234     ui.Annotate( "Data has been loaded; please close this dialogue to continue." );
235   }
236 }
237
238 static void pkgInvokeUpdate( void *window )
239 {
240   /* Thread procedure for performing a package catalogue update.
241    * This will download catalogue files from the remote repository,
242    * and integrate them into the locally cached catalogue XML file
243    * set.  Since this is normally a time consuming process, we must
244    * subject it to progress metering, to ensure that the user is
245    * not left staring at an apparently hung, blank window.
246    */
247   AppWindowMaker *app = GetAppWindow( GetParent( (HWND)(window) ));
248   ProgressMeterMaker ui( (HWND)(window), app );
249
250   /* After setting up the progress meter, we clear out any data
251    * which was previously loaded into the package list, reload it
252    * with the "forced download" option, and refresh the display.
253    */
254   app->ClearPackageList();
255   app->LoadPackageData( true );
256   app->UpdatePackageList();
257
258   /* During the update, the user may have selected the option for
259    * automatic dismissal of the dialogue box on completion...
260    */
261   if( IsDlgButtonChecked( (HWND)(window), IDD_AUTO_CLOSE_OPTION ) )
262     /*
263      * ...in which case, we dismiss it without further ado...
264      */
265     SendMessage( (HWND)(window), WM_COMMAND, (WPARAM)(IDOK), 0 );
266
267   else
268   { /* ...otherwise, we activate the manual dismissal button...
269      */
270     HWND dlg = GetDlgItem( (HWND)(window), IDOK );
271     if( dlg != NULL ) EnableWindow( dlg, TRUE );
272
273     /* ...and notify the user that it must be clicked to continue.
274      */
275     ui.Annotate( "Update is complete; please close this dialogue to continue." );
276   }
277 }
278
279 inline void AppWindowMaker::ExecuteScheduledActions( void )
280 {
281   /* Helper method to delegate execution of a schedule of actions from
282    * the application window to its associated action item controller.
283    */
284   pkgData->Schedule()->Execute( false );
285   pkgData->UpdateSystemMap();
286 }
287
288 class pkgDialogueSpinWait: public pkgSpinWait
289 {
290   /* Derivative class for redirection of pkgSpinWait::Report()
291    * messages to a specified dialogue box text control.
292    */
293   public:
294     pkgDialogueSpinWait( HWND msg ): dlg( msg ){}
295
296   private:
297     virtual int DispatchReport( const char *, va_list );
298     HWND dlg;
299 };
300
301 int pkgDialogueSpinWait::DispatchReport( const char *fmt, va_list argv )
302 {
303   /* Method to handle pkgSpinWait::Report() message redirection.
304    */
305   char buf[ 1 + vsnprintf( NULL, 0, fmt, argv ) ];
306   int count = vsnprintf( buf, sizeof( buf ), fmt, argv );
307   SendMessage( dlg, WM_SETTEXT, 0, (LPARAM)(buf) );
308   return count;
309 }
310
311 static void pkgApplyChanges( void *window )
312 {
313   /* Worker thread processing function, run while displaying the
314    * IDD_APPLY_EXECUTE dialogue box, to apply scheduled changes.
315    */
316   HWND msg = GetDlgItem( (HWND)(window), IDD_PROGRESS_MSG );
317   AppWindowMaker *app = GetAppWindow( GetParent( (HWND)(window) ) );
318
319   /* Set up progess reporting and diagnostic message display
320    * channels, and execute the scheduled actions.
321    */
322   pkgDialogueSpinWait stat( msg );
323   dmh_setpty( GetDlgItem( (HWND)(window), IDD_DMH_CONSOLE ) );
324   app->ExecuteScheduledActions();
325   dmh_setpty( NULL );
326
327   /* Check for successful application of all scheduled changes.
328    */
329   int error_count = app->EnumerateActions( ACTION_UNSUCCESSFUL );
330
331   /* During processing, the user may have selected the option for
332    * automatic dismissal of the dialogue box on completion...
333    */
334   if(  (error_count == 0)
335   &&  IsDlgButtonChecked( (HWND)(window), IDD_AUTO_CLOSE_OPTION )  )
336     /*
337      * ...in which case, and provided all changes were applied
338      * successfully, we dismiss it without further ado...
339      */
340     SendMessage( (HWND)(window), WM_COMMAND, (WPARAM)(IDOK), 0 );
341
342   else
343   { /* ...otherwise, we activate the manual dismissal button...
344      */
345     HWND dlg;
346     if( (dlg = GetDlgItem( (HWND)(window), IDOK )) != NULL )
347       EnableWindow( dlg, TRUE );
348
349     /* ...and notify the user that it must be clicked to continue.
350      */
351     stat.Report( (error_count == 0)
352         ? "All changes were applied successfully;"
353           " you may now close this dialogue."
354         : "Not all changes were applied successfully;"
355           " please refer to details below."
356       );
357   }
358 }
359
360 static int CALLBACK pkgDialogue
361 ( HWND window, unsigned int msg, WPARAM wParam, LPARAM lParam )
362 {
363   /* Generic handler for dialogue boxes which delegate an associated
364    * processing activity to a background thread.
365    */
366   switch( msg )
367   {
368     /* We need to handle only two classes of windows messages
369      * on behalf of such dialogue boxes...
370      */
371     case WM_INITDIALOG:
372       /* ...viz. on initial dialogue box creation, we delegate the
373        * designated activity to the background thread...
374        */
375       _beginthread( AppWindowMaker::DialogueThread, 0, (void *)(window) );
376       return TRUE;
377
378     case WM_COMMAND:
379       if( LOWORD( wParam ) == IDOK )
380       {
381         /* ...then we wait for a notification that the dialogue may be
382          * closed, (which isn't permitted until the thread completes).
383          */
384         EndDialog( window, 0 );
385         return TRUE;
386       }
387   }
388   /* Any other messages, which are directed to this dialogue box,
389    * may be safely ignored.
390    */
391   return FALSE;
392 }
393
394 /* The following static member of the AppWindowMaker class is used
395  * to pass the function reference for the worker thread process to
396  * the preceding dialogue box handler function, when it is invoked
397  * by the following helper method.
398  */
399 pkgDialogueThread *AppWindowMaker::DialogueThread = NULL;
400
401 int AppWindowMaker::DispatchDialogueThread( int id, pkgDialogueThread *handler )
402 {
403   /* Helper method to open a dialogue box, and to initiate a worker
404    * thread to handle a designated background process on its behalf.
405    */
406   DialogueThread = handler;
407   return DialogueResponse( id, pkgDialogue );
408 }
409
410 inline unsigned long AppWindowMaker::EnumerateActions( int classified )
411 {
412   /* Helper method to enumerate the actions of a specified
413    * class, within the current schedule.
414    */
415   return pkgData->Schedule()->EnumeratePendingActions( classified );
416 }
417
418 inline pkgActionItem *pkgActionItem::SuppressRedundantUpgrades( void )
419 {
420   /* Helper method to adjust the schedule of upgrades, after marking
421    * all installed packages, to exclude all those which are already at
422    * the most recently available release.
423    */
424   pkgActionItem *head;
425   if( (head = this) != NULL )
426   {
427     /* First, provided the schedule is not empty, we walk the list
428      * of scheduled actions, until we find the true first entry...
429      */
430     while( head->prev != NULL ) head = head->prev;
431     for( pkgActionItem *ref = head; ref != NULL; ref = ref->next )
432     {
433       /* ...and then, we process the list from first entry to last,
434        * selecting those entries which schedule an upgrade action...
435        */
436       if( ((ref->flags & ACTION_MASK) == ACTION_UPGRADE)
437       /*
438        * ...and for which the currently installed release is the
439        * same as that which an upgrade would install...
440        */
441       &&  (ref->selection[ to_install ] == ref->selection[ to_remove ])  )
442         /*
443          * ...in which case, we mark this entry for "no action".
444          */
445         ref->flags &= ~ACTION_MASK;
446     }
447   }
448   /* Finally, we return a pointer to the first entry in the schedule.
449    */
450   return head;
451 }
452
453 static int pkgActionCount( HWND dlg, int id, const char *fmt, int classified )
454 {
455   /* Helper function to itemise the currently scheduled actions
456    * of a specified class, recording the associated package name
457    * within the passed EDITTEXT control, and noting the count of
458    * itemised actions within the heading of the control.
459    *
460    * First, count the actions, while adding the package names
461    * to the edit control with the specified ID.
462    */
463   dmh_setpty( GetDlgItem( dlg, id ) );
464   int count = GetAppWindow( GetParent( dlg ))->EnumerateActions( classified );
465
466   /* Construct the heading, according to the specified format,
467    * and including the identified action count.
468    */
469   const char *packages = (count == 1) ? "package" : "packages";
470   char label_text[1 + snprintf( NULL, 0, fmt, count, packages )];
471   snprintf( label_text, sizeof( label_text), fmt, count, packages );
472
473   /* Finally, update the heading on the edit control, assigning
474    * it to the dialogue control with ID one greater than that of
475    * the edit control itself.
476    */
477   SendMessage( GetDlgItem( dlg, ++id ), WM_SETTEXT, 0, (LPARAM)(label_text) );
478   dmh_setpty( NULL );
479   return count;
480 }
481
482 /* Implement a macro as shorthand notation for passing of action
483  * specific argument lists to the pkgActionCount() function...
484  */
485 #define ACTION_APPLY(OP)    APPLIES_TO(OP), FMT_APPLY_##OP##S, ACTION_##OP
486 #define APPLIES_TO(OP)      IDD_APPLY_##OP##S_PACKAGES
487
488 /* ...using the appropriate selection from these action specific
489  * message formats.
490  */
491 #define FMT_APPLY_REMOVES   "%u installed %s will be removed"
492 #define FMT_APPLY_UPGRADES  "%u installed %s will be upgraded"
493 #define FMT_APPLY_INSTALLS  "%u new/upgraded %s will be installed"
494
495 static int CALLBACK pkgApplyApproved
496 ( HWND window, unsigned int msg, WPARAM wParam, LPARAM lParam )
497 {
498   /* Callback function servicing the custom dialogue box in which
499    * scheduled actions are itemised for user confirmation, prior
500    * to applying them.
501    */
502   switch( msg )
503   { case WM_INITDIALOG:
504       /* On opening the dialogue box, itemise each of the
505        * remove, upgrade, and install action classes.
506        */
507       pkgActionCount( window, ACTION_APPLY( REMOVE ) );
508       pkgActionCount( window, ACTION_APPLY( UPGRADE ) );
509       pkgActionCount( window, ACTION_APPLY( INSTALL ) );
510       return TRUE;
511
512     case WM_COMMAND:
513       /* Wait for the user to review the itemised schedule
514        * of pending changes, confirm intent to proceed...
515        */
516       long opt = LOWORD( wParam );
517       if( (opt == ID_APPLY) || (opt == ID_DISCARD) || (opt == ID_DEFER) )
518       {
519         /* ...then close the dialogue, passing the selected
520          * continuation option back to the caller.
521          */
522         EndDialog( window, opt );
523         return TRUE;
524       }
525   }
526   /* Ignore any window messages we don't recognise.
527    */
528   return FALSE;
529 }
530
531 int AppWindowMaker::Invoked( void )
532 {
533   /* Override for the WTK::MainWindowMaker::Invoked() method; it
534    * provides the hook for the initial loading of the XML database,
535    * and creation of the display controls through which its content
536    * will be presented to the user, prior to invocation of the main
537    * window's message loop.
538    *
539    * The data displays depend on the MS-Windows Common Controls API;
540    * initialise all components of this up front.
541    */
542   InitCommonControls();
543
544   /* Load the data from the XML catalogue files; this activity
545    * is invoked in a background thread, initiated from a progress
546    * dialogue derived from the "Update Catalogue" template.
547    */
548   DispatchDialogueThread( IDD_REPO_UPDATE, pkgInvokeInitDataLoad );
549
550   /* Establish the initial views of the package category selection
551    * tree, and the list of available packages; (the initial package
552    * list includes everything in the "All Packages" category).
553    */
554   InitPackageTreeView();
555   InitPackageListView();
556
557   /* Initialise the data-sheet tab control, displaying the default
558    * "no package selected" message.
559    */
560   InitPackageTabControl();
561
562   /* Force a layout adjustment, to ensure that the displayed
563    * data controls are correctly populated.
564    */
565   AdjustLayout();
566   InvalidateRect( AppWindow, NULL, FALSE );
567   UpdateWindow( AppWindow );
568
569   /* Finally, we may delegate all further processing to the main
570    * window's message loop.
571    */
572   return WTK::MainWindowMaker::Invoked();
573 }
574
575 /* The following static entities are provided to facilitate the
576  * implementation of the "discard changes" confirmation dialogue.
577  */
578 static const char *pkgConfirmAction = NULL;
579 static AppWindowMaker *pkgConfirmActionClient = NULL;
580
581 static void dlgReplaceText( HWND dlg, ... )
582 {
583   /* Local helper routine, used by AppWindowMaker::ConfirmActionDialogue(),
584    * to substitute the designated action description into the format strings,
585    * as specified within the dialogue box template.
586    *
587    * First step is to copy the format specification from the dialogue item.
588    */
589   int len = 1 + SendMessage( dlg, WM_GETTEXTLENGTH, 0, 0 );
590   char fmt[len]; SendMessage( dlg, WM_GETTEXT, len, (LPARAM)(fmt) );
591
592   /* Having established the format, we allocate a sufficient buffer, in
593    * which we prepare the formatted text...
594    */
595   va_list argv; va_start( argv, dlg );
596   char text[1 + vsnprintf( NULL, 0, fmt, argv )];
597   vsnprintf( text, sizeof( text ), fmt, argv );
598   va_end( argv );
599
600   /* ...which we then copy back to the active dialogue item.
601    */
602   SendMessage( dlg, WM_SETTEXT, 0, (LPARAM)(text) );
603 }
604
605 int CALLBACK AppWindowMaker::ConfirmActionDialogue
606 ( HWND dlg, unsigned int msg, WPARAM wparam, LPARAM lparam )
607 #define DIALOGUE_CHKSTATE pkgConfirmActionClient->EnumerateActions
608 #define DIALOGUE_DISPATCH pkgConfirmActionClient->DispatchDialogueThread
609 #define DIALOGUE_RESPONSE pkgConfirmActionClient->DialogueResponse
610 {
611   /* Call back handler for the messages generated by the "discard changes"
612    * confirmation dialogue.
613    */
614   switch( msg )
615   { case WM_INITDIALOG:
616       /* When the confirmation dialogue is invoked, we centre it within
617        * its owner window, and replace the format specification strings
618        * from its text controls with their formatted equivalents.
619        */
620       WTK::AlignWindow( dlg, WTK_ALIGN_CENTRED );
621       dlgReplaceText( GetDlgItem( dlg, IDD_PROGRESS_MSG ), pkgConfirmAction );
622       dlgReplaceText( GetDlgItem( dlg, IDD_PROGRESS_TXT ), pkgConfirmAction,
623           pkgConfirmAction
624         );
625       return TRUE;
626
627     case WM_COMMAND:
628       /* Process the messages from the dialogue's three command buttons:
629        */
630       switch( msg = LOWORD( wparam ) )
631       {
632         case IDIGNORE:
633           /* Sent when the user selects the "Discard Changes" option,
634            * we return "IDOK" to confirm that the action should be
635            * progressed, ignoring any uncommitted changes.
636            */
637           EndDialog( dlg, IDOK );
638           return TRUE;
639
640         case IDOK:
641           /* Sent when the user selects the "Review Changes" option; we
642            * shelve the active dialogue, and delegate the decision as to
643            * whether to proceed to the "Apply Changes" dialogue.
644            */
645           ShowWindow( dlg, SW_HIDE );
646           switch( DIALOGUE_RESPONSE( IDD_APPLY_APPROVE, pkgApplyApproved ) )
647           {
648             case ID_DEFER:
649               /* Handle a "Defer" request from the "Apply Changes"
650                * dialogue as equivalent to our own "Cancel Request"
651                * option.
652                */
653               msg = IDCANCEL;
654               break;
655
656             case ID_APPLY:
657               /* When "Apply" confirmation is forthcoming, we proceed to
658                * download any required packages, and invoke the scheduled
659                * remove, upgrade, or install actions.
660                */
661               DIALOGUE_DISPATCH( IDD_APPLY_DOWNLOAD, pkgInvokeDownload );
662               DIALOGUE_DISPATCH( IDD_APPLY_EXECUTE, pkgApplyChanges );
663
664               /* Ensure that all "Apply" actions completed successfully...
665                */
666               if( DIALOGUE_CHKSTATE( ACTION_UNSUCCESSFUL ) > 0 )
667                 /*
668                  * ...otherwise, cancel the ensuing action, so that the
669                  * user may have an opportunity to remedy the problem.
670                  */
671                 msg = IDCANCEL;
672           }
673           /* Regardless of the outcome from the "Review Changes" activity,
674            * simply fall through to terminate the dialogue appropriately.
675            */
676         case IDCANCEL:
677           /* Sent when the user selects the "Cancel Request" option; we
678            * simply allow the message ID to propagate back to the caller,
679            * as the response code.
680            */
681           EndDialog( dlg, msg );
682           return TRUE;
683       }
684   }
685   /* We don't handle any other message; if any is received, mark it as
686    * "unhandled", and pass it on to the default dialogue handler.
687    */
688   return FALSE;
689 }
690
691 bool AppWindowMaker::ConfirmActionRequest( const char *desc )
692 {
693   /* Helper to confirm user's intent to proceed, when a request
694    * to update the catalogue, or to quit, would result in pending
695    * installation actions being discarded.
696    *
697    * We begin by saving reference data to be passed to the static
698    * dialogue handler method...
699    */
700   pkgConfirmAction = desc;
701   pkgConfirmActionClient = this;
702
703   /* ...prior to checking for pending actions, and invoking the
704    * dialogue procedure when appropriate, or simply allowing the
705    * action to proceed, otherwise.
706    */
707   pkgActionItem *pending = pkgData->Schedule();
708   return (pending && pending->EnumeratePendingActions() > 0)
709     ? DialogBox( AppInstance, MAKEINTRESOURCE( IDD_CONFIRMATION ),
710         AppWindow, ConfirmActionDialogue
711       ) == IDOK
712     : true;
713 }
714
715 long AppWindowMaker::OnCommand( WPARAM cmd )
716 #define ACTION_PRESERVE_FAILED (ACTION_DOWNLOAD_FAILED | ACTION_APPLY_FAILED)
717 {
718   /* Handler for WM_COMMAND messages which are directed to the
719    * top level application window.
720    */
721   switch( cmd )
722   { case IDM_HELP_ABOUT:
723       /* This request is initiated by selecting "About ...", ...
724        */
725     case IDM_HELP_LEGEND:
726       /* ...and this one by selecting "Icon Legend", from the "Help"
727        * menu; in both cases, we respond by displaying an associated
728        * dialogue box.
729        */
730       WTK::GenericDialogue( AppInstance, AppWindow, cmd );
731       break;
732
733     case IDM_PACKAGE_INSTALL:
734       /* Initiated by selecting the "Mark for Installation" option
735        * from the "Package" menu, this request will schedule the
736        * currently selected package, and any currently unfulfilled
737        * dependencies, for installation.
738        */
739       Schedule( ACTION_INSTALL );
740       break;
741
742     case IDM_PACKAGE_UPGRADE:
743       /* Initiated by selecting the "Mark for Upgrade" option
744        * from the "Package" menu, this request will schedule the
745        * currently selected package, and any currently unfulfilled
746        * dependencies, for upgrade or installation, as appropriate.
747        */
748       Schedule( ACTION_UPGRADE );
749       break;
750
751     case IDM_PACKAGE_REMOVE:
752       /* Initiated by selecting the "Mark for Removal" option
753        * from the "Package" menu, this request will schedule the
754        * currently selected package for removal.
755        */
756       Schedule( ACTION_REMOVE );
757       break;
758
759     case IDM_PACKAGE_UNMARK:
760       /* Initiated by selecting the "Unmark" option from the
761        * "Package" menu, this request will cancel the effect of
762        * any previously scheduled action, in respect of the
763        * currently selected package.
764        */
765       UnmarkSelectedPackage();
766       break;
767
768     case IDM_REPO_UPDATE:
769       /* This request is initiated by selecting "Update Catalogue"
770        * from the "Repository" menu; we respond by initiating a progress
771        * dialogue, from which a background thread is invoked to download
772        * fresh copies of the package catalogue files from the remote
773        * repository, and consolidate them into the local catalogue.
774        *
775        * Note that this activity will cause any pending installation
776        * actions to be discarded; seek confirmation of user's intent
777        * to proceed, when this situation prevails.
778        */
779       if( ConfirmActionRequest( "update the catalogue" ) )
780       { DispatchDialogueThread( IDD_REPO_UPDATE, pkgInvokeUpdate );
781         UpdatePackageMenuBindings();
782       }
783       break;
784
785     case IDM_REPO_MARK_UPGRADES:
786       /* Initiated when the user selects the "Mark All Upgrades"
787        * option; in this case, we identify all packages which are
788        * already installed, and for which upgrades are available,
789        * and schedule an upgrade action in respect of each.
790        */
791       pkgData->RescheduleInstalledPackages( ACTION_UPGRADE );
792       {
793         /* After scheduling all available upgrades, we must
794          * update the package list view marker icons...
795          */
796         pkgListViewMaker pkglist( PackageListView );
797         pkglist.MarkScheduledActions(
798             pkgData->Schedule()->SuppressRedundantUpgrades()
799           );
800       }
801       /* ...and also adjust the menu bindings accordingly.
802        */
803       UpdatePackageMenuBindings();
804       break;
805
806     case IDM_REPO_APPLY:
807       /* Initiated when the user selects the "Apply Changes" option,
808        * we first reset the error trapping and download request state
809        * for all scheduled actions, then we present the user with a
810        * dialogue requesting confirmation of approval to proceed.
811        */
812       pkgData->Schedule()->Assert( 0UL, ~ACTION_PRESERVE_FAILED );
813       switch( DialogueResponse( IDD_APPLY_APPROVE, pkgApplyApproved ) )
814       {
815         /* Of the three possible responses, we simply ignore the "Defer"
816          * option, (since it requires no action), but we must explicitly
817          * handle the "Apply" and "Discard" options.
818          */
819         case ID_APPLY:
820           /* When "Apply" confirmation is forthcoming, we proceed to
821            * download any required packages, and invoke the scheduled
822            * remove, upgrade, or install actions.
823            */
824           DispatchDialogueThread( IDD_APPLY_DOWNLOAD, pkgInvokeDownload );
825           DispatchDialogueThread( IDD_APPLY_EXECUTE, pkgApplyChanges );
826
827           /* After applying changes, we fall through...
828            */
829         case ID_DISCARD:
830           /* ...so that on explicit "Discard" selection by the user,
831            * or following application of all scheduled actions, as a
832            * result of processing the "Apply" selection, we clear the
833            * actions schedule, remove all marker icons, and refresh
834            * the package list to reflect current status.
835            */
836           pkgListViewMaker pkglist( PackageListView );
837           pkglist.UpdateListView();
838           
839           /* Updating the list view clears pending action marks from
840            * every entry, but clearing the schedule may not cancel any
841            * request relating to a failed action; restore marked state
842            * for such residual actions.
843            */
844           pkglist.MarkScheduledActions(
845               pkgData->ClearScheduledActions( ACTION_PRESERVE_FAILED )
846             );
847
848           /* Clearing the schedule of actions may also affect the
849            * validity of menu options; update accordingly.
850            */
851           UpdatePackageMenuBindings();
852       }
853       break;
854
855     case IDM_REPO_QUIT:
856       /* This request is initiated by selecting the "Quit" option
857        * from the "Repository" menu; we respond by sending a WM_CLOSE
858        * message, to terminate the current application instance.
859        */
860       SendMessage( AppWindow, WM_CLOSE, 0, 0L );
861       break;
862   }
863   /* Any other message is silently ignored.
864    */
865   return EXIT_SUCCESS;
866 }
867
868 /* $RCSfile$: end of file */