source: branches/alilink/SL/DB_QUERY/db_query.cxx

Last change on this file was 18126, checked in by westram, 5 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 105.3 KB
Line 
1// ============================================================ //
2//                                                              //
3//   File      : db_query.cxx                                   //
4//   Purpose   : Database queries                               //
5//                                                              //
6//   Institute of Microbiology (Technical University Munich)    //
7//   http://www.arb-home.de/                                    //
8//                                                              //
9// ============================================================ //
10
11#include "db_query.h"
12#include "db_query_local.h"
13#include "query_expr.h"
14
15#include <dbui.h>
16#include <item_sel_list.h>
17#include <awt_config_manager.hxx>
18#include <awt_sel_boxes.hxx>
19
20#include <aw_advice.hxx>
21#include <aw_color_groups.hxx>
22#include <aw_file.hxx>
23#include <aw_msg.hxx>
24#include <aw_awar.hxx>
25#include <arb_progress.h>
26#include <aw_root.hxx>
27#include <aw_question.hxx>
28#include <rootAsWin.h>
29
30#include <ad_cb.h>
31
32#include <arb_strbuf.h>
33#include <arb_sort.h>
34#include <arb_global_defs.h>
35#include <Keeper.h>
36
37#include <list>
38#include <stack>
39
40using namespace std;
41using namespace QUERY;
42
43#define MAX_QUERY_LIST_LEN  100000
44
45#define AWAR_COLORIZE "tmp/dbquery_all/colorize"
46
47static void free_hit_description(long info) {
48    free(reinterpret_cast<char*>(info));
49}
50
51inline void SET_QUERIED(GBDATA *gb_species, DbQuery *query, const char *hitInfo, size_t hitInfoLen = 0) {
52    dbq_assert(hitInfo);
53
54    GB_raise_user_flag(gb_species, query->select_bit);
55
56    char *name = query->selector.generate_item_id(query->gb_main, gb_species);
57    char *info;
58
59    if (hitInfoLen == 0) hitInfoLen = strlen(hitInfo);
60    if (hitInfoLen>MAX_SHOWN_DATA_SIZE) {
61        char *dupInfo = strdup(hitInfo);
62        hitInfoLen    = GBS_shorten_repeated_data(dupInfo);
63        if (hitInfoLen>MAX_SHOWN_DATA_SIZE) {
64            strcpy(dupInfo+hitInfoLen-5, "[...]");
65        }
66        info = strdup(dupInfo);
67        free(dupInfo);
68    }
69    else {
70        info = strdup(hitInfo);
71    }
72
73    GBS_write_hash(query->hit_description, name, reinterpret_cast<long>(info)); // overwrite hit info (also deallocates)
74    free(name);
75}
76
77inline void CLEAR_QUERIED(GBDATA *gb_species, DbQuery *query) {
78    GB_clear_user_flag(gb_species, query->select_bit);
79
80    char *name = query->selector.generate_item_id(query->gb_main, gb_species);
81    GBS_write_hash(query->hit_description, name, 0); // delete hit info (also deallocates)
82    free(name);
83}
84
85inline const char *getHitInfo(const char *item_id, DbQuery *query) {
86    long info = GBS_read_hash(query->hit_description, item_id);
87    return reinterpret_cast<const char*>(info);
88}
89inline const char *getHitInfo(GBDATA *gb_species, DbQuery *query) {
90    char       *name   = query->selector.generate_item_id(query->gb_main, gb_species);
91    const char *result = getHitInfo(name, query);
92    free(name);
93    return result;
94}
95inline string keptHitReason(const string& currentHitReason, GBDATA *gb_item, DbQuery *query) {
96    string      reason  = string("kept because ")+currentHitReason;
97    const char *hitinfo = getHitInfo(gb_item, query);
98    if (hitinfo) reason = string(hitinfo)+" ("+reason+')';
99    return reason;
100}
101
102static void create_query_independent_awars(AW_root *aw_root, AW_default aw_def) {
103    aw_root->awar_int(AWAR_COLORIZE, 0, aw_def);
104}
105
106GBDATA *QUERY::query_get_gb_main(DbQuery *query) {
107    return query->gb_main;
108}
109
110const ItemSelector& QUERY::get_queried_itemtype(DbQuery *query) {
111    return query->selector;
112}
113
114enum EXT_QUERY_TYPES {
115    EXT_QUERY_NONE,
116    EXT_QUERY_COMPARE_LINES,
117    EXT_QUERY_COMPARE_WORDS
118};
119
120query_spec::query_spec(ItemSelector& selector_)
121    : selector(selector_),
122      gb_main(NULp),
123      gb_ref(NULp),
124      expect_hit_in_ref_list(false),
125      species_name(NULp),
126      tree_name(NULp),
127      select_bit(GB_USERFLAG_QUERY), // always == GB_USERFLAG_QUERY atm (nevertheless DO NOT hardcode)
128      use_menu(0),
129      ere_pos_fig(NULp),
130      where_pos_fig(NULp),
131      by_pos_fig(NULp),
132      qbox_pos_fig(NULp),
133      key_pos_fig(NULp),
134      query_pos_fig(NULp),
135      result_pos_fig(NULp),
136      count_pos_fig(NULp),
137      do_query_pos_fig(NULp),
138      config_pos_fig(NULp),
139      do_mark_pos_fig(NULp),
140      do_unmark_pos_fig(NULp),
141      do_delete_pos_fig(NULp),
142      do_set_pos_fig(NULp),
143      open_parser_pos_fig(NULp),
144      do_refresh_pos_fig(NULp),
145      popup_info_window(NULp),
146      info_box_pos_fig(NULp)
147{
148    dbq_assert(&selector);
149}
150
151bool query_spec::is_queried(GBDATA *gb_item) const {
152    return GB_user_flag(gb_item, select_bit);
153}
154
155bool QUERY::IS_QUERIED(GBDATA *gb_item, const DbQuery *query) {
156    return query->is_queried(gb_item);
157}
158
159long QUERY::count_queried_items(DbQuery *query, QUERY_RANGE range) {
160    GBDATA       *gb_main  = query->gb_main;
161    ItemSelector& selector = query->selector;
162
163    long count = 0;
164
165    for (GBDATA *gb_item_container = selector.get_first_item_container(gb_main, query->aws->get_root(), range);
166         gb_item_container;
167         gb_item_container = selector.get_next_item_container(gb_item_container, range))
168    {
169        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
170             gb_item;
171             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
172        {
173            if (IS_QUERIED(gb_item, query)) count++;
174        }
175    }
176    return count;
177}
178
179#if defined(WARN_TODO)
180#warning replace query_count_items by "method" of selector
181#endif
182
183static int query_count_items(DbQuery *query, QUERY_RANGE range, QUERY_MODES mode) {
184    int             count    = 0;
185    GBDATA         *gb_main  = query->gb_main;
186    ItemSelector&   selector = query->selector;
187    GB_transaction  ta(gb_main);
188
189    for (GBDATA *gb_item_container = selector.get_first_item_container(gb_main, query->aws->get_root(), range);
190         gb_item_container;
191         gb_item_container = selector.get_next_item_container(gb_item_container, range))
192    {
193        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
194             gb_item;
195             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
196        {
197            switch (mode) {
198                case QUERY_GENERATE: ++count; break;
199                case QUERY_ENLARGE:  count += !IS_QUERIED(gb_item, query); break;
200                case QUERY_REDUCE:   count +=  IS_QUERIED(gb_item, query); break;
201            }
202        }
203    }
204    return count;
205}
206
207const int MAX_CRITERIA = int(sizeof(unsigned long)*8/QUERY_SORT_CRITERIA_BITS);
208
209static void split_sort_mask(unsigned long sort_mask, QUERY_RESULT_ORDER *order) {
210    // splits the sort order bit mask 'sort_mask' into single sort criteria and write these into 'order'
211    // (order[0] will contain the primary sort criteria, order[1] the secondary, ...)
212
213    for (int o = 0; o<MAX_CRITERIA; ++o) {
214        order[o] = QUERY_RESULT_ORDER(sort_mask&QUERY_SORT_CRITERIA_MASK);
215        dbq_assert(order[o] == (order[o]&QUERY_SORT_CRITERIA_MASK));
216        sort_mask = sort_mask>>QUERY_SORT_CRITERIA_BITS;
217    }
218}
219
220static QUERY_RESULT_ORDER find_display_determining_sort_order(QUERY_RESULT_ORDER *order) {
221    // Returns the first criteria in 'order' (which has to have MAX_CRITERIA elements)
222    // that matches
223    // - QUERY_SORT_BY_1STFIELD_CONTENT or
224    // - QUERY_SORT_NUM_BY_1STFIELD_CONTENT or
225    // - QUERY_SORT_BY_HIT_DESCRIPTION
226    // (or QUERY_SORT_NONE if none of the above is used)
227
228    QUERY_RESULT_ORDER first = QUERY_SORT_NONE;
229    for (int o = 0; o<MAX_CRITERIA && first == QUERY_SORT_NONE; ++o) {
230        if (order[o] & (QUERY_SORT_BY_1STFIELD_CONTENT|QUERY_SORT_NUM_BY_1STFIELD_CONTENT|QUERY_SORT_BY_HIT_DESCRIPTION)) {
231            first = order[o];
232        }
233    }
234    return first;
235}
236
237static void remove_keydependent_sort_criteria(QUERY_RESULT_ORDER *order) {
238    // removes all sort-criteria from order which would use the order of the currently selected primary key
239
240    int n = 0;
241    for (int o = 0; o<MAX_CRITERIA; ++o) {
242        if (order[o] != QUERY_SORT_BY_1STFIELD_CONTENT && order[o] != QUERY_SORT_NUM_BY_1STFIELD_CONTENT) {
243            order[n++] = order[o];
244        }
245    }
246    for (; n<MAX_CRITERIA; ++n) {
247        order[n] = QUERY_SORT_NONE;
248    }
249}
250
251static void first_searchkey_changed_cb(AW_root *, DbQuery *query) {
252    QUERY_RESULT_ORDER order[MAX_CRITERIA];
253    split_sort_mask(query->sort_mask, order);
254
255    QUERY_RESULT_ORDER usedOrder = find_display_determining_sort_order(order);
256    if (usedOrder != QUERY_SORT_BY_HIT_DESCRIPTION && usedOrder != QUERY_SORT_NONE) { // do we display values?
257        DbQuery_update_list(query);
258    }
259}
260
261inline bool keep_criteria(QUERY_RESULT_ORDER old_criteria, QUERY_RESULT_ORDER new_criteria) {
262    return
263        old_criteria  != QUERY_SORT_NONE &&     // do not keep 'unsorted' (it is no real criteria)
264        (old_criteria != new_criteria        ||     // do not keep new criteria (added later)
265         old_criteria == QUERY_SORT_REVERSE);   // reverse may occur several times -> always keep
266}
267
268static void result_sort_order_changed_cb(AW_root *aw_root, DbQuery *query) {
269    // adds the new selected sort order to the sort order mask
270    // (added order removes itself, if it previously existed in sort order mask)
271    //
272    // if 'unsorted' is selected -> delete sort order
273
274    QUERY_RESULT_ORDER new_criteria = (QUERY_RESULT_ORDER)aw_root->awar(query->awar_sort)->read_int();
275    if (new_criteria == QUERY_SORT_NONE) {
276        query->sort_mask = QUERY_SORT_NONE; // clear sort_mask
277    }
278    else {
279        QUERY_RESULT_ORDER order[MAX_CRITERIA];
280        split_sort_mask(query->sort_mask, order);
281
282        int empty_or_same = 0;
283        for (int o = 0; o<MAX_CRITERIA; o++) {
284            if (!keep_criteria(order[o], new_criteria)) {
285                empty_or_same++; // these criteria will be skipped below
286            }
287        }
288
289        unsigned long new_sort_mask = 0;
290        for (int o = MAX_CRITERIA-(empty_or_same>0 ? 1 : 2); o >= 0; o--) {
291            if (keep_criteria(order[o], new_criteria)) {
292                new_sort_mask = (new_sort_mask<<QUERY_SORT_CRITERIA_BITS)|order[o];
293            }
294        }
295        query->sort_mask = (new_sort_mask<<QUERY_SORT_CRITERIA_BITS)|new_criteria; // add new primary key
296    }
297    DbQuery_update_list(query);
298}
299
300struct hits_sort_params : virtual Noncopyable {
301    DbQuery            *query;
302    char               *first_key;
303    GB_TYPES            first_type;
304    QUERY_RESULT_ORDER  order[MAX_CRITERIA];
305
306    hits_sort_params(DbQuery *q, const char *fk)
307        : query(q),
308          first_key(strdup(fk))
309    {
310        first_type = GBT_get_type_of_changekey(query->gb_main, first_key, query->selector.change_key_path);
311    }
312
313    ~hits_sort_params() {
314        free(first_key);
315    }
316};
317
318inline int numeric_string_cmp(const char *str1, const char *str2) {
319    const char *end1;
320    const char *end2;
321
322    double double1 = strtod(str1, const_cast<char**>(&end1));
323    double double2 = strtod(str2, const_cast<char**>(&end2));
324
325    bool conv1 = !end1[0] && str1[0];
326    bool conv2 = !end2[0] && str2[0];
327
328    bool both_converted = conv1 && conv2;
329
330    int cmp;
331    if (both_converted) {
332        cmp = double_cmp(double1, double2);
333    }
334    else {
335        bool conv_partial1 = end1>str1;
336        bool conv_partial2 = end2>str2;
337
338        if (conv_partial1 && conv_partial2) {
339            cmp = double_cmp(double1, double2);
340            if (!cmp) {
341                cmp = numeric_string_cmp(end1, end2);
342            }
343        }
344        else cmp = strcmp(str1, str2);
345    }
346    return cmp;
347}
348
349static int compare_hits(const void *cl_item1, const void *cl_item2, void *cl_param) {
350    const hits_sort_params *param = static_cast<const hits_sort_params*>(cl_param);
351
352    GBDATA *gb_item1 = (GBDATA*)cl_item1;
353    GBDATA *gb_item2 = (GBDATA*)cl_item2;
354
355    DbQuery      *query    = param->query;
356    ItemSelector& selector = query->selector;
357
358    int cmp = 0;
359
360    for (int o = 0; o<MAX_CRITERIA && cmp == 0; o++) {
361        QUERY_RESULT_ORDER criteria = param->order[o];
362
363        switch (criteria) {
364            case QUERY_SORT_NONE:
365                o = MAX_CRITERIA; // don't sort further
366                break;
367
368            case QUERY_SORT_BY_1STFIELD_CONTENT: {
369                char *field1 = GBT_read_as_string(gb_item1, param->first_key);
370                char *field2 = GBT_read_as_string(gb_item2, param->first_key);
371
372                cmp = ARB_strNULLcmp(field1, field2);
373
374                free(field2);
375                free(field1);
376                break;
377            }
378            case QUERY_SORT_NUM_BY_1STFIELD_CONTENT: {
379                switch (param->first_type) {
380                    case GB_INT: {
381                        long *have1 = GBT_read_int(gb_item1, param->first_key);
382                        long  L1    = have1 ? *have1 : 0;
383                        long *have2 = GBT_read_int(gb_item2, param->first_key);
384                        long  L2    = have2 ? *have2 : 0;
385
386                        cmp = have1 // use same logic as ARB_strNULLcmp
387                            ? (have2 ? long_cmp(L1,L2) : -1)
388                            : (have2 ? 1 : 0);
389                        break;
390                    }
391                    case GB_FLOAT: {
392                        float *have1 = GBT_read_float(gb_item1, param->first_key);
393                        float  f1    = have1 ? *have1 : 0;
394                        float *have2 = GBT_read_float(gb_item2, param->first_key);
395                        float  f2    = have2 ? *have2 : 0;
396
397                        cmp = have1
398                            ? (have2 ? float_cmp(f1,f2) : -1)
399                            : (have2 ? 1 : 0);
400                        break;
401                    }
402                    default: {
403                        char *field1 = GBT_read_as_string(gb_item1, param->first_key);
404                        char *field2 = GBT_read_as_string(gb_item2, param->first_key);
405
406                        cmp = (field1 && field2)
407                            ? numeric_string_cmp(field1, field2)
408                            : ARB_strNULLcmp(field1, field2);
409
410                        free(field2);
411                        free(field1);
412                    }
413                }
414                break;
415            }
416            case QUERY_SORT_BY_NESTED_PID: {
417                if (selector.parent_selector) {
418                    GBDATA *gb_parent1 = selector.get_parent(gb_item1);
419                    GBDATA *gb_parent2 = selector.get_parent(gb_item2);
420                   
421                    char *pid1 = selector.generate_item_id(query->gb_main, gb_parent1);
422                    char *pid2 = selector.generate_item_id(query->gb_main, gb_parent2);
423
424                    cmp = ARB_strNULLcmp(pid1, pid2);
425
426                    free(pid2);
427                    free(pid1);
428                }
429                break;
430            }
431            case QUERY_SORT_BY_ID: {
432                const char *id1 = GBT_read_char_pntr(gb_item1, selector.id_field);
433                const char *id2 = GBT_read_char_pntr(gb_item2, selector.id_field);
434
435                cmp = strcmp(id1, id2);
436                break;
437            }
438            case QUERY_SORT_BY_MARKED:
439                cmp = GB_read_flag(gb_item2)-GB_read_flag(gb_item1);
440                break;
441
442            case QUERY_SORT_BY_HIT_DESCRIPTION: {
443                const char *id1   = GBT_read_char_pntr(gb_item1, selector.id_field);
444                const char *id2   = GBT_read_char_pntr(gb_item2, selector.id_field);
445                const char *info1 = reinterpret_cast<const char *>(GBS_read_hash(query->hit_description, id1));
446                const char *info2 = reinterpret_cast<const char *>(GBS_read_hash(query->hit_description, id2));
447                cmp = ARB_strNULLcmp(info1, info2);
448                break;
449            }
450            case QUERY_SORT_REVERSE: {
451                GBDATA *tmp = gb_item1; // swap items for following compares (this is a prefix revert!)
452                gb_item1    = gb_item2;
453                gb_item2    = tmp;
454                break;
455            }
456        }
457    }
458
459    return cmp;
460}
461
462static void detectMaxNameLength(const char *key, long /*val*/, void *cl_len) {
463    int *len  = (int*)cl_len;
464    int  klen = strlen(key);
465
466    if (klen>*len) *len = klen;
467}
468
469#if defined(ASSERTION_USED)
470inline bool SLOW_is_pseudo_key(const char *key) {
471    return
472        strcmp(key, PSEUDO_FIELD_ANY_FIELD) == 0 ||
473        strcmp(key, PSEUDO_FIELD_ALL_FIELDS) == 0 ||
474        strcmp(key, PSEUDO_FIELD_ANY_FIELD_REC) == 0 ||
475        strcmp(key, PSEUDO_FIELD_ALL_FIELDS_REC) == 0;
476}
477#endif
478inline bool is_pseudo_key(const char *key) {
479    // returns true, if 'key' is a pseudo-key
480    bool is_pseudo = key[0] == '[';
481    dbq_assert(is_pseudo == SLOW_is_pseudo_key(key));
482    return is_pseudo;
483}
484
485void QUERY::DbQuery_update_list(DbQuery *query) {
486    GB_push_transaction(query->gb_main);
487
488    dbq_assert(query->hitlist);
489    query->hitlist->clear();
490
491    AW_window     *aww      = query->aws;
492    AW_root       *aw_root  = aww->get_root();
493    QUERY_RANGE    range    = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
494    ItemSelector&  selector = query->selector;
495
496    // create array of hits
497    long     count  = count_queried_items(query, range);
498    GBDATA **sorted = ARB_alloc<GBDATA*>(count);
499    {
500        long s = 0;
501
502        for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
503             gb_item_container;
504             gb_item_container = selector.get_next_item_container(gb_item_container, range))
505        {
506            for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
507                 gb_item;
508                 gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
509            {
510                if (IS_QUERIED(gb_item, query)) sorted[s++] = gb_item;
511            }
512        }
513    }
514
515    // sort hits
516
517    hits_sort_params param(query, aww->get_root()->awar(query->awar_keys[0])->read_char_pntr());
518
519    bool is_pseudo  = is_pseudo_key(param.first_key);
520    bool show_value = !is_pseudo; // cannot refer to key-value of pseudo key
521
522    if (query->sort_mask != QUERY_SORT_NONE) {    // unsorted -> don't sort
523        split_sort_mask(query->sort_mask, param.order);
524        if (is_pseudo) {
525            remove_keydependent_sort_criteria(param.order);
526        }
527        if (show_value && find_display_determining_sort_order(param.order) == QUERY_SORT_BY_HIT_DESCRIPTION) {
528            show_value = false;
529        }
530        GB_sort((void**)sorted, 0, count, compare_hits, &param);
531    }
532
533    // display hits
534
535    int name_len = selector.item_name_length;
536    if (name_len == -1) { // if name_len is unknown -> detect
537        GBS_hash_do_const_loop(query->hit_description, detectMaxNameLength, &name_len);
538    }
539
540    long i;
541    for (i = 0; i<count && i<MAX_QUERY_LIST_LEN; i++) {
542        char *name = selector.generate_item_id(query->gb_main, sorted[i]);
543        if (name) {
544            char       *toFree = NULp;
545            const char *info;
546
547            if (show_value) {
548                toFree = GBT_read_as_string(sorted[i], param.first_key);
549                if (toFree) {
550                    if (strlen(toFree)>MAX_SHOWN_DATA_SIZE) {
551                        size_t shortened_len = GBS_shorten_repeated_data(toFree);
552                        if (shortened_len>MAX_SHOWN_DATA_SIZE) {
553                            strcpy(toFree+MAX_SHOWN_DATA_SIZE-5, "[...]");
554                        }
555                    }
556                }
557                else {
558                    toFree = GBS_global_string_copy("<%s has no data>", param.first_key);
559                }
560                info = toFree;
561            }
562            else {
563                info = getHitInfo(name, query);
564                if (!info) info = "<no hit info>";
565            }
566
567            dbq_assert(info);
568            const char *line = GBS_global_string("%c %-*s :%s",
569                                                 GB_read_flag(sorted[i]) ? '*' : ' ',
570                                                 name_len, name,
571                                                 info);
572
573            query->hitlist->insert(line, name);
574            free(toFree);
575            free(name);
576        }
577    }
578
579    if (count>MAX_QUERY_LIST_LEN) {
580        query->hitlist->insert("*****  List truncated  *****", "");
581    }
582
583    free(sorted);
584
585    query->hitlist->insert_default("End of list", "");
586    query->hitlist->update();
587    aww->get_root()->awar(query->awar_count)->write_int((long)count);
588    GB_pop_transaction(query->gb_main);
589}
590
591static void mark_queried_cb(AW_window*, DbQuery *query, int mark) {
592    // Mark listed species
593    // mark = 1 -> mark listed
594    // mark | 8 -> don't change rest
595
596    ItemSelector& selector = query->selector;
597    GB_push_transaction(query->gb_main);
598
599    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, query->aws->get_root(), QUERY_ALL_ITEMS);
600         gb_item_container;
601         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
602        {
603            for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
604                 gb_item;
605                 gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
606                {
607                    if (IS_QUERIED(gb_item, query)) {
608                        GB_write_flag(gb_item, mark&1);
609                    }
610                    else if ((mark&8) == 0) {
611                        GB_write_flag(gb_item, 1-(mark&1));
612                    }
613                }
614        }
615
616    DbQuery_update_list(query);
617    GB_pop_transaction(query->gb_main);
618}
619
620void QUERY::unquery_all(void *, DbQuery *query) {
621    GB_push_transaction(query->gb_main);
622    GBDATA *gb_species;
623    for (gb_species = GBT_first_species(query->gb_main);
624                gb_species;
625                gb_species = GBT_next_species(gb_species)) {
626        CLEAR_QUERIED(gb_species, query);
627    }
628    DbQuery_update_list(query);
629    GB_pop_transaction(query->gb_main);
630}
631
632static void delete_queried_species_cb(AW_window*, DbQuery *query) {
633    ItemSelector& selector = query->selector;
634    GB_begin_transaction(query->gb_main);
635
636    long cnt = 0;
637    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, query->aws->get_root(), QUERY_ALL_ITEMS);
638         gb_item_container;
639         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
640    {
641        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
642             gb_item;
643             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
644        {
645            if (IS_QUERIED(gb_item, query)) cnt++;
646        }
647    }
648
649    if (!cnt || !aw_ask_sure("delete_queried_species", GBS_global_string("Are you sure to delete %li %s", cnt, selector.items_name))) {
650        GB_abort_transaction(query->gb_main);
651        return;
652    }
653
654    GB_ERROR error = NULp;
655
656    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, query->aws->get_root(), QUERY_ALL_ITEMS);
657         !error && gb_item_container;
658         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
659        {
660            for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
661                 !error && gb_item;
662                 gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
663                {
664                    if (IS_QUERIED(gb_item, query)) {
665                        error = GB_delete(gb_item);
666                    }
667                }
668        }
669
670    if (error) {
671        GB_abort_transaction(query->gb_main);
672        aw_message(error);
673    }
674    else {
675        DbQuery_update_list(query);
676        GB_commit_transaction(query->gb_main);
677    }
678}
679
680static GB_HASH *create_ref_hash(const DbQuery *query, const char *key, bool split_words) {
681    GBDATA  *gb_ref       = query->gb_ref;
682    bool     queried_only = query->expect_hit_in_ref_list;
683    GB_HASH *hash         = GBS_create_hash(GBT_get_species_count(gb_ref), GB_IGNORE_CASE);
684
685    for (GBDATA  *gb_species = GBT_first_species(gb_ref);
686         gb_species;
687         gb_species = GBT_next_species(gb_species))
688    {
689        if (!queried_only || IS_QUERIED(gb_species, query)) {
690            GBDATA *gb_name = GB_search(gb_species, key, GB_FIND);
691            if (gb_name) {
692                char *keyas = GB_read_as_string(gb_name);
693                if (keyas && strlen(keyas)) {
694                    if (split_words) {
695                        char *t;
696                        for (t = strtok(keyas, " "); t; t = strtok(NULp, " ")) {
697                            if (t[0]) GBS_write_hash(hash, t, (long)gb_species);
698                        }
699                    }
700                    else {
701                        GBS_write_hash(hash, keyas, (long)gb_species);
702                    }
703                }
704                free(keyas);
705            }
706        }
707    }
708    return hash;
709}
710
711class TargetItem : public QueryTarget {
712    RefPtr<GBDATA> gb_item;
713public:
714    TargetItem(const DbQuery& query) :
715        QueryTarget(query.gb_main, query.get_tree_name()),
716        gb_item(NULp)
717    {}
718
719    void aimTo(GBDATA *gb_next_item) {
720        dbq_assert(gb_next_item); // @@@ maybe allow? (to invalidate target)
721        gb_item = gb_next_item;
722    }
723    GBDATA *get_item() const {
724        dbq_assert(gb_item); // you have to call aimTo before!
725        return gb_item;
726    }
727
728    // QueryTarget interface:
729    GBDATA *get_ACI_item() const OVERRIDE { return get_item(); }
730};
731
732class ItemQueryKey FINAL_TYPE : public QueryKey, virtual Noncopyable {
733    char    *key;             // search key (=name of DB field or pseudo-field)
734    bool     hierarchical;    // is 'key' hierarchical?
735    GBQUARK  keyquark;        // valid only if get_type() == QKEY_EXPLICIT!
736
737    typedef std::stack<GBDATA*> ParentStack;
738
739    mutable GBDATA      *gb_key; // last read key-entry
740    mutable ParentStack  parent; // container(s) descended
741
742    static query_key_type detect_key_type(const char *field_name) {
743        if (is_pseudo_key(field_name)) {
744            if (strcmp(field_name, PSEUDO_FIELD_ANY_FIELD)      == 0) return QKEY_ANY;
745            if (strcmp(field_name, PSEUDO_FIELD_ANY_FIELD_REC)  == 0) return QKEY_ANY_REC;
746            if (strcmp(field_name, PSEUDO_FIELD_ALL_FIELDS)     == 0) return QKEY_ALL;
747            if (strcmp(field_name, PSEUDO_FIELD_ALL_FIELDS_REC) == 0) return QKEY_ALL_REC;
748            dbq_assert(0);
749        }
750        return QKEY_EXPLICIT;
751    }
752
753public:
754    ItemQueryKey(GBDATA *gb_main, const char *field_name) :
755        QueryKey(detect_key_type(field_name)),
756        key(strdup(field_name)),
757        hierarchical(false),
758        keyquark(0), // invalid quark
759        gb_key(NULp)
760    {
761        if (get_type() == QKEY_EXPLICIT) {
762            if (GB_first_non_key_char(key)) {
763                hierarchical = true;
764            }
765            else {
766                keyquark = GB_find_or_create_quark(gb_main, key);
767            }
768        }
769    }
770    ~ItemQueryKey() {
771        free(key);
772    }
773
774    // QueryKey interface:
775    char *get_target_data(const QueryTarget& target, GB_ERROR& error) const OVERRIDE {
776        // retrieve content of target key
777        if (!gb_key) {
778            const TargetItem& target_item = DOWNCAST_REFERENCE(const TargetItem, target);
779            gb_key = get_first_field(target_item.get_item());
780        }
781
782        char *data;
783        if (gb_key) {
784            data = GB_read_as_string(gb_key);
785            if (!data) {
786                if (GB_readable_as_string(gb_key)) {
787                    error = GB_await_error();
788                }
789                else {
790                    gb_assert(!GB_have_error());
791                    error = GBS_global_string("field '%s' has wrong type", GB_read_key_pntr(gb_key));
792                }
793            }
794        }
795        else {
796            data = strdup(""); // non-existing field -> assume "" as default value
797                               // (needed to search for missing fields using '!=*' ?)
798        }
799        return data;
800    }
801    const char *get_name()  const OVERRIDE {
802        // name of target (e.g. for reports)
803        // - for explicit keys, it simply returns the specified key name
804        // - for iterating keys, it returns the name of the DB field at current iteration position,
805        //   showing path to key relative to item (e.g. 'ali_16s/data')
806        //   If not yet iterating, it returns the name of the pseudo-field.
807
808        if (get_type() != QKEY_EXPLICIT && gb_key) {
809            const char *keyname = GB_read_key_pntr(gb_key);
810            if (!parent.empty()) {
811                const char *parent_path  = GB_get_db_path(parent.top());
812                dbq_assert(parent.top() == GB_get_father(gb_key));
813                const char *rslash       = strrchr(parent_path, '/');
814                for (int nest = parent.size()-1; rslash && nest; --nest) {
815                    rslash = strrchr(rslash-1, '/');
816                }
817                return GBS_global_string("%s/%s", rslash ? rslash+1 : "<unknown>", keyname);
818            }
819            return keyname;
820        }
821        return key;
822    }
823
824private:
825    bool accept_or_iterate() const {
826        dbq_assert(get_type() != QKEY_EXPLICIT);
827        if (gb_key) {
828            GB_TYPES ktype = GB_read_type(gb_key);
829            if (ktype == GB_DB) {
830                switch (get_type()) {
831                    case QKEY_ALL:
832                    case QKEY_ANY:
833                        return iterate(); // = skip over containers
834
835                    case QKEY_ALL_REC:
836                    case QKEY_ANY_REC:
837                        parent.push(gb_key); // remember position ..
838                        gb_key = GB_child(gb_key); // .. and descend into container
839                        break;
840
841                    case QKEY_EXPLICIT:
842                        dbq_assert(0);
843                }
844                return accept_or_iterate();
845            }
846            if (!GB_TYPE_readable_as_string(ktype)) {
847                return iterate();
848            }
849            return true;
850        }
851        if (!parent.empty()) {
852            gb_key = parent.top();
853            parent.pop();
854            return iterate();
855        }
856        return false;
857    }
858public:
859
860    bool iterate() const OVERRIDE { // iterate key to next item
861        dbq_assert(get_type() != QKEY_EXPLICIT);
862        if (gb_key) gb_key = GB_nextChild(gb_key);
863        return accept_or_iterate();
864    }
865    void reset() const OVERRIDE {
866        // restart multi-key iteration
867        gb_key = NULp;
868        parent = ParentStack();
869    }
870    // ----------
871
872
873    GBDATA *get_first_field(GBDATA *gb_item) const {
874        GBDATA *gb_field = NULp;
875
876        if (hierarchical) {
877            gb_field = GB_search(gb_item, key, GB_FIND);
878        }
879        else if (get_type() != QKEY_EXPLICIT) {
880            gb_field = GB_child(gb_item);
881            // no need to use same method as normal search (we just need any key here)
882            while (gb_field && GB_read_type(gb_field) == GB_DB) {
883                gb_field = GB_nextChild(gb_field);
884            }
885        }
886        else {
887            gb_field = GB_find_sub_by_quark(gb_item, keyquark, NULp, 0);
888        }
889
890        return gb_field;
891    }
892};
893
894inline query_operator awarvalue2query_operator(const char *awarvalue) {
895    if (strcmp(awarvalue, "and") == 0) return AND;
896    if (strcmp(awarvalue, "or")  == 0) return OR;
897    return ILLEGAL;
898}
899
900SmartPtr<QueryExpr> DbQuery::buildQueryExpr() {
901    AW_root   *aw_root  = aws->get_root();
902    QueryExpr *first_qe = NULp;
903
904    for (int idx = 0; idx<QUERY_EXPRESSIONS; ++idx) {
905        query_operator aqo = idx
906            ? awarvalue2query_operator(aw_root->awar(awar_operator[idx])->read_char_pntr())
907            : OR; // initial value is false, (false OR QUERY1) == QUERY1
908
909        if (aqo != ILLEGAL) {
910            bool         not_equal  = aw_root->awar(awar_not[idx])->read_int() != 0;
911            const char  *expression = aw_root->awar(awar_queries[idx])->read_char_pntr();
912            const char  *field_name = aw_root->awar(awar_keys[idx])->read_char_pntr();
913            QueryKeyPtr  qkey       = new ItemQueryKey(gb_main, field_name);
914
915            QueryExpr *qe = new QueryExpr(aqo, qkey, not_equal, expression);
916
917            if (!field_name[0] || strcmp(field_name, NO_FIELD_SELECTED) == 0) {
918                qe->setError("No field selected");
919            }
920
921            if (!first_qe) first_qe = qe;
922            else           first_qe->append(qe);
923        }
924    }
925
926    return first_qe;
927}
928
929static void perform_query_cb(AW_window*, DbQuery *query, EXT_QUERY_TYPES ext_query) {
930    ItemSelector& selector = query->selector;
931
932    GB_push_transaction(query->gb_main);
933
934    AW_root *aw_root = query->aws->get_root();
935    SmartPtr<QueryExpr> qexpr = query->buildQueryExpr();
936
937    QUERY_MODES mode  = (QUERY_MODES)aw_root->awar(query->awar_ere)->read_int();
938    QUERY_RANGE range = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
939    QUERY_TYPES type  = (QUERY_TYPES)aw_root->awar(query->awar_by)->read_int();
940
941    if (query->gb_ref && type != QUERY_MARKED) {  // special for merge tool!
942        char *first_query = aw_root->awar(query->awar_queries[0])->read_string();
943        if (strlen(first_query) == 0) {
944            if (!ext_query) ext_query  = EXT_QUERY_COMPARE_LINES;
945        }
946        free(first_query);
947    }
948
949    GB_ERROR error = qexpr->getError();
950
951    if (!error) {
952        size_t item_count = query_count_items(query, range, mode);
953        if (item_count) {
954            arb_progress progress("Searching", item_count);
955
956            if (query->gb_ref && // merge tool only
957                (ext_query == EXT_QUERY_COMPARE_LINES || ext_query == EXT_QUERY_COMPARE_WORDS))
958            {
959                if (qexpr->get_key_type() != QKEY_EXPLICIT) {
960                    error = "Please select an explicit field or specify a search string (for first expression)";
961                }
962                else {
963                    GB_push_transaction(query->gb_ref);
964
965                    char    *first_keyname = aw_root->awar(query->awar_keys[0])->read_string();
966                    GB_HASH *ref_hash      = create_ref_hash(query, first_keyname, ext_query == EXT_QUERY_COMPARE_WORDS);
967
968                    if (GBS_hash_elements(ref_hash) == 0) {
969                        error = GBS_global_string("No data found in field '%s'", first_keyname);
970                    }
971
972#if defined(DEBUG)
973                    printf("query: search identical %s in field %s%s\n",
974                           (ext_query == EXT_QUERY_COMPARE_WORDS ? "words" : "values"),
975                           first_keyname,
976                           query->expect_hit_in_ref_list ? " of species listed in other hitlist" : "");
977#endif // DEBUG
978
979                    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
980                         gb_item_container && !error;
981                         gb_item_container = selector.get_next_item_container(gb_item_container, range))
982                    {
983                        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
984                             gb_item && !error;
985                             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
986                        {
987                            switch (mode) {
988                                case QUERY_GENERATE: CLEAR_QUERIED(gb_item, query); break;
989                                case QUERY_ENLARGE:  if (IS_QUERIED(gb_item, query)) continue; break;
990                                case QUERY_REDUCE:   if (!IS_QUERIED(gb_item, query)) continue; break;
991                            }
992
993                            // Note: here only the first query is used (special case in merge tool only)
994
995                            ItemQueryKey&  query_key = DOWNCAST_REFERENCE(ItemQueryKey, qexpr->get_key());
996                            GBDATA        *gb_key    = query_key.get_first_field(gb_item);
997                            if (gb_key) {
998                                char *data = GB_read_as_string(gb_key);
999
1000                                if (data && data[0]) {
1001                                    string hit_reason;
1002                                    bool   this_hit = false;
1003
1004                                    if (ext_query == EXT_QUERY_COMPARE_WORDS) {
1005                                        for (char *t = strtok(data, " "); t; t = strtok(NULp, " ")) {
1006                                            GBDATA *gb_ref_pntr = (GBDATA *)GBS_read_hash(ref_hash, t);
1007                                            if (gb_ref_pntr) { // found item in other DB, with 'first_keyname' containing word from 'gb_key'
1008                                                this_hit   = true;
1009                                                hit_reason = GBS_global_string("%s%s has word '%s' in %s",
1010                                                                               query->expect_hit_in_ref_list ? "Hit " : "",
1011                                                                               selector.generate_item_id(query->gb_ref, gb_ref_pntr),
1012                                                                               t, first_keyname);
1013                                            }
1014                                        }
1015                                    }
1016                                    else {
1017                                        GBDATA *gb_ref_pntr = (GBDATA *)GBS_read_hash(ref_hash, data);
1018                                        if (gb_ref_pntr) { // found item in other DB, with identical 'first_keyname'
1019                                            this_hit   = true;
1020                                            hit_reason = GBS_global_string("%s%s matches %s",
1021                                                                           query->expect_hit_in_ref_list ? "Hit " : "",
1022                                                                           selector.generate_item_id(query->gb_ref, gb_ref_pntr),
1023                                                                           first_keyname);
1024                                        }
1025                                    }
1026
1027                                    if (type == QUERY_DONT_MATCH) {
1028                                        this_hit = !this_hit;
1029                                        if (this_hit) hit_reason = "<no matching entry>";
1030                                    }
1031
1032                                    if (this_hit) {
1033                                        dbq_assert(!hit_reason.empty());
1034                                        SET_QUERIED(gb_item, query, hit_reason.c_str(), hit_reason.length());
1035                                    }
1036                                    else CLEAR_QUERIED(gb_item, query);
1037                                }
1038                                free(data);
1039                            }
1040
1041                            progress.inc_and_check_user_abort(error);
1042                        }
1043                    }
1044
1045                    GBS_free_hash(ref_hash);
1046                    GB_pop_transaction(query->gb_ref);
1047                }
1048            }
1049            else { // "normal" query
1050                if (type == QUERY_DONT_MATCH) {
1051#if defined(DEBUG)
1052                    fputs("query: !(", stdout); qexpr->dump();
1053#endif // DEBUG
1054
1055                    qexpr->negate();
1056                    type = QUERY_MATCH;
1057
1058#if defined(DEBUG)
1059                    fputs(") => query: ", stdout); qexpr->dump(); fputc('\n', stdout);
1060#endif // DEBUG
1061                }
1062#if defined(DEBUG)
1063                else { fputs("query: ", stdout); qexpr->dump(); fputc('\n', stdout); }
1064#endif // DEBUG
1065
1066                TargetItem target_item(*query);
1067
1068                for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
1069                     gb_item_container && !error;
1070                     gb_item_container = selector.get_next_item_container(gb_item_container, range))
1071                {
1072                    for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1073                         gb_item && !error;
1074                         gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
1075                    {
1076                        switch (mode) {
1077                            case QUERY_GENERATE: CLEAR_QUERIED(gb_item, query); break;
1078                            case QUERY_ENLARGE:  if (IS_QUERIED(gb_item, query)) continue; break;
1079                            case QUERY_REDUCE:   if (!IS_QUERIED(gb_item, query)) continue; break;
1080                        }
1081
1082                        bool   hit = false;
1083                        string hit_reason;
1084
1085                        switch (type) {
1086                            case QUERY_MARKED:
1087                                hit        = GB_read_flag(gb_item);
1088                                hit_reason = "<marked>";
1089                                break;
1090
1091                            case QUERY_MATCH:
1092                                dbq_assert(ext_query == EXT_QUERY_NONE);
1093                                target_item.aimTo(gb_item);
1094                                hit = qexpr->matches(target_item, hit_reason);
1095                                break;
1096
1097                            default: dbq_assert(0); break;
1098                        }
1099
1100                        if (hit) {
1101                            dbq_assert(!hit_reason.empty());
1102
1103                            if (mode == QUERY_REDUCE) hit_reason = keptHitReason(hit_reason, gb_item, query);
1104                            SET_QUERIED(gb_item, query, hit_reason.c_str(), hit_reason.length());
1105                        }
1106                        else CLEAR_QUERIED(gb_item, query);
1107
1108                        if (error) {
1109                            error = GB_failedTo_error("query", GBT_get_name(gb_item), error);
1110                        }
1111                        else {
1112                            progress.inc_and_check_user_abort(error);
1113                        }
1114                    }
1115                }
1116            }
1117
1118            if (error) progress.done();
1119        }
1120    }
1121
1122    if (!error) error = qexpr->getError(); // check for query error
1123
1124    if (error) aw_message(error);
1125    else DbQuery_update_list(query);
1126
1127    GB_pop_transaction(query->gb_main);
1128}
1129
1130void QUERY::copy_selection_list_2_query_box(DbQuery *query, AW_selection_list *srclist, const char *hit_description) {
1131    GB_transaction ta(query->gb_main);
1132
1133    dbq_assert(strstr(hit_description, "%s")); // hit_description needs '%s' (which is replaced by visible content of 'id')
1134
1135    GB_ERROR         error     = NULp;
1136    AW_window       *aww       = query->aws;
1137    AW_root         *aw_root   = aww->get_root();
1138    GB_HASH         *list_hash = srclist->to_hash(false);
1139    QUERY_MODES  mode      = (QUERY_MODES)aw_root->awar(query->awar_ere)->read_int();
1140    QUERY_TYPES  type      = (QUERY_TYPES)aw_root->awar(query->awar_by)->read_int();
1141
1142    if (type == QUERY_MARKED) {
1143        error = "Query mode 'that are marked' does not apply here.\nEither select 'that match the query' or 'that don't match the q.'";
1144    }
1145
1146    if (type != QUERY_MATCH || mode != QUERY_GENERATE) { // different behavior as in the past -> advice
1147        AW_advice("'Move to hitlist' now depends on the values selected for\n"
1148                  " * 'Search/Add/Keep species' and\n"
1149                  " * 'that match/don't match the query'\n"
1150                  "in the search tool.",
1151                  AW_ADVICE_TOGGLE_AND_HELP,
1152                  "Behavior changed",
1153                  "next_neighbours.hlp");
1154    }
1155
1156    long inHitlist = GBS_hash_elements(list_hash);
1157    long seenInDB  = 0;
1158
1159    for (GBDATA *gb_species = GBT_first_species(query->gb_main);
1160         gb_species && !error;
1161         gb_species = GBT_next_species(gb_species))
1162    {
1163        switch (mode) {
1164            case QUERY_GENERATE: CLEAR_QUERIED(gb_species, query); break;
1165            case QUERY_ENLARGE:  if (IS_QUERIED(gb_species, query)) continue; break;
1166            case QUERY_REDUCE:   if (!IS_QUERIED(gb_species, query)) continue; break;
1167        }
1168
1169        const char *name      = GBT_get_name(gb_species);
1170        const char *displayed = reinterpret_cast<const char*>(GBS_read_hash(list_hash, name));
1171
1172        if (displayed) seenInDB++;
1173
1174        if (contradicted(displayed, type == QUERY_DONT_MATCH)) {
1175            string hit_reason = GBS_global_string(hit_description, displayed ? displayed : "<no near neighbour>");
1176
1177            if (mode == QUERY_REDUCE) hit_reason = keptHitReason(hit_reason, gb_species, query);
1178            SET_QUERIED(gb_species, query, hit_reason.c_str(), hit_reason.length());
1179        }
1180        else {
1181            CLEAR_QUERIED(gb_species, query);
1182        }
1183    }
1184
1185    if (seenInDB < inHitlist) {
1186        aw_message(GBS_global_string("%li of %li hits were found in database", seenInDB, inHitlist));
1187    }
1188
1189    GBS_free_hash(list_hash);
1190    if (error) aw_message(error);
1191    DbQuery_update_list(query);
1192}
1193
1194
1195void QUERY::search_duplicated_field_content(AW_window *, DbQuery *query, bool tokenize) {
1196    AW_root  *aw_root = query->aws->get_root();
1197    char     *key     = aw_root->awar(query->awar_keys[0])->read_string();
1198    GB_ERROR  error   = NULp;
1199
1200    if (strlen(key) == 0) {
1201        error = "Please select a key (in the first query expression)";
1202    }
1203    else if (is_pseudo_key(key)) {
1204        error = "You have to select an explicit key (in the first query expression)";
1205    }
1206    else {
1207        GB_transaction dumy(query->gb_main);
1208        ItemSelector&  selector = query->selector;
1209
1210        GBDATA      *gb_species_data = GBT_get_species_data(query->gb_main);
1211        long         hashsize;
1212        QUERY_RANGE  range           = QUERY_ALL_ITEMS;
1213        QUERY_TYPES  type            = (QUERY_TYPES)aw_root->awar(query->awar_by)->read_int();
1214
1215        switch (selector.type) {
1216            case QUERY_ITEM_SPECIES: {
1217                hashsize = GB_number_of_subentries(gb_species_data);
1218                break;
1219            }
1220            case QUERY_ITEM_EXPERIMENTS:
1221            case QUERY_ITEM_GENES: {
1222                // handle species sub-items
1223                hashsize = 0;
1224
1225                for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
1226                     gb_item_container;
1227                     gb_item_container = selector.get_next_item_container(gb_item_container, range))
1228                {
1229                    hashsize += GB_number_of_subentries(gb_item_container);
1230                }
1231
1232                break;
1233            }
1234            default: {
1235                dbq_assert(0);
1236                hashsize = 0;
1237                break;
1238            }
1239        }
1240
1241        if (!hashsize) {
1242            error = "No items exist";
1243        }
1244        else if (type == QUERY_MARKED) {
1245            error = "'that are marked' is not applicable here";
1246        }
1247
1248        if (!error) {
1249            GB_HASH *hash = GBS_create_hash(hashsize, GB_IGNORE_CASE);
1250
1251            for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
1252                 gb_item_container;
1253                 gb_item_container = selector.get_next_item_container(gb_item_container, range))
1254            {
1255                for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1256                     gb_item;
1257                     gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
1258                {
1259                    CLEAR_QUERIED(gb_item, query);
1260                    GB_write_flag(gb_item, 0);
1261
1262                    GBDATA *gb_key = GB_search(gb_item, key, GB_FIND);  if (!gb_key) continue;
1263                    char   *data   = GB_read_as_string(gb_key);         if (!data) continue;
1264
1265                    if (tokenize) {
1266                        char *s;
1267                        for (s=strtok(data, ",; \t."); s; s = strtok(NULp, ",; \t.")) {
1268                            GBDATA *gb_old = (GBDATA *)GBS_read_hash(hash, s);
1269                            if (gb_old) {
1270                                const char *oldInfo   = NULp;
1271                                char       *firstInfo = GBS_global_string_copy("1st=%s", s);
1272
1273                                if (IS_QUERIED(gb_old, query)) {
1274                                    const char *prevInfo = getHitInfo(gb_old, query);
1275                                    if (!prevInfo) {
1276                                        oldInfo = firstInfo;
1277                                    }
1278                                    else if (!strstr(prevInfo, firstInfo)) { // not already have 1st-entry here
1279                                        oldInfo = GBS_global_string("%s %s", prevInfo, firstInfo);
1280                                    }
1281                                }
1282                                else {
1283                                    oldInfo = firstInfo;
1284                                }
1285
1286                                if (oldInfo) SET_QUERIED(gb_old, query, oldInfo);
1287                                SET_QUERIED(gb_item, query, GBS_global_string("dup=%s", s));
1288                                GB_write_flag(gb_item, 1);
1289
1290                                free(firstInfo);
1291                            }
1292                            else {
1293                                GBS_write_hash(hash, s, (long)gb_item);
1294                            }
1295                        }
1296                    }
1297                    else {
1298                        GBDATA *gb_old = (GBDATA *)GBS_read_hash(hash, data);
1299                        if (gb_old) {
1300                            if (!IS_QUERIED(gb_old, query)) {
1301                                SET_QUERIED(gb_old, query, GBS_global_string("%s (1st)", data));
1302                            }
1303                            SET_QUERIED(gb_item, query, GBS_global_string("%s (duplicate)", data));
1304                            GB_write_flag(gb_item, 1);
1305                        }
1306                        else {
1307                            GBS_write_hash(hash, data, (long)gb_item);
1308                        }
1309                    }
1310
1311                    free(data);
1312                }
1313
1314                if (type == QUERY_DONT_MATCH) {
1315                    for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1316                         gb_item;
1317                         gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
1318                    {
1319                        if (IS_QUERIED(gb_item, query)) {
1320                            CLEAR_QUERIED(gb_item, query);
1321                            GB_write_flag(gb_item, 0); // unmark
1322                        }
1323                        else {
1324                            SET_QUERIED(gb_item, query, tokenize ? "<entry with unique words>" : "<unique entry>");
1325                        }
1326                    }
1327                }
1328            }
1329
1330            GBS_free_hash(hash);
1331        }
1332
1333        if (type != QUERY_MATCH) {
1334            AW_advice("'Find equal entries' now depends on the values selected for\n"
1335                      " * 'that match/don't match the query'\n"
1336                      "in the search tool.",
1337                      AW_ADVICE_TOGGLE_AND_HELP,
1338                      "Behavior changed",
1339                      "search_duplicates.hlp");
1340        }
1341    }
1342
1343    free(key);
1344
1345    if (error) aw_message(error);
1346    DbQuery_update_list(query);
1347}
1348
1349static void modify_fields_of_queried_cb(AW_window*, DbQuery *query) {
1350    ItemSelector&  selector = query->selector;
1351    AW_root       *aw_root  = query->aws->get_root();
1352    GB_ERROR       error    = GB_begin_transaction(query->gb_main);
1353
1354    const char *key;
1355    if (!error) {
1356        key = prepare_and_get_selected_itemfield(aw_root, query->awar_parskey, query->gb_main, selector);
1357        if (!key) error = GB_await_error();
1358    }
1359
1360    if (!error) {
1361        GB_TYPES  key_type        = GBT_get_type_of_changekey(query->gb_main, key, selector.change_key_path);
1362        bool      safe_conversion = (key_type != GB_STRING) && !aw_root->awar(query->awar_acceptConvError)->read_int();
1363        char     *command         = aw_root->awar(query->awar_parsvalue)->read_string();
1364
1365        if (!strlen(command)) error = "Please enter a command";
1366
1367        if (!error) {
1368            long  ncount = aw_root->awar(query->awar_count)->read_int();
1369            char *deftag = aw_root->awar(query->awar_deftag)->read_string();
1370            char *tag    = aw_root->awar(query->awar_tag)->read_string();
1371
1372            {
1373                long use_tag = aw_root->awar(query->awar_use_tag)->read_int();
1374                if (!use_tag || !strlen(tag)) {
1375                    freenull(tag);
1376                }
1377            }
1378            int double_pars = aw_root->awar(query->awar_double_pars)->read_int();
1379
1380            arb_progress progress("Parse fields", ncount);
1381            QUERY_RANGE  range = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
1382            GBL_env      env(query->gb_main, query->get_tree_name());
1383
1384            for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
1385                 !error && gb_item_container;
1386                 gb_item_container = selector.get_next_item_container(gb_item_container, range))
1387            {
1388                for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1389                     !error && gb_item;
1390                     gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
1391                {
1392                    if (IS_QUERIED(gb_item, query)) {
1393                        GBDATA *gb_new = GB_search(gb_item, key, GB_FIND);
1394                        error          = GB_incur_error_if(!gb_new);
1395
1396                        if (!error) {
1397                            GBL_call_env callEnv(gb_item, env);
1398
1399                            char *str    = gb_new ? GB_read_as_string(gb_new) : strdup("");
1400                            char *parsed = NULp;
1401
1402                            if (double_pars) {
1403                                char *com2 = GB_command_interpreter_in_env(str, command, callEnv);
1404                                if (com2) {
1405                                    if (tag) parsed = GBS_modify_tagged_string_with_ACI("", deftag, tag, com2, callEnv);
1406                                    else parsed     = GB_command_interpreter_in_env    ("", com2, callEnv);
1407
1408                                    free(com2);
1409                                }
1410                            }
1411                            else {
1412                                if (tag) parsed = GBS_modify_tagged_string_with_ACI(str, deftag, tag, command, callEnv);
1413                                else parsed     = GB_command_interpreter_in_env    (str, command, callEnv);
1414                            }
1415
1416                            if (!parsed) error = GB_await_error();
1417                            else {
1418                                if (strcmp(parsed, str) != 0) { // any change?
1419                                    if (gb_new && parsed[0] == 0) { // empty result -> delete field
1420                                        error = GB_delete(gb_new);
1421                                    }
1422                                    else {
1423                                        if (!gb_new) {
1424                                            gb_new = GB_search(gb_item, key, key_type);
1425                                            if (!gb_new) error = GB_await_error();
1426                                        }
1427                                        if (!error) {
1428                                            error = GB_write_autoconv_string(gb_new, parsed); // <- field is actually written HERE
1429                                            if (!error && safe_conversion) {
1430                                                dbq_assert(key_type != GB_STRING);
1431                                                char *resulting = GB_read_as_string(gb_new);
1432                                                if (!resulting) {
1433                                                    error = GB_await_error();
1434                                                }
1435                                                else {
1436                                                    if (strcmp(resulting, parsed) != 0) {
1437                                                        error = GBS_global_string("Conversion error: writing '%s'\n"
1438                                                                                  "resulted in '%s'\n"
1439                                                                                  "(mark checkbox to accept conversion errors)",
1440                                                                                  parsed, resulting);
1441                                                    }
1442                                                }
1443                                            }
1444                                        }
1445                                    }
1446                                }
1447                                free(parsed);
1448                            }
1449                            free(str);
1450                            progress.inc_and_check_user_abort(error);
1451                        }
1452                    }
1453                }
1454            }
1455
1456            delete tag;
1457            free(deftag);
1458
1459            if (error) progress.done();
1460        }
1461
1462        free(command);
1463    }
1464
1465    error = GB_end_transaction(query->gb_main, error);
1466    aw_message_if(error);
1467}
1468
1469static void predef_prg(AW_root *aw_root, DbQuery *query) {
1470    char *str = aw_root->awar(query->awar_parspredefined)->read_string();
1471    char *brk = strchr(str, '#');
1472    if (brk) {
1473        *(brk++) = 0;
1474        char *kv = str;
1475        if (!strcmp(str, "ali_*/data")) {
1476            GB_transaction ta(query->gb_main);
1477            char *use = GBT_get_default_alignment(query->gb_main);
1478            kv = GBS_global_string_copy("%s/data", use);
1479            free(use);
1480        }
1481        aw_root->awar(query->awar_parskey)->write_string(kv);
1482        if (kv != str) free(kv);
1483        aw_root->awar(query->awar_parsvalue)->write_string(brk);
1484    }
1485    else {
1486        aw_root->awar(query->awar_parsvalue)->write_string(str);
1487    }
1488    free(str);
1489}
1490
1491static void colorize_queried_cb(AW_window *, DbQuery *query) {
1492    ItemSelector&   selector    = query->selector;
1493    GB_transaction  ta(query->gb_main);
1494    GB_ERROR        error       = NULp;
1495    AW_root        *aw_root     = query->aws->get_root();
1496    int             color_group = aw_root->awar(AWAR_COLORIZE)->read_int();
1497    QUERY_RANGE     range       = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
1498    bool            changed     = false;
1499
1500    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
1501         !error && gb_item_container;
1502         gb_item_container = selector.get_next_item_container(gb_item_container, range))
1503    {
1504        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1505             !error && gb_item;
1506             gb_item       = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
1507        {
1508            if (IS_QUERIED(gb_item, query)) {
1509                error               = GBT_set_color_group(gb_item, color_group);
1510                if (!error) changed = true;
1511            }
1512        }
1513    }
1514
1515    if (error) GB_export_error(error);
1516    else if (changed) selector.trigger_display_refresh();
1517}
1518
1519static void colorize_marked_cb(AW_window *aww, BoundItemSel *cmd) {
1520    ItemSelector&   sel         = cmd->selector;
1521    GB_transaction  ta(cmd->gb_main);
1522    GB_ERROR        error       = NULp;
1523    AW_root        *aw_root     = aww->get_root();
1524    int             color_group = aw_root->awar(AWAR_COLORIZE)->read_int();
1525    QUERY_RANGE     range       = QUERY_ALL_ITEMS;         // @@@ FIXME: make customizable
1526
1527    for (GBDATA *gb_item_container = sel.get_first_item_container(cmd->gb_main, aw_root, range);
1528         !error && gb_item_container;
1529         gb_item_container = sel.get_next_item_container(gb_item_container, range))
1530    {
1531        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1532             !error && gb_item;
1533             gb_item       = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
1534        {
1535            if (GB_read_flag(gb_item)) {
1536                error = GBT_set_color_group(gb_item, color_group);
1537            }
1538        }
1539    }
1540
1541    if (error) GB_export_error(error);
1542}
1543
1544enum mark_mode {
1545    UNMARK,
1546    MARK,
1547    INVERT,
1548};
1549
1550static void mark_colored_cb(AW_window *aww, BoundItemSel *cmd, mark_mode mode) {
1551    // @@@ mark_colored_cb is obsolete! (will be replaced by dynamic coloring in the future)
1552    ItemSelector&  sel         = cmd->selector;
1553    AW_root       *aw_root     = aww->get_root();
1554    int            color_group = aw_root->awar(AWAR_COLORIZE)->read_int();
1555    QUERY_RANGE    range       = QUERY_ALL_ITEMS;          // @@@ FIXME: make customizable
1556
1557    GB_transaction ta(cmd->gb_main);
1558
1559    for (GBDATA *gb_item_container = sel.get_first_item_container(cmd->gb_main, aw_root, range);
1560         gb_item_container;
1561         gb_item_container = sel.get_next_item_container(gb_item_container, range))
1562    {
1563        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1564             gb_item;
1565             gb_item = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
1566        {
1567            int my_color = GBT_get_color_group(gb_item);
1568            if (my_color == color_group) {
1569                bool marked = GB_read_flag(gb_item);
1570
1571                switch (mode) {
1572                    case UNMARK: marked = 0;       break;
1573                    case MARK:   marked = 1;       break;
1574                    case INVERT: marked = !marked; break;
1575
1576                    default: dbq_assert(0); break;
1577                }
1578
1579                GB_write_flag(gb_item, marked);
1580            }
1581        }
1582    }
1583
1584}
1585
1586// --------------------------------------------------------------------------------
1587// color sets
1588
1589struct color_save_data {
1590    BoundItemSel      *bsel;
1591    AW_selection_list *colorsets;
1592
1593    const char *get_items_name() const {
1594        return bsel->selector.items_name;
1595    }
1596};
1597
1598#define AWAR_COLOR_LOADSAVE_NAME "tmp/colorset/name"
1599
1600inline GBDATA *get_colorset_root(BoundItemSel *bsel) {
1601    return GBT_colorset_root(bsel->gb_main, bsel->selector.items_name);
1602}
1603
1604static void update_colorset_selection_list(const color_save_data *csd) {
1605    ConstStrArray foundSets;
1606    {
1607        GB_transaction ta(csd->bsel->gb_main);
1608        GBT_get_colorset_names(foundSets, get_colorset_root(csd->bsel));
1609    }
1610
1611    AW_selection_list *sel = csd->colorsets;
1612    sel->clear();
1613    sel->insert_default("<new colorset>", "");
1614    for (size_t i = 0; i<foundSets.size(); ++i) {
1615        sel->insert(foundSets[i], foundSets[i]);
1616    }
1617    sel->update();
1618}
1619
1620static void colorset_changed_cb(GBDATA*, const color_save_data *csd, GB_CB_TYPE cbt) {
1621    if (cbt&GB_CB_CHANGED) {
1622        update_colorset_selection_list(csd);
1623    }
1624}
1625
1626static void create_colorset_representation(BoundItemSel *bsel, AW_root *aw_root, StrArray& colordefs, GB_ERROR& error) {
1627    ItemSelector&  sel     = bsel->selector;
1628    GBDATA        *gb_main = bsel->gb_main;
1629
1630    dbq_assert(!error);
1631
1632    for (GBDATA *gb_item_container = sel.get_first_item_container(gb_main, aw_root, QUERY_ALL_ITEMS);
1633         gb_item_container;
1634         gb_item_container = sel.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
1635    {
1636        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1637             gb_item;
1638             gb_item = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
1639        {
1640            int color = GBT_get_color_group(gb_item);
1641            if (color>0) {
1642                char *id        = sel.generate_item_id(gb_main, gb_item);
1643                char *color_def = GBS_global_string_copy("%s=%i", id, color);
1644
1645                colordefs.put(color_def);
1646                free(id);
1647            }
1648        }
1649    }
1650
1651    if (colordefs.empty()) {
1652        error = GBS_global_string("Could not find any colored %s", sel.items_name);
1653    }
1654}
1655
1656static GB_ERROR clear_all_colors(BoundItemSel *bsel, AW_root *aw_root) {
1657    ItemSelector& sel     = bsel->selector;
1658    GB_ERROR      error   = NULp;
1659    bool          changed = false;
1660
1661    for (GBDATA *gb_item_container = sel.get_first_item_container(bsel->gb_main, aw_root, QUERY_ALL_ITEMS);
1662         !error && gb_item_container;
1663         gb_item_container = sel.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
1664    {
1665        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
1666             !error && gb_item;
1667             gb_item = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
1668        {
1669            error               = GBT_set_color_group(gb_item, 0); // clear colors
1670            if (!error) changed = true;
1671        }
1672    }
1673
1674    if (changed && !error) sel.trigger_display_refresh();
1675    return error;
1676}
1677
1678static void clear_all_colors_cb(AW_window *aww, BoundItemSel *bsel) {
1679    GB_transaction ta(bsel->gb_main);
1680    GB_ERROR       error = clear_all_colors(bsel, aww->get_root());
1681
1682    if (error) {
1683        error = ta.close(error);
1684        aw_message(error);
1685    }
1686}
1687
1688static GB_ERROR restore_colorset_representation(BoundItemSel *bsel, CharPtrArray& colordefs) {
1689    ItemSelector&  sel     = bsel->selector;
1690    GBDATA        *gb_main = bsel->gb_main;
1691    bool           changed = false;
1692    GB_ERROR       error   = NULp;
1693    int            ignores = 0;
1694
1695    for (size_t d = 0; d<colordefs.size() && !error; ++d) {
1696        const char *def   = colordefs[d];
1697        const char *equal = strchr(def, '=');
1698
1699        if (equal) {
1700            LocallyModify<char> tempSplit(const_cast<char*>(equal)[0], 0);
1701
1702            const char *id      = def;
1703            GBDATA     *gb_item = sel.find_item_by_id(gb_main, id);
1704            if (!gb_item) {
1705                aw_message(GBS_global_string("No such %s: '%s' (ignored)", sel.item_name, id)); // only warn
1706                ++ignores;
1707            }
1708            else {
1709                int color_group = atoi(equal+1);
1710                if (color_group>0) { // bugfix: saved color groups contained zero (which means "no color") by mistake; ignore
1711                    error = GBT_set_color_group(gb_item, color_group);
1712                    if (!error) changed = true;
1713                }
1714            }
1715        }
1716        else {
1717            aw_message(GBS_global_string("Invalid colordef '%s' (ignored)", def));
1718            ++ignores;
1719        }
1720    }
1721    if (changed && !error) sel.trigger_display_refresh();
1722    if (ignores>0 && !error) {
1723        aw_message(GBS_global_string("Warning: failed to restore color assignment for %i %s", ignores, sel.items_name));
1724    }
1725
1726    return error;
1727}
1728
1729enum loadsave_mode {
1730    SAVE,
1731    LOAD,
1732    OVERLAY,
1733    DELETE,
1734};
1735
1736static void loadsave_colorset_cb(AW_window *aws, BoundItemSel *bsel, loadsave_mode mode) {
1737    AW_root  *aw_root = aws->get_root();
1738    char     *name    = aw_root->awar(AWAR_COLOR_LOADSAVE_NAME)->read_string();
1739    GB_ERROR  error   = NULp;
1740
1741    if (name[0] == 0) error = "Please enter a name for the colorset.";
1742    else {
1743        GB_transaction ta(bsel->gb_main);
1744
1745        GBDATA *gb_colorset_root = get_colorset_root(bsel);
1746        GBDATA *gb_colorset      = gb_colorset_root ? GBT_find_colorset(gb_colorset_root, name) : NULp;
1747
1748        error = GB_incur_error();
1749        if (!error) {
1750            if (mode == SAVE) {
1751                if (!gb_colorset) { // create new colorset
1752                    gb_colorset             = GBT_find_or_create_colorset(gb_colorset_root, name);
1753                    if (!gb_colorset) error = GB_await_error();
1754                }
1755                dbq_assert(gb_colorset || error);
1756
1757                if (!error) {
1758                    StrArray colordefs;
1759                    create_colorset_representation(bsel, aw_root, colordefs, error);
1760                    if (!error) error = GBT_save_colorset(gb_colorset, colordefs);
1761                }
1762            }
1763            else {
1764                if (!gb_colorset) error = GBS_global_string("Colorset '%s' not found", name);
1765                else {
1766                    if (mode == LOAD || mode == OVERLAY) {
1767                        ConstStrArray colordefs;
1768                        error = GBT_load_colorset(gb_colorset, colordefs);
1769
1770                        if (!error && colordefs.empty()) error = "oops.. empty colorset";
1771                        if (!error && mode == LOAD)      error = clear_all_colors(bsel, aw_root);
1772                        if (!error)                      error = restore_colorset_representation(bsel, colordefs);
1773                    }
1774                    else {
1775                        dbq_assert(mode == DELETE);
1776                        error = GB_delete(gb_colorset);
1777                    }
1778                }
1779            }
1780        }
1781        error = ta.close(error);
1782    }
1783    free(name);
1784
1785    if (error) aw_message(error);
1786}
1787
1788static AW_window *create_loadsave_colored_window(AW_root *aw_root, color_save_data *csd) {
1789    static AW_window **aw_loadsave = NULp;
1790    if (!aw_loadsave) {
1791        // init data
1792        aw_root->awar_string(AWAR_COLOR_LOADSAVE_NAME, "", AW_ROOT_DEFAULT);
1793        ARB_calloc(aw_loadsave, QUERY_ITEM_TYPES); // contains loadsave windows for each item type
1794    }
1795
1796    QUERY_ITEM_TYPE type = csd->bsel->selector.type;
1797
1798    dbq_assert(type<QUERY_ITEM_TYPES);
1799
1800    if (!aw_loadsave[type]) {
1801        // init window
1802        AW_window_simple *aws = new AW_window_simple;
1803        {
1804            char *window_id = GBS_global_string_copy("colorset_loadsave_%s", csd->get_items_name());
1805            aws->init(aw_root, window_id, GBS_global_string("Load/Save %s colorset", csd->get_items_name()));
1806            free(window_id);
1807        }
1808
1809        aws->load_xfig("query/color_loadsave.fig");
1810
1811        aws->at("close");
1812        aws->callback(AW_POPDOWN);
1813        aws->create_button("CLOSE", "CLOSE", "C");
1814
1815        aws->at("help");
1816        aws->callback(makeHelpCallback("color_loadsave.hlp"));
1817        aws->create_button("HELP", "HELP", "H");
1818
1819        dbq_assert(!csd->colorsets);
1820        csd->colorsets = awt_create_selection_list_with_input_field(aws, AWAR_COLOR_LOADSAVE_NAME, "list", "name");
1821
1822        update_colorset_selection_list(csd);
1823
1824        aws->at("save");    aws->callback(makeWindowCallback(loadsave_colorset_cb, csd->bsel, SAVE));    aws->create_button("save",    "Save",    "S");
1825        aws->at("load");    aws->callback(makeWindowCallback(loadsave_colorset_cb, csd->bsel, LOAD));    aws->create_button("load",    "Load",    "L");
1826        aws->at("overlay"); aws->callback(makeWindowCallback(loadsave_colorset_cb, csd->bsel, OVERLAY)); aws->create_button("overlay", "Overlay", "O");
1827        aws->at("delete");  aws->callback(makeWindowCallback(loadsave_colorset_cb, csd->bsel, DELETE));  aws->create_button("delete",  "Delete",  "D");
1828
1829        aws->at("reset");
1830        aws->callback(makeWindowCallback(clear_all_colors_cb, csd->bsel));
1831        aws->create_button("reset", "Reset", "R");
1832
1833        // callbacks
1834        {
1835            GB_transaction  ta(csd->bsel->gb_main);
1836            GBDATA         *gb_colorset = get_colorset_root(csd->bsel);
1837            GB_add_callback(gb_colorset, GB_CB_CHANGED, makeDatabaseCallback(colorset_changed_cb, csd));
1838        }
1839
1840        aw_loadsave[type] = aws;
1841    }
1842
1843    return aw_loadsave[type];
1844}
1845
1846static AW_window *create_colorize_window(AW_root *aw_root, GBDATA *gb_main, DbQuery *query, ItemSelector *sel) {
1847    // invoked by   'colorize listed'                   (sel   != 0)
1848    // and          'colorize marked/mark colored'      (query != 0)
1849
1850    enum { COLORIZE_INVALID, COLORIZE_LISTED, COLORIZE_MARKED } mode = COLORIZE_INVALID;
1851
1852    create_query_independent_awars(aw_root, AW_ROOT_DEFAULT);
1853
1854    AW_window_simple *aws = new AW_window_simple;
1855
1856    dbq_assert(contradicted(query, sel));
1857
1858    if (query) {
1859        dbq_assert(mode == COLORIZE_INVALID);
1860        mode = COLORIZE_LISTED;
1861    }
1862    if (sel) {
1863        dbq_assert(mode == COLORIZE_INVALID);
1864        mode = COLORIZE_MARKED;
1865    }
1866    dbq_assert(!(mode == COLORIZE_INVALID));
1867
1868    ItemSelector& Sel  = mode == COLORIZE_LISTED ? query->selector : *sel;
1869    const char   *what = mode == COLORIZE_LISTED ? "listed" : "marked";
1870
1871    {
1872        char *macro_name  = GBS_global_string_copy("COLORIZE_%s_%s", what, Sel.items_name);
1873        char *window_name = GBS_global_string_copy("Colorize %s %s", what, Sel.items_name);
1874
1875        aws->init(aw_root, macro_name, window_name);
1876
1877        free(window_name);
1878        free(macro_name);
1879    }
1880
1881    aws->load_xfig("query/colorize.fig");
1882
1883    aws->auto_space(10, 10);
1884
1885    aws->at("close");
1886    aws->callback(AW_POPDOWN);
1887    aws->create_button("CLOSE", "CLOSE", "C");
1888
1889    aws->at("help");
1890    aws->callback(makeHelpCallback(mode == COLORIZE_LISTED ? "set_color_of_listed.hlp" : "colorize.hlp"));
1891    aws->create_button("HELP", "HELP", "H");
1892
1893    aws->at("colorize");
1894
1895    BoundItemSel *bsel = new BoundItemSel(gb_main, ((mode == COLORIZE_MARKED) ? *sel : query->selector)); // do not free, bound to CB
1896
1897    if (mode == COLORIZE_LISTED) aws->callback(makeWindowCallback(colorize_queried_cb, query));
1898    else                         aws->callback(makeWindowCallback(colorize_marked_cb, bsel));
1899
1900    aws->create_autosize_button("COLORIZE", GBS_global_string_copy("Set color of %s %s to ...", what, Sel.items_name), "S", 2);
1901
1902    {
1903        int color_group;
1904
1905        aws->create_option_menu(AWAR_COLORIZE, true);
1906        aws->insert_default_option("No color group", "none", 0);
1907        for (color_group = 1; color_group <= AW_COLOR_GROUPS; ++color_group) {
1908            char *name = AW_get_color_group_name(aw_root, color_group);
1909            aws->insert_option(name, "", color_group);
1910            free(name);
1911        }
1912        aws->update_option_menu();
1913    }
1914
1915    color_save_data *csd = new color_save_data; // do not free, bound to CB
1916    csd->bsel            = bsel;
1917    csd->colorsets       = NULp;
1918
1919    aws->at("loadsave");
1920    aws->callback(makeCreateWindowCallback(create_loadsave_colored_window, csd));
1921    aws->create_autosize_button("LOADSAVE_COLORED", "Load/Save", "L");
1922
1923    if (mode == COLORIZE_MARKED) {
1924        aws->at("mark");
1925        aws->callback(makeWindowCallback(mark_colored_cb, bsel, MARK));
1926        aws->create_autosize_button("MARK_COLORED", GBS_global_string_copy("Mark all %s of ...", Sel.items_name), "M", 2);
1927
1928        aws->at("unmark");
1929        aws->callback(makeWindowCallback(mark_colored_cb, bsel, UNMARK));
1930        aws->create_autosize_button("UNMARK_COLORED", GBS_global_string_copy("Unmark all %s of ...", Sel.items_name), "U", 2);
1931
1932        aws->at("invert");
1933        aws->callback(makeWindowCallback(mark_colored_cb, bsel, INVERT));
1934        aws->create_autosize_button("INVERT_COLORED", GBS_global_string_copy("Invert all %s of ...", Sel.items_name), "I", 2);
1935    }
1936
1937    aws->at_newline();
1938    aws->window_fit();
1939
1940    return aws;
1941}
1942
1943static AW_window *create_colorize_queried_window(AW_root *aw_root, DbQuery *query) {
1944    return create_colorize_window(aw_root, query->gb_main, query, NULp);
1945}
1946
1947AW_window *QUERY::create_colorize_items_window(AW_root *aw_root, GBDATA *gb_main, ItemSelector& sel) {
1948    return create_colorize_window(aw_root, gb_main, NULp, &sel);
1949}
1950
1951static void setup_modify_fields_config(AWT_config_definition& cdef, const DbQuery *query) {
1952    const char *typeawar = get_itemfield_type_awarname(query->awar_parskey);
1953    dbq_assert(typeawar);
1954
1955    cdef.add(query->awar_parskey,         "key");
1956    cdef.add(typeawar,                    "type");
1957    cdef.add(query->awar_use_tag,         "usetag");
1958    cdef.add(query->awar_deftag,          "deftag");
1959    cdef.add(query->awar_tag,             "modtag");
1960    cdef.add(query->awar_double_pars,     "doubleparse");
1961    cdef.add(query->awar_acceptConvError, "acceptconv");
1962    cdef.add(query->awar_parsvalue,       "aci");
1963}
1964
1965static AW_window *create_modify_fields_window(AW_root *aw_root, DbQuery *query) {
1966    AW_window_simple *aws = new AW_window_simple;
1967
1968    {
1969        char *macro_name = GBS_global_string_copy("MODIFY_DATABASE_FIELD_%s", query->selector.items_name);
1970        char *window_name = GBS_global_string_copy("MODIFY DATABASE FIELD of listed %s", query->selector.items_name);
1971
1972        aws->init(aw_root, macro_name, window_name);
1973
1974        free(window_name);
1975        free(macro_name);
1976    }
1977
1978    aws->load_xfig("query/modify_fields.fig");
1979
1980    aws->at("close");
1981    aws->callback(AW_POPDOWN);
1982    aws->create_button("CLOSE", "CLOSE", "C");
1983
1984    aws->at("help");
1985    aws->callback(makeHelpCallback("mod_field_list.hlp"));
1986    aws->create_button("HELP", "HELP", "H");
1987
1988    aws->at("helptags");
1989    aws->callback(makeHelpCallback("tags.hlp"));
1990    aws->create_button("HELP_TAGS", "Help tags", "H");
1991
1992    create_itemfield_selection_button(aws, FieldSelDef(query->awar_parskey, query->gb_main, query->selector, FIELD_FILTER_STRING_READABLE, "target field", SF_ALLOW_NEW), "field");
1993    aws->at("accept");  aws->create_toggle(query->awar_acceptConvError);
1994
1995    // ------------------------
1996    //      expert section
1997    aws->at("usetag");  aws->create_toggle(query->awar_use_tag);
1998    aws->at("deftag");  aws->create_input_field(query->awar_deftag);
1999    aws->at("tag");     aws->create_input_field(query->awar_tag);
2000    aws->at("double");  aws->create_toggle(query->awar_double_pars);
2001
2002    aws->at("parser");
2003    aws->create_text_field(query->awar_parsvalue);
2004
2005    aws->at("go");
2006    aws->callback(makeWindowCallback(modify_fields_of_queried_cb, query));
2007    aws->create_button("GO", "GO", "G");
2008
2009    aws->at("pre");
2010    AW_selection_list *programs = aws->create_selection_list(query->awar_parspredefined, true);
2011
2012    const char *sellst = NULp;
2013    switch (query->selector.type) {
2014        case QUERY_ITEM_SPECIES:     sellst = "mod_fields*.sellst"; break;
2015        case QUERY_ITEM_GENES:       sellst = "mod_gene_fields*.sellst"; break;
2016        case QUERY_ITEM_EXPERIMENTS: sellst = "mod_experiment_fields*.sellst"; break;
2017        default: dbq_assert(0); break;
2018    }
2019
2020    GB_ERROR error;
2021    if (sellst) {
2022        StorableSelectionList storable_sellist(TypedSelectionList("sellst", programs, "field modification scripts", "mod_fields"));
2023        error = storable_sellist.load(GB_path_in_ARBLIB("sellists", sellst), false);
2024    }
2025    else {
2026        error = "No default selection list for query-type";
2027    }
2028
2029    if (error) {
2030        aw_message(error);
2031    }
2032    else {
2033        aws->get_root()->awar(query->awar_parspredefined)->add_callback(makeRootCallback(predef_prg, query));
2034    }
2035
2036    aws->at("config");
2037    AWT_insert_config_manager(aws, AW_ROOT_DEFAULT,
2038                              GBS_global_string("mod_%s_fields", query->selector.item_name),
2039                              makeConfigSetupCallback(setup_modify_fields_config, query));
2040
2041    return aws;
2042}
2043
2044static void set_field_of_queried_cb(AW_window*, DbQuery *query, bool append) {
2045    // set fields of listed items
2046    ItemSelector&  selector   = query->selector;
2047    GB_ERROR       error      = NULp;
2048    AW_root       *aw_root    = query->aws->get_root();
2049    bool           allow_loss = aw_root->awar(query->awar_writelossy)->read_int();
2050
2051    char *value = aw_root->awar(query->awar_setvalue)->read_string();
2052    if (value[0] == 0) freenull(value);
2053
2054    size_t value_len = value ? strlen(value) : 0;
2055
2056    GB_begin_transaction(query->gb_main);
2057
2058    const char *key = prepare_and_get_selected_itemfield(aw_root, query->awar_writekey, query->gb_main, selector);
2059    if (!key) error = GB_await_error();
2060
2061    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, QUERY_ALL_ITEMS);
2062         !error && gb_item_container;
2063         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
2064    {
2065        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
2066             !error && gb_item;
2067             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
2068        {
2069            if (IS_QUERIED(gb_item, query)) {
2070                if (!value) { // empty value -> delete field
2071                    if (!append) {
2072                        GBDATA *gb_field    = GB_search(gb_item, key, GB_FIND);
2073                        if (gb_field) error = GB_delete(gb_field);
2074                    }
2075                    // otherwise "nothing" is appended (=noop)
2076                }
2077                else {
2078                    GBDATA *gb_field     = GBT_searchOrCreate_itemfield_according_to_changekey(gb_item, key, selector.change_key_path);
2079                    if (!gb_field) error = GB_await_error();
2080                    else {
2081                        const char   *new_content = value;
2082                        SmartCharPtr  content;
2083
2084                        if (append) {
2085                            char *old       = GB_read_as_string(gb_field);
2086                            if (!old) error = "field has incompatible type";
2087                            else {
2088                                size_t         old_len = strlen(old);
2089                                GBS_strstruct *strstr  = GBS_stropen(old_len+value_len+2);
2090
2091                                GBS_strncat(strstr, old, old_len);
2092                                GBS_strncat(strstr, value, value_len);
2093
2094                                content     = GBS_strclose(strstr);
2095                                new_content = &*content;
2096                            }
2097                        }
2098
2099                        error = GB_write_autoconv_string(gb_field, new_content);
2100
2101                        if (!allow_loss && !error) {
2102                            char *result = GB_read_as_string(gb_field);
2103                            dbq_assert(result);
2104
2105                            if (strcmp(new_content, result) != 0) {
2106                                error = GBS_global_string("value modified by type conversion\n('%s' -> '%s')", new_content, result);
2107                            }
2108                            free(result);
2109                        }
2110                    }
2111                }
2112
2113                if (error) { // add name of current item to error
2114                    const char *name = GBT_read_char_pntr(gb_item, "name");
2115                    error            = GBS_global_string("Error while writing to %s '%s':\n%s", selector.item_name, name, error);
2116                }
2117            }
2118        }
2119    }
2120
2121    GB_end_transaction_show_error(query->gb_main, error, aw_message);
2122
2123    free(value);
2124}
2125
2126static AW_window *create_writeFieldOfListed_window(AW_root *aw_root, DbQuery *query) {
2127    AW_window_simple *aws = new AW_window_simple;
2128    init_itemType_specific_window(aw_root, aws, query->selector, "WRITE_DB_FIELD_OF_LISTED", "Write field of listed %s", true);
2129    aws->load_xfig("query/write_fields.fig");
2130
2131    aws->at("close");
2132    aws->callback(AW_POPDOWN);
2133    aws->create_button("CLOSE", "CLOSE", "C");
2134
2135    aws->at("help");
2136    aws->callback(makeHelpCallback("write_field_list.hlp"));
2137    aws->create_button("HELP", "HELP", "H");
2138
2139    create_itemfield_selection_button(aws, FieldSelDef(query->awar_writekey, query->gb_main, query->selector, FIELD_FILTER_STRING_READABLE, "target field", SF_ALLOW_NEW), "field");
2140
2141    aws->at("val");
2142    aws->create_text_field(query->awar_setvalue, 2, 2);
2143
2144    aws->at("lossy");
2145    aws->label("Allow lossy conversion?");
2146    aws->create_toggle(query->awar_writelossy);
2147
2148    aws->at("create");
2149    aws->callback(makeWindowCallback(set_field_of_queried_cb, query, false));
2150    aws->create_button("SET_SINGLE_FIELD_OF_LISTED", "WRITE");
2151
2152    aws->at("do");
2153    aws->callback(makeWindowCallback(set_field_of_queried_cb, query, true));
2154    aws->create_button("APPEND_SINGLE_FIELD_OF_LISTED", "APPEND");
2155
2156    return aws;
2157}
2158
2159static void set_protection_of_queried_cb(AW_window*, DbQuery *query) {
2160    // set protection of listed items
2161    ItemSelector&  selector = query->selector;
2162    AW_root       *aw_root  = query->aws->get_root();
2163    char          *key      = aw_root->awar(query->awar_protectkey)->read_string();
2164
2165    if (strcmp(key, NO_FIELD_SELECTED) == 0) {
2166        aw_message("Please select a field for which you want to set the protection");
2167    }
2168    else {
2169        GB_begin_transaction(query->gb_main);
2170
2171        GB_ERROR  error       = NULp;
2172        GBDATA   *gb_key_data = GB_search(query->gb_main, selector.change_key_path, GB_CREATE_CONTAINER);
2173        GBDATA   *gb_key_name = GB_find_string(gb_key_data, CHANGEKEY_NAME, key, GB_IGNORE_CASE, SEARCH_GRANDCHILD);
2174
2175        if (!gb_key_name) {
2176            error = GBS_global_string("The destination field '%s' does not exists", key);
2177        }
2178        else {
2179            int         level = aw_root->awar(query->awar_setprotection)->read_int();
2180            QUERY_RANGE range = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
2181
2182            for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
2183                 !error && gb_item_container;
2184                 gb_item_container = selector.get_next_item_container(gb_item_container, range))
2185            {
2186                for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
2187                     !error && gb_item;
2188                     gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
2189                {
2190                    if (IS_QUERIED(gb_item, query)) {
2191                        GBDATA *gb_new = GB_search(gb_item, key, GB_FIND);
2192                        if (gb_new) {
2193                            error             = GB_write_security_delete(gb_new, level);
2194                            if (!error) error = GB_write_security_write(gb_new, level);
2195                        }
2196                    }
2197                }
2198            }
2199        }
2200        GB_end_transaction_show_error(query->gb_main, error, aw_message);
2201    }
2202
2203    free(key);
2204}
2205
2206static AW_window *create_set_protection_window(AW_root *aw_root, DbQuery *query) {
2207    AW_window_simple *aws = new AW_window_simple;
2208    init_itemType_specific_window(aw_root, aws, query->selector, "SET_PROTECTION_OF_FIELD_OF_LISTED", "Protect field of listed %s", true);
2209    aws->load_xfig("query/set_protection.fig");
2210
2211    aws->at("close");
2212    aws->callback(AW_POPDOWN);
2213    aws->create_button("CLOSE", "CLOSE", "C");
2214
2215    aws->at("help");
2216    aws->callback(makeHelpCallback("set_protection.hlp"));
2217    aws->create_button("HELP", "HELP", "H");
2218
2219
2220    aws->at("prot");
2221    aws->create_toggle_field(query->awar_setprotection, 0);
2222    aws->insert_toggle("0 temporary", "0", 0);
2223    aws->insert_toggle("1 checked",   "1", 1);
2224    aws->insert_toggle("2",           "2", 2);
2225    aws->insert_toggle("3",           "3", 3);
2226    aws->insert_toggle("4 normal",    "4", 4);
2227    aws->insert_toggle("5 ",          "5", 5);
2228    aws->insert_toggle("6 the truth", "5", 6);
2229    aws->update_toggle_field();
2230
2231    create_itemfield_selection_list(aws, FieldSelDef(query->awar_protectkey, query->gb_main, query->selector, FIELD_UNFILTERED, "field to protect"), "list");
2232
2233    aws->at("go");
2234    aws->callback(makeWindowCallback(set_protection_of_queried_cb, query));
2235    aws->create_autosize_button("SET_PROTECTION_OF_FIELD_OF_LISTED", "Assign\nprotection\nto field\nof listed");
2236
2237    return aws;
2238}
2239
2240static void toggle_flag_cb(AW_window *aww, DbQuery *query) {
2241    GB_transaction  ta(query->gb_main);
2242    GBDATA         *gb_item = query->selector.get_selected_item(query->gb_main, aww->get_root());
2243    if (gb_item) {
2244        long flag = GB_read_flag(gb_item);
2245        GB_write_flag(gb_item, 1-flag);
2246    }
2247    DbQuery_update_list(query);
2248}
2249
2250static void new_selection_made_cb(AW_root *aw_root, const char *awar_selection, DbQuery *query) {
2251    char *item_name = aw_root->awar(awar_selection)->read_as_string();
2252    query->selector.update_item_awars(query->gb_main, aw_root, item_name);
2253    free(item_name);
2254}
2255
2256static void query_box_setup_config(AWT_config_definition& cdef, DbQuery *query) {
2257    // this defines what is saved/restored to/from configs
2258    for (int key_id = 0; key_id<QUERY_EXPRESSIONS; ++key_id) {
2259        cdef.add(query->awar_keys[key_id], "key", key_id);
2260        cdef.add(query->awar_queries[key_id], "query", key_id);
2261        cdef.add(query->awar_not[key_id], "not", key_id);
2262        cdef.add(query->awar_operator[key_id], "operator", key_id);
2263    }
2264}
2265
2266template<typename CB>
2267static void query_rel_menu_entry(AW_window *aws, const char *id, const char *query_id, const char* label, const char *mnemonic, const char *helpText, AW_active Mask, const CB& cb) {
2268    char *rel_id = GBS_global_string_copy("%s_%s", query_id, id);
2269    aws->insert_menu_topic(rel_id, label, mnemonic, helpText, Mask, cb);
2270    free(rel_id);
2271}
2272
2273const char *DbQuery::get_tree_name() const {
2274    if (!awar_tree_name) 
2275        return NULp;
2276    else
2277        return aws->get_root()->awar(awar_tree_name)->read_char_pntr();
2278}
2279
2280DbQuery::~DbQuery() {
2281    for (int s = 0; s<QUERY_EXPRESSIONS; ++s) {
2282        free(awar_keys[s]);
2283        free(awar_queries[s]);
2284        free(awar_not[s]);
2285        free(awar_operator[s]);
2286    }
2287
2288    free(species_name);
2289
2290    free(awar_writekey);
2291    free(awar_writelossy);
2292    free(awar_protectkey);
2293    free(awar_setprotection);
2294    free(awar_setvalue);
2295
2296    free(awar_parskey);
2297    free(awar_parsvalue);
2298    free(awar_parspredefined);
2299    free(awar_acceptConvError);
2300
2301    free(awar_ere);
2302    free(awar_where);
2303    free(awar_by);
2304    free(awar_use_tag);
2305    free(awar_double_pars);
2306    free(awar_deftag);
2307    free(awar_tag);
2308    free(awar_count);
2309    free(awar_sort);
2310
2311    GBS_free_hash(hit_description);
2312}
2313
2314// ----------------------------------------
2315// store query data until DB close
2316
2317typedef Keeper<DbQuery*> QueryKeeper;
2318template<> void Keeper<DbQuery*>::destroy(DbQuery *q) { delete q; }
2319static SmartPtr<QueryKeeper> queryKeeper;
2320static void destroyKeptQueries() {
2321    queryKeeper.setNull();
2322}
2323static void keepQuery(GBDATA *gbmain, DbQuery *q) {
2324    if (queryKeeper.isNull()) {
2325        queryKeeper = new QueryKeeper;
2326        GB_atclose_callback(gbmain, makeDatabaseCallback(destroyKeptQueries));
2327    }
2328    queryKeeper->keep(q);
2329}
2330
2331// ----------------------------------------
2332
2333DbQuery *QUERY::create_query_box(AW_window *aws, query_spec *awtqs, const char *query_id) {
2334    char     buffer[256];
2335    AW_root *aw_root = aws->get_root();
2336    GBDATA  *gb_main = awtqs->gb_main;
2337    DbQuery *query   = new DbQuery(awtqs->get_queried_itemtype());
2338
2339    keepQuery(gb_main, query); // transfers ownership of query
2340
2341    // @@@ set all the things below via ctor!
2342    // @@@ create a copyable object containing everything query_spec and DbQuery have in common -> pass that object to DbQuery-ctor
2343
2344    query->gb_main                = awtqs->gb_main;
2345    query->aws                    = aws;
2346    query->gb_ref                 = awtqs->gb_ref;
2347    query->expect_hit_in_ref_list = awtqs->expect_hit_in_ref_list;
2348    query->select_bit             = awtqs->select_bit;
2349    query->species_name           = strdup(awtqs->species_name);
2350
2351    query->set_tree_awar_name(awtqs->tree_name);
2352    query->hit_description = GBS_create_dynaval_hash(query_count_items(query, QUERY_ALL_ITEMS, QUERY_GENERATE), GB_IGNORE_CASE, free_hit_description);
2353
2354    GB_push_transaction(gb_main);
2355
2356    // Create query box AWARS
2357    create_query_independent_awars(aw_root, AW_ROOT_DEFAULT);
2358
2359    {
2360        const char *default_key[QUERY_EXPRESSIONS+1] = { "name", "name", "name", NULp };
2361
2362        for (int key_id = 0; key_id<QUERY_EXPRESSIONS; ++key_id) {
2363            sprintf(buffer, "tmp/dbquery_%s/key_%i", query_id, key_id);
2364            query->awar_keys[key_id] = strdup(buffer);
2365            dbq_assert(default_key[key_id]);
2366            aw_root->awar_string(query->awar_keys[key_id], default_key[key_id], AW_ROOT_DEFAULT);
2367
2368            sprintf(buffer, "tmp/dbquery_%s/query_%i", query_id, key_id);
2369            query->awar_queries[key_id] = strdup(buffer);
2370            aw_root->awar_string(query->awar_queries[key_id], "*", AW_ROOT_DEFAULT);
2371
2372            sprintf(buffer, "tmp/dbquery_%s/not_%i", query_id, key_id);
2373            query->awar_not[key_id] = strdup(buffer);
2374            aw_root->awar_int(query->awar_not[key_id], 0, AW_ROOT_DEFAULT);
2375
2376            sprintf(buffer, "tmp/dbquery_%s/operator_%i", query_id, key_id);
2377            query->awar_operator[key_id] = strdup(buffer);
2378            aw_root->awar_string(query->awar_operator[key_id], "ign", AW_ROOT_DEFAULT);
2379        }
2380        aw_root->awar(query->awar_keys[0])->add_callback(makeRootCallback(first_searchkey_changed_cb, query));
2381    }
2382
2383    sprintf(buffer, "tmp/dbquery_%s/ere", query_id);
2384    query->awar_ere = strdup(buffer);
2385    aw_root->awar_int(query->awar_ere, QUERY_GENERATE);
2386
2387    sprintf(buffer, "tmp/dbquery_%s/where", query_id);
2388    query->awar_where = strdup(buffer);
2389    aw_root->awar_int(query->awar_where, QUERY_ALL_ITEMS);
2390
2391    sprintf(buffer, "tmp/dbquery_%s/count", query_id);
2392    query->awar_count = strdup(buffer);
2393    aw_root->awar_int(query->awar_count, 0);
2394
2395    sprintf(buffer, "tmp/dbquery_%s/sort", query_id);
2396    query->awar_sort = strdup(buffer);
2397    aw_root->awar_int(query->awar_sort, QUERY_SORT_NONE)->add_callback(makeRootCallback(result_sort_order_changed_cb, query));
2398    query->sort_mask = QUERY_SORT_NONE; // default to unsorted
2399
2400    sprintf(buffer, "tmp/dbquery_%s/by", query_id);
2401    query->awar_by = strdup(buffer);
2402    aw_root->awar_int(query->awar_by, QUERY_MATCH);
2403
2404    if (awtqs->ere_pos_fig) {
2405        aws->at(awtqs->ere_pos_fig);
2406        aws->create_toggle_field(query->awar_ere, "", "");
2407
2408        aws->insert_toggle(GBS_global_string("Search %s", query->selector.items_name), "G", (int)QUERY_GENERATE);
2409        aws->insert_toggle(GBS_global_string("Add %s", query->selector.items_name), "E", (int)QUERY_ENLARGE);
2410        aws->insert_toggle(GBS_global_string("Keep %s", query->selector.items_name), "R", (int)QUERY_REDUCE);
2411
2412        aws->update_toggle_field();
2413    }
2414    if (awtqs->where_pos_fig) {
2415        aws->at(awtqs->where_pos_fig);
2416        aws->create_toggle_field(query->awar_where, "", "");
2417        aws->insert_toggle("of current organism", "C", (int)QUERY_CURRENT_ITEM);
2418        aws->insert_toggle("of marked organisms", "M", (int)QUERY_MARKED_ITEMS);
2419        aws->insert_toggle("of all organisms", "A", (int)QUERY_ALL_ITEMS);
2420        aws->update_toggle_field();
2421
2422    }
2423    if (awtqs->by_pos_fig) {
2424        aws->at(awtqs->by_pos_fig);
2425        aws->create_toggle_field(query->awar_by, "", "");
2426        aws->insert_toggle("that match the query", "M", (int)QUERY_MATCH);
2427        aws->insert_toggle("that don't match the q.", "D", (int)QUERY_DONT_MATCH);
2428        aws->insert_toggle("that are marked", "A", (int)QUERY_MARKED);
2429        aws->update_toggle_field();
2430    }
2431
2432    // distances for multiple queries :
2433
2434#define KEY_Y_OFFSET 32
2435
2436    int xpos_calc[3] = { -1, -1, -1 }; // X-positions for elements in search expressions
2437
2438    if (awtqs->qbox_pos_fig) {
2439        aws->auto_space(1, 1);
2440        aws->at(awtqs->qbox_pos_fig);
2441
2442        SmartPtr<AW_at_storage> at_size(AW_at_storage::make(aws, AW_AT_SIZE_AND_ATTACH));
2443
2444        int xpos, ypos;
2445        aws->get_at_position(&xpos, &ypos);
2446
2447        int ypos_dummy;
2448
2449        for (int key = QUERY_EXPRESSIONS-1;  key >= 0; --key) {
2450            if (key) {
2451                aws->at(xpos, ypos+key*KEY_Y_OFFSET);
2452                aws->create_option_menu(query->awar_operator[key], true);
2453                aws->insert_option("and", "", "and");
2454                aws->insert_option("or", "", "or");
2455                aws->insert_option("ign", "", "ign");
2456                aws->update_option_menu();
2457
2458                if (xpos_calc[0] == -1) aws->get_at_position(&xpos_calc[0], &ypos_dummy);
2459            }
2460
2461            aws->at(xpos_calc[0], ypos+key*KEY_Y_OFFSET);
2462            aws->restore_at_from(*at_size);
2463
2464            create_itemfield_selection_button(aws, FieldSelDef(query->awar_keys[key], gb_main, awtqs->get_queried_itemtype(), FIELD_FILTER_STRING_READABLE, "source field", SF_PSEUDO), NULp);
2465
2466            if (xpos_calc[1] == -1) aws->get_at_position(&xpos_calc[1], &ypos_dummy);
2467
2468            aws->at(xpos_calc[1], ypos+key*KEY_Y_OFFSET);
2469            aws->create_toggle(query->awar_not[key], "#equal.xpm", "#notEqual.xpm");
2470
2471            if (xpos_calc[2] == -1) aws->get_at_position(&xpos_calc[2], &ypos_dummy);
2472        }
2473    }
2474    if (awtqs->key_pos_fig) {
2475        aws->at(awtqs->key_pos_fig);
2476        aws->create_input_field(query->awar_keys[0], 12);
2477    }
2478
2479    if (awtqs->query_pos_fig) {
2480        aws->at(awtqs->query_pos_fig);
2481
2482        SmartPtr<AW_at_storage> at_size(AW_at_storage::make(aws, AW_AT_SIZE_AND_ATTACH));
2483
2484        int xpos, ypos;
2485        aws->get_at_position(&xpos, &ypos);
2486
2487        for (int key = 0; key<QUERY_EXPRESSIONS; ++key) {
2488            aws->at(xpos_calc[2], ypos+key*KEY_Y_OFFSET);
2489            aws->restore_at_from(*at_size);
2490            aws->d_callback(makeWindowCallback(perform_query_cb, query, EXT_QUERY_NONE)); // enable ENTER in searchfield to start search
2491            aws->create_input_field(query->awar_queries[key], 12);
2492        }
2493    }
2494
2495    if (awtqs->result_pos_fig) {
2496        aws->at(awtqs->result_pos_fig);
2497        aws->d_callback(makeWindowCallback(toggle_flag_cb, query));
2498
2499        {
2500            const char *this_awar_name = ARB_keep_string(GBS_global_string_copy("tmp/dbquery_%s/select", query_id)); // do not free this cause it's passed to new_selection_made_cb
2501            AW_awar    *awar           = aw_root->awar_string(this_awar_name, "", AW_ROOT_DEFAULT);
2502
2503            query->hitlist = aws->create_selection_list(this_awar_name, 5, 5, true);
2504            awar->add_callback(makeRootCallback(new_selection_made_cb, this_awar_name, query));
2505        }
2506        query->hitlist->insert_default("end of list", "");
2507        query->hitlist->update();
2508    }
2509
2510    if (awtqs->count_pos_fig) {
2511        aws->button_length(13);
2512        aws->at(awtqs->count_pos_fig);
2513        aws->label("Hits:");
2514        aws->create_button(NULp, query->awar_count, NULp, "+");
2515
2516        if (awtqs->popup_info_window) {
2517            aws->callback(RootAsWindowCallback::simple(awtqs->popup_info_window, query->gb_main));
2518            aws->create_button("popinfo", "Info");
2519        }
2520    }
2521    else {
2522        dbq_assert(!awtqs->popup_info_window); // dont know where to display
2523    }
2524
2525    if (awtqs->config_pos_fig) {
2526        aws->button_length(0);
2527        aws->at(awtqs->config_pos_fig);
2528        char *macro_id = GBS_global_string_copy("SAVELOAD_CONFIG_%s", query_id);
2529        AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "query_box", makeConfigSetupCallback(query_box_setup_config, query), macro_id);
2530        free(macro_id);
2531    }
2532
2533    aws->button_length(18);
2534
2535    if (awtqs->do_query_pos_fig) {
2536        aws->at(awtqs->do_query_pos_fig);
2537        aws->callback(makeWindowCallback(perform_query_cb, query, EXT_QUERY_NONE));
2538        char *macro_id = GBS_global_string_copy("SEARCH_%s", query_id);
2539        aws->create_button(macro_id, "Search", "S", "+");
2540        free(macro_id);
2541    }
2542    if (awtqs->do_refresh_pos_fig) {
2543        aws->at(awtqs->do_refresh_pos_fig);
2544        aws->create_option_menu(query->awar_sort, true);
2545        aws->insert_default_option("unsorted",  "u", QUERY_SORT_NONE);
2546        aws->insert_option        ("by value",  "v", QUERY_SORT_BY_1STFIELD_CONTENT);
2547        aws->insert_option        ("by number", "n", QUERY_SORT_NUM_BY_1STFIELD_CONTENT);
2548        aws->insert_option        ("by id",     "i", QUERY_SORT_BY_ID);
2549        if (query->selector.parent_selector) {
2550            aws->insert_option    ("by parent", "p", QUERY_SORT_BY_NESTED_PID);
2551        }
2552        aws->insert_option        ("by marked", "m", QUERY_SORT_BY_MARKED);
2553        aws->insert_option        ("by hit",    "h", QUERY_SORT_BY_HIT_DESCRIPTION);
2554        aws->insert_option        ("reverse",   "r", QUERY_SORT_REVERSE);
2555        aws->update_option_menu();
2556    }
2557    else {
2558        dbq_assert(0); // hmm - no sort button here. -> tell ralf where this happens
2559    }
2560
2561    if (awtqs->do_mark_pos_fig) {
2562        aws->at(awtqs->do_mark_pos_fig);
2563        aws->help_text("mark_list.hlp");
2564        aws->callback(makeWindowCallback(mark_queried_cb, query, 1));
2565        aws->create_button("MARK_LISTED_UNMARK_REST", "Mark Listed\nUnmark Rest", "M");
2566    }
2567    if (awtqs->do_unmark_pos_fig) {
2568        aws->at(awtqs->do_unmark_pos_fig);
2569        aws->help_text("unmark_list.hlp");
2570        aws->callback(makeWindowCallback(mark_queried_cb, query, 0));
2571        aws->create_button("UNMARK_LISTED_MARK_REST", "Unmark Listed\nMark Rest", "U");
2572    }
2573    if (awtqs->do_delete_pos_fig) {
2574        aws->at(awtqs->do_delete_pos_fig);
2575        aws->help_text("del_list.hlp");
2576        aws->callback(makeWindowCallback(delete_queried_species_cb, query));
2577        char *macro_id = GBS_global_string_copy("DELETE_LISTED_%s", query_id);
2578        aws->create_button(macro_id, "Delete Listed", "D");
2579        free(macro_id);
2580    }
2581    if (awtqs->do_set_pos_fig) {
2582        query->awar_writekey      = GBS_global_string_copy("tmp/dbquery_%s/write_key",      query_id); aw_root->awar_string(query->awar_writekey);
2583        query->awar_writelossy    = GBS_global_string_copy("tmp/dbquery_%s/write_lossy",    query_id); aw_root->awar_int   (query->awar_writelossy);
2584        query->awar_protectkey    = GBS_global_string_copy("tmp/dbquery_%s/protect_key",    query_id); aw_root->awar_string(query->awar_protectkey);
2585        query->awar_setprotection = GBS_global_string_copy("tmp/dbquery_%s/set_protection", query_id); aw_root->awar_int   (query->awar_setprotection, 4);
2586        query->awar_setvalue      = GBS_global_string_copy("tmp/dbquery_%s/set_value",      query_id); aw_root->awar_string(query->awar_setvalue);
2587
2588        aws->at(awtqs->do_set_pos_fig);
2589        aws->help_text("mod_field_list.hlp");
2590        aws->callback(makeCreateWindowCallback(create_writeFieldOfListed_window, query));
2591        char *macro_id = GBS_global_string_copy("WRITE_TO_FIELDS_OF_LISTED_%s", query_id);
2592        aws->create_button(macro_id, "Write to Fields\nof Listed", "S");
2593        free(macro_id);
2594    }
2595
2596    char *Items = strdup(query->selector.items_name);
2597    Items[0]    = toupper(Items[0]);
2598
2599    if ((awtqs->use_menu || awtqs->open_parser_pos_fig)) {
2600        sprintf(buffer, "tmp/dbquery_%s/tag",                 query_id); query->awar_tag             = strdup(buffer); aw_root->awar_string(query->awar_tag);
2601        sprintf(buffer, "tmp/dbquery_%s/use_tag",             query_id); query->awar_use_tag         = strdup(buffer); aw_root->awar_int   (query->awar_use_tag);
2602        sprintf(buffer, "tmp/dbquery_%s/deftag",              query_id); query->awar_deftag          = strdup(buffer); aw_root->awar_string(query->awar_deftag);
2603        sprintf(buffer, "tmp/dbquery_%s/double_pars",         query_id); query->awar_double_pars     = strdup(buffer); aw_root->awar_int   (query->awar_double_pars);
2604        sprintf(buffer, "tmp/dbquery_%s/parskey",             query_id); query->awar_parskey         = strdup(buffer); aw_root->awar_string(query->awar_parskey);
2605        sprintf(buffer, "tmp/dbquery_%s/parsvalue",           query_id); query->awar_parsvalue       = strdup(buffer); aw_root->awar_string(query->awar_parsvalue);
2606        sprintf(buffer, "tmp/dbquery_%s/awar_parspredefined", query_id); query->awar_parspredefined  = strdup(buffer); aw_root->awar_string(query->awar_parspredefined);
2607        sprintf(buffer, "tmp/dbquery_%s/acceptConvError",     query_id); query->awar_acceptConvError = strdup(buffer); aw_root->awar_int   (query->awar_acceptConvError);
2608
2609        if (awtqs->use_menu) {
2610            sprintf(buffer, "Modify Fields of Listed %s", Items); query_rel_menu_entry(aws, "mod_fields_of_listed", query_id, buffer, "F", "mod_field_list.hlp", AWM_ALL, makeCreateWindowCallback(create_modify_fields_window, query));
2611        }
2612        else {
2613            aws->at(awtqs->open_parser_pos_fig);
2614            aws->callback(makeCreateWindowCallback(create_modify_fields_window, query));
2615            aws->create_button("MODIFY_FIELDS_OF_LISTED", "MODIFY FIELDS\nOF LISTED", "F");
2616        }
2617    }
2618    if (awtqs->use_menu) {
2619        sprintf(buffer, "Set Protection of Fields of Listed %s", Items); query_rel_menu_entry(aws, "s_prot_of_listed", query_id, buffer, "P", "set_protection.hlp", AWM_ALL, makeCreateWindowCallback(create_set_protection_window, query));
2620        aws->sep______________();
2621        sprintf(buffer, "Mark Listed %s, don't Change Rest",   Items); query_rel_menu_entry(aws, "mark_listed",             query_id, buffer, "M", "mark.hlp", AWM_ALL, makeWindowCallback(mark_queried_cb, query, 1|8));
2622        sprintf(buffer, "Mark Listed %s, Unmark Rest",         Items); query_rel_menu_entry(aws, "mark_listed_unmark_rest", query_id, buffer, "L", "mark.hlp", AWM_ALL, makeWindowCallback(mark_queried_cb, query, 1  ));
2623        sprintf(buffer, "Unmark Listed %s, don't Change Rest", Items); query_rel_menu_entry(aws, "unmark_listed",           query_id, buffer, "U", "mark.hlp", AWM_ALL, makeWindowCallback(mark_queried_cb, query, 0|8));
2624        sprintf(buffer, "Unmark Listed %s, Mark Rest",         Items); query_rel_menu_entry(aws, "unmark_listed_mark_rest", query_id, buffer, "R", "mark.hlp", AWM_ALL, makeWindowCallback(mark_queried_cb, query, 0  ));
2625        aws->sep______________();
2626
2627
2628        sprintf(buffer, "Set Color of Listed %s", Items);    query_rel_menu_entry(aws, "set_color_of_listed", query_id, buffer, "C", "set_color_of_listed.hlp", AWM_ALL, makeCreateWindowCallback(create_colorize_queried_window, query));
2629
2630        if (query->gb_ref) {
2631            dbq_assert(query->selector.type == QUERY_ITEM_SPECIES); // stuff below works only for species
2632            aws->sep______________();
2633            if (query->expect_hit_in_ref_list) {
2634                aws->insert_menu_topic("search_equal_fields_and_listed_in_I", "Search entries existing in both DBs and listed in the source DB hitlist", "S",
2635                                       "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_LINES));
2636                aws->insert_menu_topic("search_equal_words_and_listed_in_I",  "Search words existing in entries of both DBs and listed in the source DB hitlist", "w",
2637                                       "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_WORDS));
2638            }
2639            else {
2640                aws->insert_menu_topic("search_equal_field_in_both_db", "Search entries existing in both DBs", "S",
2641                                       "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_LINES));
2642                aws->insert_menu_topic("search_equal_word_in_both_db", "Search words existing in entries of both DBs", "w",
2643                                       "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_WORDS));
2644            }
2645        }
2646    }
2647
2648    free(Items);
2649
2650    GB_pop_transaction(gb_main);
2651    return query;
2652}
2653
2654// --------------------------------------------------------------------------------
2655
2656#ifdef UNIT_TESTS
2657#ifndef TEST_UNIT_H
2658#include <test_unit.h>
2659#endif
2660
2661void TEST_nullcmp() {
2662    const char *whatever = "bla";
2663    TEST_EXPECT_EQUAL(ARB_strNULLcmp(NULp, NULp), 0);
2664    TEST_EXPECT_EQUAL(ARB_strNULLcmp(whatever, whatever), 0);
2665    TEST_EXPECT_EQUAL(ARB_strNULLcmp("a", "b"), -1);
2666
2667    // document uncommon behavior: NULp is bigger than any other text!
2668    TEST_EXPECT_EQUAL(ARB_strNULLcmp(whatever, NULp), -1);
2669    TEST_EXPECT_EQUAL(ARB_strNULLcmp(NULp, whatever), 1);
2670}
2671
2672#endif // UNIT_TESTS
2673
2674// --------------------------------------------------------------------------------
2675
Note: See TracBrowser for help on using the repository browser.