The MinGW.org Installation Manager Tool
Revision | 2a0474b5df077959fd7ca149eefcd9dc4af61e1e (tree) |
---|---|
Time | 2013-07-15 06:13:50 |
Author | Keith Marshall <keithmarshall@user...> |
Commiter | Keith Marshall |
Implement GUI filtering of package list by group selection.
@@ -1,3 +1,42 @@ | ||
1 | +2013-07-14 Keith Marshall <keithmarshall@users.sourceforge.net> | |
2 | + | |
3 | + Implement GUI filtering of package list by group selection. | |
4 | + | |
5 | + * src/pkgbase.h (pkgXmlNode::IsVisibleGroupMember): | |
6 | + (pkgXmlNode::IsVisibleClass): New inline methods; declare them. | |
7 | + (pkgXmlNode::MapPackageGroupHierarchy): New inline method; declare it. | |
8 | + (pkgXmlNode::SetPackageGroupHierarchyMapper): Likewise; it sets up... | |
9 | + (pkgXmlNode::PackageGroupHierarchyMapper): ...this new private static | |
10 | + function pointer; declare it as a function reference, in terms of... | |
11 | + (pkgXmlNode::GroupHierarchyMapper): ...this new typedef; define it. | |
12 | + | |
13 | + * src/guimain.h (AppWindowMaker::PackageTreeView): Make it static. | |
14 | + (AppWindowMaker::IsPackageGroupAffiliate): Declare new static method. | |
15 | + | |
16 | + * src/pkgbind.cpp (pkgRepository::GetPackageList): Make it invoke... | |
17 | + (pkgXmlNode::MapPackageGroupHierarchy): ...this; implement it. | |
18 | + | |
19 | + * src/pkglist.cpp (pkgXmlNode::IsVisibleGroupMember): Implement it. | |
20 | + (pkgXmlNode::IsVisibleClass): Likewise; just provide a stub, for now. | |
21 | + (pkgListViewMaker::Dispatch): Use them to filter package list content. | |
22 | + (AppWindowMaker::UpdatePackageList): Redraw main window when done. | |
23 | + | |
24 | + * src/pkgtree.cpp [_WIN32_IE >= 0x0400]: Require this. | |
25 | + (AppWindowMaker::PackageTreeView): Define and initialise it. | |
26 | + (AppWindowMaker::InitPackageTreeView): Extend implementation; use... | |
27 | + (map_package_group_hierarchy_recursive, load package_group_hierarchy): | |
28 | + (is_existing_group, map_package_group_hierarchy): ...these; implement | |
29 | + them locally, as file scoped static helper functions. | |
30 | + (select_key): New static local string constant; define it. | |
31 | + (pkgInitCategoryTreeGraft): Use it; also have it invoke... | |
32 | + (pkgXmlNode::SetPackageGroupHierarchyMapper): ...this; implement it. | |
33 | + (AppWindowMaker::IsPackageGroupAffiliate): Implement it; it uses... | |
34 | + (is_affiliated, is_child_affiliate): ...these helpers; provide them as | |
35 | + locally implemented, file scoped static functions. | |
36 | + | |
37 | + * src/pkgdata.cpp (AppWindowMaker::OnNotify): Add handler for... | |
38 | + [ID_PACKAGE_TREEVIEW && TVN_SELCHANGED]: ...this notification. | |
39 | + | |
1 | 40 | 2013-07-12 Keith Marshall <keithmarshall@users.sourceforge.net> |
2 | 41 | |
3 | 42 | Correct a static string buffer aliasing issue. |
@@ -185,6 +185,7 @@ class AppWindowMaker: public WTK::MainWindowMaker | ||
185 | 185 | int DispatchDialogueThread( int, pkgDialogueThread * ); |
186 | 186 | |
187 | 187 | void LoadPackageData( bool = false ); |
188 | + static bool IsPackageGroupAffiliate( pkgXmlNode * ); | |
188 | 189 | void ClearPackageList( void ){ ListView_DeleteAllItems( PackageListView ); } |
189 | 190 | void UpdatePackageList( void ); |
190 | 191 |
@@ -210,7 +211,7 @@ class AppWindowMaker: public WTK::MainWindowMaker | ||
210 | 211 | pkgProgressMeter *AttachedProgressMeter; |
211 | 212 | HFONT DefaultFont; |
212 | 213 | |
213 | - HWND PackageTreeView; | |
214 | + static HWND PackageTreeView; | |
214 | 215 | void InitPackageTreeView( void ); |
215 | 216 | |
216 | 217 | HWND PackageListView; |
@@ -151,12 +151,28 @@ class pkgXmlNode : public TiXmlElement | ||
151 | 151 | return this ? strcmp( GetName(), tagname ) == 0 : false; |
152 | 152 | } |
153 | 153 | |
154 | + /* Methods to determine which packages should be displayed | |
155 | + * in the package list pane of the GUI client. | |
156 | + */ | |
157 | + inline bool IsVisibleGroupMember(); | |
158 | + inline bool IsVisibleClass(); | |
159 | + | |
154 | 160 | /* Methods for retrieving the system root management records |
155 | 161 | * for a specified installed subsystem. |
156 | 162 | */ |
157 | 163 | pkgXmlNode *GetSysRoot( const char* ); |
158 | 164 | pkgXmlNode *GetInstallationRecord( const char* ); |
159 | 165 | |
166 | + /* Methods for mapping the package group hierarchy. | |
167 | + */ | |
168 | + inline void SetPackageGroupHierarchyMapper(); | |
169 | + inline void MapPackageGroupHierarchy( pkgXmlNode* ); | |
170 | + | |
171 | + /* Type definition for a helper function, which must be assigned | |
172 | + * to the package group hierarchy mapper, in order to enable it. | |
173 | + */ | |
174 | + typedef void (*GroupHierarchyMapper)( pkgXmlNode*, pkgXmlNode* ); | |
175 | + | |
160 | 176 | /* The following pair of methods provide an iterator |
161 | 177 | * for enumerating the contained nodes, within the owner, |
162 | 178 | * which themselves exhibit a specified tagname. |
@@ -195,6 +211,11 @@ class pkgXmlNode : public TiXmlElement | ||
195 | 211 | */ |
196 | 212 | int InvokeScript( int, const char* ); |
197 | 213 | int DispatchScript( int, const char*, const char*, pkgXmlNode* ); |
214 | + | |
215 | + /* Hook via which the requisite helper function is attached | |
216 | + * to the package group hierarchy mapper. | |
217 | + */ | |
218 | + static GroupHierarchyMapper PackageGroupHierarchyMapper; | |
198 | 219 | }; |
199 | 220 | |
200 | 221 | enum { to_remove = 0, to_install, selection_types }; |
@@ -83,6 +83,21 @@ pkgRepository::pkgRepository | ||
83 | 83 | owner( client ), dbase( db ), repository( ref ), force_update( mode ), |
84 | 84 | expected_issue( value_assumed_new ){} |
85 | 85 | |
86 | +/* Provide the hook, via which the package group hierarchy builder | |
87 | + * may gain access to its configuration data, during loading of the | |
88 | + * package list files. | |
89 | + */ | |
90 | +pkgXmlNode::GroupHierarchyMapper | |
91 | +pkgXmlNode::PackageGroupHierarchyMapper = NULL; | |
92 | +inline void pkgXmlNode::MapPackageGroupHierarchy( pkgXmlNode *catalogue ) | |
93 | +{ | |
94 | + /* This is a no-op, unless the client attaches a handler to the | |
95 | + * hook, before invoking the package list loader. | |
96 | + */ | |
97 | + if( PackageGroupHierarchyMapper != NULL ) | |
98 | + PackageGroupHierarchyMapper( this, catalogue ); | |
99 | +} | |
100 | + | |
86 | 101 | void pkgRepository::GetPackageList( const char *dname ) |
87 | 102 | { |
88 | 103 | /* Helper to retrieve and recursively process a named package list. |
@@ -179,7 +194,11 @@ void pkgRepository::GetPackageList( const char *dname ) | ||
179 | 194 | pkgXmlNode *catalogue, *pkglist; |
180 | 195 | if( (catalogue = merge.GetRoot()) != NULL ) |
181 | 196 | { |
182 | - /* ...read it, selecting each of the "package-collection" | |
197 | + /* ...map any package group hierarchy which it specifies... | |
198 | + */ | |
199 | + dbase->MapPackageGroupHierarchy( catalogue ); | |
200 | + | |
201 | + /* ...then read it, selecting each of the "package-collection" | |
183 | 202 | * records contained within it... |
184 | 203 | */ |
185 | 204 | pkglist = catalogue->FindFirstAssociate( package_collection_key ); |
@@ -1328,6 +1328,66 @@ long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data ) | ||
1328 | 1328 | SelectPackageAction( LVHT_ONITEMICON ); |
1329 | 1329 | } |
1330 | 1330 | break; |
1331 | + | |
1332 | + /* We also need to consider notifications from the tree view... | |
1333 | + */ | |
1334 | + case ID_PACKAGE_TREEVIEW: | |
1335 | + if( ((NMHDR *)(data))->code == TVN_SELCHANGED ) | |
1336 | + { | |
1337 | + /* ...from which we are interested only in notifications | |
1338 | + * that the user has changed the package group selection. | |
1339 | + * | |
1340 | + * First, we ensure that any children of the selected | |
1341 | + * package group are made visible. | |
1342 | + */ | |
1343 | + TreeView_Expand( PackageTreeView, | |
1344 | + ((NMTREEVIEW *)(data))->itemNew.hItem, TVE_EXPAND | |
1345 | + ); | |
1346 | + | |
1347 | + /* We then clear out the previous content of the list view | |
1348 | + * pane, and reconstruct it with new content, as determined | |
1349 | + * by the new package group selection... | |
1350 | + */ | |
1351 | + ClearPackageList(); | |
1352 | + UpdatePackageList(); | |
1353 | + | |
1354 | + /* ...and reapply any scheduled action markers, which may | |
1355 | + * be applicable. | |
1356 | + */ | |
1357 | + MarkSchedule( pkgData->Schedule() ); | |
1358 | + | |
1359 | + /* Finally, provided the previous selection is not an | |
1360 | + * ancestor of the current, we may collapse any visible | |
1361 | + * subtree descending from the previous. | |
1362 | + * | |
1363 | + * FIXME: We may wish to avoid collapsing any subtree | |
1364 | + * which was designated as "expanded", in the original | |
1365 | + * group hierarchy specification. We may also wish to | |
1366 | + * provide a user option, to disable this feature. | |
1367 | + */ | |
1368 | + bool may_fold = true; | |
1369 | + HTREEITEM prev = ((NMTREEVIEW *)(data))->itemNew.hItem; | |
1370 | + while( may_fold && (prev != NULL) ) | |
1371 | + { if( prev == ((NMTREEVIEW *)(data))->itemOld.hItem ) | |
1372 | + /* | |
1373 | + * Previous selection IS an ancestor of current; | |
1374 | + * we must not collapse it. | |
1375 | + */ | |
1376 | + may_fold = false; | |
1377 | + | |
1378 | + else | |
1379 | + /* Continue tracing ancestry, back to the root. | |
1380 | + */ | |
1381 | + prev = TreeView_GetParent( PackageTreeView, prev ); | |
1382 | + } | |
1383 | + if( may_fold ) | |
1384 | + /* | |
1385 | + * Previous selection may be collapsed; do so. | |
1386 | + */ | |
1387 | + TreeView_Expand( PackageTreeView, | |
1388 | + ((NMTREEVIEW *)(data))->itemOld.hItem, TVE_COLLAPSE | |
1389 | + ); | |
1390 | + } | |
1331 | 1391 | } |
1332 | 1392 | /* Otherwise, this return causes any other notifiable events |
1333 | 1393 | * to be simply ignored, (as they were by the original stub). |
@@ -113,8 +113,24 @@ void AppWindowMaker::UpdatePackageList() | ||
113 | 113 | pkgDirectory *dir = pkgData->CatalogueAllPackages(); |
114 | 114 | dir->InOrder( &PackageList ); |
115 | 115 | delete dir; |
116 | + | |
117 | + /* Force a redraw of the application window, to ensure that the | |
118 | + * data pane content remains synchronised. | |
119 | + */ | |
120 | + InvalidateRect( AppWindow, NULL, FALSE ); | |
121 | + UpdateWindow( AppWindow ); | |
116 | 122 | } |
117 | 123 | |
124 | +inline bool pkgXmlNode::IsVisibleGroupMember() | |
125 | +{ | |
126 | + /* Hook invoked before adding a package reference to the package list, | |
127 | + * to ensure that it represents a member of the current package group. | |
128 | + */ | |
129 | + return AppWindowMaker::IsPackageGroupAffiliate( this ); | |
130 | +} | |
131 | + | |
132 | +inline bool pkgXmlNode::IsVisibleClass(){ return true; } | |
133 | + | |
118 | 134 | static char *pkgGetTitle( pkgXmlNode *pkg, const pkgXmlNode *xml_root ) |
119 | 135 | { |
120 | 136 | /* A private helper method, to retrieve the title attribute |
@@ -421,7 +437,7 @@ void pkgListViewMaker::Dispatch( pkgXmlNode *package ) | ||
421 | 437 | * dispatching the content of the directory to the display service, |
422 | 438 | * (which, in this case, populates the list view window pane). |
423 | 439 | */ |
424 | - if( package->IsElementOfType( package_key ) ) | |
440 | + if( package->IsElementOfType( package_key ) && package->IsVisibleGroupMember() ) | |
425 | 441 | { |
426 | 442 | /* Assemble the package name into the list view record block. |
427 | 443 | */ |
@@ -445,7 +461,7 @@ void pkgListViewMaker::Dispatch( pkgXmlNode *package ) | ||
445 | 461 | */ |
446 | 462 | InsertItem( package, (char *)("") ); |
447 | 463 | } |
448 | - else if( package->IsElementOfType( component_key ) ) | |
464 | + else if( package->IsElementOfType( component_key ) && package->IsVisibleClass() ) | |
449 | 465 | { |
450 | 466 | /* Handle the recursive calls for the component sub-directory, |
451 | 467 | * inheriting the package name entry from the original package |
@@ -4,7 +4,7 @@ | ||
4 | 4 | * $Id$ |
5 | 5 | * |
6 | 6 | * Written by Keith Marshall <keithmarshall@users.sourceforge.net> |
7 | - * Copyright (C) 2012, MinGW.org Project | |
7 | + * Copyright (C) 2012, 2013, MinGW.org Project | |
8 | 8 | * |
9 | 9 | * |
10 | 10 | * Implementation of the methods for the pkgTreeViewMaker class, and |
@@ -26,6 +26,8 @@ | ||
26 | 26 | * arising from the use of this software. |
27 | 27 | * |
28 | 28 | */ |
29 | +#define _WIN32_IE 0x0400 | |
30 | + | |
29 | 31 | #include "guimain.h" |
30 | 32 | #include "pkgbase.h" |
31 | 33 | #include "pkgkeys.h" |
@@ -36,9 +38,186 @@ static const char *package_group_all = "All Packages"; | ||
36 | 38 | /* The following are candidates for inclusion in "pkgkeys"; |
37 | 39 | * for now, we may keep them as local defines. |
38 | 40 | */ |
41 | +static const char *select_key = "select"; | |
39 | 42 | static const char *expand_key = "expand"; |
40 | 43 | static const char *value_true = "true"; |
41 | 44 | |
45 | +static inline | |
46 | +pkgXmlNode *is_existing_group( pkgXmlNode *dbase, const char *name ) | |
47 | +{ | |
48 | + /* Helper to check for prior existence of a specified group name, | |
49 | + * at a specified level within the XML representation of the package | |
50 | + * group hierarchy, so we may avoid adding redundant duplicates. | |
51 | + */ | |
52 | + while( dbase != NULL ) | |
53 | + { | |
54 | + /* We haven't yet considered all XML database entries, at the | |
55 | + * requisite level; check the current reference entry... | |
56 | + */ | |
57 | + if( safe_strcmp( strcasecmp, name, dbase->GetPropVal( name_key, NULL )) ) | |
58 | + /* | |
59 | + * ...returning it immediately, if it is a named duplicate of | |
60 | + * the entry we are seeking to add. | |
61 | + */ | |
62 | + return dbase; | |
63 | + | |
64 | + /* When no duplicate found yet, move on to consider the next | |
65 | + * entry at the requisite XML database level, if any. | |
66 | + */ | |
67 | + dbase = dbase->FindNextAssociate( package_group_key ); | |
68 | + } | |
69 | + /* If we get to here, then no duplicate was found; the database | |
70 | + * reference must have become NULL, which we return. | |
71 | + */ | |
72 | + return dbase; | |
73 | +} | |
74 | + | |
75 | +static void | |
76 | +map_package_group_hierarchy_recursive( pkgXmlNode *dbase, pkgXmlNode *group ) | |
77 | +{ | |
78 | + /* Helper to recursively reconstruct an in-core image of the XML | |
79 | + * structure of the package list hierarchy, as it is specified in | |
80 | + * the package list read from external storage. | |
81 | + */ | |
82 | + const char *name = group->GetPropVal( name_key, value_unknown ); | |
83 | + pkgXmlNode *ref, *map = dbase->FindFirstAssociate( package_group_key ); | |
84 | + if( (ref = is_existing_group( map, name )) == NULL ) | |
85 | + { | |
86 | + /* The group name we are seeking to add doesn't yet appear at | |
87 | + * the requisite level within the in-core XML image; construct | |
88 | + * a new record, comprising the relevant data as represented | |
89 | + * in the external database... | |
90 | + */ | |
91 | + ref = new pkgXmlNode( package_group_key ); | |
92 | + ref->SetAttribute( name_key, name ); | |
93 | + if( (name = group->GetPropVal( expand_key, NULL )) != NULL ) | |
94 | + ref->SetAttribute( expand_key, name ); | |
95 | + | |
96 | + /* ...and attach it as the last sibling of any existing | |
97 | + * in-core records at the requisite level. | |
98 | + */ | |
99 | + ref = (pkgXmlNode *)(dbase->LinkEndChild( ref )); | |
100 | + } | |
101 | + /* Recurse... | |
102 | + */ | |
103 | + group = group->FindFirstAssociate( package_group_key ); | |
104 | + while( group != NULL ) | |
105 | + { | |
106 | + /* ...to capture any newly specified children of this group, | |
107 | + * regardless of whether the group already existed, or has | |
108 | + * just been added. | |
109 | + */ | |
110 | + map_package_group_hierarchy_recursive( ref, group ); | |
111 | + group = group->FindNextAssociate( package_group_key ); | |
112 | + } | |
113 | +} | |
114 | + | |
115 | +static void | |
116 | +map_package_group_hierarchy( pkgXmlNode *dbase, pkgXmlNode *catalogue ) | |
117 | +{ | |
118 | + /* Helper to kick-start the recursive mapping of external XML | |
119 | + * records into the in-core package group hierarchy image. | |
120 | + */ | |
121 | + const char *group_hierarchy_key = "package-group-hierarchy"; | |
122 | + if( (catalogue = catalogue->FindFirstAssociate( group_hierarchy_key )) != NULL ) | |
123 | + { | |
124 | + /* We found a package group hierarchy specification... | |
125 | + */ | |
126 | + dbase = dbase->FindFirstAssociate( package_group_key ); | |
127 | + do { pkgXmlNode *group = catalogue->FindFirstAssociate( package_group_key ); | |
128 | + while( group != NULL ) | |
129 | + { | |
130 | + /* ...recursively map each top level package group | |
131 | + * which is specified within it... | |
132 | + */ | |
133 | + map_package_group_hierarchy_recursive( dbase, group ); | |
134 | + group = group->FindNextAssociate( package_group_key ); | |
135 | + } | |
136 | + /* ...and repeat for any other package group hierarchy | |
137 | + * specifications we may encounter. | |
138 | + */ | |
139 | + catalogue = catalogue->FindNextAssociate( group_hierarchy_key ); | |
140 | + } while( catalogue != NULL ); | |
141 | + } | |
142 | +} | |
143 | + | |
144 | +static void | |
145 | +load_package_group_hierarchy( HWND display, HTREEITEM parent, pkgXmlNode *group ) | |
146 | +{ | |
147 | + /* Helper to load the package group hierarchy from it's in-core XML | |
148 | + * database representation, into a Windows tree view control. | |
149 | + */ | |
150 | + TVINSERTSTRUCT ref; | |
151 | + | |
152 | + /* Establish initial state for the tree view item insertion control. | |
153 | + */ | |
154 | + ref.hParent = parent; | |
155 | + ref.item.mask = TVIF_TEXT | TVIF_CHILDREN; | |
156 | + ref.hInsertAfter = TVI_FIRST; | |
157 | + | |
158 | + do { /* For each package group specified at the current level in the | |
159 | + * package group hierarchy, retrieve its name from the XML record... | |
160 | + */ | |
161 | + pkgXmlNode *first_child = group->FindFirstAssociate( package_group_key ); | |
162 | + ref.item.pszText = (char *)(group->GetPropVal( name_key, value_unknown )); | |
163 | + /* | |
164 | + * ...note whether any descendants are to be specified... | |
165 | + */ | |
166 | + ref.item.cChildren = (first_child == NULL) ? 0 : 1; | |
167 | + /* | |
168 | + * ...and add a corresponding entry to the tree view. | |
169 | + */ | |
170 | + ref.hInsertAfter = TreeView_InsertItem( display, &ref ); | |
171 | + | |
172 | + /* Check if the current group is to be made the initial selection, | |
173 | + * when the tree view is first displayed; the last entry so marked | |
174 | + * will become the actual initial selection. (Note that, to ensure | |
175 | + * that this setting cannot be abused by package maintainers, it is | |
176 | + * defined internally by mingw-get; it is not propagated from any | |
177 | + * external XML resource). | |
178 | + */ | |
179 | + const char *option = group->GetPropVal( select_key, NULL ); | |
180 | + if( (option != NULL) && (strcmp( option, value_true ) == 0) ) | |
181 | + /* | |
182 | + * Make any group, which marked with the "select" attribute, the | |
183 | + * active selection; (note that this selection may be superseded, | |
184 | + * should another similarly marked group be encountered later). | |
185 | + */ | |
186 | + TreeView_SelectItem( display, ref.hInsertAfter ); | |
187 | + | |
188 | + /* Any descendants specified, for the current group, must be | |
189 | + * processed recursively... | |
190 | + */ | |
191 | + if( first_child != NULL ) | |
192 | + { | |
193 | + /* There is at least one generation of children, of the current | |
194 | + * tree view entry; check if this entry should be expanded, so as | |
195 | + * to make its children visible in the initial view, recursively | |
196 | + * evaluate to identify further generations of descendants... | |
197 | + */ | |
198 | + option = group->GetPropVal( expand_key, NULL ); | |
199 | + load_package_group_hierarchy( display, ref.hInsertAfter, first_child ); | |
200 | + if( (option != NULL) && (strcmp( option, value_true ) == 0) ) | |
201 | + /* | |
202 | + * ...and expand to the requested level of visibility. | |
203 | + */ | |
204 | + TreeView_Expand( display, ref.hInsertAfter, TVE_EXPAND ); | |
205 | + } | |
206 | + /* Repeat for any other package groups which may be specified at | |
207 | + * the current level within the hierarchy. | |
208 | + */ | |
209 | + group = group->FindNextAssociate( package_group_key ); | |
210 | + } while( group != NULL ); | |
211 | +} | |
212 | + | |
213 | +inline void pkgXmlNode::SetPackageGroupHierarchyMapper() | |
214 | +{ | |
215 | + /* Method to assign the preceding helper, as the active handler | |
216 | + * for pkgXmlNode::MapPackageGroupHierarchy(). | |
217 | + */ | |
218 | + PackageGroupHierarchyMapper = map_package_group_hierarchy; | |
219 | +} | |
220 | + | |
42 | 221 | EXTERN_C void pkgInitCategoryTreeGraft( pkgXmlNode *root ) |
43 | 222 | { |
44 | 223 | /* Helper function to create the graft point, at which the |
@@ -47,7 +226,10 @@ EXTERN_C void pkgInitCategoryTreeGraft( pkgXmlNode *root ) | ||
47 | 226 | * of the XML database image. |
48 | 227 | */ |
49 | 228 | pkgXmlNode *pkgtree = new pkgXmlNode( package_group_key ); |
229 | + | |
230 | + pkgtree->SetPackageGroupHierarchyMapper(); | |
50 | 231 | pkgtree->SetAttribute( name_key, package_group_all ); |
232 | + pkgtree->SetAttribute( select_key, value_true ); | |
51 | 233 | pkgtree->SetAttribute( expand_key, value_true ); |
52 | 234 | root->LinkEndChild( pkgtree ); |
53 | 235 | } |
@@ -57,8 +239,8 @@ void AppWindowMaker::InitPackageTreeView() | ||
57 | 239 | /* Create and initialise a TreeView window, in which to present |
58 | 240 | * the package group hierarchy display... |
59 | 241 | */ |
60 | - PackageTreeView = CreateWindow( WC_TREEVIEW, NULL, | |
61 | - WS_VISIBLE | WS_BORDER | WS_CHILD, 0, 0, 0, 0, | |
242 | + PackageTreeView = CreateWindow( WC_TREEVIEW, NULL, WS_VISIBLE | | |
243 | + WS_BORDER | WS_CHILD | TVS_FULLROWSELECT | TVS_SHOWSELALWAYS, 0, 0, 0, 0, | |
62 | 244 | AppWindow, (HMENU)(ID_PACKAGE_TREEVIEW), |
63 | 245 | AppInstance, NULL |
64 | 246 | ); |
@@ -67,14 +249,7 @@ void AppWindowMaker::InitPackageTreeView() | ||
67 | 249 | * displaying the category headings within the tree view. |
68 | 250 | */ |
69 | 251 | SendMessage( PackageTreeView, WM_SETFONT, (WPARAM)(DefaultFont), TRUE ); |
70 | - | |
71 | - /* Initialise a tree view insertion structure, to the appropriate | |
72 | - * state for assignment of the root entry in the tree view. | |
73 | - */ | |
74 | - TVINSERTSTRUCT cat; | |
75 | - cat.hParent = TVI_ROOT; | |
76 | - cat.item.mask = TVIF_TEXT; | |
77 | - cat.hInsertAfter = TVI_ROOT; | |
252 | + SendMessage( PackageTreeView, TVM_SETINDENT, 0, 0 ); | |
78 | 253 | |
79 | 254 | /* Retrieve the root category entry, if any, as recorded in |
80 | 255 | * the package XML database, for assignment as the root entry |
@@ -87,23 +262,181 @@ void AppWindowMaker::InitPackageTreeView() | ||
87 | 262 | * create an artificial root entry, (which will then become |
88 | 263 | * the sole entry in our tree view). |
89 | 264 | */ |
90 | - cat.item.pszText = (char *)(package_group_all); | |
91 | - TreeView_InsertItem( PackageTreeView, &cat ); | |
265 | + TVINSERTSTRUCT entry; | |
266 | + entry.hParent = TVI_ROOT; | |
267 | + entry.item.mask = TVIF_TEXT | TVIF_CHILDREN; | |
268 | + entry.item.pszText = (char *)(package_group_all); | |
269 | + entry.hInsertAfter = TVI_ROOT; | |
270 | + entry.item.cChildren = 0; | |
271 | + | |
272 | + /* Map this sole entry into the tree view pane. | |
273 | + */ | |
274 | + TreeView_InsertItem( PackageTreeView, &entry ); | |
92 | 275 | } |
93 | - else while( tree != NULL ) | |
94 | - { | |
276 | + else | |
95 | 277 | /* The package group hierarchy has been incorporated into |
96 | 278 | * the in-core image of the XML database; create a windows |
97 | - * "tree view" representation of its structure. | |
98 | - * | |
99 | - * FIXME: this currently creates only the root of the tree; | |
100 | - * we need to walk the XML hierarchy, and add an additional | |
101 | - * tree view node for each element found. | |
279 | + * "tree view", into which we load a representation of the | |
280 | + * structure of this hierarchy. | |
281 | + */ | |
282 | + load_package_group_hierarchy( PackageTreeView, TVI_ROOT, tree ); | |
283 | +} | |
284 | + | |
285 | +static bool is_child_affiliate( HWND tree, TVITEM *ref, const char *name ) | |
286 | +{ | |
287 | + /* Helper method to recursively traverse a subtree of the | |
288 | + * package group hierarchy, to check if a specified package | |
289 | + * group name matches any descendant of the current group | |
290 | + * selection... | |
291 | + */ | |
292 | + HTREEITEM mark = ref->hItem; | |
293 | + do { if( TreeView_GetItem( tree, ref ) | |
294 | + && (strcasecmp( name, ref->pszText ) == 0) ) | |
295 | + /* | |
296 | + * ...immediately promoting any such match as an | |
297 | + * affiliate of the current selection... | |
298 | + */ | |
299 | + return true; | |
300 | + | |
301 | + /* ...otherwise, recursively traverse each child | |
302 | + * subtree, at the current level... | |
303 | + */ | |
304 | + if( ((ref->hItem = TreeView_GetChild( tree, mark )) != NULL) | |
305 | + && is_child_affiliate( tree, ref, name ) ) | |
306 | + /* | |
307 | + * ...again, immediately promoting any match... | |
308 | + */ | |
309 | + return true; | |
310 | + | |
311 | + /* ...or otherwise, rewind to the marked match point, | |
312 | + * whence we repeat the search into its next immediate | |
313 | + * sibling subtree, (if any). | |
314 | + */ | |
315 | + mark = ref->hItem = TreeView_GetNextSibling( tree, mark ); | |
316 | + } while( mark != NULL ); | |
317 | + | |
318 | + /* If we get to here, the specified package group name is NOT | |
319 | + * an affiliate of any descendant, at the current match level, | |
320 | + * of the currently selected package group. | |
321 | + */ | |
322 | + return false; | |
323 | +} | |
324 | + | |
325 | +static inline bool is_affiliated( HWND tree, const char *name ) | |
326 | +{ | |
327 | + /* Helper to initiate a determination if a specified package | |
328 | + * group name is an affiliate of the currently selected package | |
329 | + * group; i.e. if it matches the name of the selected group, or | |
330 | + * any of its descendants. | |
331 | + */ | |
332 | + TVITEM ref; | |
333 | + if( ((ref.hItem = TreeView_GetSelection( tree )) == NULL) | |
334 | + || (ref.hItem == TreeView_GetRoot( tree )) ) | |
335 | + /* | |
336 | + * Before proceeding further, we may note that ANY group name | |
337 | + * is considered to be IMPLICITLY matched, at the root of the | |
338 | + * package group tree. | |
339 | + */ | |
340 | + return true; | |
341 | + | |
342 | + /* At any level in the hierarchy, other than the root, the group | |
343 | + * name MUST be matched EXPLICITLY; note that... | |
344 | + */ | |
345 | + if( name == NULL ) | |
346 | + /* ...an unspecified name can never satisfy this criterion. | |
102 | 347 | */ |
103 | - cat.item.pszText = (char *)(tree->GetPropVal( name_key, value_unknown )); | |
104 | - HTREEITEM top = TreeView_InsertItem( PackageTreeView, &cat ); | |
105 | - tree = tree->FindNextAssociate( package_group_key ); | |
348 | + return false; | |
349 | + | |
350 | + /* As a basis for comparison, we must provide a working buffer | |
351 | + * into which we may retrieve names from the tree view... | |
352 | + */ | |
353 | + char ref_text[ref.cchTextMax = 1 + strlen( name )]; | |
354 | + | |
355 | + /* ...beginning with the currently selected tree view item... | |
356 | + */ | |
357 | + ref.mask = TVIF_TEXT; | |
358 | + ref.pszText = ref_text; | |
359 | + if( TreeView_GetItem( tree, &ref ) && (strcasecmp( name, ref_text ) == 0) ) | |
360 | + /* | |
361 | + * ...and returning immediately, when it is found to match. | |
362 | + */ | |
363 | + return true; | |
364 | + | |
365 | + /* When the selected item doesn't match, we also look for a | |
366 | + * possible match among its descendants, if any... | |
367 | + */ | |
368 | + if( (ref.hItem = TreeView_GetChild( tree, ref.hItem )) != NULL ) | |
369 | + /* | |
370 | + * ...which we also promote as a match. | |
371 | + */ | |
372 | + return is_child_affiliate( tree, &ref, name ); | |
373 | + | |
374 | + /* If we get to here, the selected tree item doesn't match; | |
375 | + * nor does it have any descendants among which a possible | |
376 | + * match might be found. | |
377 | + */ | |
378 | + return false; | |
379 | +} | |
380 | + | |
381 | +/* We use static methods to reference the tree view class object; | |
382 | + * thus, we must define a static reference pointer. (Note that its | |
383 | + * initial assignment to NULL will be updated, when the tree view | |
384 | + * class object is instantiated). | |
385 | + */ | |
386 | +HWND AppWindowMaker::PackageTreeView = NULL; | |
387 | +bool AppWindowMaker::IsPackageGroupAffiliate( pkgXmlNode *package ) | |
388 | +{ | |
389 | + /* Method to determine if a particular pkgXmlNode object is | |
390 | + * an affiliate of the currently selected package group, as | |
391 | + * specified in the tree view of the package group hierarchy. | |
392 | + * Note that this must be a static method, so that it may be | |
393 | + * invoked by methods of the pkgXmlNode object, without any | |
394 | + * requirement for that object to hold a reference to the | |
395 | + * AppWindowMaker object which owns the tree view. | |
396 | + */ | |
397 | + static const char *group_key = "group"; | |
398 | + static const char *affiliate_key = "affiliate"; | |
399 | + | |
400 | + /* An affiliation may be declared at any level, between the | |
401 | + * root of the XML document, and the pkgXmlNode specifications | |
402 | + * to which it applies; save an end-point reference, so that | |
403 | + * we may avoid overrunning the document root, as we walk | |
404 | + * back through the document... | |
405 | + */ | |
406 | + pkgXmlNode *top = package->GetDocumentRoot(); | |
407 | + while( package != top ) | |
408 | + { | |
409 | + /* ...in search of applicable "affiliate" declarations. | |
410 | + */ | |
411 | + pkgXmlNode *group = package->FindFirstAssociate( affiliate_key ); | |
412 | + while( group != NULL ) | |
413 | + { | |
414 | + /* For each declared affiliation, check if it matches the | |
415 | + * current selection in the package group hierarchy... | |
416 | + */ | |
417 | + const char *group_name = group->GetPropVal( group_key, NULL ); | |
418 | + if( is_affiliated( PackageTreeView, group_name ) ) | |
419 | + /* | |
420 | + * ...immediately returning any successful match; (just one | |
421 | + * positive match is sufficient to determine affiliation). | |
422 | + */ | |
423 | + return true; | |
424 | + | |
425 | + /* When no affiliation has yet been identified, repeat the | |
426 | + * check for any further "affiliate" declarations at the | |
427 | + * current level within the XML document hierarchy... | |
428 | + */ | |
429 | + group = group->FindNextAssociate( group_key ); | |
430 | + } | |
431 | + /* ...and at enclosing levels, as may be necessary. | |
432 | + */ | |
433 | + package = package->GetParent(); | |
106 | 434 | } |
435 | + /* If we get to here, then we found no "affiliate" declarations to | |
436 | + * be evaluated; only a root match in the package group hierarchy | |
437 | + * is sufficient, to determine affiliation. | |
438 | + */ | |
439 | + return is_affiliated( PackageTreeView, NULL ); | |
107 | 440 | } |
108 | 441 | |
109 | 442 | /* $RCSfile$: end of file */ |