OSDN Git Service

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