source: branches/alilink/SL/DB_SCANNER/db_scanner.cxx

Last change on this file was 18126, checked in by westram, 5 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.5 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : db_scanner.cxx                                    //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include <db_scanner.hxx>
12
13#include <AW_rename.hxx>
14#include <aw_select.hxx>
15#include <aw_msg.hxx>
16#include <aw_root.hxx>
17
18#include <ad_cb.h>
19
20#include <arb_progress.h>
21#include <arb_str.h>
22#include <arb_strbuf.h>
23#include <arb_strarray.h>
24
25#include <StrUniquifier.h>
26
27const size_t INFO_WIDTH = 1000;
28
29STATIC_ASSERT(INFO_WIDTH<=AW_selection_list_entry::MAX_DISPLAY_LENGTH);
30
31void DbScanner::editfield_value_changed_cb() {
32    if (ignore_editfield_change) return;
33
34    LocallyModify<bool> do_not_recurse(ignore_editfield_change, true);
35
36    bool update_self = false;
37    {
38        GB_begin_transaction(gb_main);
39
40        GB_ERROR    error      = NULp;
41        const char *field_name = awar_selected_field->read_char_pntr();
42
43        if (!gb_item) {
44            error = "No item selected";
45        }
46        else if (!field_name[0]) {
47            error = "No field selected";
48        }
49        else if (!awar_edit_enabled->read_int()) { // edit disabled
50            awar_editfield->write_string("");
51            error = "Change ignored (edit is disabled!)";
52        }
53        else {
54            char *value    = awar_editfield->read_string();
55            int   vlen     = strlen(value);
56
57            while (vlen>0 && value[vlen-1] == '\n') vlen--; // remove trailing newlines
58            value[vlen]     = 0;
59
60            GBDATA *gbd = search_selected_field();
61
62            if (!gbd) { // field does not exist -> create new element
63                if (vlen) {
64                    GBDATA *gb_key      = GBT_get_changekey(gb_main, field_name, selector.change_key_path);
65                    GBDATA *gb_key_type = GB_entry(gb_key, CHANGEKEY_TYPE);
66                    GBDATA *gb_new      = GB_search(gb_item, field_name, (GB_TYPES)GB_read_int(gb_key_type));
67                    if (!gb_new) error  = GB_await_error();
68                    else    error       = GB_write_autoconv_string(gb_new, value);
69                }
70            }
71            else { // change old element
72                if (strcmp(field_name, "name") == 0) { // This is a real rename !!! // @@@ should use MutableItemSelector::id_field (no real problem)
73                    if (selector.type == QUERY_ITEM_SPECIES) { // species
74                        arb_progress  progress("Manual change of species ID");
75                        char         *name = nulldup(GBT_read_name(gb_item));
76
77                        if (strlen(value)) {
78                            GBT_begin_rename_session(gb_main, 0);
79
80                            error = GBT_rename_species(name, value, false);
81
82                            if (error) GBT_abort_rename_session();
83                            else error = GBT_commit_rename_session();
84                        }
85                        else {
86                            error = AWTC_recreate_name(gb_item);
87                        }
88
89                        free(name);
90                    }
91                    else { // non-species (gene, experiment, etc.)
92                        if (strlen(value)) {
93                            GBDATA *gb_exists    = NULp;
94                            GBDATA *gb_item_data = GB_get_father(gb_item);
95
96                            for (gb_exists = selector.get_first_item(gb_item_data, QUERY_ALL_ITEMS);
97                                 gb_exists;
98                                 gb_exists = selector.get_next_item(gb_exists, QUERY_ALL_ITEMS))
99                            {
100                                if (ARB_stricmp(GBT_read_name(gb_exists), value) == 0) break;
101                            }
102
103                            if (gb_exists) error = GBS_global_string("There is already a %s named '%s'", selector.item_name, value);
104                            else error           = GB_write_autoconv_string(gbd, value);
105                        }
106                        else {
107                            error = "The 'name' field can't be empty.";
108                        }
109                    }
110
111                    if (!error) update_self = true;
112                }
113                else {
114                    if (vlen) {
115                        error = GB_write_autoconv_string(gbd, value);
116                    }
117                    else {
118                        if (GB_child(gbd)) {
119                            error = "Sorry, cannot perform a deletion.\n(The selected entry has child entries. Delete them first.)";
120                        }
121                        else {
122                            error = GB_delete(gbd);
123                        }
124                    }
125                }
126            }
127            free(value);
128        }
129
130        aw_message_if(GB_end_transaction(gb_main, error));
131    }
132
133    awar_selected_field->touch(); // force refresh of edit-box
134
135    if (update_self) { // if the name changed -> rewrite awars AFTER transaction was closed
136        GB_transaction ta(gb_main);
137
138        char *my_id = selector.generate_item_id(gb_main, gb_item);
139        selector.update_item_awars(gb_main, get_root(), my_id); // update awars (e.g. AWAR_SPECIES_NAME)
140        free(my_id);
141    }
142}
143
144void DbScanner::toggle_marked_cb() {
145    if (gb_item && !ignore_marktoggle_change) {
146        long flag = awar_mark->read_int();
147        GB_transaction ta(gb_main);
148        GB_write_flag(gb_item, flag);
149    }
150}
151
152void DbScanner::remap_edit_box() {
153    GB_transaction ta(gb_main);
154
155    GBDATA *gb_wanted_field = NULp;
156    if (awar_edit_enabled->read_int()) {
157        gb_wanted_field = search_selected_field(); // map only if editing is allowed
158    }
159
160    if (gb_wanted_field != gb_field) {
161        DatabaseCallback editbox_update_cb = makeDatabaseCallback(DbScanner::field_changed_cb, this);
162        if (gb_field) GB_remove_callback(gb_field, GB_CB_CHANGED_OR_DELETED, editbox_update_cb);
163
164        gb_field = gb_wanted_field; // change field
165
166        if (gb_field) {
167            GB_add_callback(gb_field, GB_CB_CHANGED_OR_DELETED, editbox_update_cb);
168        }
169        field_changed_cb(GB_CB_CHANGED);
170    }
171}
172
173inline const char *awarname(const char *scanner_id, const char *entry) {
174    return GBS_global_string("tmp/dbscan/%s/%s", scanner_id, entry);
175}
176
177void DbScanner::create_awars(const char *scanner_id, bool have_edit_field, bool have_edit_toggle, bool have_mark_toggle) {
178    arb_assert(implicated(have_edit_toggle, have_edit_field));
179
180    // create scanner-local AWARS
181    const char *list_awarname = awarname(scanner_id, "list");
182    if (get_root()->awar_no_error(list_awarname)) { // awar already exists
183        GBK_terminatef("multiple scanners bound to AWAR '%s'", list_awarname);
184    }
185    awar_selected_field = get_root()->awar_string(list_awarname, "", AW_ROOT_DEFAULT);
186
187    awar_mapped_item_ID = get_root()->awar_string(awarname(scanner_id, "id"), "<undef>", AW_ROOT_DEFAULT);
188    if (have_edit_field) {
189        awar_editfield = get_root()->awar_string(awarname(scanner_id, "edit"), "", AW_ROOT_DEFAULT);
190        if (have_edit_toggle) {
191            awar_edit_enabled = get_root()->awar_int(awarname(scanner_id, "edit_enable"), true, AW_ROOT_DEFAULT);
192        }
193    }
194    if (have_mark_toggle) {
195        awar_mark = get_root()->awar_int(awarname(scanner_id, "mark"), true, AW_ROOT_DEFAULT);
196    }
197}
198
199void DbScanner::create_field_edit_widgets(const char *edit_pos_fig, const char *edit_enable_pos_fig) {
200    arb_assert(edit_pos_fig);
201
202    RootCallback   remap_rcb = makeRootCallback(DbScanner::remap_edit_box, this);
203    WindowCallback remap_wcb = makeWindowCallback(DbScanner::remap_edit_box, this);
204
205    if (edit_enable_pos_fig) {
206        aws->at(edit_enable_pos_fig);
207        aws->create_toggle(awar_edit_enabled->awar_name);
208    }
209
210    awar_selected_field->add_callback(remap_rcb);
211    awar_mapped_item_ID->add_callback(remap_rcb); // refresh edit-field if item changes
212    if (edit_enable_pos_fig) awar_edit_enabled->add_callback(remap_rcb);
213    awar_editfield->add_callback(makeRootCallback(DbScanner::editfield_value_changed_cb, this));
214
215    aws->at(edit_pos_fig);
216    aws->create_text_field(awar_editfield->awar_name, 20, 10);
217}
218
219
220DbScanner *DbScanner::create(GBDATA         *gb_main,
221                             const char     *scanner_id,
222                             AW_window      *aws,
223                             const char     *box_pos_fig,
224                             const char     *edit_pos_fig,
225                             const char     *edit_enable_pos_fig,
226                             DB_SCANNERMODE  scannermode,
227                             const char     *mark_pos_fig,
228                             ItemSelector&   selector)
229{
230    /* create an unmapped scanner box and optionally some buttons,
231     * the return value is used as handle to further scanner functions
232     */
233
234    DbScanner *scanner = new DbScanner(scannermode, selector, aws, gb_main);
235
236    if (gb_main) GB_push_transaction(gb_main);
237
238    scanner->create_awars(scanner_id, edit_pos_fig, edit_enable_pos_fig, mark_pos_fig);
239
240    // create GUI elements
241    aws->at(box_pos_fig);
242    scanner->create_field_selection_list();
243    if (mark_pos_fig) {
244        aws->at(mark_pos_fig);
245        scanner->create_mark_toggle();
246    }
247    if (edit_pos_fig) {
248        scanner->create_field_edit_widgets(edit_pos_fig, edit_enable_pos_fig);
249    }
250
251    if (gb_main) GB_pop_transaction(gb_main);
252    aws->set_popup_callback(makeWindowCallback(DbScanner::changed_cb, scanner, GB_CB_CHANGED));
253    return scanner;
254}
255
256void DbScanner::append_field_data(GBS_strstruct& buf, GB_TYPES type, GBDATA *gbd) {
257    /*! insert 'type', protection and data into 'buf'.
258     * note that 'gbd' may be NULp -> only insert type
259     */
260
261    buf.put(GB_type_2_char(type));
262
263    if (gbd) { // existing entry
264        buf.put(GB_read_security_write(gbd)+'0');
265        buf.put(':');
266        buf.put(' ');
267
268        char *data = GB_read_as_string(gbd);
269        if (data) {
270            int rest    = INFO_WIDTH-buf.get_position();
271            int ssize   = strlen(data);
272
273            arb_assert(rest>0);
274            arb_assert(ssize>=0);
275
276            if (ssize > rest) {
277                ssize = GBS_shorten_repeated_data(data);
278                if (ssize > rest) {
279                    if (ssize>5) strcpy(data+rest-5, "[...]");
280                    ssize = rest;
281                }
282            }
283
284            buf.ncat(data, ssize);
285            free(data);
286        }
287        else {
288            buf.ncat("<unprintable>", 13);
289        }
290    }
291    else { // non-existing entry
292        buf.ncat(" :", 2);
293    }
294}
295
296
297
298int DbScanner::fill_fields_recursive(GBDATA *gbd, const int depth, GBS_strstruct& buf) {
299    static StrUniquifier trackKeys; // track keys with identical names and assign unique IDs.
300
301    int max_keyname_length = 0;
302    arb_assert(buf.empty());
303    for (int i = 0; i<depth; ++i) buf.ncat(": ", 2);
304
305    const char *key    = GB_read_key_pntr(gbd);
306    int         keylen = strlen(key);
307    buf.ncatTrimmed(key, keylen, previous_max_keyname_length);
308    buf.put(' ');
309    max_keyname_length = std::max(max_keyname_length, keylen);
310
311    bool isContainer = GB_read_type(gbd) == GB_DB;
312    if (isContainer) buf.ncat("<CONTAINER>:", 12);
313    else             append_field_data(buf, GB_read_type(gbd), gbd);
314
315    {
316        const char *id = trackKeys.make_unique_key(key); // create unique IDs for multiple fields with same key
317        field_sel->insert(buf.get_data(), id);
318        buf.erase();
319    }
320
321    if (isContainer) {
322        for (GBDATA *gb2 = GB_child(gbd); gb2; gb2 = GB_nextChild(gb2)) {
323            max_keyname_length = std::max(max_keyname_length, fill_fields_recursive(gb2, depth+1, buf));
324        }
325    }
326
327    // cleanup static variable (at top of recursion):
328    if (!depth) trackKeys.clear();
329
330    return max_keyname_length;
331}
332
333int DbScanner::fill_fields_by_keydata(GBS_strstruct& buf) {
334    int max_keyname_length = 0;
335
336    GBDATA *gb_key_data = GB_search(gb_main, get_selector().change_key_path, GB_CREATE_CONTAINER);
337
338    for (int existing = 1; existing >= 0; --existing) {
339        for (GBDATA *gb_key = GB_entry(gb_key_data, CHANGEKEY); gb_key; gb_key = GB_nextEntry(gb_key)) {
340            GBDATA *gb_key_hidden = GB_entry(gb_key, CHANGEKEY_HIDDEN);
341            if (gb_key_hidden && GB_read_int(gb_key_hidden)) continue; // don't show hidden fields in 'species information' window
342
343            GBDATA *gb_key_name = GB_entry(gb_key, CHANGEKEY_NAME);
344            if (!gb_key_name) continue;
345
346            GBDATA *gb_key_type = GB_entry(gb_key, CHANGEKEY_TYPE);
347
348            const char *key = GB_read_char_pntr(gb_key_name);
349            GBDATA     *gbd = GB_search(gb_item, key, GB_FIND);
350
351            if ((!existing) == (!gbd)) { // first print only existing; then non-existing entries
352                buf.erase();
353
354                int keylen         = strlen(key);
355                buf.ncatTrimmed(key, keylen, previous_max_keyname_length);
356                buf.put(' ');
357                max_keyname_length = std::max(max_keyname_length, keylen);
358
359                GB_TYPES type = GB_TYPES(GB_read_int(gb_key_type));
360                append_field_data(buf, type, gbd);
361
362                field_sel->insert(buf.get_data(), key);
363            }
364        }
365    }
366
367    return max_keyname_length;
368}
369
370void DbScanner::field_changed_cb(GB_CB_TYPE cbtype) {
371    if (cbtype == GB_CB_DELETE) {
372        gb_field = NULp;
373    }
374
375    LocallyModify<bool> just_a_refresh(ignore_editfield_change, true);
376
377    if (gb_field) {
378        char *data = GB_read_as_string(gb_field);
379        if (!data) data = strdup("<you cannot edit this datatype>");
380        awar_editfield->write_string(data);
381        free(data);
382    }
383    else {
384        awar_editfield->write_string("");
385    }
386}
387
388inline bool width_adjusted(int& prevMaxLen, int currMaxLen) {
389    bool adjusted = false;
390    if (prevMaxLen != currMaxLen) {
391        prevMaxLen = currMaxLen;
392        adjusted = true;
393    }
394    return adjusted;
395}
396
397void DbScanner::changed_cb(GB_CB_TYPE cbtype) {
398    // performs updates necessary when mapped item changes:
399    // - refresh field selection list
400    // - update display of item name
401
402    if (cbtype == GB_CB_DELETE) {
403        gb_item = NULp;
404    }
405    if (gb_item && !aws->is_shown()) {
406        return;
407    }
408
409    // update selection list
410    field_sel->clear();
411    if (gb_item) {
412        GB_transaction ta(gb_main);
413        GBS_strstruct buf;
414
415        int fillCount = 0;
416        while (true) {
417            int max_keyname_length = 0;
418            switch (scannermode) {
419                case DB_SCANNER:   max_keyname_length = fill_fields_recursive(gb_item, 0, buf); break;
420                case DB_KEYVIEWER: max_keyname_length = fill_fields_by_keydata(buf); break;
421            }
422            fillCount++;
423
424            if (!width_adjusted(previous_max_keyname_length, max_keyname_length)) break;
425
426            // otherwise occurring keyname length changed. refill (once) to fix used width.
427            if (fillCount>=2) {
428                arb_assert(0); // formatting should not fail multiple times
429                break;
430            }
431            field_sel->clear();
432        }
433    }
434
435    field_sel->insert_default("", "");
436    field_sel->update();
437
438    if (gb_item) {
439        GB_transaction ta(gb_main);
440
441        if (awar_mark) {
442            LocallyModify<bool> ignoreUpdate(ignore_marktoggle_change, true);
443            long flag = GB_read_flag(gb_item);
444            awar_mark->write_int(flag);
445        }
446
447        char *id = get_mapped_item_id();
448        awar_mapped_item_ID->write_string(id);
449        free(id);
450    }
451    else {
452        awar_mapped_item_ID->write_string("<none selected>");
453    }
454    awar_selected_field->touch();
455}
456
457void DbScanner::keydata_modified_cb(GB_CB_TYPE cbtype) {
458    // unmap edit field if 'key_data' has been changed (entries might have been deleted)
459
460    bool deselect = false;
461    {
462        const char *field_name = awar_selected_field->read_char_pntr();
463        if (field_name[0]) {
464            GBDATA *gb_key = GBT_get_changekey(gb_main, field_name, selector.change_key_path);
465
466            if (!gb_key) { // key unknown
467                deselect = true;
468            }
469            else {
470                GBDATA *gb_key_hidden = GB_entry(gb_key, CHANGEKEY_HIDDEN);
471                if (gb_key_hidden && GB_read_int(gb_key_hidden)) { // key hidden -> deselect
472                    deselect = true;
473                }
474            }
475        }
476    }
477
478    if (deselect) awar_selected_field->write_string(""); // deselect field
479    changed_cb(cbtype);
480}
481
482void DbScanner::Map(GBDATA *gb_new_item, const char *key_path) {
483    GB_transaction ta(gb_main);
484
485    GBDATA *gb_key_data = GB_search(gb_main, key_path, GB_CREATE_CONTAINER);
486
487    if (gb_item) {
488        GB_remove_callback(gb_item, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(DbScanner::changed_cb, this));
489        if (scannermode == DB_KEYVIEWER) {
490            GB_remove_callback(gb_key_data, GB_CB_CHANGED, makeDatabaseCallback(DbScanner::keydata_modified_cb, this));
491        }
492    }
493
494    gb_item = gb_new_item;
495
496    if (gb_item) {
497        GB_add_callback(gb_item, GB_CB_CHANGED_OR_DELETED, makeDatabaseCallback(DbScanner::changed_cb, this));
498        if (scannermode == DB_KEYVIEWER) {
499            GB_add_callback(gb_key_data, GB_CB_CHANGED, makeDatabaseCallback(DbScanner::keydata_modified_cb, this));
500        }
501    }
502
503    changed_cb(GB_CB_CHANGED);
504}
505
506void collectKeysRegisteredInDatabase(StrArray& fields, GBDATA *gb_main, ItemSelector& sel, bool skipContainers, bool skipHidden) {
507    arb_assert(gb_main);
508
509    GB_transaction ta(gb_main);
510
511    GBDATA *gb_key_data = GB_search(gb_main, sel.change_key_path, GB_FIND);
512    GBDATA *gb_key      = gb_key_data ? GB_entry(gb_key_data, CHANGEKEY) : NULp;
513
514    while (gb_key) {
515        bool skipThis = false;
516        if (!skipThis && skipContainers) {
517            GB_TYPES type = GB_TYPES(*GBT_read_int(gb_key, CHANGEKEY_TYPE));
518            skipThis      = type == GB_DB; // skip when type is container
519        }
520        if (!skipThis && skipHidden) {
521            GBDATA *gb_key_hidden = GB_entry(gb_key, CHANGEKEY_HIDDEN);
522            skipThis = gb_key_hidden && GB_read_int(gb_key_hidden); // skip when hidden
523        }
524
525        if (!skipThis) {
526            char *key_name = GBT_read_string(gb_key, CHANGEKEY_NAME);
527            fields.put(key_name);
528        }
529
530        GBDATA *gb_next_key = GB_nextEntry(gb_key);
531        gb_key              = gb_next_key;
532    }
533}
534
Note: See TracBrowser for help on using the repository browser.