OSDN Git Service

Remove a grossly heuristic list view hack.
[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, immediately return it,
266        * (with the required cast to non-const).
267        */
268       return (char *)(title);
269
270     /* If we haven't yet found any title attribute, check for any
271      * further description elements at the current XML nesting level.
272      */
273     desc = desc->FindNextAssociate( description_key );
274   }
275
276   /* If we've exhausted all description elements at the current XML
277    * nesting level, without finding a title attribute, and we haven't
278    * checked all enclosing levels back to the document root...
279    */
280   if( pkg != xml_root )
281     /*
282      * ...then continue the search in the immediately enclosing level.
283      */
284     return GetTitle( pkg->GetParent() );
285
286   /* If we get to here, then we've searched all levels back to the
287    * document root, and have failed to find any title attribute; we
288    * have nothing to return.
289    */
290   return NULL;
291 }
292
293 static inline
294 char *version_string_copy( char *buf, const char *text, int fill = 0 )
295 {
296   /* Local helper function to construct a package version string
297    * from individual version specific elements of the tarname.
298    */
299   if( text != NULL )
300   {
301     /* First, if a fill character is specified, copy it as the
302      * first character of the result; (we assume that we are
303      * appending to a previously constructed result, and that
304      * this is the field separator character).
305      */
306     if( fill != 0 ) *buf++ = fill;
307
308     /* Now, append "text" up to, and including its final NUL
309      * terminator; (note that we do NOT guard against buffer
310      * overrun, as we have complete control over the calling
311      * context, where we allocated a result buffer at least
312      * as long as the tarname string from which the composed
313      * version string is extracted, and the composed result
314      * can never exceed the original length of this).
315      */
316     do { if( (*buf = *text) != '\0' ) ++buf; } while( *text++ != '\0' );
317   }
318   /* Finally, we return a pointer to the terminating NUL of
319    * the result, so as to facilitate appending further text.
320    */
321   return buf;
322 }
323
324 char *pkgListViewMaker::GetVersionString( char *buf, pkgSpecs *pkg )
325 {
326   /* Helper method to construct a fully qualified version string
327    * from the decomposed package tarname form in a pkgSpecs structure.
328    *
329    * We begin with the concatenation of package version and build ID
330    * fields, retrieved from the pkgSpecs representation...
331    */
332   char *update = version_string_copy( buf, pkg->GetPackageVersion() );
333   update = version_string_copy( update, pkg->GetPackageBuild(), '-' );
334   if( pkg->GetSubSystemVersion() != NULL )
335   {
336     /* ...then, we append the sub-system ID, if applicable...
337      */
338     update = version_string_copy( update, pkg->GetSubSystemName(), '-' );
339     update = version_string_copy( update, pkg->GetSubSystemVersion(), '-' );
340     update = version_string_copy( update, pkg->GetSubSystemBuild(), '-' );
341   }
342   /* ...and finally, we return a pointer to the buffer in which
343    * we constructed the fully qualified version string.
344    */
345   return buf;
346 }
347
348 void pkgListViewMaker::Dispatch( pkgXmlNode *package )
349 {
350   /* Implementation of the "dispatcher" method, which is required
351    * by any derivative of the pkgDirectoryViewerEngine class, for
352    * dispatching the content of the directory to the display service,
353    * (which, in this case, populates the list view window pane).
354    */
355   if( package->IsElementOfType( package_key ) )
356   {
357     /* Assemble the package name into the list view record block.
358      */
359     package_name = (char *)(package->GetPropVal( name_key, value_unknown ));
360
361     /* When processing a pkgDirectory entry for a package entity,
362      * generate a sub-directory listing for any contained package
363      * components...
364      */
365     pkgDirectory *dir;
366     if( (dir = EnumerateComponents( package )) != NULL )
367     {
368       /* ...and recurse, to process any which are found...
369        */
370       dir->InOrder( this );
371       delete dir;
372     }
373     else
374       /* ...otherwise, simply insert an unclassified list entry
375        * for the bare package name, omitting the component class.
376        */
377       InsertItem( package, "" );
378   }
379   else if( package->IsElementOfType( component_key ) )
380   {
381     /* Handle the recursive calls for the component sub-directory,
382      * inheriting the package name entry from the original package
383      * entity, and emit an appropriately classified list view entry
384      * for each identified component package.
385      */
386     InsertItem( package, (char *)(package->GetPropVal( class_key, "" )) );
387   }
388 }
389
390 /* $RCSfile$: end of file */