6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2012, MinGW.org Project
10 * Implementation of XML data loading services for the mingw-get GUI.
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.
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.
35 #include <wtkexcept.h>
37 /* The DMH sub-system provides the following function, but it does not
38 * declare the prototype; (this is to obviate any automatic requirement
39 * for every DMH client to include windows.h). We have already included
40 * windows.h indirectly, via guimain.h, so it is convenient for us to
41 * declare the requisite prototype here.
43 EXTERN_C void dmh_setpty( HWND );
45 class ProgressMeterMaker: public pkgProgressMeter
47 /* A locally defined class, supporting progress metering
48 * for package catalogue update and load operations.
51 ProgressMeterMaker( HWND, HWND, AppWindowMaker * );
53 virtual int Annotate( const char *, ... );
54 virtual void SetRange( int, int );
55 virtual void SetValue( int );
58 HWND message_line, progress_bar;
62 pkgProgressMeter *AppWindowMaker::AttachProgressMeter( pkgProgressMeter *meter )
64 /* A local helper method for attaching a progress meter to the
65 * controlling class instance for the main application window.
67 if( AttachedProgressMeter == NULL )
68 AttachedProgressMeter = meter;
69 return AttachedProgressMeter;
72 inline void AppWindowMaker::DetachProgressMeter( pkgProgressMeter *meter )
74 /* A local helper method for detaching a progress meter from the
75 * controlling class instance for the main application window.
77 if( meter == AttachedProgressMeter )
79 pkgData->DetachProgressMeter( meter );
80 AttachedProgressMeter = NULL;
84 /* We need to provide a destructor for the abstract base class, from which
85 * our progress meters are derived; here is as good a place as any.
87 pkgProgressMeter::~pkgProgressMeter(){ referrer->DetachProgressMeter( this ); }
89 /* We must also provide the implementation of our local progress meter class.
91 ProgressMeterMaker::ProgressMeterMaker
92 ( HWND annotation, HWND indicator, AppWindowMaker *owner ):
93 pkgProgressMeter( owner ), message_line( annotation ), progress_bar( indicator )
95 /* Constructor creates an instance of the progress meter class, attaching it
96 * to the main application window, with an initial metering range of 0..100%,
97 * and a starting indicated completion state of 0%.
99 owner->AttachProgressMeter( this );
104 int ProgressMeterMaker::Annotate( const char *fmt, ... )
106 /* Method to add a printf() style annotation to the progress meter dialogue.
109 va_start( argv, fmt );
110 char annotation[1 + vsnprintf( NULL, 0, fmt, argv )];
111 int len = vsnprintf( annotation, sizeof( annotation ), fmt, argv );
114 SendMessage( message_line, WM_SETTEXT, 0, (LPARAM)(annotation) );
118 void ProgressMeterMaker::SetRange( int min, int max )
120 /* Method to adjust the range of the progress meter, to represent any
121 * arbitrary range of discrete values, rather than percentage units.
123 SendMessage( progress_bar, PBM_SETRANGE, 0, MAKELPARAM( min, max ) );
126 void ProgressMeterMaker::SetValue( int value )
128 /* Method to update the indicated completion state of a progress meter,
129 * to represent any arbitrary value within its assigned metering range.
131 SendMessage( progress_bar, PBM_SETPOS, value, 0 );
134 /* Implementation of service routines, for loading the package catalogue
135 * from its defining collection of XML files.
137 void AppWindowMaker::LoadPackageData( bool force_update )
139 /* Helper method to load the package database from its
140 * defining collection of XML catalogue files.
143 if( pkgData == NULL )
145 /* This is the first request to load the database;
146 * establish the load starting point as "profile.xml",
149 if( access( dfile = xmlfile( profile_key ), R_OK ) != 0 )
151 /* ...or as "defaults.xml" otherwise.
153 free( (void *)(dfile) );
154 dfile = xmlfile( defaults_key );
158 { /* This is a reload request; in this case we adopt the
159 * starting point as established for the initial load...
161 dfile = strdup( pkgData->Value() );
163 * ...and clear out all stale data from the previous
169 /* Commence loading...
171 if( ! (pkgData = new pkgXmlDocument( dfile ))->IsOk() )
173 * ...bailing out on failure to access the initial file.
175 throw WTK::runtime_error( WTK::error_text(
176 "%s: cannot open package database", dfile
179 /* Once the initial file has been loaded, its name is
180 * recorded within the XML data image itself; thus, we
181 * may release the heap memory used to establish it
182 * prior to opening the file.
184 free( (void *)(dfile) );
186 /* Establish the repository URI references, for retrieval
187 * of the downloadable catalogue files, and load them...
189 pkgData->AttachProgressMeter( AttachedProgressMeter );
190 if( pkgData->BindRepositories( force_update ) == NULL )
192 * ...once again, bailing out on failure.
194 throw WTK::runtime_error( "Cannot read package catalogue" );
196 /* Finally, load the installation records pertaining to
197 * the active system map.
199 pkgData->LoadSystemMap();
202 static void pkgInvokeInitDataLoad( void *window )
204 /* Thread procedure for performing the initial catalogue load, on
205 * application start-up. This will load from locally cached data
206 * files, when available; however, it will also initiate a download
207 * from the remote repository, for any file which is missing from
208 * the local cache. Since this may be a time consuming process,
209 * we subject it to progress metering, to ensure that the user is
210 * not left staring at an apparently hung, blank window.
212 HWND msg = GetDlgItem( (HWND)(window), IDD_PROGRESS_MSG );
213 HWND dlg = GetDlgItem( (HWND)(window), IDD_PROGRESS_BAR );
214 AppWindowMaker *app = GetAppWindow( GetParent( (HWND)(window) ));
215 SendMessage( (HWND)(window),
216 WM_SETTEXT, 0, (LPARAM)("Loading Package Catalogue")
218 ProgressMeterMaker ui( msg, dlg, app );
220 /* For this activity, we request automatic dismissal of the dialogue,
221 * when loading has been completed; the user will have an opportunity
222 * to countermand this choice, if loading is delayed by the required
223 * download of any missing local catalogue file.
225 dlg = GetDlgItem( (HWND)(window), IDD_AUTO_CLOSE_OPTION );
226 SendMessage( dlg, WM_SETTEXT, 0,
227 (LPARAM)("Close dialogue automatically, when loading is complete.")
229 CheckDlgButton( (HWND)(window), IDD_AUTO_CLOSE_OPTION, BST_CHECKED );
231 /* We've now set up the initial state for the progress meter dialogue;
232 * proceed to load, (and perhaps download), the XML data files.
234 app->LoadPackageData( false );
236 /* When loading has been completed, automatically dismiss the dialogue...
238 if( IsDlgButtonChecked( (HWND)(window), IDD_AUTO_CLOSE_OPTION ) )
239 SendMessage( (HWND)(window), WM_COMMAND, (WPARAM)(IDOK), 0 );
241 /* ...unless the user has countermanded the automatic dismissal request...
244 { /* ...in which case, we activate the manual dismissal button...
246 if( (dlg = GetDlgItem( (HWND)(window), IDOK )) != NULL )
247 EnableWindow( dlg, TRUE );
249 /* ...and notify the user that it must be clicked to continue.
251 ui.Annotate( "Data has been loaded; please close this dialogue to continue." );
255 static void pkgInvokeUpdate( void *window )
257 /* Thread procedure for performing a package catalogue update.
258 * This will download catalogue files from the remote repository,
259 * and integrate them into the locally cached catalogue XML file
260 * set. Since this is normally a time consuming process, we must
261 * subject it to progress metering, to ensure that the user is
262 * not left staring at an apparently hung, blank window.
264 HWND msg = GetDlgItem( (HWND)(window), IDD_PROGRESS_MSG );
265 HWND dlg = GetDlgItem( (HWND)(window), IDD_PROGRESS_BAR );
266 AppWindowMaker *app = GetAppWindow( GetParent( (HWND)(window) ));
267 ProgressMeterMaker ui( msg, dlg, app );
269 /* After setting up the progress meter, we clear out any data
270 * which was previously loaded into the package list, reload it
271 * with the "forced download" option, and refresh the display.
273 app->ClearPackageList();
274 app->LoadPackageData( true );
275 app->UpdatePackageList();
277 /* During the update, the user may have selected the option for
278 * automatic dismissal of the dialogue box on completion...
280 if( IsDlgButtonChecked( (HWND)(window), IDD_AUTO_CLOSE_OPTION ) )
282 * ...in which case, we dismiss it without further ado...
284 SendMessage( (HWND)(window), WM_COMMAND, (WPARAM)(IDOK), 0 );
287 { /* ...otherwise, we activate the manual dismissal button...
289 if( (dlg = GetDlgItem( (HWND)(window), IDOK )) != NULL )
290 EnableWindow( dlg, TRUE );
292 /* ...and notify the user that it must be clicked to continue.
294 ui.Annotate( "Update is complete; please close this dialogue to continue." );
298 static int CALLBACK pkgDialogue
299 ( HWND window, unsigned int msg, WPARAM wParam, LPARAM lParam )
301 /* Generic handler for dialogue boxes which delegate an associated
302 * processing activity to a background thread.
306 /* We need to handle only two classes of windows messages
307 * on behalf of such dialogue boxes...
310 /* ...viz. on initial dialogue box creation, we delegate the
311 * designated activity to the background thread...
313 _beginthread( AppWindowMaker::DialogueThread, 0, (void *)(window) );
317 if( LOWORD( wParam ) == IDOK )
319 /* ...then we wait for a notification that the dialogue may be
320 * closed, (which isn't permitted until the thread completes).
322 EndDialog( window, 0 );
326 /* Any other messages, which are directed to this dialogue box,
327 * may be safely ignored.
332 /* The following static member of the AppWindowMaker class is used
333 * to pass the function reference for the worker thread process to
334 * the preceding dialogue box handler function, when it is invoked
335 * by the following helper method.
337 pkgDialogueThread *AppWindowMaker::DialogueThread = NULL;
339 int AppWindowMaker::DispatchDialogueThread( int id, pkgDialogueThread *handler )
341 /* Helper method to open a dialogue box, and to initiate a worker
342 * thread to handle a designated background process on its behalf.
344 DialogueThread = handler;
345 return DialogueResponse( id, pkgDialogue );
348 inline unsigned long AppWindowMaker::EnumerateActions( int classified )
350 /* Helper method to enumerate the actions of a specified
351 * class, within the current schedule.
353 return pkgData->Schedule()->EnumeratePendingActions( classified );
356 pkgActionItem *pkgActionItem::Clear( pkgActionItem *schedule, unsigned long mask )
357 # define ACTION_PRESERVE_FAILED (ACTION_DOWNLOAD_FAILED | ACTION_APPLY_FAILED)
359 /* Method to remove those action items which have no attribute flags in common
360 * with the specified mask, from the schedule; return the residual schedule of
361 * items, if any, which were not removed. (Note that specifying a mask with a
362 * value of 0UL, which is the default, results in removal of all items).
364 pkgActionItem *residual = NULL;
366 /* Starting at the specified item, or the invoking class object item
367 * if no starting point is specified...
369 if( (schedule != NULL) || ((schedule = this) != NULL) )
371 /* ...and provided this starting point is not NULL, walk back to
372 * the first item in the associated task schedule...
374 while( schedule->prev != NULL ) schedule = schedule->prev;
375 while( schedule != NULL )
377 /* ...then, processing each scheduled task item in sequence, and
378 * keeping track of the next to be processed...
380 pkgActionItem *nextptr = schedule->next;
381 if( (schedule->flags & mask) == 0 )
383 * ...delete each which doesn't match any masked attribute...
388 /* ...otherwise add it to the residual schedule.
392 /* In either event, move on to the next item in sequence, if any.
397 /* Ultimately, return a pointer to the last item added to the residual
398 * schedule, or NULL if all items were deleted.
403 void pkgActionItem::Reset
404 ( unsigned long set, unsigned long mask, pkgActionItem *schedule )
406 /* A method to manipulate the control, error trapping, and state
407 * flags for all items in the specified schedule of actions.
409 * Starting at the specified item, or the invoking class object
410 * item if no starting point is specified...
412 if( (schedule != NULL) || ((schedule = this) != NULL) )
414 /* ...and provided this starting point is not NULL, walk back
415 * to the first item in the associated task schedule...
417 while( schedule->prev != NULL ) schedule = schedule->prev;
418 while( schedule != NULL )
420 /* ...then, processing each scheduled task item in sequence,
421 * update the flags according to the specified mask and new
424 schedule->flags = (flags & mask) | set;
426 * ...before moving on to the next item in the sequence.
428 schedule = schedule->next;
433 static int pkgActionCount( HWND dlg, int id, const char *fmt, int classified )
435 /* Helper function to itemise the currently scheduled actions
436 * of a specified class, recording the associated package name
437 * within the passed EDITTEXT control, and noting the count of
438 * itemised actions within the heading of the control.
440 * First, count the actions, while adding the package names
441 * to the edit control with the specified ID.
443 dmh_setpty( GetDlgItem( dlg, id ) );
444 int count = GetAppWindow( GetParent( dlg ))->EnumerateActions( classified );
446 /* Construct the heading, according to the specified format,
447 * and including the identified action count.
449 const char *packages = (count == 1) ? "package" : "packages";
450 char label_text[1 + snprintf( NULL, 0, fmt, count, packages )];
451 snprintf( label_text, sizeof( label_text), fmt, count, packages );
453 /* Finally, update the heading on the edit control, assigning
454 * it to the dialogue control with ID one greater than that of
455 * the edit control itself.
457 SendMessage( GetDlgItem( dlg, ++id ), WM_SETTEXT, 0, (LPARAM)(label_text) );
462 /* Implement a macro as shorthand notation for passing of action
463 * specific argument lists to the pkgActionCount() function...
465 #define ACTION_APPLY(OP) APPLIES_TO(OP), FMT_APPLY_##OP##S, ACTION_##OP
466 #define APPLIES_TO(OP) IDD_APPLY_##OP##S_PACKAGES
468 /* ...using the appropriate selection from these action specific
471 #define FMT_APPLY_REMOVES "%u installed %s will be removed"
472 #define FMT_APPLY_UPGRADES "%u installed %s will be upgraded"
473 #define FMT_APPLY_INSTALLS "%u new/upgraded %s will be installed"
475 static int CALLBACK pkgApplyApproved
476 ( HWND window, unsigned int msg, WPARAM wParam, LPARAM lParam )
478 /* Callback function servicing the custom dialogue box in which
479 * scheduled actions are itemised for user confirmation, prior
483 { case WM_INITDIALOG:
484 /* On opening the dialogue box, itemise each of the
485 * remove, upgrade, and install action classes.
487 pkgActionCount( window, ACTION_APPLY( REMOVE ) );
488 pkgActionCount( window, ACTION_APPLY( UPGRADE ) );
489 pkgActionCount( window, ACTION_APPLY( INSTALL ) );
493 /* Wait for the user to review the itemised schedule
494 * of pending changes, confirm intent to proceed...
496 long opt = LOWORD( wParam );
497 if( (opt == ID_APPLY) || (opt == ID_DISCARD) || (opt == ID_DEFER) )
499 /* ...then close the dialogue, passing the selected
500 * continuation option back to the caller.
502 EndDialog( window, opt );
506 /* Ignore any window messages we don't recognise.
511 int AppWindowMaker::Invoked( void )
513 /* Override for the WTK::MainWindowMaker::Invoked() method; it
514 * provides the hook for the initial loading of the XML database,
515 * and creation of the display controls through which its content
516 * will be presented to the user, prior to invocation of the main
517 * window's message loop.
519 * The data displays depend on the MS-Windows Common Controls API;
520 * initialise all components of this up front.
522 InitCommonControls();
524 /* Load the data from the XML catalogue files, and construct
525 * the initial view of the available package list; this activity
526 * is invoked in a background thread, initiated from a progress
527 * dialogue derived from the "Update Catalogue" template.
529 DispatchDialogueThread( IDD_REPO_UPDATE, pkgInvokeInitDataLoad );
530 InitPackageListView();
532 /* Initialise the data-sheet tab control, displaying the default
533 * "no package selected" message.
535 InitPackageTabControl();
537 /* Force a layout adjustment, to ensure that the displayed
538 * data controls are correctly populated.
542 /* Finally, we may delegate all further processing to the main
543 * window's message loop.
545 return WTK::MainWindowMaker::Invoked();
548 long AppWindowMaker::OnCommand( WPARAM cmd )
550 /* Handler for WM_COMMAND messages which are directed to the
551 * top level application window.
554 { case IDM_HELP_ABOUT:
555 /* This request is initiated by selecting "About mingw-get"
556 * from the "Help" menu; we respond by displaying the "about"
559 WTK::GenericDialogue( AppInstance, AppWindow, IDD_HELP_ABOUT );
562 case IDM_PACKAGE_INSTALL:
563 /* Initiated by selecting the "Mark for Installation" option
564 * from the "Package" menu, this request will schedule the
565 * currently selected package, and any currently unfulfilled
566 * dependencies, for installation.
568 Schedule( ACTION_INSTALL );
571 case IDM_PACKAGE_UPGRADE:
572 /* Initiated by selecting the "Mark for Upgrade" option
573 * from the "Package" menu, this request will schedule the
574 * currently selected package, and any currently unfulfilled
575 * dependencies, for upgrade or installation, as appropriate.
577 Schedule( ACTION_UPGRADE );
580 case IDM_PACKAGE_REMOVE:
581 /* Initiated by selecting the "Mark for Removal" option
582 * from the "Package" menu, this request will schedule the
583 * currently selected package for removal.
585 Schedule( ACTION_REMOVE );
588 case IDM_PACKAGE_UNMARK:
589 /* Initiated by selecting the "Unmark" option from the
590 * "Package" menu, this request will cancel the effect of
591 * any previously scheduled action, in respect of the
592 * currently selected package.
594 UnmarkSelectedPackage();
597 case IDM_REPO_UPDATE:
598 /* This request is initiated by selecting "Update Catalogue"
599 * from the "Repository" menu; we respond by initiating a progress
600 * dialogue, from which a background thread is invoked to download
601 * fresh copies of the package catalogue files from the remote
602 * repository, and consolidate them into the local catalogue.
604 DispatchDialogueThread( IDD_REPO_UPDATE, pkgInvokeUpdate );
608 /* Initiated when the user selects the "Apply Changes" option,
609 * we first reset the error trapping and download request state
610 * for all scheduled actions, then we present the user with a
611 * dialogue requesting confirmation of approval to proceed.
613 pkgData->Schedule()->Reset( ACTION_DOWNLOAD, ~ACTION_PRESERVE_FAILED );
614 switch( DialogueResponse( IDD_APPLY_APPROVE, pkgApplyApproved ) )
616 /* Of the three possible responses, we simply ignore the "Defer"
617 * option, (since it requires no action), but we must explicitly
618 * handle the "Apply" and "Discard" options.
621 /* When "Apply" confirmation is forthcoming, we proceed to
622 * download any required packages, and invoke the scheduled
623 * remove, upgrade, or install actions.
625 * FIXME: the pkgInvokeDownload() thread handler has yet to
628 DispatchDialogueThread( IDD_APPLY_DOWNLOAD, pkgInvokeDownload );
629 // DispatchDialogueThread( IDD_APPLY_MONITORED, pkgApplyChanges );
631 /* After applying changes, we fall through...
634 /* ...so that on explicit "Discard" selection by the user,
635 * or following application of all scheduled actions, as a
636 * result of processing the "Apply" selection, we clear the
637 * actions schedule, remove all marker icons, and refresh
638 * the package list to reflect current status.
640 pkgListViewMaker pkglist( PackageListView );
641 pkglist.UpdateListView();
643 /* Updating the list view clears pending action marks from
644 * every entry, but clearing the schedule may not cancel any
645 * request relating to a failed action; restore marked state
646 * for such residual actions.
648 if( pkgData->ClearScheduledActions( ACTION_PRESERVE_FAILED ) != NULL )
649 pkglist.MarkScheduledActions( pkgData->Schedule() );
651 /* Clearing the schedule of actions may also affect the
652 * validity of menu options; update accordingly.
654 UpdatePackageMenuBindings();
659 /* This request is initiated by selecting the "Quit" option
660 * from the "Repository" menu; we respond by sending a WM_CLOSE
661 * message, to terminate the current application instance.
663 SendMessage( AppWindow, WM_CLOSE, 0, 0L );
666 /* Any other message is silently ignored.
671 /* $RCSfile$: end of file */