6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2012, 2013, MinGW.org Project
10 * Implementation of the methods for the pkgListViewMaker class, and
11 * its AppWindowMaker client API, to support the display of the package
12 * list in the mingw-get graphical user interface.
15 * This is free software. Permission is granted to copy, modify and
16 * redistribute this software, under the provisions of the GNU General
17 * Public License, Version 3, (or, at your option, any later version),
18 * as published by the Free Software Foundation; see the file COPYING
19 * for licensing details.
21 * Note, in particular, that this software is provided "as is", in the
22 * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
23 * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
24 * PARTICULAR PURPOSE. Under no circumstances will the author, or the
25 * MinGW Project, accept liability for any damages, however caused,
26 * arising from the use of this software.
29 #define _WIN32_IE 0x0300
38 #include <wtkexcept.h>
40 void AppWindowMaker::InitPackageListView()
42 /* Create and initialise a ListView window, in which to present
45 PackageListView = CreateWindow( WC_LISTVIEW, NULL,
46 WS_VISIBLE | WS_BORDER | WS_CHILD | WS_EX_CLIENTEDGE |
47 LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS, 0, 0, 0, 0,
48 AppWindow, (HMENU)(ID_PACKAGE_LISTVIEW),
51 /* ...and set its extended style attributes.
53 ListView_SetExtendedListViewStyle( PackageListView,
54 LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_ONECLICKACTIVATE
57 /* Propagate the default font, as set for the main window itself,
58 * to the list view control.
60 SendMessage( PackageListView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
62 /* Assign an image list, to furnish the package status icons.
64 HIMAGELIST iconlist = ImageList_Create( 16, 16, ILC_COLOR32 | ILC_MASK, 13, 0 );
65 for( int index = 0; index <= PKGSTATE( PURGE ); index++ )
67 HICON image = LoadIcon( AppInstance, MAKEINTRESOURCE(ID_PKGSTATE(index)) );
68 if( ImageList_AddIcon( iconlist, image ) == -1 )
69 throw WTK::runtime_error( "Error loading package status icons" );
71 ListView_SetImageList( PackageListView, iconlist, LVSIL_SMALL );
73 /* Initialise the table layout, and assign column headings.
76 struct { int id; int width; const char *text; } headings[] =
78 /* Specify the column headings for the package list table.
80 { ID_PKGLIST_TABLE_HEADINGS, 20, "" },
81 { ID_PKGNAME_COLUMN_HEADING, 150, "Package" },
82 { ID_PKGTYPE_COLUMN_HEADING, 48, "Class" },
83 { ID_INSTVER_COLUMN_HEADING, 125, "Installed Version" },
84 { ID_REPOVER_COLUMN_HEADING, 125, "Repository Version" },
85 { ID_PKGDESC_COLUMN_HEADING, 400, "Description" },
87 * This all-null entry terminates the sequence.
91 table.fmt = LVCFMT_LEFT;
92 table.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
93 for( int index = 0; headings[index].text != NULL; index++ )
95 /* Loop over the columns, setting the initial width
96 * (in pixels), and assigning the heading to each.
98 table.cx = headings[index].width;
99 table.pszText = (char *)(headings[index].text);
100 ListView_InsertColumn( PackageListView, index, &table );
102 /* "Update" the package list, to initialise the list view.
107 void AppWindowMaker::UpdatePackageList()
109 /* Scan the XML package catalogue, adding a ListView item entry
110 * for each package record.
112 pkgListViewMaker PackageList( PackageListView );
113 pkgDirectory *dir = pkgData->CatalogueAllPackages();
114 dir->InOrder( &PackageList );
117 /* Force a redraw of the application window, to ensure that the
118 * data pane content remains synchronised.
120 InvalidateRect( AppWindow, NULL, FALSE );
121 UpdateWindow( AppWindow );
124 inline bool pkgXmlNode::IsVisibleGroupMember()
126 /* Hook invoked before adding a package reference to the package list,
127 * to ensure that it represents a member of the current package group.
129 return AppWindowMaker::IsPackageGroupAffiliate( this );
132 /* FIXME: Complementary check for component class visibility requires a
133 * future component class visibility control; for the time being, we may
134 * assume that any component package should be visible if it belongs to
135 * any visible package group, (including a component subset of such a
136 * group, which may comprise only itself).
138 inline bool pkgXmlNode::IsVisibleClass(){ return IsVisibleGroupMember(); }
140 static char *pkgGetTitle( pkgXmlNode *pkg, const pkgXmlNode *xml_root )
142 /* A private helper method, to retrieve the title attribute
143 * associated with the description for the nominated package.
145 * Note: this should really return a const char *, but then
146 * we would need to cast it to non-const for mapping into the
147 * ill-formed structure of Microsoft's LVITEM, so we may just
148 * as well return the non-const result anyway.
150 pkgXmlNode *desc = pkg->FindFirstAssociate( description_key );
151 while( desc != NULL )
153 /* Handling it internally as the const which it should be...
156 if( (title = desc->GetPropVal( title_key, NULL )) != NULL )
158 * As soon as we find a description element with an
159 * assigned title attribute, immediately return it,
160 * (with the required cast to non-const).
162 return (char *)(title);
164 /* If we haven't yet found any title attribute, check for any
165 * further description elements at the current XML nesting level.
167 desc = desc->FindNextAssociate( description_key );
170 /* If we've exhausted all description elements at the current XML
171 * nesting level, without finding a title attribute, and we haven't
172 * checked all enclosing levels back to the document root...
174 if( pkg != xml_root )
176 * ...then continue the search in the immediately enclosing level.
178 return pkgGetTitle( pkg->GetParent(), xml_root );
180 /* If we get to here, then we've searched all levels back to the
181 * document root, and have failed to find any title attribute; we
182 * have nothing to return.
187 static inline char *pkgGetTitle( pkgXmlNode *pkg )
189 /* Overload the preceding function, to automatically generate
190 * the required reference to the XML document root.
192 return pkgGetTitle( pkg->GetParent(), pkg->GetDocumentRoot() );
196 char *version_string_copy( char *buf, const char *text, int fill = 0 )
198 /* Local helper function to construct a package version string
199 * from individual version specific elements of the tarname.
203 /* First, if a fill character is specified, copy it as the
204 * first character of the result; (we assume that we are
205 * appending to a previously constructed result, and that
206 * this is the field separator character).
208 if( fill != 0 ) *buf++ = fill;
210 /* Now, append "text" up to, and including its final NUL
211 * terminator; (note that we do NOT guard against buffer
212 * overrun, as we have complete control over the calling
213 * context, where we allocated a result buffer at least
214 * as long as the tarname string from which the composed
215 * version string is extracted, and the composed result
216 * can never exceed the original length of this).
218 do { if( (*buf = *text) != '\0' ) ++buf; } while( *text++ != '\0' );
220 /* Finally, we return a pointer to the terminating NUL of
221 * the result, so as to facilitate appending further text.
226 static char *pkgVersionString( char *buf, pkgSpecs *pkg )
228 /* Helper method to construct a fully qualified version string
229 * from the decomposed package tarname form in a pkgSpecs structure.
231 * We begin with the concatenation of package version and build ID
232 * fields, retrieved from the pkgSpecs representation...
234 const char *pkg_version_tag;
235 char *update = version_string_copy( buf, pkg->GetPackageVersion() );
236 update = version_string_copy( update, pkg->GetPackageBuild(), '-' );
237 if( (pkg_version_tag = pkg->GetSubSystemVersion()) != NULL )
239 /* ...then, we append the sub-system ID, if applicable.
241 update = version_string_copy( update, pkg->GetSubSystemName(), '-' );
242 update = version_string_copy( update, pkg_version_tag, '-' );
243 update = version_string_copy( update, pkg->GetSubSystemBuild(), '-' );
245 if( (pkg_version_tag = pkg->GetReleaseStatus()) != NULL )
247 /* When the package name also includes a release classification,
248 * then we also append this to the displayed version number (noting
249 * that an initial '$' is special, and should be suppressed)...
251 if( *pkg_version_tag == '$' ) ++pkg_version_tag;
252 update = version_string_copy( update, pkg_version_tag, '-' );
254 * ...followed by any serialisation index for the release...
256 update = version_string_copy( update, pkg->GetReleaseIndex(), '-' );
258 /* ...and finally, we return a pointer to the buffer in which
259 * we constructed the fully qualified version string.
264 pkgListViewMaker::pkgListViewMaker( HWND pane ): ListView( pane )
266 /* Constructor: initialise the invariant parameters within the
267 * embedded W32API ListView control structure.
269 content.stateMask = 0;
270 content.mask = LVIF_PARAM | LVIF_IMAGE | LVIF_STATE;
271 content.iSubItem = 0;
275 EXTERN_C pkgXmlNode *pkgGetStatus( pkgXmlNode *pkg, pkgActionItem *avail )
277 /* Helper function to acquire release availability and installation
278 * status attributes for a specified package.
280 pkg = pkg->FindFirstAssociate( release_key );
283 /* Examine each available release specification for the nominated
284 * package; select the one which represents the latest (most recent)
287 avail->SelectIfMostRecentFit( pkg );
289 /* Also check for the presence of an installation record for each
290 * release; if found, mark it as the currently installed release;
291 * (we assign the "to-remove" attribute, but we don't action it).
293 if( pkg->GetInstallationRecord( pkg->GetPropVal( tarname_key, NULL )) != NULL )
294 avail->SelectPackage( pkg, to_remove );
296 /* Cycle, until all known releases have been examined.
298 pkg = pkg->FindNextAssociate( release_key );
300 /* Check the identification of any currently installed release; this
301 * will capture property data for any installed release which may have
302 * been subsequently withdrawn from distribution.
304 avail->ConfirmInstallationStatus();
306 /* Finally, return a pointer to the specification for the installed
307 * release, if any, of the package under consideration.
309 return avail->Selection( to_remove );
312 void pkgListViewMaker::InsertItem( pkgXmlNode *pkg, char *class_name )
314 /* Private method to add a single package record, as an individual
315 * row item, to the displayed list view table; begin by filling in
316 * the appropriate fields within the "content" structure...
320 content.lParam = (unsigned long)(pkg);
322 /* ...then delegate the actual entry construction to...
324 UpdateItem( class_name, true );
327 inline bool pkgListViewMaker::GetItem( void )
329 /* An inline helper method to load the content structure from the
330 * next available item, if any, in the package list view; returns
331 * true when content is successfully loaded, else returns false.
333 content.iItem++; return ListView_GetItem( ListView, &content );
336 void pkgListViewMaker::UpdateItem( char *class_name, bool new_entry )
338 /* Assign a temporary action item, through which we may identify
339 * the latest available, and currently installed (if any), version
340 * attributes for the package under consideration.
343 pkgXmlNode *package = (pkgXmlNode *)(content.lParam);
344 pkgXmlNode *installed = pkgGetStatus( package, &avail );
346 /* Decompose the package tarname identifier for the latest available
347 * release, into its individual package specification properties.
349 pkgSpecs latest( package = avail.Selection() );
351 /* Allocate a temporary working text buffer, in which to format
352 * package property values for display...
354 size_t len = strlen( package->GetPropVal( tarname_key, value_none ) );
355 if( installed != NULL )
357 /* ...ensuring that it is at least as large as the longer of the
358 * latest or installed release tarname.
360 size_t altlen = strlen( installed->GetPropVal( tarname_key, value_none ) );
361 if( altlen > len ) len = altlen;
365 /* Choose a suitable icon for representation of the installation
366 * status of the package under consideration...
368 if( installed != NULL )
370 /* ...noting that, when it is already installed...
372 pkgSpecs current( installed );
373 content.iImage = (latest > current)
374 ? /* ...and, when the latest available is NEWER than the
375 * installed version, then we choose the icon indicating
376 * an installed package with an available update...
378 PKGSTATE( INSTALLED_OLD )
380 : /* ...or, when the latest available is NOT NEWER than
381 * the installed version, then we choose the alternative
382 * icon, simply indicating an installed package...
384 PKGSTATE( INSTALLED_CURRENT );
386 /* ...and also, load the version identification string for
387 * the installed version into the working text buffer.
389 pkgVersionString( buf, ¤t );
392 { /* Alternatively, for any package which is not recorded as
393 * installed, choose the icon indicating an available, but
394 * not (yet) installed package...
396 content.iImage = PKGSTATE( AVAILABLE );
398 /* ...and mark the list view column entry, for the installed
404 /* When compiling a new list entry...
408 /* ...add the package identification record, as a new item...
410 ListView_InsertItem( ListView, &content );
412 * ...and fill in the text for the package name, class name,
413 * and package description columns...
415 ListView_SetItemText( ListView, content.iItem, 1, package_name );
416 ListView_SetItemText( ListView, content.iItem, 2, class_name );
417 ListView_SetItemText( ListView, content.iItem, 5, pkgGetTitle( package ) );
420 /* ...otherwise, this is simply a request to update an
421 * existing item, in place; do so. (Note that, in this
422 * case, we DO NOT touch the package name, class name,
423 * or package description column content).
425 ListView_SetItem( ListView, &content );
427 /* Always fill in the text, as established above, in the
428 * column which identifies the currently installed version,
429 * if any, or clear it if the package is not installed.
431 ListView_SetItemText( ListView, content.iItem, 3, buf );
433 /* Finally, fill in the text of the column which identifies
434 * the latest available version of the package.
436 ListView_SetItemText( ListView, content.iItem, 4, pkgVersionString( buf, &latest ) );
439 void pkgListViewMaker::Dispatch( pkgXmlNode *package )
441 /* Implementation of the "dispatcher" method, which is required
442 * by any derivative of the pkgDirectoryViewerEngine class, for
443 * dispatching the content of the directory to the display service,
444 * (which, in this case, populates the list view window pane).
446 if( package->IsElementOfType( package_key ) )
448 /* Assemble the package name into the list view record block.
450 package_name = (char *)(package->GetPropVal( name_key, value_unknown ));
452 /* When processing a pkgDirectory entry for a package entity,
453 * generate a sub-directory listing for any contained package
457 if( (dir = EnumerateComponents( package )) != NULL )
459 /* ...and recurse, to process any which are found...
461 dir->InOrder( this );
464 else if( package->IsVisibleGroupMember() )
466 * ...otherwise, simply insert an unclassified list entry
467 * for the bare package name, omitting the component class.
469 InsertItem( package, (char *)("") );
471 else if( package->IsElementOfType( component_key ) && package->IsVisibleClass() )
473 /* Handle the recursive calls for the component sub-directory,
474 * inheriting the package name entry from the original package
475 * entity, and emit an appropriately classified list view entry
476 * for each identified component package.
478 InsertItem( package, (char *)(package->GetPropVal( class_key, "" )) );
482 void pkgListViewMaker::MarkScheduledActions( pkgActionItem *schedule )
484 /* Method to reassign icons to entries within the package list view,
485 * indicating any which have been marked for installation, upgrade,
486 * or removal of the associated package.
488 if( schedule != NULL )
489 for( content.iItem = -1; GetItem(); )
491 /* Visiting every entry in the list...
494 if( (ref = schedule->GetReference( (pkgXmlNode *)(content.lParam) )) != NULL )
496 /* ...identify those which are associated with a scheduled action...
498 unsigned long opcode;
499 if( (opcode = ref->HasAttribute( ACTION_MASK )) == ACTION_INSTALL )
501 /* ...selecting the appropriate icon to mark those packages
502 * which have been scheduled for installation...
504 * FIXME: we should also consider that such packages
505 * may have been scheduled for reinstallation.
507 content.iImage = PKGSTATE( AVAILABLE_INSTALL );
509 else if( opcode == ACTION_UPGRADE )
511 /* ...those which have been scheduled for upgrade...
513 content.iImage = PKGSTATE( UPGRADE );
515 else if( opcode == ACTION_REMOVE )
517 /* ...and those which have been scheduled for removal.
519 content.iImage = PKGSTATE( REMOVE );
522 { /* Where a scheduled action is any other than those above,
523 * handle as if there was no scheduled action...
527 * ...and ensure that the list view entry reflects the
528 * normal display state for the associated package.
534 * Where an action mark is appropriate, ensure that it
535 * is applied to the list view entry.
537 ListView_SetItem( ListView, &content );
542 void pkgListViewMaker::UpdateListView( void )
544 /* A simple helper method, to refresh the content of the
545 * package list view, resetting each item to its initial
546 * unmarked display state.
548 for( content.iItem = -1; GetItem(); ) UpdateItem( NULL );
551 /* $RCSfile$: end of file */