source: branches/stable/NTREE/NT_group_search.cxx

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