source: tags/ms_r18q1/NTREE/NT_group_search.cxx

Last change on this file was 17110, checked in by westram, 6 years ago
File size: 42.1 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 <arb_strarray.h>
28#include <ad_cb_prot.h>
29
30using 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
75inline 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
80enum TreeSearchRange {
81    SEARCH_CURRENT_TREE,
82    SEARCH_SELECTED_TREES,
83    SEARCH_ALL_TREES,
84};
85
86enum DuplicateMode {
87    DONT_MIND_DUPLICATES,
88    ONLY_DUPLICATES,
89    ONLY_UNIQUE,
90};
91
92// ---------------------
93//      data for UI
94
95class 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
145public:
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
212void 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}
219void 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
253void 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
267void 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
296void 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
327void 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}
336void GroupUIdata::remove_all_results() {
337    group_search->forget_results();
338    refill_result_list();
339}
340
341void 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
349void 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:
357static void popdown_search_window_cb(AW_window*, GroupUIdata *data) { data->cleanup(); }
358static void runGroupSearch_cb(AW_window*, GroupUIdata *data) { data->run_search(); }
359static void remove_hit_cb(AW_window*, GroupUIdata *data) { data->remove_selected_result(); }
360static void clear_results_cb(AW_window*, GroupUIdata *data) { data->remove_all_results(); }
361static void delete_selected_group_cb(AW_window*, GroupUIdata *data) { data->delete_selected_group(); }
362static void delete_listed_groups_cb(AW_window*, GroupUIdata *data) { data->delete_listed_groups(); }
363static void rename_selected_group_cb(AW_window*, GroupUIdata *data) { data->rename_selected_group(); }
364static void rename_listed_groups_cb(AW_window*, GroupUIdata *data) { data->rename_listed_groups(); }
365static void double_click_group_cb(AW_window*, GroupUIdata *data) { data->toggle_selected_group_folding(); }
366static void listed_groups_folding_cb(AW_window*, GroupUIdata *data, GroupFoldingMode mode) { data->change_listed_groups_folding(mode); }
367static void group_mark_cb(AW_window*, GroupUIdata *data, GroupMarkMode mode) { data->mark_species(mode); }
368
369
370static 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
405static bool inside_group_selection = false;
406
407void 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
434static 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
446static bool nameChangedByGroupChange = false;
447
448void 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
464void 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
490void 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
513void 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
522void 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
534void 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
543void 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
554enum 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
561void 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
600void 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
651static 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
676static 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
747static 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
809static 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
829static 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
842static 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
853void 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
Note: See TracBrowser for help on using the repository browser.