OSDN Git Service

62ac2eedc7a6d2d15dc90c8a087010908f2d78ea
[mingw/mingw-get.git] / src / pkgtree.cpp
1 /*
2  * pkgtree.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2012, 2013, MinGW.org Project
8  *
9  *
10  * Implementation of the methods for the pkgTreeViewMaker class, and
11  * its AppWindowMaker client API, to support the display of the package
12  * category tree in the mingw-get graphical 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 0x0400
30
31 #include "guimain.h"
32 #include "pkgbase.h"
33 #include "pkgkeys.h"
34
35 #define PKG_AFFILIATE_ALL  1
36
37 static const char *package_group_key = "package-group";
38 static const char *package_group_all = "All Packages";
39
40 /* The following are candidates for inclusion in "pkgkeys";
41  * for now, we may keep them as local defines.
42  */
43 static const char *select_key = "select";
44 static const char *expand_key = "expand";
45 static const char *value_true = "true";
46
47 static inline
48 pkgXmlNode *is_existing_group( pkgXmlNode *dbase, const char *name )
49 {
50   /* Helper to check for prior existence of a specified group name,
51    * at a specified level within the XML representation of the package
52    * group hierarchy, so we may avoid adding redundant duplicates.
53    */
54   while( dbase != NULL )
55   {
56     /* We haven't yet considered all XML database entries, at the
57      * requisite level; check the current reference entry...
58      */
59     if( safe_strcmp( strcasecmp, name, dbase->GetPropVal( name_key, NULL )) )
60       /*
61        * ...returning it immediately, if it is a named duplicate of
62        * the entry we are seeking to add.
63        */
64       return dbase;
65
66     /* When no duplicate found yet, move on to consider the next
67      * entry at the requisite XML database level, if any.
68      */
69     dbase = dbase->FindNextAssociate( package_group_key );
70   }
71   /* If we get to here, then no duplicate was found; the database
72    * reference must have become NULL, which we return.
73    */
74   return dbase;
75 }
76
77 static void
78 map_package_group_hierarchy_recursive( pkgXmlNode *dbase, pkgXmlNode *group )
79 {
80   /* Helper to recursively reconstruct an in-core image of the XML
81    * structure of the package list hierarchy, as it is specified in
82    * the package list read from external storage.
83    */
84   const char *name = group->GetPropVal( name_key, value_unknown );
85   pkgXmlNode *ref, *map = dbase->FindFirstAssociate( package_group_key ); 
86   if( (ref = is_existing_group( map, name )) == NULL )
87   {
88     /* The group name we are seeking to add doesn't yet appear at
89      * the requisite level within the in-core XML image; construct
90      * a new record, comprising the relevant data as represented
91      * in the external database...
92      */
93     ref = new pkgXmlNode( package_group_key );
94     ref->SetAttribute( name_key, name );
95     if( (name = group->GetPropVal( expand_key, NULL )) != NULL )
96       ref->SetAttribute( expand_key, name );
97
98     /* ...and attach it as the last sibling of any existing
99      * in-core records at the requisite level.
100      */
101     ref = (pkgXmlNode *)(dbase->LinkEndChild( ref ));
102   }
103   /* Recurse...
104    */
105   group = group->FindFirstAssociate( package_group_key );
106   while( group != NULL )
107   {
108     /* ...to capture any newly specified children of this group,
109      * regardless of whether the group already existed, or has
110      * just been added.
111      */
112     map_package_group_hierarchy_recursive( ref, group );
113     group = group->FindNextAssociate( package_group_key );
114   }
115 }
116
117 static void
118 map_package_group_hierarchy( pkgXmlNode *dbase, pkgXmlNode *catalogue )
119 {
120   /* Helper to kick-start the recursive mapping of external XML
121    * records into the in-core package group hierarchy image.
122    */
123   const char *group_hierarchy_key = "package-group-hierarchy";
124   if( (catalogue = catalogue->FindFirstAssociate( group_hierarchy_key )) != NULL )
125   {
126     /* We found a package group hierarchy specification...
127      */
128     dbase = dbase->FindFirstAssociate( package_group_key );
129     do { pkgXmlNode *group = catalogue->FindFirstAssociate( package_group_key );
130           while( group != NULL )
131           {
132             /* ...recursively map each top level package group
133              * which is specified within it...
134              */
135             map_package_group_hierarchy_recursive( dbase, group );
136             group = group->FindNextAssociate( package_group_key );
137           }
138           /* ...and repeat for any other package group hierarchy
139            * specifications we may encounter.
140            */
141           catalogue = catalogue->FindNextAssociate( group_hierarchy_key );
142        } while( catalogue != NULL );
143   }
144 }
145
146 static void
147 load_package_group_hierarchy( HWND display, HTREEITEM parent, pkgXmlNode *group )
148 {
149   /* Helper to load the package group hierarchy from it's in-core XML
150    * database representation, into a Windows tree view control.
151    */
152   TVINSERTSTRUCT ref;
153   static long root_affiliate = PKG_AFFILIATE_ALL;
154
155   /* Establish initial state for the tree view item insertion control.
156    */
157   ref.hParent = parent;
158   ref.hInsertAfter = TVI_FIRST;
159   ref.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_CHILDREN;
160   ref.item.lParam = root_affiliate;
161   root_affiliate = 0;
162
163   do { /* For each package group specified at the current level in the
164         * package group hierarchy, retrieve its name from the XML record...
165         */
166        pkgXmlNode *first_child = group->FindFirstAssociate( package_group_key );
167        ref.item.pszText = (char *)(group->GetPropVal( name_key, value_unknown ));
168        /*
169         * ...note whether any descendants are to be specified...
170         */
171        ref.item.cChildren = (first_child == NULL) ? 0 : 1;
172        /*
173         * ...and add a corresponding entry to the tree view.
174         */
175        ref.hInsertAfter = TreeView_InsertItem( display, &ref );
176        ref.item.lParam = 0;
177
178        /* Check if the current group is to be made the initial selection,
179         * when the tree view is first displayed; the last entry so marked
180         * will become the actual initial selection.  (Note that, to ensure
181         * that this setting cannot be abused by package maintainers, it is
182         * defined internally by mingw-get; it is not propagated from any
183         * external XML resource).
184         */
185        const char *option = group->GetPropVal( select_key, NULL );
186        if( (option != NULL) && (strcmp( option, value_true ) == 0) )
187          /*
188           * Make any group, which marked with the "select" attribute, the
189           * active selection; (note that this selection may be superseded,
190           * should another similarly marked group be encountered later).
191           */
192          TreeView_SelectItem( display, ref.hInsertAfter );
193
194        /* Any descendants specified, for the current group, must be
195         * processed recursively...
196         */
197        if( first_child != NULL )
198        {
199          /* There is at least one generation of children, of the current
200           * tree view entry; check if this entry should be expanded, so as
201           * to make its children visible in the initial view, recursively
202           * evaluate to identify further generations of descendants...
203           */
204          option = group->GetPropVal( expand_key, NULL );
205          load_package_group_hierarchy( display, ref.hInsertAfter, first_child );
206          if( (option != NULL) && (strcmp( option, value_true ) == 0) )
207            /*
208             * ...and expand to the requested level of visibility.
209             */
210            TreeView_Expand( display, ref.hInsertAfter, TVE_EXPAND );
211        }
212        /* Repeat for any other package groups which may be specified at
213         * the current level within the hierarchy.
214         */
215        group = group->FindNextAssociate( package_group_key );
216      } while( group != NULL );
217 }
218
219 inline void pkgXmlNode::SetPackageGroupHierarchyMapper()
220 {
221   /* Method to assign the preceding helper, as the active handler
222    * for pkgXmlNode::MapPackageGroupHierarchy().
223    */
224   PackageGroupHierarchyMapper = map_package_group_hierarchy;
225 }
226
227 EXTERN_C void pkgInitCategoryTreeGraft( pkgXmlNode *root )
228 {
229   /* Helper function to create the graft point, at which the
230    * category tree records, as defined by the XML package group
231    * hierarchy, will be attached to the internal representation
232    * of the XML database image.
233    */
234   pkgXmlNode *pkgtree = new pkgXmlNode( package_group_key );
235
236   pkgtree->SetPackageGroupHierarchyMapper();
237   pkgtree->SetAttribute( name_key, package_group_all );
238   pkgtree->SetAttribute( select_key, value_true );
239   pkgtree->SetAttribute( expand_key, value_true );
240   root->LinkEndChild( pkgtree );
241 }
242
243 void AppWindowMaker::InitPackageTreeView()
244 {
245   /* Create and initialise a TreeView window, in which to present
246    * the package group hierarchy display...
247    */
248   PackageTreeView = CreateWindow( WC_TREEVIEW, NULL, WS_VISIBLE |
249       WS_BORDER | WS_CHILD | TVS_FULLROWSELECT | TVS_SHOWSELALWAYS, 0, 0, 0, 0,
250       AppWindow, (HMENU)(ID_PACKAGE_TREEVIEW),
251       AppInstance, NULL
252     );
253
254   /* Assign the application's chosen default font, for use when
255    * displaying the category headings within the tree view.
256    */
257   SendMessage( PackageTreeView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
258   SendMessage( PackageTreeView, TVM_SETINDENT, 0, 0 );
259
260   /* Retrieve the root category entry, if any, as recorded in
261    * the package XML database, for assignment as the root entry
262    * in the category tree view.
263    */
264   pkgXmlNode *tree = pkgData->GetRoot();
265   if( (tree = tree->FindFirstAssociate( package_group_key )) == NULL )
266   {
267     /* There was no category tree recorded in the XML database;
268      * create an artificial root entry, (which will then become
269      * the sole entry in our tree view).
270      */
271     TVINSERTSTRUCT entry;
272     entry.hParent = TVI_ROOT;
273     entry.item.mask = TVIF_TEXT | TVIF_CHILDREN;
274     entry.item.pszText = (char *)(package_group_all);
275     entry.hInsertAfter = TVI_ROOT;
276     entry.item.cChildren = 0;
277
278     /* Map this sole entry into the tree view pane.
279      */
280     TreeView_InsertItem( PackageTreeView, &entry );
281   }
282   else
283   { /* The package group hierarchy has been incorporated into
284      * the in-core image of the XML database; create a windows
285      * "tree view", into which we load a representation of the
286      * structure of this hierarchy.
287      */
288     load_package_group_hierarchy( PackageTreeView, TVI_ROOT, tree );
289
290     /* Augment this "All Packages" hierarchy...
291      */
292     if( (tree = new pkgXmlNode( package_group_key )) != NULL )
293     {
294       /* ...by the addition of a further "Basic Setup"
295        * category at root level...
296        */
297       tree->SetAttribute( name_key, "Basic Setup" );
298       if( SetupToolInvoked )
299         /*
300          * ...which becomes the default group selection when
301          * running the installer from within the setup tool.
302          */
303         tree->SetAttribute( select_key, value_true );
304       load_package_group_hierarchy( PackageTreeView, TVI_ROOT, tree );
305
306       /* The XML node, from which we created the "Basic Setup"
307        * group entry, has not been attached to the in-core image
308        * of the XML database; we have no need to keep it.
309        */
310       delete tree;
311     }
312   }
313 }
314
315 static bool is_child_affiliate( HWND tree, TVITEM *ref, const char *name )
316 {
317   /* Helper method to recursively traverse a subtree of the
318    * package group hierarchy, to check if a specified package
319    * group name matches any descendant of the current group
320    * selection...
321    */
322   HTREEITEM mark = ref->hItem;
323   do { if(  TreeView_GetItem( tree, ref )
324        &&  (strcasecmp( name, ref->pszText ) == 0)  )
325          /*
326           * ...immediately promoting any such match as an
327           * affiliate of the current selection...
328           */
329          return true;
330
331        /* ...otherwise, recursively traverse each child
332         * subtree, at the current level...
333         */
334        if( ((ref->hItem = TreeView_GetChild( tree, mark )) != NULL)
335        &&    is_child_affiliate( tree, ref, name )                   )
336          /*
337           * ...again, immediately promoting any match...
338           */
339          return true;
340
341        /* ...or otherwise, rewind to the marked match point,
342         * whence we repeat the search into its next immediate
343         * sibling subtree, (if any).
344         */
345        mark = ref->hItem = TreeView_GetNextSibling( tree, mark );
346      } while( mark != NULL );
347
348   /* If we get to here, the specified package group name is NOT
349    * an affiliate of any descendant, at the current match level,
350    * of the currently selected package group.
351    */
352   return false;
353 }
354
355 static inline long group_attributes( HWND tree, TVITEM *ref )
356 {
357   /* Helper to retrieve the application specific attributes
358    * which are associated with a package group tree item.
359    */
360   ref->mask = TVIF_PARAM;
361   TreeView_GetItem( tree, ref );
362   return ref->lParam;
363 }
364
365 static inline bool is_affiliated( HWND tree, const char *name )
366 {
367   /* Helper to initiate a determination if a specified package
368    * group name is an affiliate of the currently selected package
369    * group; i.e. if it matches the name of the selected group, or
370    * any of its descendants.
371    */
372   TVITEM ref;
373   if(  ((ref.hItem = TreeView_GetSelection( tree )) == NULL)
374   ||   ((group_attributes( tree, &ref ) & PKG_AFFILIATE_ALL) != 0)  )
375     /*
376      * Before proceeding further, we may note that ANY group name
377      * is considered to be IMPLICITLY matched, for specified groups
378      * (nominally at the root) within the package group tree.
379      */
380     return true;
381
382   /* At any level in the hierarchy, other than the root, the group
383    * name MUST be matched EXPLICITLY; note that...
384    */
385   if( name == NULL )
386     /* ...an unspecified name can never satisfy this criterion.
387      */
388     return false;
389
390   /* As a basis for comparison, we must provide a working buffer
391    * into which we may retrieve names from the tree view...
392    */
393   char ref_text[ref.cchTextMax = 1 + strlen( name )];
394
395   /* ...beginning with the currently selected tree view item...
396    */
397   ref.mask = TVIF_TEXT;
398   ref.pszText = ref_text;
399   if( TreeView_GetItem( tree, &ref ) && (strcasecmp( name, ref_text ) == 0) )
400     /*
401      * ...and returning immediately, when it is found to match.
402      */
403     return true;
404
405   /* When the selected item doesn't match, we also look for a
406    * possible match among its descendants, if any...
407    */
408   if( (ref.hItem = TreeView_GetChild( tree, ref.hItem )) != NULL )
409     /*
410      * ...which we also promote as a match.
411      */
412     return is_child_affiliate( tree, &ref, name );
413
414   /* If we get to here, the selected tree item doesn't match;
415    * nor does it have any descendants among which a possible
416    * match might be found.
417    */
418   return false;
419 }
420
421 /* We use static methods to reference the tree view class object;
422  * thus, we must define a static reference pointer.  (Note that its
423  * initial assignment to NULL will be updated, when the tree view
424  * class object is instantiated).
425  */
426 HWND AppWindowMaker::PackageTreeView = NULL;
427 bool AppWindowMaker::IsPackageGroupAffiliate( pkgXmlNode *package )
428 {
429   /* Method to determine if a particular pkgXmlNode object is
430    * an affiliate of the currently selected package group, as
431    * specified in the tree view of the package group hierarchy.
432    * Note that this must be a static method, so that it may be
433    * invoked by methods of the pkgXmlNode object, without any
434    * requirement for that object to hold a reference to the
435    * AppWindowMaker object which owns the tree view.
436    */
437   static const char *group_key = "group";
438   static const char *affiliate_key = "affiliate";
439
440   /* An affiliation may be declared at any level, between the
441    * root of the XML document, and the pkgXmlNode specifications
442    * to which it applies; save an end-point reference, so that
443    * we may avoid overrunning the document root, as we walk
444    * back through the document...
445    */
446   pkgXmlNode *top = package->GetDocumentRoot();
447   while( package != top )
448   {
449     /* ...in search of applicable "affiliate" declarations.
450      */
451     pkgXmlNode *group = package->FindFirstAssociate( affiliate_key );
452     while( group != NULL )
453     {
454       /* For each declared affiliation, check if it matches the
455        * current selection in the package group hierarchy...
456        */
457       const char *group_name = group->GetPropVal( group_key, NULL );
458       if( is_affiliated( PackageTreeView, group_name ) )
459         /*
460          * ...immediately returning any successful match; (just one
461          * positive match is sufficient to determine affiliation).
462          */
463         return true;
464
465       /* When no affiliation has yet been identified, repeat the
466        * check for any further "affiliate" declarations at the
467        * current level within the XML document hierarchy...
468        */
469       group = group->FindNextAssociate( group_key );
470     }
471     /* ...and at enclosing levels, as may be necessary.
472      */
473     package = package->GetParent();
474   }
475   /* If we get to here, then we found no "affiliate" declarations to
476    * be evaluated; only a root match in the package group hierarchy
477    * is sufficient, to determine affiliation.
478    */
479   return is_affiliated( PackageTreeView, NULL );
480 }
481
482 /* $RCSfile$: end of file */