OSDN Git Service

c22d6f8a33db5e6d09ed8ecc6a11b6c2792d6f45
[mingw/mingw-get.git] / src / pkglist.cpp
1 /*
2  * pkglist.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2012, MinGW Project
8  *
9  *
10  * Implementation of the methods for the pkgListViewMaker class, to
11  * support the display of the package list in the mingw-get graphical
12  * user interface.
13  *
14  *
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.
20  *
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.
27  *
28  */
29 #define _WIN32_IE  0x0300
30
31 #include "guimain.h"
32 #include "pkgbase.h"
33 #include "pkglist.h"
34 #include "pkgkeys.h"
35 #include "pkginfo.h"
36
37 #include <wtkexcept.h>
38
39 void AppWindowMaker::InitPackageListView()
40 {
41   /* Create and initialise a ListView window, in which to present
42    * the package list...
43    */
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),
48       AppInstance, NULL
49     );
50   /* ...and set its extended style attributes.
51    */
52   ListView_SetExtendedListViewStyle( PackageListView,
53       LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_ONECLICKACTIVATE
54     );
55
56   /* Propagate the default font, as set for the main window itself,
57    * to the list view control.
58    */
59   SendMessage( PackageListView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
60
61   /* Assign an image list, to furnish the package status icons.
62    */
63   HIMAGELIST iconlist = ImageList_Create( 16, 16, ILC_COLOR32 | ILC_MASK, 13, 0 );
64   for( int index = 0; index <= PKGSTATE( PURGE ); index++ )
65   {
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" );
69   }
70   ListView_SetImageList( PackageListView, iconlist, LVSIL_SMALL );
71
72   /* Initialise the table layout, and assign column headings.
73    */
74   LVCOLUMN table;
75   struct { int id; int width; char *text; } headings[] =
76   {
77     /* Specify the column headings for the package list table.
78      */
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" },
85     /*
86      * This all-null entry terminates the sequence.
87      */
88     { 0, 0, NULL }
89   };
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++ )
93   {
94     /* Loop over the columns, setting the initial width
95      * (in pixels), and assigning the heading to each.
96      */
97     table.cx = headings[index].width;
98     table.pszText = headings[index].text;
99     ListView_InsertColumn( PackageListView, index, &table );
100   }
101   /* "Update" the package list, to initialise the list view.
102    */
103   UpdatePackageList();
104 }
105
106 void AppWindowMaker::UpdatePackageList()
107 {
108   /* Scan the XML package catalogue, adding a ListView item entry
109    * for each package record.
110    */
111   pkgListViewMaker PackageList( PackageListView );
112   pkgDirectory *dir = pkgData->CatalogueAllPackages();
113   dir->InOrder( &PackageList );
114   delete dir;
115 }
116
117 pkgListViewMaker::pkgListViewMaker( HWND pane ): ListView( pane )
118 {
119   /* Constructor: initialise the invariant parameters within the
120    * embedded W32API ListView control structure.
121    */
122   content.stateMask = 0;
123   content.mask = LVIF_PARAM | LVIF_TEXT | LVIF_IMAGE | LVIF_STATE;
124   content.iItem = -1;
125 }
126
127 void pkgListViewMaker::InsertItem( pkgXmlNode *pkg, char *class_name )
128 {
129   /* Private method to add a single package record, as an individual
130    * row item, to the displayed list view table.
131    */
132   content.state = 0;
133   content.iItem += 1;
134   content.iSubItem = 0;
135   content.lParam = (unsigned long)(pkg);
136   content.pszText = "";
137
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.
141    */
142   pkgActionItem avail;
143   pkgXmlNode *rel = pkg->FindFirstAssociate( release_key );
144   while( rel != NULL )
145   {
146     /* Examine each available release specification for the nominated
147      * package; select the one which represents the latest (most recent)
148      * available release.
149      */
150     avail.SelectIfMostRecentFit( rel );
151
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).
155      */
156     if( rel->GetInstallationRecord( rel->GetPropVal( tarname_key, NULL )) != NULL )
157       avail.SelectPackage( rel, to_remove );
158
159     /* Cycle, until all known releases have been examined.
160      */
161     rel = rel->FindNextAssociate( release_key );
162   }
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.
166    */
167   avail.ConfirmInstallationStatus();
168
169   /* Decompose the package tarname identifier for the latest available
170    * release, into its individual package specification properties.
171    */
172   pkgSpecs latest( rel = avail.Selection() );
173
174   /* Allocate a temporary working text buffer, in which to format
175    * package property values for display...
176    */
177   size_t len = strlen( rel->GetPropVal( tarname_key, value_none ) );
178   if( (rel = avail.Selection( to_remove )) != NULL )
179   {
180     /* ...ensuring that it is at least as large as the longer of the
181      * latest or installed release tarname.
182      */
183     size_t altlen = strlen( rel->GetPropVal( tarname_key, value_none ) );
184     if( altlen > len ) len = altlen;
185   }
186   char buf[1 + len];
187
188   /* Choose a suitable icon for representation of the installation
189    * status of the package under consideration...
190    */
191   if( rel != NULL )
192   {
193     /* ...noting that, when it is already installed...
194      */
195     pkgSpecs current( rel );
196     if( latest > current )
197       /*
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...
201        */
202       content.iImage = PKGSTATE( INSTALLED_OLD );
203     else
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...
207        */
208       content.iImage = PKGSTATE( INSTALLED_CURRENT );
209
210     /* ...and also, load the version identification string for
211      * the installed version into the working text buffer.
212      */
213     GetVersionString( buf, &current );
214   }
215   else
216     /* Alternatively, for any package which is not recorded as
217      * installed, choose the icon indicating an available, but
218      * not (yet) installed package.
219      */
220     content.iImage = PKGSTATE( AVAILABLE );
221
222   /* Add the package identification record, as a list item...
223    */
224   ListView_InsertItem( ListView, &content );
225   /*
226    * ...and fill in the text for the package name and class columns.
227    */
228   ListView_SetItemText( ListView, content.iItem, 1, package_name );
229   ListView_SetItemText( ListView, content.iItem, 2, class_name );
230
231   /* When an installed package release has been identified...
232    */
233   if( rel != NULL )
234     /*
235      * ...fill in the version identificaton text appropriately.
236      */
237     ListView_SetItemText( ListView, content.iItem, 3, buf );
238
239   /* Finally, fill in the text for the latest version identification
240    * and package description columns.
241    */
242   ListView_SetItemText( ListView, content.iItem, 4, GetVersionString( buf, &latest ) );
243   ListView_SetItemText( ListView, content.iItem, 5, GetTitle( avail.Selection()) );
244 }
245
246 char *pkgListViewMaker::GetTitle( pkgXmlNode *pkg, const pkgXmlNode *xml_root )
247 {
248   /* A private helper method, to retrieve the title attribute
249    * associated with the description for the nominated package.
250    *
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.
255    */
256   pkgXmlNode *desc = pkg->FindFirstAssociate( description_key );
257   while( desc != NULL )
258   {
259     /* Handling it internally as the const which it should be...
260      */
261     const char *title;
262     if( (title = desc->GetPropVal( title_key, NULL )) != NULL )
263     {
264       /* As soon as we find a description element with an
265        * assigned title attribute...
266        */
267 #if 0
268       /* ...and noting that some package descriptions may
269        * redundantly respecify the package name as a colon
270        * delimited prefix to this title attribute...
271        *
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.
279        */
280       for( const char *prefix = title; *prefix != '\0'; prefix++ )
281         if( *prefix == ':' )
282         {
283           /* ...we strip that redundant prefix away...
284            */
285           title = ++prefix;
286           prefix = "";
287         }
288       /* ...then trim away any leading white space...
289        */
290       while( isspace( *title ) )
291         ++title;
292 #endif
293       /* ...before immediately returning the title, (with the
294        * required cast to non-const).
295        */
296       return (char *)(title);
297     }
298
299     /* If we haven't yet found any title attribute, check for any
300      * further description elements at the current XML nesting level.
301      */
302     desc = desc->FindNextAssociate( description_key );
303   }
304
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...
308    */
309   if( pkg != xml_root )
310     /*
311      * ...then continue the search in the immediately enclosing level.
312      */
313     return GetTitle( pkg->GetParent() );
314
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.
318    */
319   return NULL;
320 }
321
322 static inline
323 char *version_string_copy( char *buf, const char *text, int fill = 0 )
324 {
325   /* Local helper function to construct a package version string
326    * from individual version specific elements of the tarname.
327    */
328   if( text != NULL )
329   {
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).
334      */
335     if( fill != 0 ) *buf++ = fill;
336
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).
344      */
345     do { if( (*buf = *text) != '\0' ) ++buf; } while( *text++ != '\0' );
346   }
347   /* Finally, we return a pointer to the terminating NUL of
348    * the result, so as to facilitate appending further text.
349    */
350   return buf;
351 }
352
353 char *pkgListViewMaker::GetVersionString( char *buf, pkgSpecs *pkg )
354 {
355   /* Helper method to construct a fully qualified version string
356    * from the decomposed package tarname form in a pkgSpecs structure.
357    *
358    * We begin with the concatenation of package version and build ID
359    * fields, retrieved from the pkgSpecs representation...
360    */
361   char *update = version_string_copy( buf, pkg->GetPackageVersion() );
362   update = version_string_copy( update, pkg->GetPackageBuild(), '-' );
363   if( pkg->GetSubSystemVersion() != NULL )
364   {
365     /* ...then, we append the sub-system ID, if applicable...
366      */
367     update = version_string_copy( update, pkg->GetSubSystemName(), '-' );
368     update = version_string_copy( update, pkg->GetSubSystemVersion(), '-' );
369     update = version_string_copy( update, pkg->GetSubSystemBuild(), '-' );
370   }
371   /* ...and finally, we return a pointer to the buffer in which
372    * we constructed the fully qualified version string.
373    */
374   return buf;
375 }
376
377 void pkgListViewMaker::Dispatch( pkgXmlNode *package )
378 {
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).
383    */
384   if( package->IsElementOfType( package_key ) )
385   {
386     /* Assemble the package name into the list view record block.
387      */
388     package_name = (char *)(package->GetPropVal( name_key, value_unknown ));
389
390     /* When processing a pkgDirectory entry for a package entity,
391      * generate a sub-directory listing for any contained package
392      * components...
393      */
394     pkgDirectory *dir;
395     if( (dir = EnumerateComponents( package )) != NULL )
396     {
397       /* ...and recurse, to process any which are found...
398        */
399       dir->InOrder( this );
400       delete dir;
401     }
402     else
403       /* ...otherwise, simply insert an unclassified list entry
404        * for the bare package name, omitting the component class.
405        */
406       InsertItem( package, "" );
407   }
408   else if( package->IsElementOfType( component_key ) )
409   {
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.
414      */
415     InsertItem( package, (char *)(package->GetPropVal( class_key, "" )) );
416   }
417 }
418
419 /* $RCSfile$: end of file */