1 | // ============================================================= // |
---|
2 | // // |
---|
3 | // File : NT_group_search.cxx // |
---|
4 | // Purpose : GUI for group search // |
---|
5 | // // |
---|
6 | // Coded by Ralf Westram (coder@reallysoft.de) in April 2017 // |
---|
7 | // http://www.arb-home.de/ // |
---|
8 | // // |
---|
9 | // ============================================================= // |
---|
10 | |
---|
11 | #include "NT_group_search.h" |
---|
12 | #include "NT_local.h" |
---|
13 | #include "ad_trees.h" |
---|
14 | |
---|
15 | #include <group_search.h> |
---|
16 | #include <TreeDisplay.hxx> |
---|
17 | |
---|
18 | #include <awt_config_manager.hxx> |
---|
19 | #include <awt_sel_boxes.hxx> |
---|
20 | |
---|
21 | #include <aw_select.hxx> |
---|
22 | #include <aw_root.hxx> |
---|
23 | #include <aw_awar.hxx> |
---|
24 | #include <aw_msg.hxx> |
---|
25 | #include <aw_awar_defs.hxx> |
---|
26 | |
---|
27 | #include <arb_strarray.h> |
---|
28 | #include <ad_cb_prot.h> |
---|
29 | |
---|
30 | using namespace std; |
---|
31 | |
---|
32 | #if defined(DEVEL_RALF) && 0 |
---|
33 | #define TRACE(msg) fprintf(stderr, "TRACE: %s\n", (msg)) |
---|
34 | #else // !DEVEL_RALF |
---|
35 | #define TRACE(msg) |
---|
36 | #endif |
---|
37 | |
---|
38 | // -------------------------------- |
---|
39 | // AWARs for group search |
---|
40 | |
---|
41 | #define GS_AWARS "group_search/" |
---|
42 | #define GS_AWARS_DUPS GS_AWARS "dup/" |
---|
43 | #define GS_AWARS_TMP "tmp/" GS_AWARS |
---|
44 | |
---|
45 | #define AWAR_MAYBE_INVALID_GROUP GS_AWARS_TMP "sellist" // may point to deleted or unlisted group |
---|
46 | #define AWAR_SELECTED_RESULT_GROUP GS_AWARS_TMP "selected" // bound to AWAR_MAYBE_INVALID_GROUP, but never points to deleted or unlisted group |
---|
47 | #define AWAR_GROUP_HIT_COUNT GS_AWARS_TMP "hits" |
---|
48 | #define AWAR_SELECTED_GROUP_NAME GS_AWARS_TMP "selname" |
---|
49 | #define AWAR_RESULTING_GROUP_NAME GS_AWARS_TMP "resname" |
---|
50 | #define AWAR_TREE_SELECTED GS_AWARS_TMP "treesel" |
---|
51 | #define AWAR_RESULT_ORDER GS_AWARS_TMP "order" |
---|
52 | |
---|
53 | #define AWAR_SEARCH_WHICH_TREES GS_AWARS "trees" |
---|
54 | #define AWAR_SEARCH_MODE GS_AWARS "mode" |
---|
55 | #define AWAR_MATCH_MODE GS_AWARS "match" |
---|
56 | #define AWAR_MARK_TARGET GS_AWARS "markwhat" |
---|
57 | #define AWAR_RENAME_EXPRESSION GS_AWARS "aci" |
---|
58 | |
---|
59 | #define AWAR_DUPLICATE_MODE GS_AWARS_DUPS "mode" |
---|
60 | #define AWAR_DUP_TREE_MODE GS_AWARS_DUPS "locmode" |
---|
61 | #define AWAR_DUP_NAME_MATCH GS_AWARS_DUPS "namematch" |
---|
62 | #define AWAR_DUP_MIN_CLUSTER_SIZE GS_AWARS_DUPS "clustsize" |
---|
63 | #define AWAR_DUP_MIN_WORDS GS_AWARS_DUPS "minwords" |
---|
64 | #define AWAR_DUP_IGNORE_CASE GS_AWARS_DUPS "ignore_case" |
---|
65 | #define AWAR_DUP_EXCLUDED_WORDS GS_AWARS_DUPS "excluded" |
---|
66 | #define AWAR_DUP_WORD_SEPARATORS GS_AWARS_DUPS "separators" |
---|
67 | |
---|
68 | #define AWARFORMAT_CRIT_OPERATOR GS_AWARS "op%i" |
---|
69 | #define AWARFORMAT_CRIT_KEY GS_AWARS "key%i" |
---|
70 | #define AWARFORMAT_CRIT_EQUALS GS_AWARS "equals%i" |
---|
71 | #define AWARFORMAT_CRIT_MATCHES GS_AWARS "match%i" |
---|
72 | |
---|
73 | #define MAX_CRITERIA 3 |
---|
74 | |
---|
75 | inline const char *criterion_awar_name(const char *format, int crit) { |
---|
76 | gs_assert(crit>=1 && crit<=MAX_CRITERIA); |
---|
77 | return GBS_global_string(format, crit); |
---|
78 | } |
---|
79 | |
---|
80 | enum TreeSearchRange { |
---|
81 | SEARCH_CURRENT_TREE, |
---|
82 | SEARCH_SELECTED_TREES, |
---|
83 | SEARCH_ALL_TREES, |
---|
84 | }; |
---|
85 | |
---|
86 | enum DuplicateMode { |
---|
87 | DONT_MIND_DUPLICATES, |
---|
88 | ONLY_DUPLICATES, |
---|
89 | ONLY_UNIQUE, |
---|
90 | }; |
---|
91 | |
---|
92 | // --------------------- |
---|
93 | // data for UI |
---|
94 | |
---|
95 | class GroupUIdata : virtual Noncopyable { |
---|
96 | GBDATA *gb_main; |
---|
97 | SmartPtr<GroupSearch> group_search; |
---|
98 | bool show_tree_name; // show treename in result list? |
---|
99 | AW_selection_list *result_list; |
---|
100 | AW_selection *tree_list; |
---|
101 | |
---|
102 | void update_search_filters(); |
---|
103 | void update_search_range(); |
---|
104 | void update_duplicate_settings(); |
---|
105 | |
---|
106 | void refresh_hit_count(size_t count) { |
---|
107 | AW_root::SINGLETON->awar(AWAR_GROUP_HIT_COUNT)->write_int(count); // update hit count |
---|
108 | } |
---|
109 | void result_order_changed_cb(AW_root *awr) { |
---|
110 | if (group_search.isSet()) { |
---|
111 | GroupSortCriterion crit = GroupSortCriterion(awr->awar(AWAR_RESULT_ORDER)->read_int()); |
---|
112 | group_search->addSortCriterion(crit); |
---|
113 | refill_result_list(); |
---|
114 | } |
---|
115 | } |
---|
116 | void result_list_awar_changed_cb(AW_root *awr); |
---|
117 | void selected_group_name_changed_cb(AW_root *awr); |
---|
118 | void selected_group_changed_cb(AW_root *awr); |
---|
119 | void update_resulting_groupname_cb(AW_root *awr); |
---|
120 | |
---|
121 | // callback wrappers: |
---|
122 | static void refresh_result_list_cb(GroupSearch*, GroupUIdata *data) { data->refill_result_list(); } |
---|
123 | static void cleanup_on_exit(GBDATA*, GroupUIdata *data) { data->cleanup(); } |
---|
124 | static void result_order_changed_cb(AW_root *awr, GroupUIdata *data) { data->result_order_changed_cb(awr); } |
---|
125 | static void result_list_awar_changed_cb(AW_root *awr, GroupUIdata *data) { data->result_list_awar_changed_cb(awr); } |
---|
126 | static void selected_group_name_changed_cb(AW_root *awr, GroupUIdata *data) { data->selected_group_name_changed_cb(awr); } |
---|
127 | static void selected_group_changed_cb(AW_root *awr, GroupUIdata *data) { data->selected_group_changed_cb(awr); } |
---|
128 | static void update_resulting_groupname_cb(AW_root *awr, GroupUIdata *data) { data->update_resulting_groupname_cb(awr); } |
---|
129 | |
---|
130 | void install_callbacks(AW_root *awr) { |
---|
131 | awr->awar(AWAR_RESULT_ORDER) ->add_callback(makeRootCallback(GroupUIdata::result_order_changed_cb, this)); |
---|
132 | awr->awar(AWAR_MAYBE_INVALID_GROUP) ->add_callback(makeRootCallback(GroupUIdata::result_list_awar_changed_cb, this)); |
---|
133 | awr->awar(AWAR_SELECTED_GROUP_NAME) ->add_callback(makeRootCallback(GroupUIdata::selected_group_name_changed_cb, this)); |
---|
134 | awr->awar(AWAR_SELECTED_RESULT_GROUP)->add_callback(makeRootCallback(GroupUIdata::selected_group_changed_cb, this)); |
---|
135 | awr->awar(AWAR_RENAME_EXPRESSION) ->add_callback(makeRootCallback(GroupUIdata::update_resulting_groupname_cb, this)); |
---|
136 | } |
---|
137 | void remove_callbacks(AW_root *awr) { |
---|
138 | awr->awar(AWAR_RESULT_ORDER) ->remove_callback(makeRootCallback(GroupUIdata::result_order_changed_cb, this)); |
---|
139 | awr->awar(AWAR_MAYBE_INVALID_GROUP) ->remove_callback(makeRootCallback(GroupUIdata::result_list_awar_changed_cb, this)); |
---|
140 | awr->awar(AWAR_SELECTED_GROUP_NAME) ->remove_callback(makeRootCallback(GroupUIdata::selected_group_name_changed_cb, this)); |
---|
141 | awr->awar(AWAR_SELECTED_RESULT_GROUP)->remove_callback(makeRootCallback(GroupUIdata::selected_group_changed_cb, this)); |
---|
142 | awr->awar(AWAR_RENAME_EXPRESSION) ->remove_callback(makeRootCallback(GroupUIdata::update_resulting_groupname_cb, this)); |
---|
143 | } |
---|
144 | |
---|
145 | public: |
---|
146 | GroupUIdata(GBDATA *gb_main_) : |
---|
147 | gb_main(gb_main_), |
---|
148 | show_tree_name(false), |
---|
149 | result_list(NULp) |
---|
150 | { |
---|
151 | GB_atclose_callback(gb_main, makeDatabaseCallback(GroupUIdata::cleanup_on_exit, this)); |
---|
152 | } |
---|
153 | |
---|
154 | void initialize() { // called after popup |
---|
155 | if (group_search.isNull()) { // avoid reinit if group-search-menu-entry is pressed while group-search-window is still open |
---|
156 | TRACE("initialize GroupUIdata"); |
---|
157 | group_search = new GroupSearch(gb_main, makeGroupSearchCallback(GroupUIdata::refresh_result_list_cb, this)); |
---|
158 | install_callbacks(AW_root::SINGLETON); |
---|
159 | } |
---|
160 | } |
---|
161 | |
---|
162 | GBDATA *get_gb_main() const { return gb_main; } |
---|
163 | |
---|
164 | void announce_result_list(AW_selection_list *result_list_) { result_list = result_list_; } |
---|
165 | void announce_tree_select_list(AW_selection *tree_list_) { tree_list = tree_list_; } |
---|
166 | |
---|
167 | void cleanup() { // called on popdown |
---|
168 | TRACE("cleanup GroupUIdata"); |
---|
169 | remove_callbacks(AW_root::SINGLETON); |
---|
170 | group_search.setNull(); |
---|
171 | clear_result_list(); |
---|
172 | } |
---|
173 | |
---|
174 | void run_search() { |
---|
175 | update_search_filters(); |
---|
176 | update_search_range(); |
---|
177 | update_duplicate_settings(); |
---|
178 | |
---|
179 | { |
---|
180 | AW_root *awr = AW_root::SINGLETON; |
---|
181 | GroupSearchMode mode = GroupSearchMode(awr->awar(AWAR_SEARCH_MODE)->read_int() ^ awr->awar(AWAR_MATCH_MODE)->read_int()); |
---|
182 | group_search->perform_search(mode); |
---|
183 | } |
---|
184 | refill_result_list(); |
---|
185 | } |
---|
186 | |
---|
187 | void refill_result_list(); |
---|
188 | void remove_selected_result(); |
---|
189 | void remove_all_results(); |
---|
190 | void clear_result_list(); |
---|
191 | |
---|
192 | GBDATA *get_selected_group() const { |
---|
193 | AW_scalar awar_value = result_list->get_awar_value(); |
---|
194 | int idx = result_list->get_index_of(awar_value); |
---|
195 | |
---|
196 | return idx == -1 ? NULp : awar_value.get_pointer(); |
---|
197 | } |
---|
198 | |
---|
199 | // modify groups |
---|
200 | void delete_selected_group(); |
---|
201 | void delete_listed_groups(); |
---|
202 | |
---|
203 | void rename_selected_group(); |
---|
204 | void rename_listed_groups(); |
---|
205 | |
---|
206 | void toggle_selected_group_folding(); |
---|
207 | void change_listed_groups_folding(GroupFoldingMode mode); |
---|
208 | |
---|
209 | void mark_species(GroupMarkMode mode); |
---|
210 | }; |
---|
211 | |
---|
212 | void GroupUIdata::clear_result_list() { |
---|
213 | refresh_hit_count(0); |
---|
214 | |
---|
215 | result_list->clear(); |
---|
216 | result_list->insert_default("<none>", (GBDATA*)NULp); |
---|
217 | result_list->update(); |
---|
218 | } |
---|
219 | void GroupUIdata::refill_result_list() { |
---|
220 | const QueriedGroups& foundGroups = group_search->get_results(); |
---|
221 | |
---|
222 | AW_root *awr = AW_root::SINGLETON; |
---|
223 | AW_awar *awar_sel = awr->awar(AWAR_SELECTED_RESULT_GROUP); |
---|
224 | |
---|
225 | if (foundGroups.empty()) { |
---|
226 | clear_result_list(); |
---|
227 | awar_sel->write_pointer(NULp); |
---|
228 | } |
---|
229 | else { |
---|
230 | GB_transaction ta(gb_main); |
---|
231 | |
---|
232 | GBDATA *gb_sellist_group = awr->awar(AWAR_MAYBE_INVALID_GROUP)->read_pointer(); |
---|
233 | bool seen_selected = false; |
---|
234 | |
---|
235 | refresh_hit_count(foundGroups.size()); |
---|
236 | |
---|
237 | result_list->clear(); |
---|
238 | for (FoundGroupCIter g = foundGroups.begin(); g != foundGroups.end(); ++g) { |
---|
239 | const char *display = foundGroups.get_group_display(*g, show_tree_name); |
---|
240 | GBDATA *gb_group = g->get_pointer(); |
---|
241 | |
---|
242 | result_list->insert(display, gb_group); |
---|
243 | |
---|
244 | if (gb_group == gb_sellist_group) seen_selected = true; |
---|
245 | } |
---|
246 | result_list->insert_default("<none>", (GBDATA*)NULp); |
---|
247 | result_list->update(); |
---|
248 | |
---|
249 | awar_sel->rewrite_pointer(seen_selected ? gb_sellist_group : NULp); |
---|
250 | } |
---|
251 | } |
---|
252 | |
---|
253 | void GroupUIdata::update_search_filters() { |
---|
254 | AW_root *awr = AW_root::SINGLETON; |
---|
255 | |
---|
256 | group_search->forgetQExpressions(); |
---|
257 | for (int crit = 1; crit<=MAX_CRITERIA; ++crit) { |
---|
258 | CriterionOperator op = crit == 1 ? CO_OR : CriterionOperator(awr->awar(criterion_awar_name(AWARFORMAT_CRIT_OPERATOR, crit))->read_int()); |
---|
259 | CriterionType type = CriterionType(awr->awar(criterion_awar_name(AWARFORMAT_CRIT_KEY, crit))->read_int()); |
---|
260 | CriterionMatch mtype = CriterionMatch(awr->awar(criterion_awar_name(AWARFORMAT_CRIT_EQUALS, crit))->read_int()); |
---|
261 | const char *expression = awr->awar(criterion_awar_name(AWARFORMAT_CRIT_MATCHES, crit))->read_char_pntr(); |
---|
262 | |
---|
263 | group_search->addQueryExpression(op, type, mtype, expression); |
---|
264 | } |
---|
265 | } |
---|
266 | |
---|
267 | void GroupUIdata::update_search_range() { |
---|
268 | AW_root *awr = AW_root::SINGLETON; |
---|
269 | TreeSearchRange range = TreeSearchRange(awr->awar(AWAR_SEARCH_WHICH_TREES)->read_int()); |
---|
270 | TreeNameSet trees; |
---|
271 | |
---|
272 | switch (range) { |
---|
273 | case SEARCH_ALL_TREES: break; // empty set means "all trees" |
---|
274 | case SEARCH_CURRENT_TREE: { |
---|
275 | const char *currentTree = awr->awar(AWAR_TREE_NAME)->read_char_pntr(); |
---|
276 | if (currentTree[0]) trees.insert(currentTree); |
---|
277 | break; |
---|
278 | } |
---|
279 | case SEARCH_SELECTED_TREES: |
---|
280 | if (tree_list) { |
---|
281 | StrArray tree_names; |
---|
282 | tree_list->get_values(tree_names); |
---|
283 | for (int t = 0; tree_names[t]; ++t) { |
---|
284 | trees.insert(tree_names[t]); |
---|
285 | } |
---|
286 | } |
---|
287 | break; |
---|
288 | } |
---|
289 | if (trees.empty() && range != SEARCH_ALL_TREES) { |
---|
290 | aw_message("No tree selected -> searching all trees instead"); |
---|
291 | } |
---|
292 | group_search->setSearchRange(trees); |
---|
293 | show_tree_name = trees.size() != 1; // (0->all) |
---|
294 | } |
---|
295 | |
---|
296 | void GroupUIdata::update_duplicate_settings() { |
---|
297 | AW_root *awr = AW_root::SINGLETON; |
---|
298 | DuplicateMode dmode = DuplicateMode(awr->awar(AWAR_DUPLICATE_MODE)->read_int()); |
---|
299 | |
---|
300 | if (dmode == DONT_MIND_DUPLICATES) { |
---|
301 | group_search->forgetDupCriteria(); |
---|
302 | } |
---|
303 | else { |
---|
304 | gs_assert(dmode == ONLY_UNIQUE || dmode == ONLY_DUPLICATES); |
---|
305 | |
---|
306 | DupTreeCriterionType treetype = DupTreeCriterionType(awr->awar(AWAR_DUP_TREE_MODE)->read_int()); |
---|
307 | DupNameCriterionType nametype = DupNameCriterionType(awr->awar(AWAR_DUP_NAME_MATCH)->read_int()); |
---|
308 | |
---|
309 | int minClusterSize = awr->awar(AWAR_DUP_MIN_CLUSTER_SIZE)->read_int(); |
---|
310 | bool listDups = dmode == ONLY_DUPLICATES; |
---|
311 | GB_CASE sens = awr->awar(AWAR_DUP_IGNORE_CASE)->read_int() ? GB_IGNORE_CASE : GB_MIND_CASE; |
---|
312 | |
---|
313 | if (nametype == DNC_WHOLENAME) { |
---|
314 | group_search->setDupCriteria(listDups, nametype, sens, treetype, minClusterSize); |
---|
315 | } |
---|
316 | else { |
---|
317 | int minWords = awr->awar(AWAR_DUP_MIN_WORDS)->read_int(); |
---|
318 | const char *excludedWords = awr->awar(AWAR_DUP_EXCLUDED_WORDS)->read_char_pntr(); |
---|
319 | const char *wordSeparators = awr->awar(AWAR_DUP_WORD_SEPARATORS)->read_char_pntr(); |
---|
320 | |
---|
321 | group_search->setDupCriteria(listDups, nametype, sens, minWords, excludedWords, wordSeparators, treetype, minClusterSize); |
---|
322 | } |
---|
323 | } |
---|
324 | } |
---|
325 | |
---|
326 | |
---|
327 | void GroupUIdata::remove_selected_result() { |
---|
328 | int selidx = result_list->get_index_of_selected(); |
---|
329 | if (selidx != -1) { // group is selected |
---|
330 | result_list->move_selection(1); |
---|
331 | result_list->delete_element_at(selidx); |
---|
332 | result_list->update(); |
---|
333 | group_search->remove_hit(selidx); |
---|
334 | } |
---|
335 | } |
---|
336 | void GroupUIdata::remove_all_results() { |
---|
337 | group_search->forget_results(); |
---|
338 | refill_result_list(); |
---|
339 | } |
---|
340 | |
---|
341 | void GroupUIdata::delete_selected_group() { |
---|
342 | int sel = result_list->get_index_of_selected(); |
---|
343 | if (sel != -1) { // group is selected |
---|
344 | result_list->select_default(); // avoid invalid access to group deleted below |
---|
345 | aw_message_if(group_search->delete_group(sel)); |
---|
346 | } |
---|
347 | } |
---|
348 | |
---|
349 | void GroupUIdata::delete_listed_groups() { |
---|
350 | if (group_search->has_results()) { |
---|
351 | result_list->select_default(); // avoid invalid access to group deleted below |
---|
352 | aw_message_if(group_search->delete_found_groups()); |
---|
353 | } |
---|
354 | } |
---|
355 | |
---|
356 | // callback wrappers: |
---|
357 | static void popdown_search_window_cb(AW_window*, GroupUIdata *data) { data->cleanup(); } |
---|
358 | static void runGroupSearch_cb(AW_window*, GroupUIdata *data) { data->run_search(); } |
---|
359 | static void remove_hit_cb(AW_window*, GroupUIdata *data) { data->remove_selected_result(); } |
---|
360 | static void clear_results_cb(AW_window*, GroupUIdata *data) { data->remove_all_results(); } |
---|
361 | static void delete_selected_group_cb(AW_window*, GroupUIdata *data) { data->delete_selected_group(); } |
---|
362 | static void delete_listed_groups_cb(AW_window*, GroupUIdata *data) { data->delete_listed_groups(); } |
---|
363 | static void rename_selected_group_cb(AW_window*, GroupUIdata *data) { data->rename_selected_group(); } |
---|
364 | static void rename_listed_groups_cb(AW_window*, GroupUIdata *data) { data->rename_listed_groups(); } |
---|
365 | static void double_click_group_cb(AW_window*, GroupUIdata *data) { data->toggle_selected_group_folding(); } |
---|
366 | static void listed_groups_folding_cb(AW_window*, GroupUIdata *data, GroupFoldingMode mode) { data->change_listed_groups_folding(mode); } |
---|
367 | static void group_mark_cb(AW_window*, GroupUIdata *data, GroupMarkMode mode) { data->mark_species(mode); } |
---|
368 | |
---|
369 | |
---|
370 | static TREE_canvas *get_canvas_showing(GBDATA *gb_group) { |
---|
371 | // search whether any canvas shows the corresponding tree |
---|
372 | // if not -> search first visible canvas + switch tree |
---|
373 | |
---|
374 | TREE_canvas *ntw = NULp; |
---|
375 | { |
---|
376 | GB_transaction ta(gb_group); |
---|
377 | |
---|
378 | GBDATA *gb_tree = GB_get_father(gb_group); |
---|
379 | char *tree_name = GB_read_key(gb_tree); |
---|
380 | TREE_canvas *first_vis = NULp; |
---|
381 | |
---|
382 | for (int ci = 0; ci<MAX_NT_WINDOWS && !ntw; ++ci) { |
---|
383 | TREE_canvas *tc = NT_get_canvas_by_index(ci); |
---|
384 | if (tc && tc->is_shown()) { // @@@ does not detect iconified windows |
---|
385 | if (!first_vis) first_vis = tc; |
---|
386 | if (strcmp(tc->get_awar_tree()->read_char_pntr(), tree_name) == 0) ntw = tc; |
---|
387 | } |
---|
388 | } |
---|
389 | |
---|
390 | if (!ntw) { |
---|
391 | if (!first_vis) { |
---|
392 | first_vis = NT_get_canvas_by_index(0); |
---|
393 | first_vis->aww->activate(); // popup first (if all windows hidden) |
---|
394 | } |
---|
395 | ntw = first_vis; |
---|
396 | nt_assert(ntw); |
---|
397 | ntw->get_awar_tree()->write_string(tree_name); |
---|
398 | } |
---|
399 | free(tree_name); |
---|
400 | } |
---|
401 | |
---|
402 | return ntw; |
---|
403 | } |
---|
404 | |
---|
405 | static bool inside_group_selection = false; |
---|
406 | |
---|
407 | void GroupUIdata::result_list_awar_changed_cb(AW_root *awr) { |
---|
408 | LocallyModify<bool> avoid_recursion(inside_group_selection, true); |
---|
409 | |
---|
410 | GBDATA *gb_group = awr->awar(AWAR_MAYBE_INVALID_GROUP)->read_pointer(); |
---|
411 | TRACE(GBS_global_string("result_list_awar_changed_cb to %p", gb_group)); |
---|
412 | |
---|
413 | if (gb_group) { |
---|
414 | TREE_canvas *ntw = get_canvas_showing(gb_group); |
---|
415 | |
---|
416 | GB_transaction ta(gb_group); |
---|
417 | ntw->get_graphic_tree()->select_group(gb_group); |
---|
418 | |
---|
419 | awr->awar(AWAR_SELECTED_RESULT_GROUP)->rewrite_pointer(get_selected_group()); |
---|
420 | } |
---|
421 | else { |
---|
422 | // @@@ why need extra refresh here? |
---|
423 | // @@@ does not auto-fold |
---|
424 | // both should be handled by deselect_group?! |
---|
425 | TREE_canvas *ntw = NT_get_canvas_by_index(0); // use any canvas here |
---|
426 | AWT_auto_refresh force(ntw); |
---|
427 | ntw->get_graphic_tree()->deselect_group(); |
---|
428 | ntw->request_refresh(); |
---|
429 | |
---|
430 | awr->awar(AWAR_SELECTED_RESULT_GROUP)->rewrite_pointer(NULp); |
---|
431 | } |
---|
432 | } |
---|
433 | |
---|
434 | static void selected_group_changed_by_canvas_cb(AW_root *awr) { |
---|
435 | if (!inside_group_selection) { |
---|
436 | LocallyModify<bool> avoid_recursion(inside_group_selection, true); |
---|
437 | |
---|
438 | GBDATA *gb_group = awr->awar(AWAR_GROUP)->read_pointer(); |
---|
439 | TRACE(GBS_global_string("selected_group_changed_by_canvas_cb to %p", gb_group)); |
---|
440 | if (gb_group) { |
---|
441 | awr->awar(AWAR_MAYBE_INVALID_GROUP)->write_pointer(gb_group); |
---|
442 | } |
---|
443 | } |
---|
444 | } |
---|
445 | |
---|
446 | static bool nameChangedByGroupChange = false; |
---|
447 | |
---|
448 | void GroupUIdata::selected_group_changed_cb(AW_root *awr) { |
---|
449 | GBDATA *gb_group = awr->awar(AWAR_SELECTED_RESULT_GROUP)->read_pointer(); |
---|
450 | TRACE(GBS_global_string("selected_group_changed_cb to %p", gb_group)); |
---|
451 | |
---|
452 | LocallyModify<bool> ignoreNameChange(nameChangedByGroupChange, true); |
---|
453 | |
---|
454 | const char *name = ""; |
---|
455 | if (gb_group) { |
---|
456 | GB_transaction ta(gb_group); |
---|
457 | name = FoundGroup(gb_group).get_name(); |
---|
458 | } |
---|
459 | awr->awar(AWAR_SELECTED_GROUP_NAME)->write_string(name); |
---|
460 | // ensure update of rename-result even if a group-change did NOT change the name (because both groups have same name) |
---|
461 | update_resulting_groupname_cb(awr); |
---|
462 | } |
---|
463 | |
---|
464 | void GroupUIdata::update_resulting_groupname_cb(AW_root *awr) { |
---|
465 | TRACE("update_resulting_groupname_cb"); |
---|
466 | |
---|
467 | char *curr_name = awr->awar(AWAR_SELECTED_GROUP_NAME)->read_string(); |
---|
468 | char *acisrt = awr->awar(AWAR_RENAME_EXPRESSION)->read_string(); |
---|
469 | |
---|
470 | ARB_ERROR error; |
---|
471 | int idx = result_list->get_index_of_selected(); |
---|
472 | char *result = GS_calc_resulting_groupname(gb_main, group_search->get_results(), idx, curr_name, acisrt, error); |
---|
473 | |
---|
474 | if (result) { |
---|
475 | error.expect_no_error(); |
---|
476 | if (!result[0]) { // empty result (will be skipped in batch-rename) |
---|
477 | result = strdup("<empty result> => group would be skipped in batch-rename"); |
---|
478 | } |
---|
479 | } |
---|
480 | else { |
---|
481 | result = strdup(error.deliver()); // show error in result field |
---|
482 | } |
---|
483 | awr->awar(AWAR_RESULTING_GROUP_NAME)->write_string(result); |
---|
484 | |
---|
485 | free(result); |
---|
486 | free(acisrt); |
---|
487 | free(curr_name); |
---|
488 | } |
---|
489 | |
---|
490 | void GroupUIdata::selected_group_name_changed_cb(AW_root *awr) { |
---|
491 | if (!nameChangedByGroupChange) { |
---|
492 | GB_ERROR error = NULp; |
---|
493 | AW_awar *awar_selected_result = awr->awar(AWAR_SELECTED_RESULT_GROUP); |
---|
494 | GBDATA *gb_group = awar_selected_result->read_pointer(); |
---|
495 | |
---|
496 | if (!gb_group) error = "select a group to rename it"; |
---|
497 | else { |
---|
498 | char *new_name = GBS_trim(awr->awar(AWAR_SELECTED_GROUP_NAME)->read_char_pntr()); |
---|
499 | if (!new_name[0]) error = "empty group name not allowed"; |
---|
500 | else { |
---|
501 | GB_transaction ta(gb_group); |
---|
502 | error = GBT_write_name_to_groupData(gb_group, false, new_name, true); |
---|
503 | } |
---|
504 | } |
---|
505 | |
---|
506 | if (error) { |
---|
507 | aw_message(error); |
---|
508 | awar_selected_result->touch(); // refill groupname inputfield |
---|
509 | } |
---|
510 | } |
---|
511 | } |
---|
512 | |
---|
513 | void GroupUIdata::rename_selected_group() { |
---|
514 | int sel = result_list->get_index_of_selected(); |
---|
515 | if (sel != -1) { // group is selected |
---|
516 | const char *acisrt = AW_root::SINGLETON->awar(AWAR_RENAME_EXPRESSION)->read_char_pntr(); |
---|
517 | aw_message_if(group_search->rename_group(sel, acisrt).deliver()); |
---|
518 | // Note: no refresh needed here (triggered by taxonomy callbacks) |
---|
519 | } |
---|
520 | } |
---|
521 | |
---|
522 | void GroupUIdata::rename_listed_groups() { |
---|
523 | if (group_search.isNull()) { |
---|
524 | aw_message("Please rerun group search"); |
---|
525 | return; |
---|
526 | } |
---|
527 | if (group_search->has_results()) { |
---|
528 | const char *acisrt = AW_root::SINGLETON->awar(AWAR_RENAME_EXPRESSION)->read_char_pntr(); |
---|
529 | aw_message_if(group_search->rename_found_groups(acisrt).deliver()); |
---|
530 | // Note: no refresh needed here (triggered by taxonomy callbacks) |
---|
531 | } |
---|
532 | } |
---|
533 | |
---|
534 | void GroupUIdata::toggle_selected_group_folding() { |
---|
535 | int sel = result_list->get_index_of_selected(); |
---|
536 | if (sel != -1) { // group is selected |
---|
537 | ARB_ERROR error = group_search->fold_group(sel, GFM_TOGGLE); |
---|
538 | aw_message_if(error); |
---|
539 | if (!error) AW_root::SINGLETON->awar(AWAR_GROUP)->touch(); // trigger recenter + refresh of changed group |
---|
540 | } |
---|
541 | } |
---|
542 | |
---|
543 | void GroupUIdata::change_listed_groups_folding(GroupFoldingMode foldmode) { |
---|
544 | if (group_search->has_results() || (foldmode & GFM_COLLAPSE_REST)) { |
---|
545 | ARB_ERROR error = group_search->fold_found_groups(foldmode); |
---|
546 | aw_message_if(error); |
---|
547 | if (!error) { |
---|
548 | AW_root::SINGLETON->awar(AWAR_GROUP)->write_pointer(NULp); // deselect group (otherwise wont fold subtree containing selected) |
---|
549 | AW_root::SINGLETON->awar(AWAR_TREE_REFRESH)->touch(); // force expose of all trees (triggers reload if DB-changes) |
---|
550 | } |
---|
551 | } |
---|
552 | } |
---|
553 | |
---|
554 | enum GroupMarkTarget { |
---|
555 | GMT_ALL_SPECIES, // targets all species in DB |
---|
556 | GMT_SELECTED_GROUP, // targets all species contained in SELECTED group |
---|
557 | GMT_ANY_GROUP, // targets all species contained in ANY listed group |
---|
558 | GMT_ALL_GROUPS, // targets all species contained in ALL listed groups |
---|
559 | }; |
---|
560 | |
---|
561 | void GroupUIdata::mark_species(GroupMarkMode mode) { |
---|
562 | GroupMarkTarget target = GroupMarkTarget(AW_root::SINGLETON->awar(AWAR_MARK_TARGET)->read_int()); |
---|
563 | bool anyGroup = false; |
---|
564 | ARB_ERROR error; |
---|
565 | GB_transaction ta(gb_main); |
---|
566 | |
---|
567 | switch (target) { |
---|
568 | case GMT_ALL_SPECIES: |
---|
569 | GBT_mark_all(gb_main, int(mode)); |
---|
570 | break; |
---|
571 | |
---|
572 | case GMT_SELECTED_GROUP: { |
---|
573 | int sel = result_list->get_index_of_selected(); |
---|
574 | if (sel != -1) { |
---|
575 | error = group_search->set_marks_in_group(sel, mode); |
---|
576 | } |
---|
577 | else { |
---|
578 | error = "Please select a group"; |
---|
579 | } |
---|
580 | |
---|
581 | break; |
---|
582 | } |
---|
583 | case GMT_ANY_GROUP: |
---|
584 | anyGroup = true; |
---|
585 | // fall-through |
---|
586 | case GMT_ALL_GROUPS: |
---|
587 | if (group_search->has_results()) { |
---|
588 | error = group_search->set_marks_in_found_groups(mode, anyGroup ? UNITE : INTERSECT); |
---|
589 | } |
---|
590 | else { |
---|
591 | error = "No results listed"; |
---|
592 | } |
---|
593 | break; |
---|
594 | } |
---|
595 | |
---|
596 | error = ta.close(error); |
---|
597 | aw_message_if(error.deliver()); |
---|
598 | } |
---|
599 | |
---|
600 | void create_group_search_awars(AW_root *aw_root, AW_default props) { |
---|
601 | aw_root->awar_pointer(AWAR_MAYBE_INVALID_GROUP, NULp, props); |
---|
602 | aw_root->awar_pointer(AWAR_SELECTED_RESULT_GROUP, NULp, props); |
---|
603 | |
---|
604 | CriterionType deftype[MAX_CRITERIA] = { |
---|
605 | CT_NAME, |
---|
606 | CT_SIZE, |
---|
607 | CT_MARKED_PC, |
---|
608 | }; |
---|
609 | |
---|
610 | for (int crit = 1; crit<=MAX_CRITERIA; ++crit) { |
---|
611 | if (crit>1) { |
---|
612 | aw_root->awar_int(criterion_awar_name(AWARFORMAT_CRIT_OPERATOR, crit), CO_IGNORE, props); |
---|
613 | } |
---|
614 | aw_root->awar_int (criterion_awar_name(AWARFORMAT_CRIT_KEY, crit), deftype[crit-1], props); |
---|
615 | aw_root->awar_int (criterion_awar_name(AWARFORMAT_CRIT_EQUALS, crit), CM_MATCH, props); |
---|
616 | aw_root->awar_string(criterion_awar_name(AWARFORMAT_CRIT_MATCHES, crit), crit == 1 ? "*" : "", props); |
---|
617 | } |
---|
618 | |
---|
619 | aw_root->awar_string(AWAR_SELECTED_GROUP_NAME, "", props); |
---|
620 | aw_root->awar_string(AWAR_RENAME_EXPRESSION, "", props); |
---|
621 | aw_root->awar_string(AWAR_RESULTING_GROUP_NAME, "???", props); |
---|
622 | aw_root->awar_string(AWAR_TREE_SELECTED, "", props); |
---|
623 | |
---|
624 | aw_root->awar_string(AWAR_DUP_EXCLUDED_WORDS, |
---|
625 | "and or" |
---|
626 | " branch group clade subsection" |
---|
627 | " order family genus" |
---|
628 | " incertae sedis" |
---|
629 | " other unknown unclassified miscellaneous" |
---|
630 | , props); |
---|
631 | aw_root->awar_string(AWAR_DUP_WORD_SEPARATORS, ",; /()[]_", props); |
---|
632 | |
---|
633 | aw_root->awar_int(AWAR_GROUP_HIT_COUNT, 0, props); |
---|
634 | aw_root->awar_int(AWAR_SEARCH_WHICH_TREES, SEARCH_CURRENT_TREE, props); |
---|
635 | aw_root->awar_int(AWAR_SEARCH_MODE, GSM_FIND, props); |
---|
636 | aw_root->awar_int(AWAR_MATCH_MODE, GSM_MATCH, props); |
---|
637 | aw_root->awar_int(AWAR_DUPLICATE_MODE, DONT_MIND_DUPLICATES, props); |
---|
638 | aw_root->awar_int(AWAR_DUP_TREE_MODE, DLC_SAME_TREE, props); |
---|
639 | aw_root->awar_int(AWAR_DUP_MIN_CLUSTER_SIZE, 2, props)->set_min(2); |
---|
640 | aw_root->awar_int(AWAR_DUP_NAME_MATCH, DNC_WHOLENAME, props); |
---|
641 | aw_root->awar_int(AWAR_DUP_IGNORE_CASE, 1, props); |
---|
642 | aw_root->awar_int(AWAR_DUP_MIN_WORDS, 2, props)->set_min(1); |
---|
643 | aw_root->awar_int(AWAR_RESULT_ORDER, GSC_NONE, props); |
---|
644 | aw_root->awar_int(AWAR_MARK_TARGET, GMT_SELECTED_GROUP, props); |
---|
645 | |
---|
646 | // perma-callbacks: |
---|
647 | aw_root->awar(AWAR_GROUP) ->add_callback(selected_group_changed_by_canvas_cb); |
---|
648 | // more callbacks are installed above in .@install_callbacks |
---|
649 | } |
---|
650 | |
---|
651 | static AW_window *create_tree_select_window_cb(AW_root *aw_root, GroupUIdata *data) { |
---|
652 | AW_window_simple *aws = new AW_window_simple; |
---|
653 | |
---|
654 | aw_root->awar(AWAR_SEARCH_WHICH_TREES)->write_int(SEARCH_SELECTED_TREES); // switch target-range to 'selected trees' |
---|
655 | |
---|
656 | aws->init(aw_root, "GROUP_TREES", "Select trees to search"); |
---|
657 | aws->load_xfig("group_trees.fig"); |
---|
658 | |
---|
659 | aws->callback(AW_POPDOWN); |
---|
660 | aws->at("close"); |
---|
661 | aws->create_button("CLOSE", "CLOSE", "C"); |
---|
662 | |
---|
663 | aws->callback(makeHelpCallback("group_trees.hlp")); |
---|
664 | aws->at("help"); |
---|
665 | aws->create_button("HELP", "HELP", "H"); |
---|
666 | |
---|
667 | aws->at("list"); |
---|
668 | AW_DB_selection *all_trees = awt_create_TREE_selection_list(data->get_gb_main(), aws, AWAR_TREE_SELECTED, true); |
---|
669 | AW_selection *selected_trees = awt_create_subset_selection_list(aws, all_trees->get_sellist(), "selected", "add", "sort"); |
---|
670 | |
---|
671 | data->announce_tree_select_list(selected_trees); |
---|
672 | |
---|
673 | return aws; |
---|
674 | } |
---|
675 | |
---|
676 | static AW_window *create_group_rename_window_cb(AW_root *awr, GroupUIdata *data) { |
---|
677 | AW_window_simple *aws = new AW_window_simple; |
---|
678 | aws->init(awr, "RENAME_GROUPS", "Rename taxonomic groups"); |
---|
679 | |
---|
680 | const int PAD = 10; |
---|
681 | |
---|
682 | aws->at(PAD, PAD); |
---|
683 | aws->auto_space(PAD/2, PAD/2); |
---|
684 | |
---|
685 | aws->button_length(7); |
---|
686 | |
---|
687 | aws->callback(AW_POPDOWN); |
---|
688 | aws->create_button("CLOSE", "Close", "C"); |
---|
689 | |
---|
690 | aws->at_shift(450, 0); // defines minimum window width |
---|
691 | |
---|
692 | aws->at_attach(-PAD, 0); |
---|
693 | aws->callback(makeHelpCallback("group_rename.hlp")); |
---|
694 | aws->create_button("HELP", "Help", "H"); |
---|
695 | aws->at_unattach(); |
---|
696 | |
---|
697 | const int IF_YSIZE = 32; // lineheight of attached input field |
---|
698 | |
---|
699 | const int LABEL_LENGTH = 27; |
---|
700 | const int FIELD_LENGTH = 40; // startup-length (chars) |
---|
701 | |
---|
702 | aws->label_length(LABEL_LENGTH); |
---|
703 | |
---|
704 | aws->at_newline(); |
---|
705 | int sy = aws->get_at_yposition(); |
---|
706 | |
---|
707 | aws->at_attach_to(true, false, -PAD, IF_YSIZE); |
---|
708 | aws->label("Selected group name:"); |
---|
709 | aws->create_input_field(AWAR_SELECTED_GROUP_NAME, FIELD_LENGTH); |
---|
710 | |
---|
711 | aws->at_newline(); |
---|
712 | int inputlineHeight = aws->get_at_yposition()-sy; |
---|
713 | |
---|
714 | aws->at_attach_to(true, false, -PAD, IF_YSIZE); |
---|
715 | aws->label("Modify using ACI/SRT:"); |
---|
716 | aws->create_input_field(AWAR_RENAME_EXPRESSION, FIELD_LENGTH); |
---|
717 | aws->at_unattach(); |
---|
718 | |
---|
719 | aws->at_newline(); |
---|
720 | int rx, ry; |
---|
721 | aws->get_at_position(&rx, &ry); |
---|
722 | aws->at_shift(0, inputlineHeight); // reserve space for "Resulting group name" |
---|
723 | |
---|
724 | int by = aws->get_at_yposition(); |
---|
725 | aws->at_attach(0, -PAD); |
---|
726 | aws->callback(makeWindowCallback(rename_selected_group_cb, data)); |
---|
727 | aws->create_autosize_button(NULp, "Apply to\nselected group"); |
---|
728 | aws->at_attach(0, -PAD); |
---|
729 | aws->callback(makeWindowCallback(rename_listed_groups_cb, data)); |
---|
730 | aws->create_autosize_button(NULp, "Apply to all\nlisted groups"); |
---|
731 | aws->at_unattach(); |
---|
732 | |
---|
733 | aws->at_newline(); |
---|
734 | int bheight = aws->get_at_yposition()-by; |
---|
735 | |
---|
736 | aws->at(rx, ry); |
---|
737 | aws->at_attach_to(true, true, -PAD, -(PAD+bheight)); |
---|
738 | aws->label("Resulting group name:"); |
---|
739 | aws->create_button(NULp, AWAR_RESULTING_GROUP_NAME, NULp, "+"); |
---|
740 | aws->at_unattach(); |
---|
741 | |
---|
742 | aws->window_fit(); |
---|
743 | |
---|
744 | return aws; |
---|
745 | } |
---|
746 | |
---|
747 | static AW_window *create_dup_config_window_cb(AW_root *awr) { |
---|
748 | AW_window_simple *aws = new AW_window_simple; |
---|
749 | aws->init(awr, "DUP_CONFIG", "Configure group duplicate search"); |
---|
750 | |
---|
751 | const int PAD = 10; |
---|
752 | const int IF_YSIZE = 32; // lineheight of attached input field |
---|
753 | |
---|
754 | aws->at(PAD, PAD); |
---|
755 | aws->auto_space(PAD/2, PAD/2); |
---|
756 | |
---|
757 | aws->button_length(7); |
---|
758 | |
---|
759 | aws->callback(AW_POPDOWN); |
---|
760 | aws->create_button("CLOSE", "Close", "C"); |
---|
761 | |
---|
762 | const int LONG_LABEL_LENGTH = 30; |
---|
763 | const int SHORT_LABEL_LENGTH = 15; |
---|
764 | const int LONG_FIELD_LENGTH = 40; // used for string input field |
---|
765 | const int SHORT_FIELD_LENGTH = 7; // used for numeric input fields |
---|
766 | |
---|
767 | aws->label_length(LONG_LABEL_LENGTH); |
---|
768 | |
---|
769 | aws->at_newline(); |
---|
770 | aws->label("Min. size of duplicate cluster:"); |
---|
771 | aws->create_input_field(AWAR_DUP_MIN_CLUSTER_SIZE, SHORT_FIELD_LENGTH); |
---|
772 | |
---|
773 | aws->at_newline(); |
---|
774 | aws->label("Search duplicates"); |
---|
775 | aws->create_option_menu(AWAR_DUP_TREE_MODE, true); |
---|
776 | aws->insert_default_option("inside same tree", "s", DLC_SAME_TREE); |
---|
777 | aws->insert_option ("in different trees", "d", DLC_DIFF_TREE); |
---|
778 | aws->insert_option ("anywhere", "a", DLC_ANYWHERE); |
---|
779 | aws->update_option_menu(); |
---|
780 | |
---|
781 | aws->at_newline(); |
---|
782 | aws->label("Ignore case?"); |
---|
783 | aws->create_toggle(AWAR_DUP_IGNORE_CASE); |
---|
784 | |
---|
785 | aws->at_newline(); |
---|
786 | aws->create_toggle_field(AWAR_DUP_NAME_MATCH, "Duplicates are names that", "N"); |
---|
787 | aws->insert_default_toggle("match whole name", "n", DNC_WHOLENAME); |
---|
788 | aws->insert_toggle ("match wordwise", "w", DNC_WORDWISE); |
---|
789 | aws->update_toggle_field(); |
---|
790 | |
---|
791 | aws->at_newline(); |
---|
792 | aws->label("Min. number of matching words"); |
---|
793 | aws->create_input_field(AWAR_DUP_MIN_WORDS, SHORT_FIELD_LENGTH); |
---|
794 | |
---|
795 | aws->at_newline(); |
---|
796 | aws->label("Word separators"); |
---|
797 | aws->create_input_field(AWAR_DUP_WORD_SEPARATORS, 2*SHORT_FIELD_LENGTH); |
---|
798 | |
---|
799 | aws->at_newline(); |
---|
800 | aws->label_length(SHORT_LABEL_LENGTH); |
---|
801 | aws->at_attach_to(true, false, -PAD, IF_YSIZE); |
---|
802 | aws->label("Ignored words"); |
---|
803 | aws->create_input_field(AWAR_DUP_EXCLUDED_WORDS, LONG_FIELD_LENGTH); |
---|
804 | |
---|
805 | aws->window_fit(); |
---|
806 | return aws; |
---|
807 | } |
---|
808 | |
---|
809 | static AWT_config_mapping_def group_search_config_mapping[] = { |
---|
810 | { AWAR_SEARCH_WHICH_TREES, "searched_trees" }, |
---|
811 | { AWAR_SEARCH_MODE, "search_mode" }, |
---|
812 | { AWAR_MATCH_MODE, "match_mode" }, |
---|
813 | |
---|
814 | { AWAR_DUPLICATE_MODE, "dup_mode" }, |
---|
815 | { AWAR_DUP_TREE_MODE, "dup_tree_mode" }, |
---|
816 | { AWAR_DUP_MIN_CLUSTER_SIZE, "dup_min_size" }, |
---|
817 | { AWAR_DUP_NAME_MATCH, "dup_match_mode" }, |
---|
818 | { AWAR_DUP_IGNORE_CASE, "dup_ignore_case" }, |
---|
819 | { AWAR_DUP_MIN_WORDS, "dup_min_words" }, |
---|
820 | { AWAR_DUP_EXCLUDED_WORDS, "dup_excluded_words" }, |
---|
821 | { AWAR_DUP_WORD_SEPARATORS, "dup_word_separators" }, |
---|
822 | |
---|
823 | { AWAR_MARK_TARGET, "mark_target" }, |
---|
824 | { AWAR_RENAME_EXPRESSION, "rename_script" }, |
---|
825 | |
---|
826 | { NULp, NULp }, |
---|
827 | }; |
---|
828 | |
---|
829 | static void create_search_config_setup_cb(AWT_config_definition& def) { |
---|
830 | def.add(group_search_config_mapping); // fixed parameters |
---|
831 | |
---|
832 | for (int crit = 1; crit<=MAX_CRITERIA; ++crit) { |
---|
833 | if (crit>1) { |
---|
834 | def.add(criterion_awar_name(AWARFORMAT_CRIT_OPERATOR, crit), "op", crit); |
---|
835 | } |
---|
836 | def.add(criterion_awar_name(AWARFORMAT_CRIT_KEY, crit), "sel", crit); |
---|
837 | def.add(criterion_awar_name(AWARFORMAT_CRIT_EQUALS, crit), "eq", crit); |
---|
838 | def.add(criterion_awar_name(AWARFORMAT_CRIT_MATCHES, crit), "expr", crit); |
---|
839 | } |
---|
840 | } |
---|
841 | |
---|
842 | static AWT_predefined_config predefined_group_search[] = { |
---|
843 | { "*tagged_find", "Search expression for \"tagged\" groupnames", "dup_mode='0';eq1='0';expr1='*[*]*';match_mode='0';op2='2';op3='2';sel1='0'" }, |
---|
844 | { "*tags_remove_all", "Expression for batch-rename:\n- removes any prefix(es) in \"[..]\" from groupnames", "rename_script='/\\\\[.*\\\\]//'" }, |
---|
845 | { "*tag_prefix", "Expression for batch-rename:\n- adds prefix \"[TAG] \" to groupnames", "rename_script='\"[TAG] \";dd'" }, |
---|
846 | { "*swap_AND_names", "Batch-rename:\n \"X and Y\" -> \"Y and X\"\n \"X, Y and Z\" -> \"Z, X and Y\" ...", "rename_script=':* and *=*2 and *1:* and *, *=*1, *2 and *3:*, * and *, *=*1, *2, *3 and *4'" }, |
---|
847 | { "*rename_enumerated", "Batch-rename:\n- appends running number to group-name\n (using hitlist order)", "rename_script='dd;\"_\";command(\"\\\\\"00\\\\\";hitidx|merge|tail(3)\")'" }, |
---|
848 | { "*remove_numeric_suffix", "Batch-rename:\n- removes numeric suffixes \n like \"_1\", \"_003\"", "rename_script='/_[0-9]+$//'" }, |
---|
849 | |
---|
850 | { NULp, NULp, NULp } |
---|
851 | }; |
---|
852 | |
---|
853 | void popup_group_search_window(AW_window *aw_parent, GBDATA *gb_main) { |
---|
854 | static AW_window *awgs = NULp; |
---|
855 | static GroupUIdata data(gb_main); |
---|
856 | |
---|
857 | data.initialize(); |
---|
858 | |
---|
859 | if (!awgs) { |
---|
860 | AW_window_simple *aws = new AW_window_simple; |
---|
861 | AW_root *aw_root = aw_parent->get_root(); |
---|
862 | |
---|
863 | aws->init(aw_root, "GROUP_SEARCH", "Search taxonomic groups"); |
---|
864 | aws->load_xfig("group_search.fig"); |
---|
865 | |
---|
866 | aws->button_length(7); |
---|
867 | |
---|
868 | aws->at("close"); |
---|
869 | aws->callback(AW_POPDOWN); |
---|
870 | aws->create_button("CLOSE", "Close", "C"); |
---|
871 | |
---|
872 | aws->at("help"); |
---|
873 | aws->callback(makeHelpCallback("group_search.hlp")); |
---|
874 | aws->create_button("HELP", "Help", "H"); |
---|
875 | |
---|
876 | aws->at("config"); |
---|
877 | AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "group_search", makeConfigSetupCallback(create_search_config_setup_cb), NULp, predefined_group_search); |
---|
878 | |
---|
879 | aws->at("trees"); |
---|
880 | aws->create_option_menu(AWAR_SEARCH_WHICH_TREES, true); |
---|
881 | aws->insert_default_option("current tree", "c", SEARCH_CURRENT_TREE); |
---|
882 | aws->insert_option ("selected trees", "s", SEARCH_SELECTED_TREES); |
---|
883 | aws->insert_option ("all trees", "a", SEARCH_ALL_TREES); |
---|
884 | aws->update_option_menu(); |
---|
885 | |
---|
886 | aws->at("select"); |
---|
887 | aws->callback(makeCreateWindowCallback(create_tree_select_window_cb, &data)); |
---|
888 | aws->create_autosize_button("select_trees", "(select)"); |
---|
889 | |
---|
890 | aws->at("mode"); |
---|
891 | aws->create_option_menu(AWAR_SEARCH_MODE, true); |
---|
892 | aws->insert_default_option("list", "l", GSM_FIND); |
---|
893 | aws->insert_option ("add", "a", GSM_ADD); |
---|
894 | aws->insert_option ("keep", "k", GSM_KEEP); |
---|
895 | aws->insert_option ("remove", "r", GSM_REMOVE); |
---|
896 | aws->update_option_menu(); |
---|
897 | |
---|
898 | aws->at("not"); |
---|
899 | aws->create_option_menu(AWAR_MATCH_MODE, true); |
---|
900 | aws->insert_default_option("match", "m", GSM_MATCH); |
---|
901 | aws->insert_option ("don't match", "d", GSM_MISMATCH); |
---|
902 | aws->update_option_menu(); |
---|
903 | |
---|
904 | aws->at("dups"); |
---|
905 | aws->create_option_menu(AWAR_DUPLICATE_MODE, true); |
---|
906 | aws->insert_default_option("No", "n", DONT_MIND_DUPLICATES); |
---|
907 | aws->insert_option ("duplicate groups only", "d", ONLY_DUPLICATES); |
---|
908 | aws->insert_option ("unique groups only", "u", ONLY_UNIQUE); |
---|
909 | aws->update_option_menu(); |
---|
910 | |
---|
911 | aws->at("dupconf"); |
---|
912 | aws->callback(create_dup_config_window_cb); |
---|
913 | aws->create_autosize_button("config_dup", "Configure"); |
---|
914 | |
---|
915 | WindowCallback search_wcb = makeWindowCallback(runGroupSearch_cb, &data); |
---|
916 | |
---|
917 | for (int crit = 1; crit<=MAX_CRITERIA; ++crit) { |
---|
918 | if (crit>1) { |
---|
919 | aws->at(GBS_global_string("op%i", crit)); |
---|
920 | aws->create_option_menu(criterion_awar_name(AWARFORMAT_CRIT_OPERATOR, crit), true); |
---|
921 | aws->insert_option ("and", "a", CO_AND); |
---|
922 | aws->insert_option ("or", "o", CO_OR); |
---|
923 | aws->insert_default_option("ign", "i", CO_IGNORE); |
---|
924 | aws->update_option_menu(); |
---|
925 | } |
---|
926 | |
---|
927 | aws->at(GBS_global_string("crit%i", crit)); |
---|
928 | aws->create_option_menu(criterion_awar_name(AWARFORMAT_CRIT_KEY, crit), true); |
---|
929 | aws->insert_default_option("groupname", "g", CT_NAME); |
---|
930 | aws->insert_option ("parent", "p", CT_PARENT_DIRECT); |
---|
931 | aws->insert_option ("parent (any)", "a", CT_PARENT_ANY); |
---|
932 | aws->insert_option ("parent (all)", "l", CT_PARENT_ALL); |
---|
933 | aws->insert_option ("nesting", "n", CT_NESTING_LEVEL); |
---|
934 | aws->insert_option ("folded", "f", CT_FOLDED); |
---|
935 | aws->insert_option ("size", "s", CT_SIZE); |
---|
936 | aws->insert_option ("marked", "m", CT_MARKED); |
---|
937 | aws->insert_option ("marked%", "%", CT_MARKED_PC); |
---|
938 | aws->insert_option ("zombies", "z", CT_ZOMBIES); |
---|
939 | aws->insert_option ("AID", "A", CT_AID); |
---|
940 | aws->insert_option ("keeled", "k", CT_KEELED); |
---|
941 | aws->update_option_menu(); |
---|
942 | |
---|
943 | aws->at(GBS_global_string("eq%i", crit)); |
---|
944 | aws->create_toggle(criterion_awar_name(AWARFORMAT_CRIT_EQUALS, crit), "#equal.xpm", "#notEqual.xpm"); |
---|
945 | |
---|
946 | aws->at(GBS_global_string("content%i", crit)); |
---|
947 | aws->d_callback(search_wcb); // ENTER in search field |
---|
948 | aws->create_input_field(criterion_awar_name(AWARFORMAT_CRIT_MATCHES, crit)); |
---|
949 | } |
---|
950 | |
---|
951 | aws->button_length(18); |
---|
952 | |
---|
953 | aws->at("doquery"); |
---|
954 | aws->callback(search_wcb); |
---|
955 | aws->create_button("SEARCH", "Search", "S", "+"); |
---|
956 | |
---|
957 | aws->button_length(13); |
---|
958 | |
---|
959 | aws->at("count"); |
---|
960 | aws->label("Hits:"); |
---|
961 | aws->create_button(NULp, AWAR_GROUP_HIT_COUNT, NULp, "+"); |
---|
962 | |
---|
963 | aws->at("result"); |
---|
964 | aws->d_callback(makeWindowCallback(double_click_group_cb, &data)); |
---|
965 | AW_selection_list *result_list = aws->create_selection_list(AWAR_MAYBE_INVALID_GROUP, true); |
---|
966 | data.announce_result_list(result_list); |
---|
967 | data.clear_result_list(); |
---|
968 | |
---|
969 | aws->at("order"); |
---|
970 | aws->create_option_menu(AWAR_RESULT_ORDER, true); |
---|
971 | aws->insert_default_option("unsorted", "u", GSC_NONE); |
---|
972 | aws->insert_option ("by name", "n", GSC_NAME); |
---|
973 | aws->insert_option ("by nesting", "g", GSC_NESTING); |
---|
974 | aws->insert_option ("by size", "s", GSC_SIZE); |
---|
975 | aws->insert_option ("by marked", "m", GSC_MARKED); |
---|
976 | aws->insert_option ("by marked%", "%", GSC_MARKED_PC); |
---|
977 | aws->insert_option ("by treename", "t", GSC_TREENAME); |
---|
978 | aws->insert_option ("by treeorder", "o", GSC_TREEORDER); |
---|
979 | aws->insert_option ("by hit", "h", GSC_HIT_REASON); |
---|
980 | aws->insert_option ("by cluster", "c", GSC_CLUSTER); |
---|
981 | aws->insert_option ("by AID", "A", GSC_AID); |
---|
982 | aws->insert_option ("by keeled", "k", GSC_KEELED); |
---|
983 | aws->insert_option ("reverse", "r", GSC_REVERSE); |
---|
984 | aws->update_option_menu(); |
---|
985 | |
---|
986 | // actions (results): |
---|
987 | aws->button_length(6); |
---|
988 | |
---|
989 | aws->at("remhit"); |
---|
990 | aws->callback(makeWindowCallback(remove_hit_cb, &data)); |
---|
991 | aws->create_button("rm_sel", "Remove"); |
---|
992 | |
---|
993 | aws->at("clear"); |
---|
994 | aws->callback(makeWindowCallback(clear_results_cb, &data)); |
---|
995 | aws->create_button("clear", "Clear"); |
---|
996 | |
---|
997 | // actions (groups): |
---|
998 | // ..... rename |
---|
999 | aws->at("rename"); |
---|
1000 | aws->callback(makeCreateWindowCallback(create_group_rename_window_cb, &data)); |
---|
1001 | aws->create_button("rename", "Rename ..."); |
---|
1002 | |
---|
1003 | // ..... expand/collapse |
---|
1004 | aws->at("expand"); |
---|
1005 | aws->callback(makeWindowCallback(listed_groups_folding_cb, &data, GFM_EXPANDREC)); |
---|
1006 | aws->create_button("expand_listed", "Expand listed"); |
---|
1007 | |
---|
1008 | aws->at("expcol"); |
---|
1009 | aws->callback(makeWindowCallback(listed_groups_folding_cb, &data, GFM_EXPANDREC_COLLREST)); |
---|
1010 | aws->create_button("explst_collrest", "Expand listed\ncollapse rest"); |
---|
1011 | |
---|
1012 | aws->at("expparent"); |
---|
1013 | aws->callback(makeWindowCallback(listed_groups_folding_cb, &data, GFM_EXPANDPARENTS)); |
---|
1014 | aws->create_button("expand_parents", "Expand parents"); |
---|
1015 | |
---|
1016 | aws->at("collapse"); |
---|
1017 | aws->callback(makeWindowCallback(listed_groups_folding_cb, &data, GFM_COLLAPSE)); |
---|
1018 | aws->create_button("collapse_listed", "Collapse listed"); |
---|
1019 | |
---|
1020 | // ..... delete |
---|
1021 | aws->at("delete"); |
---|
1022 | aws->callback(makeWindowCallback(delete_selected_group_cb, &data)); |
---|
1023 | aws->create_button("del_sel", "Destroy\nselected group"); |
---|
1024 | |
---|
1025 | aws->at("dellisted"); |
---|
1026 | aws->callback(makeWindowCallback(delete_listed_groups_cb, &data)); |
---|
1027 | aws->create_button("del_listed", "Destroy all\nlisted groups"); |
---|
1028 | |
---|
1029 | // ..... mark/unmark |
---|
1030 | aws->at("mark"); |
---|
1031 | aws->callback(makeWindowCallback(group_mark_cb, &data, GMM_MARK)); |
---|
1032 | aws->create_button("mark", "Mark"); |
---|
1033 | aws->at("unmark"); |
---|
1034 | aws->callback(makeWindowCallback(group_mark_cb, &data, GMM_UNMARK)); |
---|
1035 | aws->create_button("unmark", "Unmark"); |
---|
1036 | aws->at("inv"); |
---|
1037 | aws->callback(makeWindowCallback(group_mark_cb, &data, GMM_INVERT)); |
---|
1038 | aws->create_button("invert", "Inv"); |
---|
1039 | |
---|
1040 | aws->at("mwhich"); // needs to be created AFTER delete buttons |
---|
1041 | aws->create_option_menu(AWAR_MARK_TARGET, true); |
---|
1042 | aws->insert_default_option("selected", "s", GMT_SELECTED_GROUP); |
---|
1043 | aws->insert_option ("any listed", "l", GMT_ANY_GROUP); |
---|
1044 | aws->insert_option ("all listed", "a", GMT_ALL_GROUPS); |
---|
1045 | aws->insert_option ("database", "d", GMT_ALL_SPECIES); |
---|
1046 | aws->update_option_menu(); |
---|
1047 | |
---|
1048 | // trigger cleanup on popdown: |
---|
1049 | aws->on_hide(makeWindowCallback(popdown_search_window_cb, &data)); |
---|
1050 | awgs = aws; |
---|
1051 | } |
---|
1052 | |
---|
1053 | awgs->activate(); |
---|
1054 | } |
---|
1055 | |
---|