6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2012, MinGW Project
10 * Implementation of the methods for the pkgListViewMaker class, to
11 * support the display of the package list in the mingw-get graphical
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
37 #include <wtkexcept.h>
39 void AppWindowMaker::InitPackageListView()
41 /* Create and initialise a ListView window, in which to present
44 PackageListView = CreateWindow( WC_LISTVIEW, NULL,
45 WS_VISIBLE | WS_BORDER | WS_CHILD | WS_EX_CLIENTEDGE |
46 LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS, 0, 0, 0, 0,
47 AppWindow, (HMENU)(ID_PACKAGE_LISTVIEW),
50 /* ...and set its extended style attributes.
52 ListView_SetExtendedListViewStyle( PackageListView,
53 LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_ONECLICKACTIVATE
56 /* Propagate the default font, as set for the main window itself,
57 * to the list view control.
59 SendMessage( PackageListView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
61 /* Assign an image list, to furnish the package status icons.
63 HIMAGELIST iconlist = ImageList_Create( 16, 16, ILC_COLOR32 | ILC_MASK, 13, 0 );
64 for( int index = 0; index <= PKGSTATE( PURGE ); index++ )
66 HICON image = LoadIcon( AppInstance, MAKEINTRESOURCE(ID_PKGSTATE(index)) );
67 if( ImageList_AddIcon( iconlist, image ) == -1 )
68 throw WTK::runtime_error( "Error loading package status icons" );
70 ListView_SetImageList( PackageListView, iconlist, LVSIL_SMALL );
72 /* Initialise the table layout, and assign column headings.
75 struct { int id; int width; char *text; } headings[] =
77 /* Specify the column headings for the package list table.
79 { ID_PKGLIST_TABLE_HEADINGS, 20, "" },
80 { ID_PKGNAME_COLUMN_HEADING, 150, "Package" },
81 { ID_PKGTYPE_COLUMN_HEADING, 48, "Class" },
82 { ID_INSTVER_COLUMN_HEADING, 125, "Installed Version" },
83 { ID_REPOVER_COLUMN_HEADING, 125, "Repository Version" },
84 { ID_PKGDESC_COLUMN_HEADING, 400, "Description" },
86 * This all-null entry terminates the sequence.
90 table.fmt = LVCFMT_LEFT;
91 table.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
92 for( int index = 0; headings[index].text != NULL; index++ )
94 /* Loop over the columns, setting the initial width
95 * (in pixels), and assigning the heading to each.
97 table.cx = headings[index].width;
98 table.pszText = headings[index].text;
99 ListView_InsertColumn( PackageListView, index, &table );
101 /* "Update" the package list, to initialise the list view.
106 void AppWindowMaker::UpdatePackageList()
108 /* Scan the XML package catalogue, adding a ListView item entry
109 * for each package record.
111 pkgListViewMaker PackageList( PackageListView );
112 pkgDirectory *dir = pkgData->CatalogueAllPackages();
113 dir->InOrder( &PackageList );
117 pkgListViewMaker::pkgListViewMaker( HWND pane ): ListView( pane )
119 /* Constructor: initialise the invariant parameters within the
120 * embedded W32API ListView control structure.
122 content.stateMask = 0;
123 content.mask = LVIF_PARAM | LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
127 void pkgListViewMaker::InsertItem( pkgXmlNode *pkg, char *class_name )
129 /* Private method to add a single package record, as an individual
130 * row item, to the displayed list view table.
134 content.iSubItem = 0;
135 content.lParam = (unsigned long)(pkg);
136 content.pszText = "";
138 /* Assign a temporary action item, through which we may identify
139 * the latest available, and currently installed (if any), version
140 * attributes for the package under consideration.
143 pkgXmlNode *rel = pkg->FindFirstAssociate( release_key );
146 /* Examine each available release specification for the nominated
147 * package; select the one which represents the latest (most recent)
150 avail.SelectIfMostRecentFit( rel );
152 /* Also check for the presence of an installation record for each
153 * release; if found, mark it as the currently installed release;
154 * (we assign the "to-remove" attribute, but we don't action it).
156 if( rel->GetInstallationRecord( rel->GetPropVal( tarname_key, NULL )) != NULL )
157 avail.SelectPackage( rel, to_remove );
159 /* Cycle, until all known releases have been examined.
161 rel = rel->FindNextAssociate( release_key );
163 /* Check the identification of any currently installed release; this
164 * will capture property data for any installed release which may have
165 * been subsequently withdrawn from distribution.
167 avail.ConfirmInstallationStatus();
169 /* Decompose the package tarname identifier for the latest available
170 * release, into its individual package specification properties.
172 pkgSpecs latest( rel = avail.Selection() );
174 /* Allocate a temporary working text buffer, in which to format
175 * package property values for display...
177 size_t len = strlen( rel->GetPropVal( tarname_key, value_none ) );
178 if( (rel = avail.Selection( to_remove )) != NULL )
180 /* ...ensuring that it is at least as large as the longer of the
181 * latest or installed release tarname.
183 size_t altlen = strlen( rel->GetPropVal( tarname_key, value_none ) );
184 if( altlen > len ) len = altlen;
188 /* Choose a suitable icon for representation of the installation
189 * status of the package under consideration...
193 /* ...noting that, when it is already installed...
195 pkgSpecs current( rel );
196 if( latest > current )
198 * ...and, when the latest available is NEWER than the
199 * installed version, then we choose the icon indicating
200 * an installed package with an available update...
202 content.iImage = PKGSTATE( INSTALLED_OLD );
204 /* ...or, when the latest available is NOT NEWER than
205 * the installed version, then we choose the alternative
206 * icon, simply indicating an installed package...
208 content.iImage = PKGSTATE( INSTALLED_CURRENT );
210 /* ...and also, load the version identification string for
211 * the installed version into the working text buffer.
213 GetVersionString( buf, ¤t );
216 /* Alternatively, for any package which is not recorded as
217 * installed, choose the icon indicating an available, but
218 * not (yet) installed package.
220 content.iImage = PKGSTATE( AVAILABLE );
222 /* Add the package identification record, as a list item...
224 ListView_InsertItem( ListView, &content );
226 * ...and fill in the text for the package name and class columns.
228 ListView_SetItemText( ListView, content.iItem, 1, package_name );
229 ListView_SetItemText( ListView, content.iItem, 2, class_name );
231 /* When an installed package release has been identified...
235 * ...fill in the version identificaton text appropriately.
237 ListView_SetItemText( ListView, content.iItem, 3, buf );
239 /* Finally, fill in the text for the latest version identification
240 * and package description columns.
242 ListView_SetItemText( ListView, content.iItem, 4, GetVersionString( buf, &latest ) );
243 ListView_SetItemText( ListView, content.iItem, 5, GetTitle( avail.Selection()) );
246 char *pkgListViewMaker::GetTitle( pkgXmlNode *pkg, const pkgXmlNode *xml_root )
248 /* A private helper method, to retrieve the title attribute
249 * associated with the description for the nominated package.
251 * Note: this should really return a const char *, but then
252 * we would need to cast it to non-const for mapping into the
253 * ill-formed structure of Microsoft's LVITEM, so we may just
254 * as well return the non-const result anyway.
256 pkgXmlNode *desc = pkg->FindFirstAssociate( description_key );
257 while( desc != NULL )
259 /* Handling it internally as the const which it should be...
262 if( (title = desc->GetPropVal( title_key, NULL )) != NULL )
264 /* As soon as we find a description element with an
265 * assigned title attribute...
268 /* ...and noting that some package descriptions may
269 * redundantly respecify the package name as a colon
270 * delimited prefix to this title attribute...
272 * FIXME: ultimately, I'd like to remove this hack;
273 * package descriptions have no need to redundantly
274 * specify the package name as a prefix to the title,
275 * (since mingw-get can retrieve it from the package
276 * element containing the description anyway), and
277 * package maintainers should not rely on this
278 * hack to clean up the redundancy.
280 for( const char *prefix = title; *prefix != '\0'; prefix++ )
283 /* ...we strip that redundant prefix away...
288 /* ...then trim away any leading white space...
290 while( isspace( *title ) )
293 /* ...before immediately returning the title, (with the
294 * required cast to non-const).
296 return (char *)(title);
299 /* If we haven't yet found any title attribute, check for any
300 * further description elements at the current XML nesting level.
302 desc = desc->FindNextAssociate( description_key );
305 /* If we've exhausted all description elements at the current XML
306 * nesting level, without finding a title attribute, and we haven't
307 * checked all enclosing levels back to the document root...
309 if( pkg != xml_root )
311 * ...then continue the search in the immediately enclosing level.
313 return GetTitle( pkg->GetParent() );
315 /* If we get to here, then we've searched all levels back to the
316 * document root, and have failed to find any title attribute; we
317 * have nothing to return.
323 char *version_string_copy( char *buf, const char *text, int fill = 0 )
325 /* Local helper function to construct a package version string
326 * from individual version specific elements of the tarname.
330 /* First, if a fill character is specified, copy it as the
331 * first character of the result; (we assume that we are
332 * appending to a previously constructed result, and that
333 * this is the field separator character).
335 if( fill != 0 ) *buf++ = fill;
337 /* Now, append "text" up to, and including its final NUL
338 * terminator; (note that we do NOT guard against buffer
339 * overrun, as we have complete control over the calling
340 * context, where we allocated a result buffer at least
341 * as long as the tarname string from which the composed
342 * version string is extracted, and the composed result
343 * can never exceed the original length of this).
345 do { if( (*buf = *text) != '\0' ) ++buf; } while( *text++ != '\0' );
347 /* Finally, we return a pointer to the terminating NUL of
348 * the result, so as to facilitate appending further text.
353 char *pkgListViewMaker::GetVersionString( char *buf, pkgSpecs *pkg )
355 /* Helper method to construct a fully qualified version string
356 * from the decomposed package tarname form in a pkgSpecs structure.
358 * We begin with the concatenation of package version and build ID
359 * fields, retrieved from the pkgSpecs representation...
361 char *update = version_string_copy( buf, pkg->GetPackageVersion() );
362 update = version_string_copy( update, pkg->GetPackageBuild(), '-' );
363 if( pkg->GetSubSystemVersion() != NULL )
365 /* ...then, we append the sub-system ID, if applicable...
367 update = version_string_copy( update, pkg->GetSubSystemName(), '-' );
368 update = version_string_copy( update, pkg->GetSubSystemVersion(), '-' );
369 update = version_string_copy( update, pkg->GetSubSystemBuild(), '-' );
371 /* ...and finally, we return a pointer to the buffer in which
372 * we constructed the fully qualified version string.
377 void pkgListViewMaker::Dispatch( pkgXmlNode *package )
379 /* Implementation of the "dispatcher" method, which is required
380 * by any derivative of the pkgDirectoryViewerEngine class, for
381 * dispatching the content of the directory to the display service,
382 * (which, in this case, populates the list view window pane).
384 if( package->IsElementOfType( package_key ) )
386 /* Assemble the package name into the list view record block.
388 package_name = (char *)(package->GetPropVal( name_key, value_unknown ));
390 /* When processing a pkgDirectory entry for a package entity,
391 * generate a sub-directory listing for any contained package
395 if( (dir = EnumerateComponents( package )) != NULL )
397 /* ...and recurse, to process any which are found...
399 dir->InOrder( this );
403 /* ...otherwise, simply insert an unclassified list entry
404 * for the bare package name, omitting the component class.
406 InsertItem( package, "" );
408 else if( package->IsElementOfType( component_key ) )
410 /* Handle the recursive calls for the component sub-directory,
411 * inheriting the package name entry from the original package
412 * entity, and emit an appropriately classified list view entry
413 * for each identified component package.
415 InsertItem( package, (char *)(package->GetPropVal( class_key, "" )) );
419 /* $RCSfile$: end of file */