6 * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7 * Copyright (C) 2012, 2013, MinGW.org Project
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.
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 0x0400
35 #define PKG_AFFILIATE_ALL 1
37 static const char *package_group_key = "package-group";
38 static const char *package_group_all = "All Packages";
40 /* The following are candidates for inclusion in "pkgkeys";
41 * for now, we may keep them as local defines.
43 static const char *select_key = "select";
44 static const char *expand_key = "expand";
45 static const char *value_true = "true";
48 pkgXmlNode *is_existing_group( pkgXmlNode *dbase, const char *name )
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.
54 while( dbase != NULL )
56 /* We haven't yet considered all XML database entries, at the
57 * requisite level; check the current reference entry...
59 if( safe_strcmp( strcasecmp, name, dbase->GetPropVal( name_key, NULL )) )
61 * ...returning it immediately, if it is a named duplicate of
62 * the entry we are seeking to add.
66 /* When no duplicate found yet, move on to consider the next
67 * entry at the requisite XML database level, if any.
69 dbase = dbase->FindNextAssociate( package_group_key );
71 /* If we get to here, then no duplicate was found; the database
72 * reference must have become NULL, which we return.
78 map_package_group_hierarchy_recursive( pkgXmlNode *dbase, pkgXmlNode *group )
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.
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 )
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...
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 );
98 /* ...and attach it as the last sibling of any existing
99 * in-core records at the requisite level.
101 ref = (pkgXmlNode *)(dbase->LinkEndChild( ref ));
105 group = group->FindFirstAssociate( package_group_key );
106 while( group != NULL )
108 /* ...to capture any newly specified children of this group,
109 * regardless of whether the group already existed, or has
112 map_package_group_hierarchy_recursive( ref, group );
113 group = group->FindNextAssociate( package_group_key );
118 map_package_group_hierarchy( pkgXmlNode *dbase, pkgXmlNode *catalogue )
120 /* Helper to kick-start the recursive mapping of external XML
121 * records into the in-core package group hierarchy image.
123 const char *group_hierarchy_key = "package-group-hierarchy";
124 if( (catalogue = catalogue->FindFirstAssociate( group_hierarchy_key )) != NULL )
126 /* We found a package group hierarchy specification...
128 dbase = dbase->FindFirstAssociate( package_group_key );
129 do { pkgXmlNode *group = catalogue->FindFirstAssociate( package_group_key );
130 while( group != NULL )
132 /* ...recursively map each top level package group
133 * which is specified within it...
135 map_package_group_hierarchy_recursive( dbase, group );
136 group = group->FindNextAssociate( package_group_key );
138 /* ...and repeat for any other package group hierarchy
139 * specifications we may encounter.
141 catalogue = catalogue->FindNextAssociate( group_hierarchy_key );
142 } while( catalogue != NULL );
147 load_package_group_hierarchy( HWND display, HTREEITEM parent, pkgXmlNode *group )
149 /* Helper to load the package group hierarchy from it's in-core XML
150 * database representation, into a Windows tree view control.
153 static long root_affiliate = PKG_AFFILIATE_ALL;
155 /* Establish initial state for the tree view item insertion control.
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;
163 do { /* For each package group specified at the current level in the
164 * package group hierarchy, retrieve its name from the XML record...
166 pkgXmlNode *first_child = group->FindFirstAssociate( package_group_key );
167 ref.item.pszText = (char *)(group->GetPropVal( name_key, value_unknown ));
169 * ...note whether any descendants are to be specified...
171 ref.item.cChildren = (first_child == NULL) ? 0 : 1;
173 * ...and add a corresponding entry to the tree view.
175 ref.hInsertAfter = TreeView_InsertItem( display, &ref );
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).
185 const char *option = group->GetPropVal( select_key, NULL );
186 if( (option != NULL) && (strcmp( option, value_true ) == 0) )
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).
192 TreeView_SelectItem( display, ref.hInsertAfter );
194 /* Any descendants specified, for the current group, must be
195 * processed recursively...
197 if( first_child != NULL )
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...
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) )
208 * ...and expand to the requested level of visibility.
210 TreeView_Expand( display, ref.hInsertAfter, TVE_EXPAND );
212 /* Repeat for any other package groups which may be specified at
213 * the current level within the hierarchy.
215 group = group->FindNextAssociate( package_group_key );
216 } while( group != NULL );
219 inline void pkgXmlNode::SetPackageGroupHierarchyMapper()
221 /* Method to assign the preceding helper, as the active handler
222 * for pkgXmlNode::MapPackageGroupHierarchy().
224 PackageGroupHierarchyMapper = map_package_group_hierarchy;
227 EXTERN_C void pkgInitCategoryTreeGraft( pkgXmlNode *root )
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.
234 pkgXmlNode *pkgtree = new pkgXmlNode( package_group_key );
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 );
243 void AppWindowMaker::InitPackageTreeView()
245 /* Create and initialise a TreeView window, in which to present
246 * the package group hierarchy display...
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),
254 /* Assign the application's chosen default font, for use when
255 * displaying the category headings within the tree view.
257 SendMessage( PackageTreeView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
258 SendMessage( PackageTreeView, TVM_SETINDENT, 0, 0 );
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.
264 pkgXmlNode *tree = pkgData->GetRoot();
265 if( (tree = tree->FindFirstAssociate( package_group_key )) == NULL )
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).
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;
278 /* Map this sole entry into the tree view pane.
280 TreeView_InsertItem( PackageTreeView, &entry );
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.
288 load_package_group_hierarchy( PackageTreeView, TVI_ROOT, tree );
290 /* Augment this "All Packages" hierarchy...
292 if( (tree = new pkgXmlNode( package_group_key )) != NULL )
294 /* ...by the addition of a further "Basic Setup"
295 * category at root level...
297 tree->SetAttribute( name_key, "Basic Setup" );
298 if( SetupToolInvoked )
300 * ...which becomes the default group selection when
301 * running the installer from within the setup tool.
303 tree->SetAttribute( select_key, value_true );
304 load_package_group_hierarchy( PackageTreeView, TVI_ROOT, tree );
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.
315 static bool is_child_affiliate( HWND tree, TVITEM *ref, const char *name )
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
322 HTREEITEM mark = ref->hItem;
323 do { if( TreeView_GetItem( tree, ref )
324 && (strcasecmp( name, ref->pszText ) == 0) )
326 * ...immediately promoting any such match as an
327 * affiliate of the current selection...
331 /* ...otherwise, recursively traverse each child
332 * subtree, at the current level...
334 if( ((ref->hItem = TreeView_GetChild( tree, mark )) != NULL)
335 && is_child_affiliate( tree, ref, name ) )
337 * ...again, immediately promoting any match...
341 /* ...or otherwise, rewind to the marked match point,
342 * whence we repeat the search into its next immediate
343 * sibling subtree, (if any).
345 mark = ref->hItem = TreeView_GetNextSibling( tree, mark );
346 } while( mark != NULL );
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.
355 static inline long group_attributes( HWND tree, TVITEM *ref )
357 /* Helper to retrieve the application specific attributes
358 * which are associated with a package group tree item.
360 ref->mask = TVIF_PARAM;
361 TreeView_GetItem( tree, ref );
365 static inline bool is_affiliated( HWND tree, const char *name )
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.
373 if( ((ref.hItem = TreeView_GetSelection( tree )) == NULL)
374 || ((group_attributes( tree, &ref ) & PKG_AFFILIATE_ALL) != 0) )
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.
382 /* At any level in the hierarchy, other than the root, the group
383 * name MUST be matched EXPLICITLY; note that...
386 /* ...an unspecified name can never satisfy this criterion.
390 /* As a basis for comparison, we must provide a working buffer
391 * into which we may retrieve names from the tree view...
393 char ref_text[ref.cchTextMax = 1 + strlen( name )];
395 /* ...beginning with the currently selected tree view item...
397 ref.mask = TVIF_TEXT;
398 ref.pszText = ref_text;
399 if( TreeView_GetItem( tree, &ref ) && (strcasecmp( name, ref_text ) == 0) )
401 * ...and returning immediately, when it is found to match.
405 /* When the selected item doesn't match, we also look for a
406 * possible match among its descendants, if any...
408 if( (ref.hItem = TreeView_GetChild( tree, ref.hItem )) != NULL )
410 * ...which we also promote as a match.
412 return is_child_affiliate( tree, &ref, name );
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.
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).
426 HWND AppWindowMaker::PackageTreeView = NULL;
427 bool AppWindowMaker::IsPackageGroupAffiliate( pkgXmlNode *package )
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.
437 static const char *group_key = "group";
438 static const char *affiliate_key = "affiliate";
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...
446 pkgXmlNode *top = package->GetDocumentRoot();
447 while( package != top )
449 /* ...in search of applicable "affiliate" declarations.
451 pkgXmlNode *group = package->FindFirstAssociate( affiliate_key );
452 while( group != NULL )
454 /* For each declared affiliation, check if it matches the
455 * current selection in the package group hierarchy...
457 const char *group_name = group->GetPropVal( group_key, NULL );
458 if( is_affiliated( PackageTreeView, group_name ) )
460 * ...immediately returning any successful match; (just one
461 * positive match is sufficient to determine affiliation).
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...
469 group = group->FindNextAssociate( affiliate_key );
471 /* ...and at enclosing levels, as may be necessary.
473 package = package->GetParent();
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.
479 return is_affiliated( PackageTreeView, NULL );
482 /* $RCSfile$: end of file */