// ============================================================ //
//                                                              //
//   File      : db_query.cxx                                   //
//   Purpose   : Database queries                               //
//                                                              //
//   Institute of Microbiology (Technical University Munich)    //
//   http://www.arb-home.de/                                    //
//                                                              //
// ============================================================ //

#include "db_query.h"
#include "db_query_local.h"
#include "query_expr.h"

#include <dbui.h>
#include <item_sel_list.h>
#include <config_manager.hxx>
#include <sel_boxes.hxx>

#include <aw_advice.hxx>
#include <aw_color_groups.hxx>
#include <aw_file.hxx>
#include <aw_msg.hxx>
#include <aw_awar.hxx>
#include <arb_progress.h>
#include <aw_root.hxx>
#include <aw_question.hxx>
#include <rootAsWin.h>

#include <ad_cb.h>

#include <arb_strbuf.h>
#include <arb_sort.h>
#include <arb_global_defs.h>
#include <Keeper.h>

#include <list>
#include <stack>

using namespace std;
using namespace QUERY;

#define MAX_QUERY_LIST_LEN  100000

#define AWAR_COLORIZE            "tmp/dbquery_all/colorize"
#define AWAR_COLOR_LOADSAVE_NAME "tmp/colorset/name"

static void free_hit_description(long info) {
    free(reinterpret_cast<char*>(info));
}

inline void SET_QUERIED(GBDATA *gb_species, DbQuery *query, const char *hitInfo, size_t hitInfoLen = 0) {
    dbq_assert(hitInfo);

    GB_raise_user_flag(gb_species, query->select_bit);

    char *name = query->selector.generate_item_id(query->gb_main, gb_species);
    char *info;

    if (hitInfoLen == 0) hitInfoLen = strlen(hitInfo);
    if (hitInfoLen>MAX_SHOWN_DATA_SIZE) {
        char *dupInfo = strdup(hitInfo);
        hitInfoLen    = GBS_shorten_repeated_data(dupInfo);
        if (hitInfoLen>MAX_SHOWN_DATA_SIZE) {
            strcpy(dupInfo+hitInfoLen-5, "[...]");
        }
        info = strdup(dupInfo);
        free(dupInfo);
    }
    else {
        info = strdup(hitInfo);
    }

    GBS_write_hash(query->hit_description, name, reinterpret_cast<long>(info)); // overwrite hit info (also deallocates)
    free(name);
}

inline void CLEAR_QUERIED(GBDATA *gb_species, DbQuery *query) {
    GB_clear_user_flag(gb_species, query->select_bit);

    char *name = query->selector.generate_item_id(query->gb_main, gb_species);
    GBS_write_hash(query->hit_description, name, 0); // delete hit info (also deallocates)
    free(name);
}

inline const char *getHitInfo(const char *item_id, DbQuery *query) {
    long info = GBS_read_hash(query->hit_description, item_id);
    return reinterpret_cast<const char*>(info);
}
inline const char *getHitInfo(GBDATA *gb_species, DbQuery *query) {
    char       *name   = query->selector.generate_item_id(query->gb_main, gb_species);
    const char *result = getHitInfo(name, query);
    free(name);
    return result;
}
inline string keptHitReason(const string& currentHitReason, GBDATA *gb_item, DbQuery *query) {
    string      reason  = string("kept because ")+currentHitReason;
    const char *hitinfo = getHitInfo(gb_item, query);
    if (hitinfo) reason = string(hitinfo)+" ("+reason+')';
    return reason;
}

static void create_query_independent_awars(AW_root *aw_root, AW_default aw_def) {
    aw_root->awar_int(AWAR_COLORIZE, 0, aw_def);
    aw_root->awar_string(AWAR_COLOR_LOADSAVE_NAME, "", aw_def);
}

GBDATA *QUERY::query_get_gb_main(DbQuery *query) {
    return query->gb_main;
}

const ItemSelector& QUERY::get_queried_itemtype(DbQuery *query) {
    return query->selector;
}

enum EXT_QUERY_TYPES {
    EXT_QUERY_NONE,
    EXT_QUERY_COMPARE_LINES,
    EXT_QUERY_COMPARE_WORDS
};

query_spec::query_spec(ItemSelector& selector_)
    : selector(selector_),
      gb_main(NULp),
      gb_ref(NULp),
      expect_hit_in_ref_list(false),
      species_name(NULp),
      tree_name(NULp),
      select_bit(GB_USERFLAG_QUERY), // always == GB_USERFLAG_QUERY atm (nevertheless DO NOT hardcode)
      ere_pos_fig(NULp),
      where_pos_fig(NULp),
      by_pos_fig(NULp),
      qbox_pos_fig(NULp),
      key_pos_fig(NULp),
      query_pos_fig(NULp),
      result_pos_fig(NULp),
      count_pos_fig(NULp),
      do_query_pos_fig(NULp),
      config_pos_fig(NULp),
      do_mark_pos_fig(NULp),
      do_unmark_pos_fig(NULp),
      do_mark_nt_pos_fig(NULp),
      do_unmark_nt_pos_fig(NULp),
      do_delete_pos_fig(NULp),
      do_set_pos_fig(NULp),
      do_refresh_pos_fig(NULp),
      popup_info_window(NULp),
      info_box_pos_fig(NULp)
{
    dbq_assert(&selector);
}

bool query_spec::is_queried(GBDATA *gb_item) const {
    return GB_user_flag(gb_item, select_bit);
}

bool QUERY::IS_QUERIED(GBDATA *gb_item, const DbQuery *query) {
    return query->is_queried(gb_item);
}

long QUERY::count_queried_items(DbQuery *query, QUERY_RANGE range) {
    GBDATA       *gb_main  = query->gb_main;
    ItemSelector& selector = query->selector;

    long count = 0;

    for (GBDATA *gb_item_container = selector.get_first_item_container(gb_main, query->aws->get_root(), range);
         gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, range))
    {
        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             gb_item;
             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            if (IS_QUERIED(gb_item, query)) count++;
        }
    }
    return count;
}

// @@@ replace query_count_items by "method" of selector
static int query_count_items(DbQuery *query, QUERY_RANGE range, QUERY_MODES mode) {
    int             count    = 0;
    GBDATA         *gb_main  = query->gb_main;
    ItemSelector&   selector = query->selector;
    GB_transaction  ta(gb_main);

    for (GBDATA *gb_item_container = selector.get_first_item_container(gb_main, query->aws->get_root(), range);
         gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, range))
    {
        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             gb_item;
             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            switch (mode) {
                case QUERY_GENERATE: ++count; break;
                case QUERY_ENLARGE:  count += !IS_QUERIED(gb_item, query); break;
                case QUERY_REDUCE:   count +=  IS_QUERIED(gb_item, query); break;
            }
        }
    }
    return count;
}

const int MAX_CRITERIA = int(sizeof(unsigned long)*8/QUERY_SORT_CRITERIA_BITS);

static void split_sort_mask(unsigned long sort_mask, QUERY_RESULT_ORDER *order) {
    // splits the sort order bit mask 'sort_mask' into single sort criteria and write these into 'order'
    // (order[0] will contain the primary sort criteria, order[1] the secondary, ...)

    for (int o = 0; o<MAX_CRITERIA; ++o) {
        order[o] = QUERY_RESULT_ORDER(sort_mask&QUERY_SORT_CRITERIA_MASK);
        dbq_assert(order[o] == (order[o]&QUERY_SORT_CRITERIA_MASK));
        sort_mask = sort_mask>>QUERY_SORT_CRITERIA_BITS;
    }
}

static QUERY_RESULT_ORDER find_display_determining_sort_order(QUERY_RESULT_ORDER *order) {
    // Returns the first criteria in 'order' (which has to have MAX_CRITERIA elements)
    // that matches
    // - QUERY_SORT_BY_1STFIELD_CONTENT or
    // - QUERY_SORT_NUM_BY_1STFIELD_CONTENT or
    // - QUERY_SORT_BY_HIT_DESCRIPTION
    // (or QUERY_SORT_NONE if none of the above is used)

    QUERY_RESULT_ORDER first = QUERY_SORT_NONE;
    for (int o = 0; o<MAX_CRITERIA && first == QUERY_SORT_NONE; ++o) {
        if (order[o] & (QUERY_SORT_BY_1STFIELD_CONTENT|QUERY_SORT_NUM_BY_1STFIELD_CONTENT|QUERY_SORT_BY_HIT_DESCRIPTION)) {
            first = order[o];
        }
    }
    return first;
}

static void remove_keydependent_sort_criteria(QUERY_RESULT_ORDER *order) {
    // removes all sort-criteria from order which would use the order of the currently selected primary key

    int n = 0;
    for (int o = 0; o<MAX_CRITERIA; ++o) {
        if (order[o] != QUERY_SORT_BY_1STFIELD_CONTENT && order[o] != QUERY_SORT_NUM_BY_1STFIELD_CONTENT) {
            order[n++] = order[o];
        }
    }
    for (; n<MAX_CRITERIA; ++n) {
        order[n] = QUERY_SORT_NONE;
    }
}

static void first_searchkey_changed_cb(AW_root *, DbQuery *query) {
    QUERY_RESULT_ORDER order[MAX_CRITERIA];
    split_sort_mask(query->sort_mask, order);

    QUERY_RESULT_ORDER usedOrder = find_display_determining_sort_order(order);
    if (usedOrder != QUERY_SORT_BY_HIT_DESCRIPTION && usedOrder != QUERY_SORT_NONE) { // do we display values?
        DbQuery_update_list(query);
    }
}

inline bool keep_criteria(QUERY_RESULT_ORDER old_criteria, QUERY_RESULT_ORDER new_criteria) {
    return
        old_criteria  != QUERY_SORT_NONE &&     // do not keep 'unsorted' (it is no real criteria)
        (old_criteria != new_criteria        ||     // do not keep new criteria (added later)
         old_criteria == QUERY_SORT_REVERSE);   // reverse may occur several times -> always keep
}

static void result_sort_order_changed_cb(AW_root *aw_root, DbQuery *query) {
    // adds the new selected sort order to the sort order mask
    // (added order removes itself, if it previously existed in sort order mask)
    //
    // if 'unsorted' is selected -> delete sort order

    QUERY_RESULT_ORDER new_criteria = (QUERY_RESULT_ORDER)aw_root->awar(query->awar_sort)->read_int();
    if (new_criteria == QUERY_SORT_NONE) {
        query->sort_mask = QUERY_SORT_NONE; // clear sort_mask
    }
    else {
        QUERY_RESULT_ORDER order[MAX_CRITERIA];
        split_sort_mask(query->sort_mask, order);

        int empty_or_same = 0;
        for (int o = 0; o<MAX_CRITERIA; o++) {
            if (!keep_criteria(order[o], new_criteria)) {
                empty_or_same++; // these criteria will be skipped below
            }
        }

        unsigned long new_sort_mask = 0;
        for (int o = MAX_CRITERIA-(empty_or_same>0 ? 1 : 2); o >= 0; o--) {
            if (keep_criteria(order[o], new_criteria)) {
                new_sort_mask = (new_sort_mask<<QUERY_SORT_CRITERIA_BITS)|order[o];
            }
        }
        query->sort_mask = (new_sort_mask<<QUERY_SORT_CRITERIA_BITS)|new_criteria; // add new primary key
    }
    DbQuery_update_list(query);
}

struct hits_sort_params : virtual Noncopyable {
    DbQuery            *query;
    char               *first_key;
    GB_TYPES            first_type;
    QUERY_RESULT_ORDER  order[MAX_CRITERIA];

    hits_sort_params(DbQuery *q, const char *fk)
        : query(q),
          first_key(strdup(fk))
    {
        first_type = GBT_get_type_of_changekey(query->gb_main, first_key, query->selector.change_key_path);
    }

    ~hits_sort_params() {
        free(first_key);
    }
};

inline int numeric_string_cmp(const char *str1, const char *str2) {
    const char *end1;
    const char *end2;

    double double1 = strtod(str1, const_cast<char**>(&end1));
    double double2 = strtod(str2, const_cast<char**>(&end2));

    bool conv1 = !end1[0] && str1[0];
    bool conv2 = !end2[0] && str2[0];

    bool both_converted = conv1 && conv2;

    int cmp;
    if (both_converted) {
        cmp = double_cmp(double1, double2);
    }
    else {
        bool conv_partial1 = end1>str1;
        bool conv_partial2 = end2>str2;

        if (conv_partial1 && conv_partial2) {
            cmp = double_cmp(double1, double2);
            if (!cmp) {
                cmp = numeric_string_cmp(end1, end2);
            }
        }
        else cmp = strcmp(str1, str2);
    }
    return cmp;
}

static int compare_hits(const void *cl_item1, const void *cl_item2, void *cl_param) {
    const hits_sort_params *param = static_cast<const hits_sort_params*>(cl_param);

    GBDATA *gb_item1 = (GBDATA*)cl_item1;
    GBDATA *gb_item2 = (GBDATA*)cl_item2;

    DbQuery      *query    = param->query;
    ItemSelector& selector = query->selector;

    int cmp = 0;

    for (int o = 0; o<MAX_CRITERIA && cmp == 0; o++) {
        QUERY_RESULT_ORDER criteria = param->order[o];

        switch (criteria) {
            case QUERY_SORT_NONE:
                o = MAX_CRITERIA; // don't sort further
                break;

            case QUERY_SORT_BY_1STFIELD_CONTENT: {
                char *field1 = GBT_read_as_string(gb_item1, param->first_key);
                char *field2 = GBT_read_as_string(gb_item2, param->first_key);

                cmp = ARB_strNULLcmp(field1, field2);

                free(field2);
                free(field1);
                break;
            }
            case QUERY_SORT_NUM_BY_1STFIELD_CONTENT: {
                switch (param->first_type) {
                    case GB_INT: {
                        long *have1 = GBT_read_int(gb_item1, param->first_key);
                        long  L1    = have1 ? *have1 : 0;
                        long *have2 = GBT_read_int(gb_item2, param->first_key);
                        long  L2    = have2 ? *have2 : 0;

                        cmp = have1 // use same logic as ARB_strNULLcmp
                            ? (have2 ? long_cmp(L1,L2) : -1)
                            : (have2 ? 1 : 0);
                        break;
                    }
                    case GB_FLOAT: {
                        float *have1 = GBT_read_float(gb_item1, param->first_key);
                        float  f1    = have1 ? *have1 : 0;
                        float *have2 = GBT_read_float(gb_item2, param->first_key);
                        float  f2    = have2 ? *have2 : 0;

                        cmp = have1
                            ? (have2 ? float_cmp(f1,f2) : -1)
                            : (have2 ? 1 : 0);
                        break;
                    }
                    default: {
                        char *field1 = GBT_read_as_string(gb_item1, param->first_key);
                        char *field2 = GBT_read_as_string(gb_item2, param->first_key);

                        cmp = (field1 && field2)
                            ? numeric_string_cmp(field1, field2)
                            : ARB_strNULLcmp(field1, field2);

                        free(field2);
                        free(field1);
                    }
                }
                break;
            }
            case QUERY_SORT_BY_NESTED_PID: {
                if (selector.parent_selector) {
                    GBDATA *gb_parent1 = selector.get_parent(gb_item1);
                    GBDATA *gb_parent2 = selector.get_parent(gb_item2);

                    char *pid1 = selector.generate_item_id(query->gb_main, gb_parent1);
                    char *pid2 = selector.generate_item_id(query->gb_main, gb_parent2);

                    cmp = ARB_strNULLcmp(pid1, pid2);

                    free(pid2);
                    free(pid1);
                }
                break;
            }
            case QUERY_SORT_BY_ID: {
                const char *id1 = GBT_read_char_pntr(gb_item1, selector.id_field);
                const char *id2 = GBT_read_char_pntr(gb_item2, selector.id_field);

                cmp = strcmp(id1, id2);
                break;
            }
            case QUERY_SORT_BY_MARKED:
                cmp = GB_read_flag(gb_item2)-GB_read_flag(gb_item1);
                break;

            case QUERY_SORT_BY_HIT_DESCRIPTION: {
                const char *id1   = GBT_read_char_pntr(gb_item1, selector.id_field);
                const char *id2   = GBT_read_char_pntr(gb_item2, selector.id_field);
                const char *info1 = reinterpret_cast<const char *>(GBS_read_hash(query->hit_description, id1));
                const char *info2 = reinterpret_cast<const char *>(GBS_read_hash(query->hit_description, id2));
                cmp = ARB_strNULLcmp(info1, info2);
                break;
            }
            case QUERY_SORT_REVERSE: {
                GBDATA *tmp = gb_item1; // swap items for following compares (this is a prefix revert!)
                gb_item1    = gb_item2;
                gb_item2    = tmp;
                break;
            }
        }
    }

    return cmp;
}

static void detectMaxNameLength(const char *key, long /*val*/, void *cl_len) {
    int *len  = (int*)cl_len;
    int  klen = strlen(key);

    if (klen>*len) *len = klen;
}

#if defined(ASSERTION_USED)
inline bool SLOW_is_pseudo_key(const char *key) {
    return
        strcmp(key, PSEUDO_FIELD_ANY_FIELD) == 0 ||
        strcmp(key, PSEUDO_FIELD_ALL_FIELDS) == 0 ||
        strcmp(key, PSEUDO_FIELD_ANY_FIELD_REC) == 0 ||
        strcmp(key, PSEUDO_FIELD_ALL_FIELDS_REC) == 0;
}
#endif
inline bool is_pseudo_key(const char *key) {
    // returns true, if 'key' is a pseudo-key
    bool is_pseudo = key[0] == '[';
    dbq_assert(is_pseudo == SLOW_is_pseudo_key(key));
    return is_pseudo;
}

void QUERY::DbQuery_update_list(DbQuery *query) {
    GB_push_transaction(query->gb_main);

    dbq_assert(query->hitlist);
    query->hitlist->clear();

    AW_window     *aww      = query->aws;
    AW_root       *aw_root  = aww->get_root();
    QUERY_RANGE    range    = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
    ItemSelector&  selector = query->selector;

    // create array of hits
    long     count  = count_queried_items(query, range);
    GBDATA **sorted = ARB_alloc<GBDATA*>(count);
    {
        long s = 0;

        for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
             gb_item_container;
             gb_item_container = selector.get_next_item_container(gb_item_container, range))
        {
            for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                 gb_item;
                 gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
            {
                if (IS_QUERIED(gb_item, query)) sorted[s++] = gb_item;
            }
        }
    }

    // sort hits

    hits_sort_params param(query, aww->get_root()->awar(query->awar_keys[0])->read_char_pntr());

    bool is_pseudo  = is_pseudo_key(param.first_key);
    bool show_value = !is_pseudo; // cannot refer to key-value of pseudo key

    if (query->sort_mask != QUERY_SORT_NONE) {    // unsorted -> don't sort
        split_sort_mask(query->sort_mask, param.order);
        if (is_pseudo) {
            remove_keydependent_sort_criteria(param.order);
        }
        if (show_value && find_display_determining_sort_order(param.order) == QUERY_SORT_BY_HIT_DESCRIPTION) {
            show_value = false;
        }
        GB_sort((void**)sorted, 0, count, compare_hits, &param);
    }

    // display hits

    int name_len = selector.item_name_length;
    if (name_len == -1) { // if name_len is unknown -> detect
        GBS_hash_do_const_loop(query->hit_description, detectMaxNameLength, &name_len);
    }

    long i;
    for (i = 0; i<count && i<MAX_QUERY_LIST_LEN; i++) {
        char *name = selector.generate_item_id(query->gb_main, sorted[i]);
        if (name) {
            char       *toFree = NULp;
            const char *info;

            if (show_value) {
                toFree = GBT_read_as_string(sorted[i], param.first_key);
                if (toFree) {
                    if (strlen(toFree)>MAX_SHOWN_DATA_SIZE) {
                        size_t shortened_len = GBS_shorten_repeated_data(toFree);
                        if (shortened_len>MAX_SHOWN_DATA_SIZE) {
                            strcpy(toFree+MAX_SHOWN_DATA_SIZE-5, "[...]");
                        }
                    }
                }
                else {
                    toFree = GBS_global_string_copy("<%s has no data>", param.first_key);
                }
                info = toFree;
            }
            else {
                info = getHitInfo(name, query);
                if (!info) info = "<no hit info>";
            }

            dbq_assert(info);
            const char *line = GBS_global_string("%c %-*s :%s",
                                                 GB_read_flag(sorted[i]) ? '*' : ' ',
                                                 name_len, name,
                                                 info);

            query->hitlist->insert(line, name);
            free(toFree);
            free(name);
        }
    }

    if (count>MAX_QUERY_LIST_LEN) {
        query->hitlist->insert("*****  List truncated  *****", "");
    }

    free(sorted);

    query->hitlist->insert_default("End of list", "");
    query->hitlist->update();
    aww->get_root()->awar(query->awar_count)->write_int((long)count);
    GB_pop_transaction(query->gb_main);
}

static void mark_queried_cb(AW_window*, DbQuery *query, int mark) {
    // Mark listed species
    // mark = 1 -> mark listed
    // mark | 8 -> don't change rest

    ItemSelector& selector = query->selector;
    GB_push_transaction(query->gb_main);

    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, query->aws->get_root(), QUERY_ALL_ITEMS);
         gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
        {
            for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                 gb_item;
                 gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                {
                    if (IS_QUERIED(gb_item, query)) {
                        GB_write_flag(gb_item, mark&1);
                    }
                    else if ((mark&8) == 0) {
                        GB_write_flag(gb_item, 1-(mark&1));
                    }
                }
        }

    DbQuery_update_list(query);
    GB_pop_transaction(query->gb_main);
}

void QUERY::unquery_all(void *, DbQuery *query) {
    GB_push_transaction(query->gb_main);
    GBDATA *gb_species;
    for (gb_species = GBT_first_species(query->gb_main);
                gb_species;
                gb_species = GBT_next_species(gb_species)) {
        CLEAR_QUERIED(gb_species, query);
    }
    DbQuery_update_list(query);
    GB_pop_transaction(query->gb_main);
}

static void delete_queried_species_cb(AW_window*, DbQuery *query) {
    ItemSelector& selector = query->selector;
    GB_begin_transaction(query->gb_main);

    long cnt = 0;
    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, query->aws->get_root(), QUERY_ALL_ITEMS);
         gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
    {
        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             gb_item;
             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            if (IS_QUERIED(gb_item, query)) cnt++;
        }
    }

    if (!cnt || !aw_ask_sure("delete_queried_species", GBS_global_string("Are you sure to delete %li %s", cnt, selector.items_name))) {
        GB_abort_transaction(query->gb_main);
        return;
    }

    GB_ERROR error = NULp;

    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, query->aws->get_root(), QUERY_ALL_ITEMS);
         !error && gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
        {
            for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                 !error && gb_item;
                 gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                {
                    if (IS_QUERIED(gb_item, query)) {
                        error = GB_delete(gb_item);
                    }
                }
        }

    if (error) {
        GB_abort_transaction(query->gb_main);
        aw_message(error);
    }
    else {
        DbQuery_update_list(query);
        GB_commit_transaction(query->gb_main);
    }
}

static GB_HASH *create_ref_hash(const DbQuery *query, const char *key, bool split_words) {
    GBDATA  *gb_ref       = query->gb_ref;
    bool     queried_only = query->expect_hit_in_ref_list;
    GB_HASH *hash         = GBS_create_hash(GBT_get_species_count(gb_ref), GB_IGNORE_CASE);

    for (GBDATA  *gb_species = GBT_first_species(gb_ref);
         gb_species;
         gb_species = GBT_next_species(gb_species))
    {
        if (!queried_only || IS_QUERIED(gb_species, query)) {
            GBDATA *gb_name = GB_search(gb_species, key, GB_FIND);
            if (gb_name) {
                char *keyas = GB_read_as_string(gb_name);
                if (keyas && strlen(keyas)) {
                    if (split_words) {
                        char *t;
                        for (t = strtok(keyas, " "); t; t = strtok(NULp, " ")) {
                            if (t[0]) GBS_write_hash(hash, t, (long)gb_species);
                        }
                    }
                    else {
                        GBS_write_hash(hash, keyas, (long)gb_species);
                    }
                }
                free(keyas);
            }
        }
    }
    return hash;
}

class TargetItem : public QueryTarget {
    RefPtr<GBDATA> gb_item;
public:
    TargetItem(const DbQuery& query) :
        QueryTarget(query.gb_main, query.get_tree_name()),
        gb_item(NULp)
    {}

    void aimTo(GBDATA *gb_next_item) {
        dbq_assert(gb_next_item); // @@@ maybe allow? (to invalidate target)
        gb_item = gb_next_item;
    }
    GBDATA *get_item() const {
        dbq_assert(gb_item); // you have to call aimTo before!
        return gb_item;
    }

    // QueryTarget interface:
    GBDATA *get_ACI_item() const OVERRIDE { return get_item(); }
};

class ItemQueryKey FINAL_TYPE : public QueryKey, virtual Noncopyable {
    char    *key;             // search key (=name of DB field or pseudo-field)
    bool     hierarchical;    // is 'key' hierarchical?
    GBQUARK  keyquark;        // valid only if get_type() == QKEY_EXPLICIT!

    typedef std::stack<GBDATA*> ParentStack;

    mutable GBDATA      *gb_key; // last read key-entry
    mutable ParentStack  parent; // container(s) descended

    static query_key_type detect_key_type(const char *field_name) {
        if (is_pseudo_key(field_name)) {
            if (strcmp(field_name, PSEUDO_FIELD_ANY_FIELD)      == 0) return QKEY_ANY;
            if (strcmp(field_name, PSEUDO_FIELD_ANY_FIELD_REC)  == 0) return QKEY_ANY_REC;
            if (strcmp(field_name, PSEUDO_FIELD_ALL_FIELDS)     == 0) return QKEY_ALL;
            if (strcmp(field_name, PSEUDO_FIELD_ALL_FIELDS_REC) == 0) return QKEY_ALL_REC;
            dbq_assert(0);
        }
        return QKEY_EXPLICIT;
    }

public:
    ItemQueryKey(GBDATA *gb_main, const char *field_name) :
        QueryKey(detect_key_type(field_name)),
        key(strdup(field_name)),
        hierarchical(false),
        keyquark(0), // invalid quark
        gb_key(NULp)
    {
        if (get_type() == QKEY_EXPLICIT) {
            if (GB_first_non_key_char(key)) {
                hierarchical = true;
            }
            else {
                keyquark = GB_find_or_create_quark(gb_main, key);
            }
        }
    }
    ~ItemQueryKey() {
        free(key);
    }

    // QueryKey interface:
    char *get_target_data(const QueryTarget& target, GB_ERROR& error) const OVERRIDE {
        // retrieve content of target key
        if (!gb_key) {
            const TargetItem& target_item = DOWNCAST_REFERENCE(const TargetItem, target);
            gb_key = get_first_field(target_item.get_item());
        }

        char *data;
        if (gb_key) {
            data = GB_read_as_string(gb_key);
            if (!data) {
                if (GB_readable_as_string(gb_key)) {
                    error = GB_await_error();
                }
                else {
                    gb_assert(!GB_have_error());
                    error = GBS_global_string("field '%s' has wrong type", GB_read_key_pntr(gb_key));
                }
            }
        }
        else {
            data = strdup(""); // non-existing field -> assume "" as default value
                               // (needed to search for missing fields using '!=*' ?)
        }
        return data;
    }
    const char *get_name()  const OVERRIDE {
        // name of target (e.g. for reports)
        // - for explicit keys, it simply returns the specified key name
        // - for iterating keys, it returns the name of the DB field at current iteration position,
        //   showing path to key relative to item (e.g. 'ali_16s/data')
        //   If not yet iterating, it returns the name of the pseudo-field.

        if (get_type() != QKEY_EXPLICIT && gb_key) {
            const char *keyname = GB_read_key_pntr(gb_key);
            if (!parent.empty()) {
                const char *parent_path  = GB_get_db_path(parent.top());
                dbq_assert(parent.top() == GB_get_father(gb_key));
                const char *rslash       = strrchr(parent_path, '/');
                for (int nest = parent.size()-1; rslash && nest; --nest) {
                    rslash = strrchr(rslash-1, '/');
                }
                return GBS_global_string("%s/%s", rslash ? rslash+1 : "<unknown>", keyname);
            }
            return keyname;
        }
        return key;
    }

private:
    bool accept_or_iterate() const {
        dbq_assert(get_type() != QKEY_EXPLICIT);
        if (gb_key) {
            GB_TYPES ktype = GB_read_type(gb_key);
            if (ktype == GB_DB) {
                switch (get_type()) {
                    case QKEY_ALL:
                    case QKEY_ANY:
                        return iterate(); // = skip over containers

                    case QKEY_ALL_REC:
                    case QKEY_ANY_REC:
                        parent.push(gb_key); // remember position ..
                        gb_key = GB_child(gb_key); // .. and descend into container
                        break;

                    case QKEY_EXPLICIT:
                        dbq_assert(0);
                }
                return accept_or_iterate();
            }
            if (!GB_TYPE_readable_as_string(ktype)) {
                return iterate();
            }
            return true;
        }
        if (!parent.empty()) {
            gb_key = parent.top();
            parent.pop();
            return iterate();
        }
        return false;
    }
public:

    bool iterate() const OVERRIDE { // iterate key to next item
        dbq_assert(get_type() != QKEY_EXPLICIT);
        if (gb_key) gb_key = GB_nextChild(gb_key);
        return accept_or_iterate();
    }
    void reset() const OVERRIDE {
        // restart multi-key iteration
        gb_key = NULp;
        parent = ParentStack();
    }
    // ----------


    GBDATA *get_first_field(GBDATA *gb_item) const {
        GBDATA *gb_field = NULp;

        if (hierarchical) {
            gb_field = GB_search(gb_item, key, GB_FIND);
        }
        else if (get_type() != QKEY_EXPLICIT) {
            gb_field = GB_child(gb_item);
            // no need to use same method as normal search (we just need any key here)
            while (gb_field && GB_read_type(gb_field) == GB_DB) {
                gb_field = GB_nextChild(gb_field);
            }
        }
        else {
            gb_field = GB_find_sub_by_quark(gb_item, keyquark, NULp, 0);
        }

        return gb_field;
    }
};

inline query_operator awarvalue2query_operator(const char *awarvalue) {
    if (strcmp(awarvalue, "and") == 0) return AND;
    if (strcmp(awarvalue, "or")  == 0) return OR;
    return ILLEGAL;
}

SmartPtr<QueryExpr> DbQuery::buildQueryExpr() {
    AW_root   *aw_root  = aws->get_root();
    QueryExpr *first_qe = NULp;

    for (int idx = 0; idx<QUERY_EXPRESSIONS; ++idx) {
        query_operator aqo = idx
            ? awarvalue2query_operator(aw_root->awar(awar_operator[idx])->read_char_pntr())
            : OR; // initial value is false, (false OR QUERY1) == QUERY1

        if (aqo != ILLEGAL) {
            bool         not_equal  = aw_root->awar(awar_not[idx])->read_int() != 0;
            const char  *expression = aw_root->awar(awar_queries[idx])->read_char_pntr();
            const char  *field_name = aw_root->awar(awar_keys[idx])->read_char_pntr();
            QueryKeyPtr  qkey       = new ItemQueryKey(gb_main, field_name);

            QueryExpr *qe = new QueryExpr(aqo, qkey, not_equal, expression);

            if (!field_name[0] || strcmp(field_name, NO_FIELD_SELECTED) == 0) {
                qe->setError("No field selected");
            }

            if (!first_qe) first_qe = qe;
            else           first_qe->append(qe);
        }
    }

    return first_qe;
}

static void perform_query_cb(AW_window*, DbQuery *query, EXT_QUERY_TYPES ext_query) {
    ItemSelector& selector = query->selector;

    GB_push_transaction(query->gb_main);

    AW_root *aw_root = query->aws->get_root();
    SmartPtr<QueryExpr> qexpr = query->buildQueryExpr();

    QUERY_MODES mode  = (QUERY_MODES)aw_root->awar(query->awar_ere)->read_int();
    QUERY_RANGE range = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
    QUERY_TYPES type  = (QUERY_TYPES)aw_root->awar(query->awar_by)->read_int();

    if (query->gb_ref && type != QUERY_MARKED) {  // special for merge tool!
        char *first_query = aw_root->awar(query->awar_queries[0])->read_string();
        if (strlen(first_query) == 0) {
            if (!ext_query) ext_query  = EXT_QUERY_COMPARE_LINES;
        }
        free(first_query);
    }

    GB_ERROR error = qexpr->getError();

    if (!error) {
        size_t item_count = query_count_items(query, range, mode);
        if (item_count) {
            arb_progress progress("Searching", item_count);

            if (query->gb_ref && // merge tool only
                (ext_query == EXT_QUERY_COMPARE_LINES || ext_query == EXT_QUERY_COMPARE_WORDS))
            {
                if (qexpr->get_key_type() != QKEY_EXPLICIT) {
                    error = "Please select an explicit field or specify a search string (for first expression)";
                }
                else {
                    GB_push_transaction(query->gb_ref);

                    char    *first_keyname = aw_root->awar(query->awar_keys[0])->read_string();
                    GB_HASH *ref_hash      = create_ref_hash(query, first_keyname, ext_query == EXT_QUERY_COMPARE_WORDS);

                    if (GBS_hash_elements(ref_hash) == 0) {
                        error = GBS_global_string("No data found in field '%s'", first_keyname);
                    }

#if defined(DEBUG)
                    printf("query: search identical %s in field %s%s\n",
                           (ext_query == EXT_QUERY_COMPARE_WORDS ? "words" : "values"),
                           first_keyname,
                           query->expect_hit_in_ref_list ? " of species listed in other hitlist" : "");
#endif // DEBUG

                    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
                         gb_item_container && !error;
                         gb_item_container = selector.get_next_item_container(gb_item_container, range))
                    {
                        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                             gb_item && !error;
                             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                        {
                            switch (mode) {
                                case QUERY_GENERATE: CLEAR_QUERIED(gb_item, query); break;
                                case QUERY_ENLARGE:  if (IS_QUERIED(gb_item, query)) continue; break;
                                case QUERY_REDUCE:   if (!IS_QUERIED(gb_item, query)) continue; break;
                            }

                            // Note: here only the first query is used (special case in merge tool only)

                            ItemQueryKey&  query_key = DOWNCAST_REFERENCE(ItemQueryKey, qexpr->get_key());
                            GBDATA        *gb_key    = query_key.get_first_field(gb_item);
                            if (gb_key) {
                                char *data = GB_read_as_string(gb_key);

                                if (data && data[0]) {
                                    string hit_reason;
                                    bool   this_hit = false;

                                    if (ext_query == EXT_QUERY_COMPARE_WORDS) {
                                        for (char *t = strtok(data, " "); t; t = strtok(NULp, " ")) {
                                            GBDATA *gb_ref_pntr = (GBDATA *)GBS_read_hash(ref_hash, t);
                                            if (gb_ref_pntr) { // found item in other DB, with 'first_keyname' containing word from 'gb_key'
                                                this_hit   = true;
                                                hit_reason = GBS_global_string("%s%s has word '%s' in %s",
                                                                               query->expect_hit_in_ref_list ? "Hit " : "",
                                                                               selector.generate_item_id(query->gb_ref, gb_ref_pntr),
                                                                               t, first_keyname);
                                            }
                                        }
                                    }
                                    else {
                                        GBDATA *gb_ref_pntr = (GBDATA *)GBS_read_hash(ref_hash, data);
                                        if (gb_ref_pntr) { // found item in other DB, with identical 'first_keyname'
                                            this_hit   = true;
                                            hit_reason = GBS_global_string("%s%s matches %s",
                                                                           query->expect_hit_in_ref_list ? "Hit " : "",
                                                                           selector.generate_item_id(query->gb_ref, gb_ref_pntr),
                                                                           first_keyname);
                                        }
                                    }

                                    if (type == QUERY_DONT_MATCH) {
                                        this_hit = !this_hit;
                                        if (this_hit) hit_reason = "<no matching entry>";
                                    }

                                    if (this_hit) {
                                        dbq_assert(!hit_reason.empty());
                                        SET_QUERIED(gb_item, query, hit_reason.c_str(), hit_reason.length());
                                    }
                                    else CLEAR_QUERIED(gb_item, query);
                                }
                                free(data);
                            }

                            progress.inc_and_check_user_abort(error);
                        }
                    }

                    GBS_free_hash(ref_hash);
                    GB_pop_transaction(query->gb_ref);
                }
            }
            else { // "normal" query
                if (type == QUERY_DONT_MATCH) {
#if defined(DEBUG)
                    fputs("query: !(", stdout); qexpr->dump();
#endif // DEBUG

                    qexpr->negate();
                    type = QUERY_MATCH;

#if defined(DEBUG)
                    fputs(") => query: ", stdout); qexpr->dump(); fputc('\n', stdout);
#endif // DEBUG
                }
#if defined(DEBUG)
                else { fputs("query: ", stdout); qexpr->dump(); fputc('\n', stdout); }
#endif // DEBUG

                TargetItem target_item(*query);

                for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
                     gb_item_container && !error;
                     gb_item_container = selector.get_next_item_container(gb_item_container, range))
                {
                    for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                         gb_item && !error;
                         gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                    {
                        switch (mode) {
                            case QUERY_GENERATE: CLEAR_QUERIED(gb_item, query); break;
                            case QUERY_ENLARGE:  if (IS_QUERIED(gb_item, query)) continue; break;
                            case QUERY_REDUCE:   if (!IS_QUERIED(gb_item, query)) continue; break;
                        }

                        bool   hit = false;
                        string hit_reason;

                        switch (type) {
                            case QUERY_MARKED:
                                hit        = GB_read_flag(gb_item);
                                hit_reason = "<marked>";
                                break;

                            case QUERY_MATCH:
                                dbq_assert(ext_query == EXT_QUERY_NONE);
                                target_item.aimTo(gb_item);
                                hit = qexpr->matches(target_item, hit_reason);
                                break;

                            default: dbq_assert(0); break;
                        }

                        if (hit) {
                            dbq_assert(!hit_reason.empty());

                            if (mode == QUERY_REDUCE) hit_reason = keptHitReason(hit_reason, gb_item, query);
                            SET_QUERIED(gb_item, query, hit_reason.c_str(), hit_reason.length());
                        }
                        else CLEAR_QUERIED(gb_item, query);

                        if (error) {
                            error = GB_failedTo_error("query", GBT_get_name_or_description(gb_item), error);
                        }
                        else {
                            progress.inc_and_check_user_abort(error);
                        }
                    }
                }
            }

            if (error) progress.done();
        }
    }

    if (!error) error = qexpr->getError(); // check for query error

    if (error) aw_message(error);
    else DbQuery_update_list(query);

    GB_pop_transaction(query->gb_main);
}

void QUERY::copy_selection_list_2_query_box(DbQuery *query, AW_selection_list *srclist, const char *hit_description) {
    GB_transaction ta(query->gb_main);

    dbq_assert(strstr(hit_description, "%s")); // hit_description needs '%s' (which is replaced by visible content of 'id')

    GB_ERROR         error     = NULp;
    AW_window       *aww       = query->aws;
    AW_root         *aw_root   = aww->get_root();
    GB_HASH         *list_hash = srclist->to_hash(false);
    QUERY_MODES  mode      = (QUERY_MODES)aw_root->awar(query->awar_ere)->read_int();
    QUERY_TYPES  type      = (QUERY_TYPES)aw_root->awar(query->awar_by)->read_int();

    if (type == QUERY_MARKED) {
        error = "Query mode 'that are marked' does not apply here.\nEither select 'that match the query' or 'that don't match the q.'";
    }

    if (type != QUERY_MATCH || mode != QUERY_GENERATE) { // different behavior as in the past -> advice
        AW_advice("'Move to hitlist' now depends on the values selected for\n"
                  " * 'Search/Add/Keep species' and\n"
                  " * 'that match/don't match the query'\n"
                  "in the search tool.",
                  AW_ADVICE_TOGGLE_AND_HELP,
                  "Behavior changed",
                  "next_neighbours.hlp");
    }

    long inHitlist = GBS_hash_elements(list_hash);
    long seenInDB  = 0;

    for (GBDATA *gb_species = GBT_first_species(query->gb_main);
         gb_species && !error;
         gb_species = GBT_next_species(gb_species))
    {
        switch (mode) {
            case QUERY_GENERATE: CLEAR_QUERIED(gb_species, query); break;
            case QUERY_ENLARGE:  if (IS_QUERIED(gb_species, query)) continue; break;
            case QUERY_REDUCE:   if (!IS_QUERIED(gb_species, query)) continue; break;
        }

        const char *displayed = reinterpret_cast<const char*>(GBS_read_hash(list_hash, GBT_get_name_or_description(gb_species)));

        if (displayed) seenInDB++;

        if (contradicted(displayed, type == QUERY_DONT_MATCH)) {
            string hit_reason = GBS_global_string(hit_description, displayed ? displayed : "<no near neighbour>");

            if (mode == QUERY_REDUCE) hit_reason = keptHitReason(hit_reason, gb_species, query);
            SET_QUERIED(gb_species, query, hit_reason.c_str(), hit_reason.length());
        }
        else {
            CLEAR_QUERIED(gb_species, query);
        }
    }

    if (seenInDB < inHitlist) {
        aw_message(GBS_global_string("%li of %li hits were found in database", seenInDB, inHitlist));
    }

    GBS_free_hash(list_hash);
    if (error) aw_message(error);
    DbQuery_update_list(query);
}


void QUERY::search_duplicated_field_content(AW_window *, DbQuery *query, bool tokenize) {
    AW_root  *aw_root = query->aws->get_root();
    char     *key     = aw_root->awar(query->awar_keys[0])->read_string();
    GB_ERROR  error   = NULp;

    if (strlen(key) == 0) {
        error = "Please select a key (in the first query expression)";
    }
    else if (is_pseudo_key(key)) {
        error = "You have to select an explicit key (in the first query expression)";
    }
    else {
        GB_transaction dumy(query->gb_main);
        ItemSelector&  selector = query->selector;

        GBDATA      *gb_species_data = GBT_get_species_data(query->gb_main);
        long         hashsize;
        QUERY_RANGE  range           = QUERY_ALL_ITEMS;
        QUERY_TYPES  type            = (QUERY_TYPES)aw_root->awar(query->awar_by)->read_int();

        switch (selector.type) {
            case QUERY_ITEM_SPECIES:
            case QUERY_ITEM_ORGANISM: {
                hashsize = GB_number_of_subentries(gb_species_data);
                break;
            }
            case QUERY_ITEM_EXPERIMENT:
            case QUERY_ITEM_GENE: {
                // handle species sub-items
                hashsize = 0;

                for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
                     gb_item_container;
                     gb_item_container = selector.get_next_item_container(gb_item_container, range))
                {
                    hashsize += GB_number_of_subentries(gb_item_container);
                }

                break;
            }
            case QUERY_ITEM_SAI: {
                 dbq_assert(0); // search+query not fit for SAI
                 hashsize = 0;
                 break;
             }
        }

        if (!hashsize) {
            error = "No items exist";
        }
        else if (type == QUERY_MARKED) {
            error = "'that are marked' is not applicable here";
        }

        if (!error) {
            GB_HASH *hash = GBS_create_hash(hashsize, GB_IGNORE_CASE);

            for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
                 gb_item_container;
                 gb_item_container = selector.get_next_item_container(gb_item_container, range))
            {
                for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                     gb_item;
                     gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                {
                    CLEAR_QUERIED(gb_item, query);
                    GB_write_flag(gb_item, 0);

                    GBDATA *gb_key = GB_search(gb_item, key, GB_FIND);  if (!gb_key) continue;
                    char   *data   = GB_read_as_string(gb_key);         if (!data) continue;

                    if (tokenize) {
                        char *s;
                        for (s=strtok(data, ",; \t."); s; s = strtok(NULp, ",; \t.")) {
                            GBDATA *gb_old = (GBDATA *)GBS_read_hash(hash, s);
                            if (gb_old) {
                                const char *oldInfo   = NULp;
                                char       *firstInfo = GBS_global_string_copy("1st=%s", s);

                                if (IS_QUERIED(gb_old, query)) {
                                    const char *prevInfo = getHitInfo(gb_old, query);
                                    if (!prevInfo) {
                                        oldInfo = firstInfo;
                                    }
                                    else if (!strstr(prevInfo, firstInfo)) { // not already have 1st-entry here
                                        oldInfo = GBS_global_string("%s %s", prevInfo, firstInfo);
                                    }
                                }
                                else {
                                    oldInfo = firstInfo;
                                }

                                if (oldInfo) SET_QUERIED(gb_old, query, oldInfo);
                                SET_QUERIED(gb_item, query, GBS_global_string("dup=%s", s));
                                GB_write_flag(gb_item, 1);

                                free(firstInfo);
                            }
                            else {
                                GBS_write_hash(hash, s, (long)gb_item);
                            }
                        }
                    }
                    else {
                        GBDATA *gb_old = (GBDATA *)GBS_read_hash(hash, data);
                        if (gb_old) {
                            if (!IS_QUERIED(gb_old, query)) {
                                SET_QUERIED(gb_old, query, GBS_global_string("%s (1st)", data));
                            }
                            SET_QUERIED(gb_item, query, GBS_global_string("%s (duplicate)", data));
                            GB_write_flag(gb_item, 1);
                        }
                        else {
                            GBS_write_hash(hash, data, (long)gb_item);
                        }
                    }

                    free(data);
                }

                if (type == QUERY_DONT_MATCH) {
                    for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                         gb_item;
                         gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                    {
                        if (IS_QUERIED(gb_item, query)) {
                            CLEAR_QUERIED(gb_item, query);
                            GB_write_flag(gb_item, 0); // unmark
                        }
                        else {
                            SET_QUERIED(gb_item, query, tokenize ? "<entry with unique words>" : "<unique entry>");
                        }
                    }
                }
            }

            GBS_free_hash(hash);
        }

        if (type != QUERY_MATCH) {
            AW_advice("'Find equal entries' now depends on the values selected for\n"
                      " * 'that match/don't match the query'\n"
                      "in the search tool.",
                      AW_ADVICE_TOGGLE_AND_HELP,
                      "Behavior changed",
                      "search_duplicates.hlp");
        }
    }

    free(key);

    if (error) aw_message(error);
    DbQuery_update_list(query);
}

static void modify_fields_of_queried_cb(AW_window*, DbQuery *query) {
    ItemSelector&  selector = query->selector;
    AW_root       *aw_root  = query->aws->get_root();
    GB_ERROR       error    = GB_begin_transaction(query->gb_main);

    const char *key;
    if (!error) {
        bool        acceptIdMod = aw_root->awar(query->awar_acceptIdMod)->read_int();
        FailIfField failMode    = acceptIdMod ? FIF_ALLOW_ID_CHANGE : FIF_STANDARD;
        key                     = prepare_and_get_selected_itemfield(aw_root, query->awar_parskey, query->gb_main, selector, failMode);
        if (!key) error         = GB_await_error();
    }

    if (!error) {
        GB_TYPES  key_type        = GBT_get_type_of_changekey(query->gb_main, key, selector.change_key_path);
        bool      safe_conversion = (key_type != GB_STRING) && !aw_root->awar(query->awar_acceptConvError)->read_int();
        char     *command         = aw_root->awar(query->awar_parsvalue)->read_string();

        if (!strlen(command)) error = "Please enter a command";

        if (!error) {
            long  ncount = aw_root->awar(query->awar_count)->read_int();
            char *deftag = aw_root->awar(query->awar_deftag)->read_string();
            char *tag    = aw_root->awar(query->awar_tag)->read_string();

            {
                long use_tag = aw_root->awar(query->awar_use_tag)->read_int();
                if (!use_tag || !strlen(tag)) {
                    freenull(tag);
                }
            }
            int double_pars = aw_root->awar(query->awar_double_pars)->read_int();

            arb_progress progress("Parse fields", ncount);
            QUERY_RANGE  range = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
            GBL_env      env(query->gb_main, query->get_tree_name());

            for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
                 !error && gb_item_container;
                 gb_item_container = selector.get_next_item_container(gb_item_container, range))
            {
                for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                     !error && gb_item;
                     gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                {
                    if (IS_QUERIED(gb_item, query)) {
                        GBDATA *gb_new = GB_search(gb_item, key, GB_FIND);
                        error          = GB_incur_error_if(!gb_new);

                        if (!error) {
                            GBL_call_env callEnv(gb_item, env);

                            char *str    = gb_new ? GB_read_as_string(gb_new) : strdup("");
                            char *parsed = NULp;

                            if (double_pars) {
                                char *com2 = GB_command_interpreter_in_env(str, command, callEnv);
                                if (com2) {
                                    if (tag) parsed = GBS_modify_tagged_string_with_ACI("", deftag, tag, com2, callEnv);
                                    else parsed     = GB_command_interpreter_in_env    ("", com2, callEnv);

                                    free(com2);
                                }
                            }
                            else {
                                if (tag) parsed = GBS_modify_tagged_string_with_ACI(str, deftag, tag, command, callEnv);
                                else parsed     = GB_command_interpreter_in_env    (str, command, callEnv);
                            }

                            if (!parsed) error = GB_await_error();
                            else {
                                if (strcmp(parsed, str) != 0) { // any change?
                                    if (gb_new && parsed[0] == 0) { // empty result -> delete field
                                        error = GB_delete(gb_new);
                                    }
                                    else {
                                        if (!gb_new) {
                                            gb_new = GB_search(gb_item, key, key_type);
                                            if (!gb_new) error = GB_await_error();
                                        }
                                        if (!error) {
                                            error = GB_write_autoconv_string(gb_new, parsed); // <- field is actually written HERE
                                            if (!error && safe_conversion) {
                                                dbq_assert(key_type != GB_STRING);
                                                char *resulting = GB_read_as_string(gb_new);
                                                if (!resulting) {
                                                    error = GB_await_error();
                                                }
                                                else {
                                                    if (strcmp(resulting, parsed) != 0) {
                                                        error = GBS_global_string("Conversion error: writing '%s'\n"
                                                                                  "resulted in '%s'\n"
                                                                                  "(mark checkbox to accept conversion errors)",
                                                                                  parsed, resulting);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                                free(parsed);
                            }
                            free(str);
                        }

                        if (error) {
                            char *errCopy = strdup(error);
                            char *itemId  = selector.generate_item_id(query->gb_main, gb_item);

                            error = GBS_global_string("While modifying field of %s '%s':\n%s",
                                                      selector.item_name, itemId, errCopy);

                            free(itemId);
                            free(errCopy);
                        }

                        progress.inc_and_check_user_abort(error);
                    }
                }
            }

            delete tag;
            free(deftag);

            if (error) progress.done();
        }

        free(command);
    }

    error = GB_end_transaction(query->gb_main, error);
    aw_message_if(error);
}

static void predef_prg(AW_root *aw_root, DbQuery *query) {
    char *str = aw_root->awar(query->awar_parspredefined)->read_string();
    char *brk = strchr(str, '#');
    if (brk) {
        *(brk++) = 0;
        char *kv = str;
        if (!strcmp(str, "ali_*/data")) { // when the target field contains this string -> replace it with current alignment
            GB_transaction  ta(query->gb_main);
            char           *use = GBT_get_default_alignment(query->gb_main);
            if (!use) {
                aw_message(GB_await_error());
            }
            else {
                kv = GBS_global_string_copy("%s/data", use);
                free(use);
            }
        }
        aw_root->awar(query->awar_parskey)->write_string(kv);
        if (kv != str) free(kv);
        aw_root->awar(query->awar_parsvalue)->write_string(brk);
    }
    else {
        aw_root->awar(query->awar_parsvalue)->write_string(str);
    }
    free(str);
}

static void colorize_queried_cb(AW_window *, DbQuery *query) {
    ItemSelector&   selector    = query->selector;
    GB_transaction  ta(query->gb_main);
    GB_ERROR        error       = NULp;
    AW_root        *aw_root     = query->aws->get_root();
    int             color_group = aw_root->awar(AWAR_COLORIZE)->read_int();
    QUERY_RANGE     range       = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();
    bool            changed     = false;

    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
         !error && gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, range))
    {
        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             !error && gb_item;
             gb_item       = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            if (IS_QUERIED(gb_item, query)) {
                error               = GBT_set_color_group(gb_item, color_group);
                if (!error) changed = true;
            }
        }
    }

    if (error) GB_export_error(error);
    else if (changed) selector.trigger_display_refresh();
}

static void colorize_marked_cb(AW_window *aww, BoundItemSel *cmd) {
    ItemSelector&   sel         = cmd->selector;
    GB_transaction  ta(cmd->gb_main);
    GB_ERROR        error       = NULp;
    AW_root        *aw_root     = aww->get_root();
    int             color_group = aw_root->awar(AWAR_COLORIZE)->read_int();
    QUERY_RANGE     range       = QUERY_ALL_ITEMS;         // @@@ FIXME: make customizable

    for (GBDATA *gb_item_container = sel.get_first_item_container(cmd->gb_main, aw_root, range);
         !error && gb_item_container;
         gb_item_container = sel.get_next_item_container(gb_item_container, range))
    {
        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             !error && gb_item;
             gb_item       = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            if (GB_read_flag(gb_item)) {
                error = GBT_set_color_group(gb_item, color_group);
            }
        }
    }

    if (error) GB_export_error(error);
}

enum mark_mode {
    UNMARK,
    MARK,
    INVERT,
};

static void mark_colored_cb(AW_window *aww, BoundItemSel *cmd, mark_mode mode) {
    // @@@ mark_colored_cb is obsolete! (will be replaced by dynamic coloring in the future)
    ItemSelector&  sel         = cmd->selector;
    AW_root       *aw_root     = aww->get_root();
    int            color_group = aw_root->awar(AWAR_COLORIZE)->read_int();
    QUERY_RANGE    range       = QUERY_ALL_ITEMS;          // @@@ FIXME: make customizable

    GB_transaction ta(cmd->gb_main);

    for (GBDATA *gb_item_container = sel.get_first_item_container(cmd->gb_main, aw_root, range);
         gb_item_container;
         gb_item_container = sel.get_next_item_container(gb_item_container, range))
    {
        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             gb_item;
             gb_item = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            int my_color = GBT_get_color_group(gb_item);
            if (my_color == color_group) {
                bool marked = GB_read_flag(gb_item);

                switch (mode) {
                    case UNMARK: marked = 0;       break;
                    case MARK:   marked = 1;       break;
                    case INVERT: marked = !marked; break;

                    default: dbq_assert(0); break;
                }

                GB_write_flag(gb_item, marked);
            }
        }
    }

}

// --------------------------------------------------------------------------------
// color sets

class colorset_data {
    BoundItemSel      *bsel;
    AW_selection_list *colorsets;

public:
    colorset_data(BoundItemSel *bsel_) : bsel(bsel_), colorsets(NULp) { }

    BoundItemSel *get_selector() { return bsel; }

    const char *get_items_name() const { return bsel->selector.items_name; }
    const char *get_items_key() const { return ITEMS_get_key(bsel->selector); }
    const char *get_items_type_key() const { return ITEMS_get_type_key(bsel->selector.type); }

    GBDATA *get_db_root_container() const { return GBT_colorset_root(bsel->gb_main, get_items_type_key()); }

    void update_selection_list() const {
        dbq_assert(colorsets);

        ConstStrArray foundSets;
        {
            GB_transaction ta(bsel->gb_main);
            GBT_get_colorset_names(foundSets, get_db_root_container());
        }

        colorsets->clear();
        colorsets->insert_default("<new colorset>", "");
        for (size_t i = 0; i<foundSets.size(); ++i) {
            colorsets->insert(foundSets[i], foundSets[i]);
        }
        colorsets->update();
    }

    void create_selection_list(AW_window *aws) {
        dbq_assert(!colorsets);
        colorsets = awt_create_selection_list_with_input_field(aws, AWAR_COLOR_LOADSAVE_NAME, "list", "name");
        update_selection_list();
    }
};

static void colorset_changed_cb(GBDATA*, const colorset_data *csd, GB_CB_TYPE cbt) {
    if (cbt&GB_CB_CHANGED) {
        csd->update_selection_list();
    }
}

static void create_colorset_representation(BoundItemSel *bsel, AW_root *aw_root, StrArray& colordefs, GB_ERROR& error) {
    ItemSelector&  sel     = bsel->selector;
    GBDATA        *gb_main = bsel->gb_main;

    dbq_assert(!error);

    for (GBDATA *gb_item_container = sel.get_first_item_container(gb_main, aw_root, QUERY_ALL_ITEMS);
         gb_item_container;
         gb_item_container = sel.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
    {
        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             gb_item;
             gb_item = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            int color = GBT_get_color_group(gb_item);
            if (color>0) {
                char *id        = sel.generate_item_id(gb_main, gb_item);
                char *color_def = GBS_global_string_copy("%s=%i", id, color);

                colordefs.put(color_def);
                free(id);
            }
        }
    }

    if (colordefs.empty()) {
        error = GBS_global_string("Could not find any colored %s", sel.items_name);
    }
}

static GB_ERROR clear_all_colors(BoundItemSel *bsel, AW_root *aw_root) {
    ItemSelector& sel     = bsel->selector;
    GB_ERROR      error   = NULp;
    bool          changed = false;

    for (GBDATA *gb_item_container = sel.get_first_item_container(bsel->gb_main, aw_root, QUERY_ALL_ITEMS);
         !error && gb_item_container;
         gb_item_container = sel.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
    {
        for (GBDATA *gb_item = sel.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             !error && gb_item;
             gb_item = sel.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            error               = GBT_set_color_group(gb_item, 0); // clear colors
            if (!error) changed = true;
        }
    }

    if (changed && !error) sel.trigger_display_refresh();
    return error;
}

static void clear_all_colors_cb(AW_window *aww, BoundItemSel *bsel) {
    GB_transaction ta(bsel->gb_main);
    GB_ERROR       error = clear_all_colors(bsel, aww->get_root());

    if (error) {
        error = ta.close(error);
        aw_message(error);
    }
}

static GB_ERROR restore_colorset_representation(BoundItemSel *bsel, CharPtrArray& colordefs) {
    ItemSelector&  sel     = bsel->selector;
    GBDATA        *gb_main = bsel->gb_main;
    bool           changed = false;
    GB_ERROR       error   = NULp;
    int            ignores = 0;

    for (size_t d = 0; d<colordefs.size() && !error; ++d) {
        const char *def   = colordefs[d];
        const char *equal = strchr(def, '=');

        if (equal) {
            LocallyModify<char> tempSplit(const_cast<char*>(equal)[0], 0);

            const char *id      = def;
            GBDATA     *gb_item = sel.find_item_by_id(gb_main, id);
            if (!gb_item) {
                aw_message(GBS_global_string("No such %s: '%s' (ignored)", sel.item_name, id)); // only warn
                ++ignores;
            }
            else {
                int color_group = atoi(equal+1);
                if (color_group>0) { // bugfix: saved color groups contained zero (which means "no color") by mistake; ignore
                    error = GBT_set_color_group(gb_item, color_group);
                    if (!error) changed = true;
                }
            }
        }
        else {
            aw_message(GBS_global_string("Invalid colordef '%s' (ignored)", def));
            ++ignores;
        }
    }
    if (changed && !error) sel.trigger_display_refresh();
    if (ignores>0 && !error) {
        aw_message(GBS_global_string("Warning: failed to restore color assignment for %i %s", ignores, sel.items_name));
    }

    return error;
}

enum loadsave_mode {
    SAVE,
    LOAD,
    OVERLAY,
    DELETE,
};

static void loadsave_colorset_cb(AW_window *aws, colorset_data *csd, loadsave_mode mode) {
    AW_root  *aw_root = aws->get_root();
    char     *name    = aw_root->awar(AWAR_COLOR_LOADSAVE_NAME)->read_string();
    GB_ERROR  error   = NULp;

    if (name[0] == 0) error = "Please enter a name for the colorset.";
    else {
        BoundItemSel   *bsel = csd->get_selector();
        GB_transaction  ta(bsel->gb_main);

        GBDATA *gb_colorset_root = csd->get_db_root_container();
        GBDATA *gb_colorset      = gb_colorset_root ? GBT_find_colorset(gb_colorset_root, name) : NULp;

        error = GB_incur_error();
        if (!error) {
            if (mode == SAVE) {
                if (!gb_colorset) { // create new colorset
                    gb_colorset             = GBT_find_or_create_colorset(gb_colorset_root, name);
                    if (!gb_colorset) error = GB_await_error();
                }
                dbq_assert(gb_colorset || error);

                if (!error) {
                    StrArray colordefs;
                    create_colorset_representation(bsel, aw_root, colordefs, error);
                    if (!error) error = GBT_save_colorset(gb_colorset, colordefs);
                }
            }
            else {
                if (!gb_colorset) error = GBS_global_string("Colorset '%s' not found", name);
                else {
                    if (mode == LOAD || mode == OVERLAY) {
                        ConstStrArray colordefs;
                        error = GBT_load_colorset(gb_colorset, colordefs);

                        if (!error && colordefs.empty()) error = "oops.. empty colorset";
                        if (!error && mode == LOAD)      error = clear_all_colors(bsel, aw_root);
                        if (!error)                      error = restore_colorset_representation(bsel, colordefs);
                    }
                    else {
                        dbq_assert(mode == DELETE);
                        error = GB_delete(gb_colorset);
                    }
                }
            }
        }
        error = ta.close(error);
    }
    free(name);

    if (error) aw_message(error);
}

static AW_window *create_loadsave_colored_window(AW_root *aw_root, colorset_data *csd) {
    AW_window_simple *aws = new AW_window_simple;
    {
        char *window_id = GBS_global_string_copy("colorset_loadsave_%s", csd->get_items_key());
        aws->init(aw_root, window_id, GBS_global_string("Load/Save %s colorset", csd->get_items_name()));
        free(window_id);
    }

    aws->load_xfig("query/color_loadsave.fig");

    aws->at("close");
    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->at("help");
    aws->callback(makeHelpCallback("color_loadsave.hlp"));
    aws->create_button("HELP", "HELP", "H");

    csd->create_selection_list(aws);

    aws->at("save");    aws->callback(makeWindowCallback(loadsave_colorset_cb, csd, SAVE));    aws->create_button("save",    "Save",    "S");
    aws->at("load");    aws->callback(makeWindowCallback(loadsave_colorset_cb, csd, LOAD));    aws->create_button("load",    "Load",    "L");
    aws->at("overlay"); aws->callback(makeWindowCallback(loadsave_colorset_cb, csd, OVERLAY)); aws->create_button("overlay", "Overlay", "O");
    aws->at("delete");  aws->callback(makeWindowCallback(loadsave_colorset_cb, csd, DELETE));  aws->create_button("delete",  "Delete",  "D");

    aws->at("reset");
    aws->callback(makeWindowCallback(clear_all_colors_cb, csd->get_selector()));
    aws->create_button("reset", "Reset", "R");

    // callbacks
    {
        GB_transaction  ta(csd->get_selector()->gb_main);
        GBDATA         *gb_colorset = csd->get_db_root_container();
        GB_add_callback(gb_colorset, GB_CB_CHANGED, makeDatabaseCallback(colorset_changed_cb, csd));
    }

    return aws;
}

static AW_window *create_colorize_window(AW_root *aw_root, GBDATA *gb_main, DbQuery *query, ItemSelector *sel) {
    // invoked by   'colorize listed'                   (sel   != 0)
    // and          'colorize marked/mark colored'      (query != 0)

    enum { COLORIZE_INVALID, COLORIZE_LISTED, COLORIZE_MARKED } mode = COLORIZE_INVALID;

    create_query_independent_awars(aw_root, AW_ROOT_DEFAULT);

    AW_window_simple *aws = new AW_window_simple;

    dbq_assert(contradicted(query, sel));

    if (query) {
        dbq_assert(mode == COLORIZE_INVALID);
        mode = COLORIZE_LISTED;
    }
    if (sel) {
        dbq_assert(mode == COLORIZE_INVALID);
        mode = COLORIZE_MARKED;
    }
    dbq_assert(!(mode == COLORIZE_INVALID));

    ItemSelector& Sel  = mode == COLORIZE_LISTED ? query->selector : *sel;
    const char   *what = mode == COLORIZE_LISTED ? "listed" : "marked";

    {
        char *id           = GBS_global_string_copy("COLORIZE_%s", what);
        char *title_format = GBS_global_string_copy("Colorize %s %%s", what);

        init_item_specific_window(aw_root, aws, Sel, id, true, title_format, true);

        free(title_format);
        free(id);
    }

    aws->load_xfig("query/colorize.fig");

    aws->auto_space(10, 10);

    aws->at("close");
    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->at("help");
    aws->callback(makeHelpCallback(mode == COLORIZE_LISTED ? "set_color_of_listed.hlp" : "colorize.hlp"));
    aws->create_button("HELP", "HELP", "H");

    aws->at("colorize");

    BoundItemSel *bsel = new BoundItemSel(gb_main, ((mode == COLORIZE_MARKED) ? *sel : query->selector)); // do not free, bound to CB

    if (mode == COLORIZE_LISTED) aws->callback(makeWindowCallback(colorize_queried_cb, query));
    else                         aws->callback(makeWindowCallback(colorize_marked_cb, bsel));

    aws->create_autosize_button("COLORIZE", GBS_global_string_copy("Set color of %s %s to ...", what, Sel.items_name), "S", 2);

    {
        int color_group;

        aws->create_option_menu(AWAR_COLORIZE);
        aws->insert_default_option("No color group", "none", 0);
        for (color_group = 1; color_group <= AW_COLOR_GROUPS; ++color_group) {
            char *name = AW_get_color_group_name(aw_root, color_group);
            aws->insert_option(name, "", color_group);
            free(name);
        }
        aws->update_option_menu();
    }

    colorset_data *csd = new colorset_data(bsel); // do not free, bound to CB

    aws->at("loadsave");
    aws->callback(makeCreateWindowCallback(create_loadsave_colored_window, csd));
    aws->create_autosize_button("LOADSAVE_COLORED", "Load/Save", "L");

    if (mode == COLORIZE_MARKED) {
        aws->at("mark");
        aws->callback(makeWindowCallback(mark_colored_cb, bsel, MARK));
        aws->create_autosize_button("MARK_COLORED", GBS_global_string_copy("Mark all %s of ...", Sel.items_name), "M", 2);

        aws->at("unmark");
        aws->callback(makeWindowCallback(mark_colored_cb, bsel, UNMARK));
        aws->create_autosize_button("UNMARK_COLORED", GBS_global_string_copy("Unmark all %s of ...", Sel.items_name), "U", 2);

        aws->at("invert");
        aws->callback(makeWindowCallback(mark_colored_cb, bsel, INVERT));
        aws->create_autosize_button("INVERT_COLORED", GBS_global_string_copy("Invert all %s of ...", Sel.items_name), "I", 2);
    }

    aws->at_newline();
    aws->window_fit();

    return aws;
}

static AW_window *create_colorize_queried_window(AW_root *aw_root, DbQuery *query) {
    return create_colorize_window(aw_root, query->gb_main, query, NULp);
}

AW_window *QUERY::create_colorize_items_window(AW_root *aw_root, GBDATA *gb_main, ItemSelector& sel) {
    return create_colorize_window(aw_root, gb_main, NULp, &sel);
}

static void setup_modify_fields_config(AWT_config_definition& cdef, const DbQuery *query) {
    const char *typeawar = get_itemfield_type_awarname(query->awar_parskey);
    dbq_assert(typeawar);

    cdef.add(query->awar_parskey,         "key");
    cdef.add(typeawar,                    "type");
    cdef.add(query->awar_use_tag,         "usetag");
    cdef.add(query->awar_deftag,          "deftag");
    cdef.add(query->awar_tag,             "modtag");
    cdef.add(query->awar_double_pars,     "doubleparse");
    cdef.add(query->awar_acceptConvError, "acceptconv");
    cdef.add(query->awar_acceptIdMod,     "acceptidmod");
    cdef.add(query->awar_parsvalue,       "aci");
}

static AW_window *create_modify_fields_window(AW_root *aw_root, DbQuery *query) {
    AW_window_simple *aws = new AW_window_simple;

    init_item_specific_window(aw_root, aws, query->selector, "MODIFY_DATABASE_FIELD", true, "MODIFY DATABASE FIELD of listed %s", true);

    aws->load_xfig("query/modify_fields.fig");

    aws->at("close");
    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->at("help");
    aws->callback(makeHelpCallback("mod_field_list.hlp"));
    aws->create_button("HELP", "HELP", "H");

    aws->at("helptags");
    aws->callback(makeHelpCallback("tags.hlp"));
    aws->create_button("HELP_TAGS", "Help tags", "H");

    create_itemfield_selection_button(aws, FieldSelDef(query->awar_parskey, query->gb_main, query->selector, FIELD_FILTER_STRING_READABLE, "target field", SF_ALLOW_NEW), "field");
    aws->at("acceptErr");   aws->create_toggle(query->awar_acceptConvError);

    // ------------------------
    //      expert section
    aws->at("usetag");   aws->create_toggle     (query->awar_use_tag);
    aws->at("deftag");   aws->create_input_field(query->awar_deftag);
    aws->at("tag");      aws->create_input_field(query->awar_tag);
    aws->at("double");   aws->create_toggle     (query->awar_double_pars);
    aws->at("acceptID"); aws->create_toggle     (query->awar_acceptIdMod);
    // ------------------------

    aws->at("parser");
    aws->create_text_field(query->awar_parsvalue);

    aws->at("go");
    aws->callback(makeWindowCallback(modify_fields_of_queried_cb, query));
    aws->create_button("GO", "GO", "G");

    aws->at("pre");
    AW_selection_list *programs = aws->create_selection_list(query->awar_parspredefined);

    const char *sellst = NULp;
    switch (query->selector.type) {
        case QUERY_ITEM_SPECIES:
        case QUERY_ITEM_ORGANISM:    sellst = "mod_fields*.sellst"; break;
        case QUERY_ITEM_GENE:        sellst = "mod_gene_fields*.sellst"; break;
        case QUERY_ITEM_EXPERIMENT:  sellst = "mod_experiment_fields*.sellst"; break;
        case QUERY_ITEM_SAI:  dbq_assert(0); break; // search+query not fit for SAI
    }

    GB_ERROR error;
    if (sellst) {
        StorableSelectionList storable_sellist(TypedSelectionList("sellst", programs, "field modification scripts", "mod_fields"));
        error = storable_sellist.load(GB_concat_path_in_ARBLIB("sellists", sellst), false);
    }
    else {
        error = "No default selection list for query-type";
    }

    if (error) {
        aw_message(error);
    }
    else {
        aws->get_root()->awar(query->awar_parspredefined)->add_callback(makeRootCallback(predef_prg, query));
    }

    aws->at("config");
    const char *item_name = ITEMS_get_possible_former_itemname(query->selector);
    AWT_define_config_manager_conversion(GBS_global_string("mod_%s_fields", item_name), "mod_fields", item_name);
    AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "mod_fields", makeConfigSetupCallback(setup_modify_fields_config, query));

    return aws;
}

static void set_field_of_queried_cb(AW_window*, DbQuery *query, bool append) {
    // set fields of listed items
    ItemSelector&  selector   = query->selector;
    GB_ERROR       error      = NULp;
    AW_root       *aw_root    = query->aws->get_root();
    bool           allow_loss = aw_root->awar(query->awar_writelossy)->read_int();

    char *value = aw_root->awar(query->awar_setvalue)->read_string();
    if (value[0] == 0) freenull(value);

    size_t value_len = value ? strlen(value) : 0;

    GB_begin_transaction(query->gb_main);

    const char *key = prepare_and_get_selected_itemfield(aw_root, query->awar_writekey, query->gb_main, selector);
    if (!key) error = GB_await_error();

    for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, QUERY_ALL_ITEMS);
         !error && gb_item_container;
         gb_item_container = selector.get_next_item_container(gb_item_container, QUERY_ALL_ITEMS))
    {
        for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
             !error && gb_item;
             gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
        {
            if (IS_QUERIED(gb_item, query)) {
                if (!value) { // empty value -> delete field
                    if (!append) {
                        GBDATA *gb_field    = GB_search(gb_item, key, GB_FIND);
                        if (gb_field) error = GB_delete(gb_field);
                    }
                    // otherwise "nothing" is appended (=noop)
                }
                else {
                    GBDATA *gb_field     = GBT_searchOrCreate_itemfield_according_to_changekey(gb_item, key, selector.change_key_path);
                    if (!gb_field) error = GB_await_error();
                    else {
                        const char   *new_content = value;
                        SmartCharPtr  content;

                        if (append) {
                            char *old       = GB_read_as_string(gb_field);
                            if (!old) error = "field has incompatible type";
                            else {
                                size_t        old_len = strlen(old);
                                GBS_strstruct buf(old_len+value_len+2);

                                buf.ncat(old, old_len);
                                buf.ncat(value, value_len);

                                content     = buf.release();
                                new_content = &*content;
                            }
                        }

                        error = GB_write_autoconv_string(gb_field, new_content);

                        if (!allow_loss && !error) {
                            char *result = GB_read_as_string(gb_field);
                            dbq_assert(result);

                            if (strcmp(new_content, result) != 0) {
                                error = GBS_global_string("value modified by type conversion\n('%s' -> '%s')", new_content, result);
                            }
                            free(result);
                        }
                    }
                }

                if (error) { // add name of current item to error
                    const char *name = GBT_read_char_pntr(gb_item, "name");
                    error            = GBS_global_string("Error while writing to %s '%s':\n%s", selector.item_name, name, error);
                }
            }
        }
    }

    GB_end_transaction_show_error(query->gb_main, error, aw_message);

    free(value);
}

static AW_window *create_writeFieldOfListed_window(AW_root *aw_root, DbQuery *query) {
    AW_window_simple *aws = new AW_window_simple;
    init_item_specific_window(aw_root, aws, query->selector, "WRITE_DB_FIELD_OF_LISTED", false, "Write field of listed %s", true);
    aws->load_xfig("query/write_fields.fig");

    aws->at("close");
    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->at("help");
    aws->callback(makeHelpCallback("write_field_list.hlp"));
    aws->create_button("HELP", "HELP", "H");

    create_itemfield_selection_button(aws, FieldSelDef(query->awar_writekey, query->gb_main, query->selector, FIELD_FILTER_STRING_READABLE, "target field", SF_ALLOW_NEW), "field");

    aws->at("val");
    aws->create_text_field(query->awar_setvalue, 2, 2);

    aws->at("lossy");
    aws->label("Allow lossy conversion?");
    aws->create_toggle(query->awar_writelossy);

    aws->at("create");
    aws->callback(makeWindowCallback(set_field_of_queried_cb, query, false));
    aws->create_button("SET_SINGLE_FIELD_OF_LISTED", "WRITE");

    aws->at("do");
    aws->callback(makeWindowCallback(set_field_of_queried_cb, query, true));
    aws->create_button("APPEND_SINGLE_FIELD_OF_LISTED", "APPEND");

    return aws;
}

static void set_protection_of_queried_cb(AW_window*, DbQuery *query) {
    // set protection of listed items
    ItemSelector&  selector = query->selector;
    AW_root       *aw_root  = query->aws->get_root();
    char          *key      = aw_root->awar(query->awar_protectkey)->read_string();

    if (strcmp(key, NO_FIELD_SELECTED) == 0) {
        aw_message("Please select a field for which you want to set the protection");
    }
    else {
        GB_begin_transaction(query->gb_main);

        GB_ERROR  error       = NULp;
        GBDATA   *gb_key_data = GB_search(query->gb_main, selector.change_key_path, GB_CREATE_CONTAINER);
        GBDATA   *gb_key_name = GB_find_string(gb_key_data, CHANGEKEY_NAME, key, GB_IGNORE_CASE, SEARCH_GRANDCHILD);

        if (!gb_key_name) {
            error = GBS_global_string("The destination field '%s' does not exists", key);
        }
        else {
            int         level = aw_root->awar(query->awar_setprotection)->read_int();
            QUERY_RANGE range = (QUERY_RANGE)aw_root->awar(query->awar_where)->read_int();

            for (GBDATA *gb_item_container = selector.get_first_item_container(query->gb_main, aw_root, range);
                 !error && gb_item_container;
                 gb_item_container = selector.get_next_item_container(gb_item_container, range))
            {
                for (GBDATA *gb_item = selector.get_first_item(gb_item_container, QUERY_ALL_ITEMS);
                     !error && gb_item;
                     gb_item = selector.get_next_item(gb_item, QUERY_ALL_ITEMS))
                {
                    if (IS_QUERIED(gb_item, query)) {
                        GBDATA *gb_new = GB_search(gb_item, key, GB_FIND);
                        if (gb_new) {
                            error             = GB_write_security_delete(gb_new, level);
                            if (!error) error = GB_write_security_write(gb_new, level);
                        }
                    }
                }
            }
        }
        GB_end_transaction_show_error(query->gb_main, error, aw_message);
    }

    free(key);
}

static AW_window *create_set_protection_window(AW_root *aw_root, DbQuery *query) {
    AW_window_simple *aws = new AW_window_simple;
    init_item_specific_window(aw_root, aws, query->selector, "SET_PROTECTION_OF_FIELD_OF_LISTED", false, "Protect field of listed %s", true);
    aws->load_xfig("query/set_protection.fig");

    aws->at("close");
    aws->callback(AW_POPDOWN);
    aws->create_button("CLOSE", "CLOSE", "C");

    aws->at("help");
    aws->callback(makeHelpCallback("set_protection.hlp"));
    aws->create_button("HELP", "HELP", "H");


    aws->at("prot");
    aws->create_toggle_field(query->awar_setprotection, AW_VERTICAL);
    aws->insert_toggle("0 temporary", "0", 0);
    aws->insert_toggle("1 checked",   "1", 1);
    aws->insert_toggle("2",           "2", 2);
    aws->insert_toggle("3",           "3", 3);
    aws->insert_toggle("4 normal",    "4", 4);
    aws->insert_toggle("5 ",          "5", 5);
    aws->insert_toggle("6 the truth", "5", 6);
    aws->update_toggle_field();

    create_itemfield_selection_list(aws, FieldSelDef(query->awar_protectkey, query->gb_main, query->selector, FIELD_UNFILTERED, "field to protect"), "list");

    aws->at("go");
    aws->callback(makeWindowCallback(set_protection_of_queried_cb, query));
    aws->create_autosize_button("SET_PROTECTION_OF_FIELD_OF_LISTED", "Assign\nprotection\nto field\nof listed");

    return aws;
}

static void toggle_flag_cb(AW_window *aww, DbQuery *query) {
    GB_transaction  ta(query->gb_main);
    GBDATA         *gb_item = query->selector.get_selected_item(query->gb_main, aww->get_root());
    if (gb_item) {
        long flag = GB_read_flag(gb_item);
        GB_write_flag(gb_item, 1-flag);
    }
    DbQuery_update_list(query);
}

static void new_selection_made_cb(AW_root *aw_root, const char *awar_selection, DbQuery *query) {
    char *item_name = aw_root->awar(awar_selection)->read_as_string();
    query->selector.update_item_awars(query->gb_main, aw_root, item_name);
    free(item_name);
}

static void query_box_setup_config(AWT_config_definition& cdef, DbQuery *query) {
    // this defines what is saved/restored to/from configs
    for (int key_id = 0; key_id<QUERY_EXPRESSIONS; ++key_id) {
        cdef.add(query->awar_keys[key_id], "key", key_id);
        cdef.add(query->awar_queries[key_id], "query", key_id);
        cdef.add(query->awar_not[key_id], "not", key_id);
        cdef.add(query->awar_operator[key_id], "operator", key_id);
    }
}

template<typename CB>
static 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) {
    char *rel_id = GBS_global_string_copy("%s_%s", query_id, id);
    aws->insert_menu_topic(rel_id, label, mnemonic, helpText, Mask, cb);
    free(rel_id);
}

const char *DbQuery::get_tree_name() const {
    if (!awar_tree_name)
        return NULp;
    else
        return aws->get_root()->awar(awar_tree_name)->read_char_pntr();
}

DbQuery::~DbQuery() {
    for (int s = 0; s<QUERY_EXPRESSIONS; ++s) {
        free(awar_keys[s]);
        free(awar_queries[s]);
        free(awar_not[s]);
        free(awar_operator[s]);
    }

    free(species_name);

    free(awar_writekey);
    free(awar_writelossy);
    free(awar_protectkey);
    free(awar_setprotection);
    free(awar_setvalue);

    free(awar_parskey);
    free(awar_parsvalue);
    free(awar_parspredefined);
    free(awar_acceptConvError);
    free(awar_acceptIdMod);

    free(awar_ere);
    free(awar_where);
    free(awar_by);
    free(awar_use_tag);
    free(awar_double_pars);
    free(awar_deftag);
    free(awar_tag);
    free(awar_count);
    free(awar_sort);

    GBS_free_hash(hit_description);
}

// ----------------------------------------
// store query data until DB close

typedef Keeper<DbQuery*> QueryKeeper;
template<> void Keeper<DbQuery*>::destroy(DbQuery *q) { delete q; }
static SmartPtr<QueryKeeper> queryKeeper;
static void destroyKeptQueries() {
    queryKeeper.setNull();
}
static void keepQuery(GBDATA *gbmain, DbQuery *q) {
    if (queryKeeper.isNull()) {
        queryKeeper = new QueryKeeper;
        GB_atclose_callback(gbmain, makeDatabaseCallback(destroyKeptQueries));
    }
    queryKeeper->keep(q);
}

// ----------------------------------------

DbQuery *QUERY::create_query_box(AW_window *aws, query_spec *awtqs, const char *query_id) {
    char     buffer[256];
    AW_root *aw_root = aws->get_root();
    GBDATA  *gb_main = awtqs->gb_main;
    DbQuery *query   = new DbQuery(awtqs->get_queried_itemtype());

    keepQuery(gb_main, query); // transfers ownership of query

    // @@@ set all the things below via ctor!
    // @@@ create a copyable object containing everything query_spec and DbQuery have in common -> pass that object to DbQuery-ctor

    query->gb_main                = awtqs->gb_main;
    query->aws                    = aws;
    query->gb_ref                 = awtqs->gb_ref;
    query->expect_hit_in_ref_list = awtqs->expect_hit_in_ref_list;
    query->select_bit             = awtqs->select_bit;
    query->species_name           = strdup(awtqs->species_name);

    query->set_tree_awar_name(awtqs->tree_name);
    query->hit_description = GBS_create_dynaval_hash(query_count_items(query, QUERY_ALL_ITEMS, QUERY_GENERATE), GB_IGNORE_CASE, free_hit_description);

    GB_push_transaction(gb_main);

    // Create query box AWARS
    create_query_independent_awars(aw_root, AW_ROOT_DEFAULT);

    {
        const char *default_key[QUERY_EXPRESSIONS+1] = { "name", "name", "name", NULp };

        for (int key_id = 0; key_id<QUERY_EXPRESSIONS; ++key_id) {
            sprintf(buffer, "tmp/dbquery_%s/key_%i", query_id, key_id);
            query->awar_keys[key_id] = strdup(buffer);
            dbq_assert(default_key[key_id]);
            aw_root->awar_string(query->awar_keys[key_id], default_key[key_id], AW_ROOT_DEFAULT);

            sprintf(buffer, "tmp/dbquery_%s/query_%i", query_id, key_id);
            query->awar_queries[key_id] = strdup(buffer);
            aw_root->awar_string(query->awar_queries[key_id], "*", AW_ROOT_DEFAULT);

            sprintf(buffer, "tmp/dbquery_%s/not_%i", query_id, key_id);
            query->awar_not[key_id] = strdup(buffer);
            aw_root->awar_int(query->awar_not[key_id], 0, AW_ROOT_DEFAULT);

            sprintf(buffer, "tmp/dbquery_%s/operator_%i", query_id, key_id);
            query->awar_operator[key_id] = strdup(buffer);
            aw_root->awar_string(query->awar_operator[key_id], "ign", AW_ROOT_DEFAULT);
        }
        aw_root->awar(query->awar_keys[0])->add_callback(makeRootCallback(first_searchkey_changed_cb, query));
    }

    sprintf(buffer, "tmp/dbquery_%s/ere", query_id);
    query->awar_ere = strdup(buffer);
    aw_root->awar_int(query->awar_ere, QUERY_GENERATE);

    sprintf(buffer, "tmp/dbquery_%s/where", query_id);
    query->awar_where = strdup(buffer);
    aw_root->awar_int(query->awar_where, QUERY_ALL_ITEMS);

    sprintf(buffer, "tmp/dbquery_%s/count", query_id);
    query->awar_count = strdup(buffer);
    aw_root->awar_int(query->awar_count, 0);

    sprintf(buffer, "tmp/dbquery_%s/sort", query_id);
    query->awar_sort = strdup(buffer);
    aw_root->awar_int(query->awar_sort, QUERY_SORT_NONE)->add_callback(makeRootCallback(result_sort_order_changed_cb, query));
    query->sort_mask = QUERY_SORT_NONE; // default to unsorted

    sprintf(buffer, "tmp/dbquery_%s/by", query_id);
    query->awar_by = strdup(buffer);
    aw_root->awar_int(query->awar_by, QUERY_MATCH);

    const char *items = query->selector.items_name;

    if (awtqs->ere_pos_fig) {
        aws->at(awtqs->ere_pos_fig);
        aws->create_toggle_field(query->awar_ere);

        aws->insert_toggle(GBS_global_string("Search %s", items), "G", (int)QUERY_GENERATE);
        aws->insert_toggle(GBS_global_string("Add %s",    items), "E", (int)QUERY_ENLARGE);
        aws->insert_toggle(GBS_global_string("Keep %s",   items), "R", (int)QUERY_REDUCE);

        aws->update_toggle_field();
    }
    if (awtqs->where_pos_fig) {
        aws->at(awtqs->where_pos_fig);
        aws->create_toggle_field(query->awar_where);
        aws->insert_toggle("of current organism", "C", (int)QUERY_CURRENT_ITEM);
        aws->insert_toggle("of marked organisms", "M", (int)QUERY_MARKED_ITEMS);
        aws->insert_toggle("of all organisms", "A", (int)QUERY_ALL_ITEMS);
        aws->update_toggle_field();

    }
    if (awtqs->by_pos_fig) {
        aws->at(awtqs->by_pos_fig);
        aws->create_toggle_field(query->awar_by);
        aws->insert_toggle("that match the query", "M", (int)QUERY_MATCH);
        aws->insert_toggle("that don't match the q.", "D", (int)QUERY_DONT_MATCH);
        aws->insert_toggle("that are marked", "A", (int)QUERY_MARKED);
        aws->update_toggle_field();
    }

    // distances for multiple queries :

#define KEY_Y_OFFSET 32

    int xpos_calc[3] = { -1, -1, -1 }; // X-positions for elements in search expressions

    if (awtqs->qbox_pos_fig) {
        aws->auto_space(1, 1);
        aws->at(awtqs->qbox_pos_fig);

        SmartPtr<AW_at_storage> at_size(AW_at_storage::make(aws, AW_AT_SIZE_AND_ATTACH));

        int xpos, ypos;
        aws->get_at_position(&xpos, &ypos);

        int ypos_dummy;

        for (int key = QUERY_EXPRESSIONS-1;  key >= 0; --key) {
            if (key) {
                aws->at(xpos, ypos+key*KEY_Y_OFFSET);
                aws->create_option_menu(query->awar_operator[key]);
                aws->insert_option("and", "", "and");
                aws->insert_option("or", "", "or");
                aws->insert_option("ign", "", "ign");
                aws->update_option_menu();

                if (xpos_calc[0] == -1) aws->get_at_position(&xpos_calc[0], &ypos_dummy);
            }

            aws->at(xpos_calc[0], ypos+key*KEY_Y_OFFSET);
            aws->restore_at_from(*at_size);

            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);

            if (xpos_calc[1] == -1) aws->get_at_position(&xpos_calc[1], &ypos_dummy);

            aws->at(xpos_calc[1], ypos+key*KEY_Y_OFFSET);
            aws->create_toggle(query->awar_not[key], "#equal.xpm", "#notEqual.xpm");

            if (xpos_calc[2] == -1) aws->get_at_position(&xpos_calc[2], &ypos_dummy);
        }
    }
    if (awtqs->key_pos_fig) {
        aws->at(awtqs->key_pos_fig);
        aws->create_input_field(query->awar_keys[0], 12);
    }

    if (awtqs->query_pos_fig) {
        aws->at(awtqs->query_pos_fig);

        SmartPtr<AW_at_storage> at_size(AW_at_storage::make(aws, AW_AT_SIZE_AND_ATTACH));

        int xpos, ypos;
        aws->get_at_position(&xpos, &ypos);

        for (int key = 0; key<QUERY_EXPRESSIONS; ++key) {
            aws->at(xpos_calc[2], ypos+key*KEY_Y_OFFSET);
            aws->restore_at_from(*at_size);
            aws->d_callback(makeWindowCallback(perform_query_cb, query, EXT_QUERY_NONE)); // enable ENTER in searchfield to start search
            aws->create_input_field(query->awar_queries[key], 12);
        }
    }

    if (awtqs->result_pos_fig) {
        aws->at(awtqs->result_pos_fig);
        aws->d_callback(makeWindowCallback(toggle_flag_cb, query));

        {
            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
            AW_awar    *awar           = aw_root->awar_string(this_awar_name, "", AW_ROOT_DEFAULT);

            query->hitlist = aws->create_selection_list(this_awar_name, 5, 5);
            awar->add_callback(makeRootCallback(new_selection_made_cb, this_awar_name, query));
        }
        query->hitlist->insert_default("end of list", "");
        query->hitlist->update();
    }

    if (awtqs->count_pos_fig) {
        aws->button_length(13);
        aws->at(awtqs->count_pos_fig);
        aws->label("Hits:");
        aws->create_button(NULp, query->awar_count, NULp, "+");

        if (awtqs->popup_info_window) {
            aws->callback(RootAsWindowCallback::simple(awtqs->popup_info_window, query->gb_main));
            aws->create_button("popinfo", "Info");
        }
    }
    else {
        dbq_assert(!awtqs->popup_info_window); // dont know where to display
    }

    if (awtqs->config_pos_fig) {
        aws->button_length(0);
        aws->at(awtqs->config_pos_fig);
        char *macro_id = GBS_global_string_copy("SAVELOAD_CONFIG_%s", query_id);
        AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "query_box", makeConfigSetupCallback(query_box_setup_config, query), macro_id);
        free(macro_id);
    }

    aws->button_length(18);

    if (awtqs->do_query_pos_fig) {
        aws->at(awtqs->do_query_pos_fig);
        aws->callback(makeWindowCallback(perform_query_cb, query, EXT_QUERY_NONE));
        char *macro_id = GBS_global_string_copy("SEARCH_%s", query_id);
        aws->create_button(macro_id, "Search", "S", "+");
        free(macro_id);
    }
    if (awtqs->do_refresh_pos_fig) {
        aws->at(awtqs->do_refresh_pos_fig);
        aws->create_option_menu(query->awar_sort);
        aws->insert_default_option("unsorted",  "u", QUERY_SORT_NONE);
        aws->insert_option        ("by value",  "v", QUERY_SORT_BY_1STFIELD_CONTENT);
        aws->insert_option        ("by number", "n", QUERY_SORT_NUM_BY_1STFIELD_CONTENT);
        aws->insert_option        ("by id",     "i", QUERY_SORT_BY_ID);
        if (query->selector.parent_selector) {
            aws->insert_option    ("by parent", "p", QUERY_SORT_BY_NESTED_PID);
        }
        aws->insert_option        ("by marked", "m", QUERY_SORT_BY_MARKED);
        aws->insert_option        ("by hit",    "h", QUERY_SORT_BY_HIT_DESCRIPTION);
        aws->insert_option        ("reverse",   "r", QUERY_SORT_REVERSE);
        aws->update_option_menu();
    }
    else {
        dbq_assert(0); // hmm - no sort button here. -> tell ralf where this happens
    }

    if (awtqs->do_mark_pos_fig) {
        aws->at(awtqs->do_mark_pos_fig);
        aws->help_text("mark_list.hlp");
        aws->callback(makeWindowCallback(mark_queried_cb, query, 1));
        aws->create_button("MARK_LISTED_UNMARK_REST", "Mark Listed\nUnmark Rest", "M");
    }
    if (awtqs->do_unmark_pos_fig) {
        aws->at(awtqs->do_unmark_pos_fig);
        aws->help_text("unmark_list.hlp");
        aws->callback(makeWindowCallback(mark_queried_cb, query, 0));
        aws->create_button("UNMARK_LISTED_MARK_REST", "Unmark Listed\nMark Rest", "U");
    }

    if (awtqs->do_mark_nt_pos_fig) {
        aws->at(awtqs->do_mark_nt_pos_fig);
        aws->help_text("mark_list.hlp");
        aws->callback(makeWindowCallback(mark_queried_cb, query, 9));
        aws->create_button("MARK_LISTED_KEEP_REST", "Mark Listed", "M");
    }
    if (awtqs->do_unmark_nt_pos_fig) {
        aws->at(awtqs->do_unmark_nt_pos_fig);
        aws->help_text("unmark_list.hlp");
        aws->callback(makeWindowCallback(mark_queried_cb, query, 8));
        aws->create_button("UNMARK_LISTED_KEEP_REST", "Unmark Listed", "U");
    }

    if (awtqs->do_delete_pos_fig) {
        aws->at(awtqs->do_delete_pos_fig);
        aws->help_text("del_list.hlp");
        aws->callback(makeWindowCallback(delete_queried_species_cb, query));
        char *macro_id = GBS_global_string_copy("DELETE_LISTED_%s", query_id);
        aws->create_button(macro_id, "Delete Listed", "D");
        free(macro_id);
    }
    if (awtqs->do_set_pos_fig) {
        query->awar_writekey      = GBS_global_string_copy("tmp/dbquery_%s/write_key",      query_id); aw_root->awar_string(query->awar_writekey);
        query->awar_writelossy    = GBS_global_string_copy("tmp/dbquery_%s/write_lossy",    query_id); aw_root->awar_int   (query->awar_writelossy);
        query->awar_protectkey    = GBS_global_string_copy("tmp/dbquery_%s/protect_key",    query_id); aw_root->awar_string(query->awar_protectkey);
        query->awar_setprotection = GBS_global_string_copy("tmp/dbquery_%s/set_protection", query_id); aw_root->awar_int   (query->awar_setprotection, 4);
        query->awar_setvalue      = GBS_global_string_copy("tmp/dbquery_%s/set_value",      query_id); aw_root->awar_string(query->awar_setvalue);

        aws->at(awtqs->do_set_pos_fig);
        aws->help_text("mod_field_list.hlp");
        aws->callback(makeCreateWindowCallback(create_writeFieldOfListed_window, query));
        char *macro_id = GBS_global_string_copy("WRITE_TO_FIELDS_OF_LISTED_%s", query_id);
        aws->create_button(macro_id, "Write to Fields\nof Listed", "S");
        free(macro_id);
    }

    sprintf(buffer, "tmp/dbquery_%s/tag",                 query_id); query->awar_tag             = strdup(buffer); aw_root->awar_string(query->awar_tag);
    sprintf(buffer, "tmp/dbquery_%s/use_tag",             query_id); query->awar_use_tag         = strdup(buffer); aw_root->awar_int   (query->awar_use_tag);
    sprintf(buffer, "tmp/dbquery_%s/deftag",              query_id); query->awar_deftag          = strdup(buffer); aw_root->awar_string(query->awar_deftag);
    sprintf(buffer, "tmp/dbquery_%s/double_pars",         query_id); query->awar_double_pars     = strdup(buffer); aw_root->awar_int   (query->awar_double_pars);
    sprintf(buffer, "tmp/dbquery_%s/parskey",             query_id); query->awar_parskey         = strdup(buffer); aw_root->awar_string(query->awar_parskey);
    sprintf(buffer, "tmp/dbquery_%s/parsvalue",           query_id); query->awar_parsvalue       = strdup(buffer); aw_root->awar_string(query->awar_parsvalue);
    sprintf(buffer, "tmp/dbquery_%s/awar_parspredefined", query_id); query->awar_parspredefined  = strdup(buffer); aw_root->awar_string(query->awar_parspredefined);
    sprintf(buffer, "tmp/dbquery_%s/acceptConvError",     query_id); query->awar_acceptConvError = strdup(buffer); aw_root->awar_int   (query->awar_acceptConvError);
    sprintf(buffer, "tmp/dbquery_%s/acceptNameMod",       query_id); query->awar_acceptIdMod     = strdup(buffer); aw_root->awar_int   (query->awar_acceptIdMod);

    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));
    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));
    aws->sep______________();
    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));
    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  ));
    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));
    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  ));
    aws->sep______________();


    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));

    if (query->gb_ref) {
        dbq_assert(speciesOrOrganism(query->selector.type)); // stuff below works only for species/organisms
        aws->sep______________();
        if (query->expect_hit_in_ref_list) {
            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",
                                   "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_LINES));
            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",
                                   "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_WORDS));
        }
        else {
            aws->insert_menu_topic("search_equal_field_in_both_db", "Search entries existing in both DBs", "S",
                                   "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_LINES));
            aws->insert_menu_topic("search_equal_word_in_both_db", "Search words existing in entries of both DBs", "w",
                                   "search_equal_fields.hlp", AWM_ALL, makeWindowCallback(perform_query_cb, query, EXT_QUERY_COMPARE_WORDS));
        }
    }

    GB_pop_transaction(gb_main);
    return query;
}

// --------------------------------------------------------------------------------

#ifdef UNIT_TESTS
#ifndef TEST_UNIT_H
#include <test_unit.h>
#endif

void TEST_nullcmp() {
    const char *whatever = "bla";
    TEST_EXPECT_EQUAL(ARB_strNULLcmp(NULp, NULp), 0);
    TEST_EXPECT_EQUAL(ARB_strNULLcmp(whatever, whatever), 0);
    TEST_EXPECT_EQUAL(ARB_strNULLcmp("a", "b"), -1);

    // document uncommon behavior: NULp is bigger than any other text!
    TEST_EXPECT_EQUAL(ARB_strNULLcmp(whatever, NULp), -1);
    TEST_EXPECT_EQUAL(ARB_strNULLcmp(NULp, whatever), 1);
}

#endif // UNIT_TESTS

// --------------------------------------------------------------------------------

