source: branches/alilink/SL/XFERGUI/xfergui.cxx

Last change on this file was 18087, checked in by westram, 5 years ago
  • fix uninitialized warnings.
File size: 40.9 KB
Line 
1// ========================================================= //
2//                                                           //
3//   File      : xfergui.cxx                                 //
4//   Purpose   : GUI to configure transfer sets              //
5//                                                           //
6//   Coded by Ralf Westram (coder@reallysoft.de) in Apr 19   //
7//   http://www.arb-home.de/                                 //
8//                                                           //
9// ========================================================= //
10
11#include "xfergui.h"
12#include <xferset.h>
13
14#include <awt_prompt.hxx>
15
16#include <aw_window.hxx>
17#include <aw_root.hxx>
18#include <aw_awar.hxx>
19#include <aw_file.hxx>
20#include <aw_msg.hxx>
21#include <aw_select.hxx>
22
23#include <arb_file.h>
24#include <arb_str.h>
25#include <StrUniquifier.h>
26
27#include <set>
28#include <string>
29
30using namespace std;
31using namespace FieldTransfer;
32
33// ---------------
34//      awars
35#define AWARBASE_XFERSET_TMP  "tmp/fts/"
36#define AWARBASE_XFERRULE_TMP AWARBASE_XFERSET_TMP "rule/"
37
38#define AWAR_XFERSET_SELECTED  AWARBASE_XFERSET_TMP "focus"
39#define AWAR_XFERSET_COMMENT   AWARBASE_XFERSET_TMP "comment"
40#define AWAR_XFERSET_UNDEFINED AWARBASE_XFERSET_TMP "undefined"
41
42// fts-selection box (selects files, similar to ift/eft-selection-boxes)
43#define AWAR_XFERSET_FTSBASE   AWARBASE_XFERSET_TMP "file"
44#define AWAR_XFERSET_FTSNAME   AWAR_XFERSET_FTSBASE "/file_name"
45#define AWAR_XFERSET_FTSFILTER AWAR_XFERSET_FTSBASE "/filter"
46#define AWAR_XFERSET_FTSDIR    AWAR_XFERSET_FTSBASE "/directory"
47
48#define AWAR_XFERRULE_SELECTED       AWARBASE_XFERRULE_TMP "focus"
49#define AWAR_XFERRULE_TARGETFIELD    AWARBASE_XFERRULE_TMP "rule"
50#define AWAR_XFERRULE_ACI            AWARBASE_XFERRULE_TMP "aci"
51#define AWAR_XFERRULE_SEP            AWARBASE_XFERRULE_TMP "sep"
52#define AWAR_XFERRULE_TYPE           AWARBASE_XFERRULE_TMP "type"
53#define AWAR_XFERRULE_LOSS           AWARBASE_XFERRULE_TMP "loss"
54#define AWAR_XFERRULE_INPUT_FIELDS   AWARBASE_XFERRULE_TMP "input/fields" // contains list of fieldnames separated by ';'
55#define AWAR_XFERRULE_INPUT_SELECTED AWARBASE_XFERRULE_TMP "input/focus"  // field selected in AWAR_XFERRULE_INPUT_FIELDS (duplicate entries contain suffix! better not use content of this awar)
56#define AWAR_XFERRULE_AVAIL_SELECTED AWARBASE_XFERRULE_TMP "avail"        // selected available field
57#define AWAR_XFERRULE_AVAIL_CATEGORY AWARBASE_XFERRULE_TMP "acat"         // shown available fields
58#define AWAR_XFERRULE_FIELD          AWARBASE_XFERRULE_TMP "field"        // content of fieldname textbox
59
60// -------------------------
61//      other constants
62#define NO_XFERSET_SELECTED ""
63
64// ---------------------
65//      field order
66
67inline bool is_info(const char *field) { return field[0] == '<'; }
68inline bool is_info(const string& field) { return is_info(field.c_str()); }
69
70struct lt_field { // set order type for field selection
71    bool operator()(const string& s1, const string& s2) const {
72        int cmp = is_info(s1)-is_info(s2);
73        if (!cmp) cmp = s1.compare(s2);
74        return cmp<0;
75    }
76};
77
78// -----------------
79//      globals
80
81static const AvailableFieldScanner *currentFieldScanner = NULp;
82
83typedef set<string, lt_field> StrSet;
84typedef StrSet::iterator      StrSetIter;
85
86static StrArray knownFieldsClientInput;     // known database fields (retrieved via AvailableFieldScanner; input fields)
87static StrArray knownFieldsClientOutput;    // ---------- (dito, but output fields)
88static StrArray knownFieldsRulesetInput;    // database fields read by current ruleset
89static StrArray knownFieldsRulesetOutput;   // ---------- (dito, but fields written)
90static StrSet   knownFields;                // known database fields (merged from misc. sources according to AWAR_XFERRULE_AVAIL_CATEGORY)
91
92// --------------------------
93//      helper functions
94
95const char *XFER_getFullFTS(const char *name) {
96/*! converts name (as contained in awar passed to XFER_select_RuleSet)
97 * into full path.
98     */
99    static string result;
100    if (name[0])  result = GB_concat_path(GB_path_in_arbprop("fts"), GBS_global_string("%s.fts", name));
101    else          result = name;
102    return result.c_str();
103}
104
105inline AW_awar *awar_selected_FTS() {
106    return AW_root::SINGLETON->awar(AWAR_XFERSET_SELECTED);
107}
108static const char *get_selected_FTS() { // existing or new
109    const char *newname = awar_selected_FTS()->read_char_pntr();
110    return XFER_getFullFTS(newname);
111}
112inline void set_selected_FTS(const char *name) {
113    awar_selected_FTS()->write_string(name);
114}
115
116inline char *getNameOnly(const char *fullpath) {
117    char *nameOnly = NULp;
118    GB_split_full_path(fullpath, NULp, NULp, &nameOnly, NULp);
119    return nameOnly;
120}
121
122static RuleSetPtr getSelectedRuleset(const char*& failReason) {
123    RuleSetPtr ruleset;
124    string     fts = get_selected_FTS();
125    failReason     = NULp;
126
127    if (GB_is_readablefile(fts.c_str())) {
128        ErrorOrRuleSetPtr loaded = RuleSet::loadFrom(fts.c_str());
129        if (loaded.hasError()) {
130            loaded.getError().set_handled();
131            failReason = "load error";
132        }
133        else {
134            ruleset = loaded.getValue();
135        }
136    }
137    else {
138        failReason = "no transfer set";
139    }
140    return ruleset;
141}
142
143inline int getSelectedRuleIndex() {
144    /*! returns index of selected Rule [0..N-1]
145     * or -1 if none selected.
146     */
147    int         idx     = -1;
148    const char *rule_id = AW_root::SINGLETON->awar(AWAR_XFERRULE_SELECTED)->read_char_pntr();
149    if (ARB_strBeginsWith(rule_id, "rule")) {
150        idx = atoi(rule_id+4)-1;
151        xf_assert(idx>=0);
152    }
153    return idx;
154}
155inline const char *ruleSelId(int idx) {
156    return GBS_global_string("rule%i", idx+1);
157}
158inline GB_ERROR checkValidIndex(RuleSetPtr ruleset, int idx) {
159    /*! return error if 'idx' invalid */
160    return ruleset->validIdx(idx) ? NULp : "rule index out-of-bounds";
161}
162
163static void selectRule(int idx, int ruleCount) {
164    /*! select rule in rule selection list
165     * @param idx rule number inside RuleSet. allowed range: [0..N-1]. if idx is outside range -> deselect rule.
166     * @param ruleCount amount of defined rules (N).
167     */
168    const char  *ruleId = idx>=0 && idx<ruleCount ? ruleSelId(idx) : "";
169    AW_root::SINGLETON->awar(AWAR_XFERRULE_SELECTED)->write_string(ruleId);
170}
171
172static RulePtr getSelectedRule(const char*& failReason) {
173    RulePtr rule;
174    failReason = NULp;
175
176    int idx = getSelectedRuleIndex();
177    if (idx<0) {
178        failReason = "no rule";
179    }
180    else {
181        RuleSetPtr ruleset = getSelectedRuleset(failReason);
182        if (ruleset.isSet()) {
183            failReason = checkValidIndex(ruleset, idx);
184            if (!failReason) rule = ruleset->getPtr(idx);
185        }
186    }
187
188    return rule;
189}
190
191inline void refresh_fts_selbox() {
192    AW_refresh_fileselection(AW_root::SINGLETON, AWAR_XFERSET_FTSBASE);
193}
194inline GB_ERROR saveChangedRuleset(RuleSetPtr ruleset) {
195    GB_ERROR error = NULp;
196    string   fts   = get_selected_FTS();
197    if (fts.empty()) {
198        error = "no ruleset selected"; // (should not happen)
199    }
200    else {
201        error = ruleset->saveTo(fts.c_str());
202        if (!error) {
203            refresh_fts_selbox();
204            awar_selected_FTS()->touch();
205        }
206    }
207    return error;
208}
209
210static void overwriteSelectedRule(RulePtr newRule) {
211    GB_ERROR error = NULp;
212    int      idx   = getSelectedRuleIndex();
213
214    if (idx<0) {
215        error = "no rule selected. update impossible.";
216    }
217    else {
218        RuleSetPtr ruleset = getSelectedRuleset(error);
219        if (ruleset.isSet()) {
220            error = checkValidIndex(ruleset, idx);
221            if (!error) {
222                ruleset->replace(idx, newRule);
223                error = saveChangedRuleset(ruleset);
224            }
225        }
226    }
227    aw_message_if(error);
228}
229
230inline GB_ERROR check_valid_existing_fts(const char *fullfts) {
231    return GB_is_readablefile(fullfts) ? NULp : "no FTS selected";
232}
233static GB_ERROR check_valid_target_fts(const char *fullfts) {
234    GB_ERROR error = NULp;
235    if (!fullfts[0]) {
236        error = "no name specified";
237    }
238    else if (GB_is_readablefile(fullfts)) {
239        char *fts_nameOnly = getNameOnly(fullfts);
240        error = GBS_global_string("field transfer set '%s' already exists",
241                                  fts_nameOnly ? fts_nameOnly : fullfts);
242        free(fts_nameOnly);
243    }
244    return error;
245}
246
247// --------------------------------
248//      Rule definition window
249
250static bool ignoreRuleDetailChange = false;
251
252static void selected_rule_changed_cb(AW_root *awr) {
253    const char *failReason;
254    RulePtr     rule = getSelectedRule(failReason);
255
256    LocallyModify<bool> duringUpdate(ignoreRuleDetailChange, true);
257    const bool          haveRule = rule.isSet();
258
259    awr->awar(AWAR_XFERRULE_TARGETFIELD)->write_string(haveRule ? rule->targetField().c_str() : "");
260    awr->awar(AWAR_XFERRULE_ACI)->write_string(haveRule ? rule->getACI().c_str() : "");
261    awr->awar(AWAR_XFERRULE_SEP)->write_string(haveRule ? rule->getSeparator().c_str() : "");
262
263    GB_TYPES forcedType = GB_NONE;
264    if (haveRule && rule->forcesType()) forcedType = rule->getTargetType();
265    awr->awar(AWAR_XFERRULE_TYPE)->write_int(forcedType);
266    awr->awar(AWAR_XFERRULE_LOSS)->write_int(haveRule && rule->precisionLossPermitted());
267    awr->awar(AWAR_XFERRULE_INPUT_FIELDS)->write_string(haveRule ? rule->getSourceFields().c_str() : "");
268}
269
270static RulePtr build_rule_from_awars(AW_root *awr) {
271    // create a rule from settings currently selected in definition window
272    RulePtr newRule;
273
274    const char *src  = awr->awar(AWAR_XFERRULE_INPUT_FIELDS)->read_char_pntr();
275    const char *dest = awr->awar(AWAR_XFERRULE_TARGETFIELD)->read_char_pntr();
276    const char *aci  = awr->awar(AWAR_XFERRULE_ACI)->read_char_pntr();
277    const char *sep  = awr->awar(AWAR_XFERRULE_SEP)->read_char_pntr();
278
279    newRule = aci[0] ? Rule::makeAciConverter(src, sep, aci, dest) : Rule::makeSimple(src, sep, dest);
280
281    GB_TYPES type       = GB_TYPES(awr->awar(AWAR_XFERRULE_TYPE)->read_int());
282    bool     permitLoss = awr->awar(AWAR_XFERRULE_LOSS)->read_int();
283
284    if (type != GB_NONE) newRule->setTargetType(type);
285    if (permitLoss)      newRule->permitPrecisionLoss();
286
287    return newRule;
288}
289
290static void rebuild_rule_from_awars_cb(AW_root *awr) {
291    // rebuild a Rule from current AWAR settings
292    // (called back whenever any awar changes)
293    if (!ignoreRuleDetailChange) {
294        LocallyModify<bool> duringRebuild(ignoreRuleDetailChange, true);
295
296        const char *failReason;
297        RulePtr     selRule = getSelectedRule(failReason);
298        RulePtr     newRule = build_rule_from_awars(awr);
299
300#if defined(DEBUG)
301        printf("rebuild_rule_from_awars_cb:\n");
302        // dump both configs (of rebuild and of stored rule) allowing to compare them:
303        if (selRule.isSet()) printf("  selRule config: %s\n", selRule->getConfig().c_str());
304        if (newRule.isSet()) printf("  newRule config: %s\n", newRule->getConfig().c_str());
305#endif
306
307        if (selRule.isSet() && newRule.isSet()) {
308            string selCfg = selRule->getConfig();
309            string newCfg = newRule->getConfig();
310            if (selCfg != newCfg) { // has rule been changed?
311#if defined(DEBUG)
312                fputs("Rule has changed!\n", stdout);
313#endif
314                overwriteSelectedRule(newRule);
315            }
316        }
317    }
318}
319
320static void availfield_selected_cb(AW_root *awr) {
321    char *selField = awr->awar(AWAR_XFERRULE_AVAIL_SELECTED)->read_string();
322    awr->awar(AWAR_XFERRULE_FIELD)->write_string(selField);
323    free(selField);
324}
325
326static void refresh_availfield_selbox_cb(AW_root *awr, AW_selection_list *avail) {
327    // called if (a) list of fields changed or
328    //           (b) extraction substring changed
329
330    const char *part = awr->awar(AWAR_XFERRULE_FIELD)->read_char_pntr();
331
332    // detect whether content of AWAR_XFERRULE_FIELD is partial or full field name:
333    bool isPart = part[0];
334    for (StrSetIter f = knownFields.begin(); f != knownFields.end() && isPart; ++f) {
335        isPart = *f != part; // stop when field matches part
336    }
337
338    avail->clear();
339
340    int fieldCount      = 0;
341    int shownFieldCount = 0;
342    for (StrSetIter f = knownFields.begin(); f != knownFields.end(); ++f) {
343        const char *field = f->c_str();
344        if (is_info(field)) {
345            avail->insert(field, "");
346        }
347        else {
348            ++fieldCount;
349            if (!isPart || strcasestr(field, part)) { // filter displayed entries based on AWAR_XFERRULE_FIELD content (if isPart)
350                avail->insert(field, field);
351                ++shownFieldCount;
352            }
353        }
354    }
355
356    // add default (showing info):
357    {
358        const char *info = "";
359
360        if (fieldCount == 0) info = "<no fields detected>";
361        else if (isPart) {
362            if (shownFieldCount>0) info = GBS_global_string("<fields matching '%s'>", part);
363            else                   info = GBS_global_string("<no field matches '%s'>", part);
364        }
365
366        avail->insert_default(info, "");
367    }
368    avail->update();
369
370    awr->awar(AWAR_XFERRULE_AVAIL_SELECTED)->write_string(part); // select name (or default) in list
371}
372
373enum AvailCategory {
374    ALL_AVAILABLE_FIELDS,
375    FIELDS_BY_RULESET,
376    UNREAD_BY_RULESET,
377    UNWRITTEN_BY_RULESET,
378    INPUT_FIELDS_BY_CLIENT,
379    OUTPUT_FIELDS_BY_CLIENT,
380};
381
382inline void mergeToKnownFields(const StrArray& source) {
383    for (unsigned i = 0; i<source.size(); ++i) {
384        knownFields.insert(source[i]);
385    }
386}
387inline size_t removeFromKnownFields(const StrArray& unwanted) {
388    size_t removed = 0;
389    for (unsigned i = 0; i<unwanted.size(); ++i) {
390        removed += knownFields.erase(unwanted[i]);
391    }
392    return removed;
393}
394
395static void mergeKnownFields(AW_root *awr) {
396    // merge fields from client and from RuleSet
397    AvailCategory cat = AvailCategory(awr->awar(AWAR_XFERRULE_AVAIL_CATEGORY)->read_int());
398
399    knownFields.clear();
400    switch (cat) {
401        case INPUT_FIELDS_BY_CLIENT:  mergeToKnownFields(knownFieldsClientInput);  break;
402        case OUTPUT_FIELDS_BY_CLIENT: mergeToKnownFields(knownFieldsClientOutput); break;
403
404        case FIELDS_BY_RULESET:
405            mergeToKnownFields(knownFieldsRulesetInput);
406            mergeToKnownFields(knownFieldsRulesetOutput);
407            break;
408
409        case UNREAD_BY_RULESET: {
410            mergeToKnownFields(knownFieldsClientInput);
411            size_t removed = removeFromKnownFields(knownFieldsRulesetInput);
412            knownFields.insert(GBS_global_string("<read fields: %zu>", removed));
413            break;
414        }
415
416        case UNWRITTEN_BY_RULESET: {
417            mergeToKnownFields(knownFieldsClientOutput);
418            size_t removed = removeFromKnownFields(knownFieldsRulesetOutput);
419            knownFields.insert(GBS_global_string("<written fields: %zu>", removed));
420            break;
421        }
422
423        case ALL_AVAILABLE_FIELDS:
424            mergeToKnownFields(knownFieldsClientInput);
425            mergeToKnownFields(knownFieldsClientOutput);
426            mergeToKnownFields(knownFieldsRulesetInput);
427            mergeToKnownFields(knownFieldsRulesetOutput);
428            break;
429    }
430
431
432    awr->awar(AWAR_XFERRULE_FIELD)->touch(); // calls refresh_availfield_selbox_cb
433}
434
435static void refresh_available_fields_from_ruleset(AW_root *awr, RuleSetPtr rulesetPtr) {
436    knownFieldsRulesetInput.erase();
437    knownFieldsRulesetOutput.erase();
438    if (rulesetPtr.isSet()) {
439        rulesetPtr->extractUsedFields(knownFieldsRulesetInput, knownFieldsRulesetOutput);
440    }
441    else {
442        knownFieldsRulesetOutput.put(strdup("<no FTS selected>"));
443    }
444    mergeKnownFields(awr);
445}
446void XFER_refresh_available_fields(AW_root *awr, const AvailableFieldScanner *fieldScanner, FieldsToScan whatToScan) {
447    /*! refreshes the available fields (defined by client) shown in fts gui,
448     * if 'fieldScanner' is the currently active scanner.
449     * Otherwise do nothing (because GUI "belongs" to different client).
450     */
451    if (fieldScanner == currentFieldScanner) {
452        if (whatToScan & SCAN_INPUT_FIELDS) {
453            knownFieldsClientInput.erase();
454            currentFieldScanner->scanFields(knownFieldsClientInput, SCAN_INPUT_FIELDS);
455        }
456        if (whatToScan & SCAN_OUTPUT_FIELDS) {
457            knownFieldsClientOutput.erase();
458            currentFieldScanner->scanFields(knownFieldsClientOutput, SCAN_OUTPUT_FIELDS);
459        }
460        mergeKnownFields(awr);
461    }
462}
463
464static void refresh_inputfield_selbox_cb(AW_root *awr, AW_selection_list *input) {
465    ConstStrArray field;
466    {
467        char *fieldsStr = awr->awar(AWAR_XFERRULE_INPUT_FIELDS)->read_string();
468        GBT_splitNdestroy_string(field, fieldsStr, ";", true);
469    }
470
471    // fill into selection box:
472    input->clear();
473    {
474        StrUniquifier keyTrack; // avoid problems in list-handling caused by using multiple identical keys
475        for (int i = 0; field[i]; ++i) {
476            const char *disp = field[i];
477            xf_assert(disp && disp[0]); // NULp / empty field unwanted
478
479            const char *val = keyTrack.make_unique_key(field[i]);
480            xf_assert(val);
481
482            input->insert(disp, val);
483        }
484    }
485    input->insert_default("", "");
486    input->update();
487
488    rebuild_rule_from_awars_cb(awr); // @@@ useful?
489}
490static void refresh_rule_selection_box_cb(AW_root *awr, AW_selection_list *rules) {
491    const char *defaultText; // displays failures (to load RuleSet) in default entry
492    RuleSetPtr  ruleset = getSelectedRuleset(defaultText);
493
494    rules->clear();
495    if (!defaultText) {
496        xf_assert(ruleset.isSet());
497
498        for (size_t r = 0; r<ruleset->size(); ++r) {
499            const Rule& rule = ruleset->get(r);
500            rules->insert(rule.getShortDescription().c_str(), ruleSelId(r));
501        }
502        defaultText = "no rule";
503    }
504    rules->insert_default(GBS_global_string("<%s>", defaultText), "");
505    rules->update();
506
507    selected_rule_changed_cb(awr);
508
509    refresh_available_fields_from_ruleset(awr, ruleset);
510}
511
512static void init_rule_definition_awars(AW_root *awr) {
513    awr->awar_string(AWAR_XFERRULE_SELECTED,       "", AW_ROOT_DEFAULT);
514    awr->awar_string(AWAR_XFERRULE_TARGETFIELD,    "", AW_ROOT_DEFAULT);
515    awr->awar_string(AWAR_XFERRULE_ACI,            "", AW_ROOT_DEFAULT);
516    awr->awar_string(AWAR_XFERRULE_SEP,            "", AW_ROOT_DEFAULT);
517    awr->awar_string(AWAR_XFERRULE_INPUT_SELECTED, "", AW_ROOT_DEFAULT);
518    awr->awar_string(AWAR_XFERRULE_INPUT_FIELDS,   "", AW_ROOT_DEFAULT);
519    awr->awar_string(AWAR_XFERRULE_AVAIL_SELECTED, "", AW_ROOT_DEFAULT);
520    awr->awar_string(AWAR_XFERRULE_FIELD,          "", AW_ROOT_DEFAULT);
521
522    awr->awar_int(AWAR_XFERRULE_AVAIL_CATEGORY, ALL_AVAILABLE_FIELDS, AW_ROOT_DEFAULT);
523    awr->awar_int(AWAR_XFERRULE_TYPE,           GB_NONE,              AW_ROOT_DEFAULT);
524    awr->awar_int(AWAR_XFERRULE_LOSS,           0,                    AW_ROOT_DEFAULT);
525}
526
527static void add_rule_cb(AW_window *aww) {
528    GB_ERROR error = NULp;
529
530    RuleSetPtr ruleset = getSelectedRuleset(error);
531    if (ruleset.isSet()) {
532        int     idx = getSelectedRuleIndex();
533        RulePtr toAdd;
534
535        if (idx == -1) {
536            // if no rule is selected -> settings may be changed w/o changing any rule.
537            // Clicking ADD in that situation builds and adds a new rule from these settings:
538            toAdd = build_rule_from_awars(aww->get_root());
539        }
540        else {
541            toAdd = Rule::makeSimple("", "", ""); // create empty default rule
542        }
543        int newIdx = ruleset->insertBefore(idx, toAdd);
544        error      = saveChangedRuleset(ruleset);
545        if (!error) selectRule(newIdx, ruleset->size());
546    }
547    aw_message_if(error);
548}
549static void del_rule_cb(AW_window*) {
550    GB_ERROR error = NULp;
551    int      idx   = getSelectedRuleIndex();
552
553    if (idx<0) {
554        error = "no rule selected. nothing deleted.";
555    }
556    else {
557        RuleSetPtr ruleset = getSelectedRuleset(error);
558        if (ruleset.isSet()) {
559            error = checkValidIndex(ruleset, idx);
560            if (!error) {
561                ruleset->erase(idx);
562                error = saveChangedRuleset(ruleset);
563                if (!error) selectRule(idx, ruleset->size());
564            }
565        }
566    }
567    aw_message_if(error);
568}
569static void rule_stack_cb(AW_window*, bool toStack) {
570    static RuleContainer stack; // uses one stack for all Rulesets
571
572    GB_ERROR   error     = NULp;
573    const bool fromStack = !toStack;
574
575    if (fromStack && stack.empty()) {
576        error = "nothing copied. cannot paste.";
577    }
578    else {
579        int idx = getSelectedRuleIndex();
580
581        if (idx<0 && toStack) {
582            error = "no rule selected. nothing copied.";
583        }
584        else {
585            RuleSetPtr ruleset = getSelectedRuleset(error);
586            if (ruleset.isSet()) {
587                if (toStack) {
588                    error = checkValidIndex(ruleset, idx);
589                    if (!error) {
590                        stack.push_back(ruleset->getPtr(idx));
591                        selectRule(idx+1, ruleset->size());
592                    }
593                }
594                else { // fromStack
595                    xf_assert(!stack.empty());
596                    RulePtr rule   = stack.back();
597                    int     newIdx = ruleset->insertBefore(idx, rule);
598
599                    error = saveChangedRuleset(ruleset);
600                    if (!error) {
601                        selectRule(newIdx, ruleset->size());
602                        stack.pop_back();
603                    }
604                }
605            }
606            else if (fromStack) {
607                error = "did not paste to nowhere (no ruleset selected)";
608            }
609        }
610    }
611    aw_message_if(error);
612}
613
614inline void updateChangedInputFields(const ConstStrArray& ifield, AW_awar *awar_input_fields) {
615    char *new_input_fields = GBT_join_strings(ifield, ';');
616    awar_input_fields->write_string(new_input_fields);
617    free(new_input_fields);
618}
619
620static void clear_field_cb(AW_window *aww) {
621    aww->get_root()->awar(AWAR_XFERRULE_FIELD)->write_string("");
622}
623
624static void add_field_cb(AW_window *aww, AW_selection_list *inputSel) {
625    AW_root    *awr   = aww->get_root();
626    const char *field = awr->awar(AWAR_XFERRULE_FIELD)->read_char_pntr();
627
628    if (!field[0]) { // empty fieldname
629        aw_message("Please select an available field or\n"
630                   "enter a custom field name in the textbox below.");
631    }
632    else {
633        // @@@ test valid field name -> abort with error!
634
635        if (inputSel) {
636            AW_awar    *awar_input_fields = awr->awar(AWAR_XFERRULE_INPUT_FIELDS);
637            const char *used_input_fields = awar_input_fields->read_char_pntr();
638
639            int selIdx;
640            if (used_input_fields[0]) { // if we already have fields..
641                ConstStrArray ifield;
642                GBT_split_string(ifield, used_input_fields, ';');
643                selIdx = inputSel->get_index_of_selected();
644                ifield.put_before(selIdx, field);
645                updateChangedInputFields(ifield, awar_input_fields);
646            }
647            else { // insert 1st field
648                awar_input_fields->write_string(field);
649                selIdx = 0;
650            }
651            inputSel->select_element_at(selIdx);
652        }
653        else {
654            awr->awar(AWAR_XFERRULE_TARGETFIELD)->write_string(field);
655        }
656    }
657}
658
659enum FieldMoveDest { MV_UP, MV_DOWN, MV_OFF };
660static void move_field_cb(AW_window *aww, FieldMoveDest dest, AW_selection_list *inputSel) {
661    AW_root    *awr               = aww->get_root();
662    AW_awar    *awar_input_fields = awr->awar(AWAR_XFERRULE_INPUT_FIELDS);
663    const char *used_input_fields = awar_input_fields->read_char_pntr();
664
665    if (used_input_fields[0]) {
666        int selIdx = inputSel->get_index_of_selected();
667        if (selIdx>=0) { // is any field selected?
668            ConstStrArray ifield;
669            GBT_split_string(ifield, used_input_fields, ';');
670
671            int  maxIdx     = ifield.size()-1;
672            bool needUpdate = false;
673
674            switch (dest) {
675                case MV_OFF:
676                    ifield.remove(selIdx);
677                    needUpdate = true;
678                    break;
679                case MV_DOWN:
680                    if (selIdx<maxIdx) {
681                        ifield.move(selIdx, selIdx+1);
682                        selIdx++;
683                        needUpdate = true;
684                    }
685                    break;
686                case MV_UP:
687                    if (selIdx>0) {
688                        ifield.move(selIdx, selIdx-1);
689                        selIdx--;
690                        needUpdate = true;
691                    }
692                    break;
693            }
694
695            if (needUpdate) {
696                updateChangedInputFields(ifield, awar_input_fields);
697                inputSel->select_element_at(selIdx);
698            }
699        }
700    }
701}
702
703static void popup_rule_definition_window(AW_root *awr) {
704    static AW_window *aw_define = NULp;
705    if (!aw_define) {
706        AW_window_simple *aws = new AW_window_simple;
707        aws->init(awr, "DEFINE_RULE", "Define rules for field transfer");
708        aws->load_xfig("fts_ruledef.fig");
709
710        aws->button_length(8);
711
712        aws->at("close");
713        aws->callback(AW_POPDOWN);
714        aws->create_button("CLOSE", "CLOSE", "C");
715
716        aws->at("help");
717        aws->callback(makeHelpCallback("xferset.hlp"));
718        aws->create_button("HELP", "HELP", "H");
719
720        // selection lists
721        aws->at("rules");
722        AW_selection_list *rules = aws->create_selection_list(AWAR_XFERRULE_SELECTED, false);
723
724        aws->at("input");
725        AW_selection_list *input = aws->create_selection_list(AWAR_XFERRULE_INPUT_SELECTED, true);
726
727        aws->at("avail");
728        AW_selection_list *avail = aws->create_selection_list(AWAR_XFERRULE_AVAIL_SELECTED, false);
729
730        // section below left selection list:
731        aws->at("undef");
732        aws->label("Transfer undefined fields?");
733        aws->create_toggle(AWAR_XFERSET_UNDEFINED);
734
735        // section below middle and right selection lists:
736        aws->at("acat");
737        aws->create_option_menu(AWAR_XFERRULE_AVAIL_CATEGORY, false);
738        aws->insert_default_option("all",       "a", ALL_AVAILABLE_FIELDS);
739        aws->insert_option        ("input",     "i", INPUT_FIELDS_BY_CLIENT);
740        aws->insert_option        ("output",    "o", OUTPUT_FIELDS_BY_CLIENT);
741        aws->insert_option        ("ruleset",   "s", FIELDS_BY_RULESET);
742        aws->insert_option        ("unread",    "r", UNREAD_BY_RULESET);
743        aws->insert_option        ("unwritten", "w", UNWRITTEN_BY_RULESET);
744        aws->update_option_menu();
745
746        aws->at("field");
747        aws->create_input_field(AWAR_XFERRULE_FIELD);
748
749        aws->at("sep");    aws->create_input_field(AWAR_XFERRULE_SEP);
750        aws->at("aci");    aws->create_input_field(AWAR_XFERRULE_ACI);
751        aws->at("target"); aws->create_input_field(AWAR_XFERRULE_TARGETFIELD);
752
753        aws->at("type");
754        aws->create_option_menu(AWAR_XFERRULE_TYPE, false);
755        aws->insert_option        ("Ascii text",        "s", GB_STRING);
756        aws->insert_option        ("Rounded numerical", "i", GB_INT);
757        aws->insert_option        ("Floating-point n.", "F", GB_FLOAT);
758        aws->insert_option        ("Bitmask (0/1)",     "B", GB_BITS);
759        aws->insert_default_option("Automatic",         "A", GB_NONE);
760        aws->update_option_menu();
761
762        aws->at("loss"); aws->create_toggle(AWAR_XFERRULE_LOSS);
763
764        // ---------------------------------------
765        //      buttons below selection lists
766        aws->button_length(6);
767        aws->auto_space(5, 5);
768
769        aws->at("rbut"); // left sellist
770        aws->callback(add_rule_cb);                              aws->create_button("ADD_RULE",   "ADD");
771        aws->callback(del_rule_cb);                              aws->create_button("DEL_RULE",   "DEL");
772        aws->callback(makeWindowCallback(rule_stack_cb, true));  aws->create_button("COPY_RULE",  "COPY");
773        aws->callback(makeWindowCallback(rule_stack_cb, false)); aws->create_button("PASTE_RULE", "PASTE");
774
775        aws->at("idel"); // center sellist
776        aws->callback(makeWindowCallback(move_field_cb, MV_OFF, input));  aws->create_button("INPUT_DROP", "DROP");
777        aws->callback(makeWindowCallback(move_field_cb, MV_UP, input));   aws->create_button("INPUT_UP",   "UP");
778        aws->callback(makeWindowCallback(move_field_cb, MV_DOWN, input)); aws->create_button("INPUT_DOWN", "DOWN");
779
780        aws->at("iadd"); // right sellist
781        aws->callback(makeWindowCallback(add_field_cb, input));                    aws->create_button("INPUT_ADD",  "FROM");
782        aws->callback(makeWindowCallback(add_field_cb, (AW_selection_list*)NULp)); aws->create_button("OUTPUT_ADD", "TO");
783        aws->callback(makeWindowCallback(clear_field_cb));                         aws->create_button("CLEAR",      "CLEAR");
784
785        // bind awar callbacks:
786        awr->awar(AWAR_XFERSET_SELECTED)       ->add_callback(makeRootCallback(refresh_rule_selection_box_cb, rules));
787        awr->awar(AWAR_XFERRULE_SELECTED)      ->add_callback(selected_rule_changed_cb);
788        awr->awar(AWAR_XFERRULE_INPUT_FIELDS)  ->add_callback(makeRootCallback(refresh_inputfield_selbox_cb, input));
789        awr->awar(AWAR_XFERRULE_FIELD)         ->add_callback(makeRootCallback(refresh_availfield_selbox_cb, avail));
790        awr->awar(AWAR_XFERRULE_AVAIL_CATEGORY)->add_callback(mergeKnownFields);
791        awr->awar(AWAR_XFERRULE_AVAIL_SELECTED)->add_callback(availfield_selected_cb);
792
793        awr->awar(AWAR_XFERRULE_TARGETFIELD)->add_callback(rebuild_rule_from_awars_cb);
794        awr->awar(AWAR_XFERRULE_ACI)        ->add_callback(rebuild_rule_from_awars_cb);
795        awr->awar(AWAR_XFERRULE_SEP)        ->add_callback(rebuild_rule_from_awars_cb);
796        awr->awar(AWAR_XFERRULE_TYPE)       ->add_callback(rebuild_rule_from_awars_cb);
797        awr->awar(AWAR_XFERRULE_LOSS)       ->add_callback(rebuild_rule_from_awars_cb);
798
799        awr->awar(AWAR_XFERSET_SELECTED)->touch(); // calls refresh_rule_selection_box_cb
800        awr->awar(AWAR_XFERRULE_FIELD)->touch();   // calls refresh_availfield_selbox_cb
801
802        aw_define = aws;
803    }
804    aw_define->activate();
805}
806
807// ----------------------------------------------
808//      AWAR change callbacks (RuleSetAdmin)
809static void fts_filesel_changed_cb(AW_root *awr) {
810    static bool recursion = false;
811    if (!recursion) {
812        LocallyModify<bool> avoid(recursion, true);
813
814        char *fts          = AW_get_selected_fullname(awr, AWAR_XFERSET_FTSBASE);
815        char *fts_nameOnly = getNameOnly(fts);
816
817        if (fts_nameOnly && !GB_is_directory(fts)) { // avoid directory (otherwise would write 'fts' to AWAR_XFERSET_SELECTED)
818            set_selected_FTS(fts_nameOnly);
819            free(fts_nameOnly);
820        }
821        else {
822            set_selected_FTS(NO_XFERSET_SELECTED);
823        }
824        free(fts);
825    }
826}
827
828static bool ignoreRulesetAwarChange = false;
829
830static void selected_fts_changed_cb(AW_root *awr) {
831    static bool recursion = false;
832    if (!recursion) {
833        LocallyModify<bool> avoid(recursion, true);
834
835        string fts = get_selected_FTS();
836        AW_set_selected_fullname(awr, AWAR_XFERSET_FTSBASE, fts.c_str());
837
838        string comment;
839        bool   transferUndef = false;
840        if (GB_is_readablefile(fts.c_str())) {
841            ErrorOrRuleSetPtr loaded = RuleSet::loadFrom(fts.c_str());
842            if (loaded.hasError()) {
843                aw_message_if(loaded.getError().deliver());
844            }
845            else {
846                const RuleSet& ruleset = *loaded.getValue();
847
848                comment       = ruleset.getComment();
849                transferUndef = ruleset.shallTransferUndefFields();
850            }
851        }
852        LocallyModify<bool> duringCommentReload(ignoreRulesetAwarChange, true);
853        awr->awar(AWAR_XFERSET_COMMENT)->write_string(comment.c_str());
854        awr->awar(AWAR_XFERSET_UNDEFINED)->write_int(transferUndef);
855    }
856}
857
858static void ruleset_awar_changed_cb(AW_root *awr, const char *awarname) {
859    if (!ignoreRulesetAwarChange) {
860        string fts = get_selected_FTS();
861        if (GB_is_readablefile(fts.c_str())) {
862            ErrorOrRuleSetPtr loaded = RuleSet::loadFrom(fts.c_str());
863            if (loaded.hasError()) {
864                aw_message_if(loaded.getError().deliver());
865            }
866            else {
867                RuleSetPtr ruleset = loaded.getValue();
868                bool       save    = false;
869
870                if (strcmp(awarname, AWAR_XFERSET_COMMENT) == 0) {
871                    string      saved   = ruleset->getComment();
872                    const char *changed = awr->awar(AWAR_XFERSET_COMMENT)->read_char_pntr();
873                    if (strcmp(saved.c_str(), changed) != 0) { // comment was changed
874                        ruleset->setComment(changed);
875                        save = true;
876                    }
877                }
878                else if (strcmp(awarname, AWAR_XFERSET_UNDEFINED) == 0) {
879                    bool transferUndef = awr->awar(AWAR_XFERSET_UNDEFINED)->read_int();
880                    if (transferUndef != ruleset->shallTransferUndefFields()) { // setting was changed
881                        ruleset->set_transferUndefFields(transferUndef);
882                        save = true;
883                    }
884                }
885                else { xf_assert(0); } // unknown awar
886
887                if (save) {
888                    GB_ERROR error = ruleset->saveTo(fts.c_str());
889                    aw_message_if(error);
890                    refresh_fts_selbox();
891                }
892            }
893        }
894        else {
895            aw_message("Please select an existing FTS, then try again to change this setting.");
896        }
897    }
898}
899
900// -----------------------------------------
901//      button callbacks (RuleSetAdmin)
902static void editRuleset_cb(AW_window *aww) {
903    string   fullfts = get_selected_FTS();
904    GB_ERROR error   = check_valid_existing_fts(fullfts.c_str());
905    if (!error) popup_rule_definition_window(aww->get_root());
906    aw_message_if(error);
907}
908static void createRuleset_cb(AW_window *aww) {
909    string fullfts = get_selected_FTS();
910    GB_ERROR error = check_valid_target_fts(fullfts.c_str());
911    if (!error) {
912        RuleSet empty;
913        error = empty.saveTo(fullfts.c_str());
914        if (!error) {
915            refresh_fts_selbox();
916            awar_selected_FTS()->touch(); // will trigger refresh on client-side (e.g. rerun test-import or -export)
917            editRuleset_cb(aww);          // simulate press EDIT
918        }
919    }
920    aw_message_if(error);
921}
922
923enum copyMoveMode {COPY_RULESET, MOVE_RULESET};
924static GB_ERROR copyMoveRuleset_cb(const char *new_name, copyMoveMode mode) {
925    // copy or rename the currently selected ruleset
926
927    // error if no RuleSet selected
928    string fullSourceFts = get_selected_FTS();
929    GB_ERROR error = check_valid_existing_fts(fullSourceFts.c_str()); // (e.g. click COPY with selected, then deselect, then start copy)
930
931    // build full target file name. fail if new_name exists.
932    string fullTargetFts;
933    if (!error) {
934        char *path;
935        GB_split_full_path(fullSourceFts.c_str(), &path, NULp, NULp, NULp);
936        if (new_name[0]) {
937            fullTargetFts = GB_concat_path(path, GB_append_suffix(new_name, "fts"));
938        }
939        error = check_valid_target_fts(fullTargetFts.c_str());
940    }
941
942    // copy or move file
943    if (!error) {
944        switch (mode) {
945            case COPY_RULESET: error = GB_safe_copy_file(fullSourceFts.c_str(), fullTargetFts.c_str()); break;
946            case MOVE_RULESET: error = GB_safe_rename_file(fullSourceFts.c_str(), fullTargetFts.c_str()); break;
947        }
948    }
949
950    // force selbox refresh + select new
951    if (!error) {
952        set_selected_FTS(NO_XFERSET_SELECTED);
953        refresh_fts_selbox();
954        set_selected_FTS(new_name);
955    }
956
957
958    return error;
959}
960
961static void askCopyMoveRuleset_cb(AW_window *, copyMoveMode mode) {
962    string   fullfts = get_selected_FTS();
963    GB_ERROR error   = check_valid_existing_fts(fullfts.c_str());
964    if (error) {
965        aw_message(error);
966    }
967    else {
968        const char *title  = NULp;
969        const char *prompt = NULp;
970        const char *butTxt = NULp;
971        switch (mode) {
972            case COPY_RULESET:
973                title  = "Copy ruleset";
974                prompt = "Enter the name of the new ruleset:";
975                butTxt = "Copy";
976                break;
977            case MOVE_RULESET:
978                title  = "Rename ruleset";
979                prompt = "Enter the new name for the ruleset:";
980                butTxt = "Rename";
981                break;
982        }
983
984        char *old_name;
985        GB_split_full_path(fullfts.c_str(), NULp, NULp, &old_name, NULp);
986        AWT_activate_prompt(title, prompt, old_name, butTxt, makeResultHandler(copyMoveRuleset_cb, mode));
987        free(old_name);
988    }
989}
990static void deleteRuleset_cb() {
991    string   fullfts = get_selected_FTS();
992    GB_ERROR error   = check_valid_existing_fts(fullfts.c_str());
993    if (!error) {
994        if (GB_unlink(fullfts.c_str())<0) {
995            error = GB_await_error();
996        }
997        else {
998            set_selected_FTS(NO_XFERSET_SELECTED); // deselect
999            refresh_fts_selbox();
1000        }
1001    }
1002    aw_message_if(error);
1003}
1004
1005static void noRuleset_cb(AW_window *) {
1006    set_selected_FTS(NO_XFERSET_SELECTED); // deselect
1007    refresh_fts_selbox();
1008}
1009// ------------------------------
1010//      RuleSet admin window
1011static void initXferAwars(AW_root *awr) {
1012    static bool initialized = false;
1013    if (!initialized) {
1014        awr->awar_string(AWAR_XFERSET_SELECTED,  "", AW_ROOT_DEFAULT);
1015        awr->awar_string(AWAR_XFERSET_COMMENT,   "", AW_ROOT_DEFAULT);
1016        awr->awar_int   (AWAR_XFERSET_UNDEFINED, 0,  AW_ROOT_DEFAULT);
1017
1018        AW_create_fileselection_awars(awr, AWAR_XFERSET_FTSBASE, GB_path_in_arbprop("fts"), ".fts", "");
1019
1020        awr->awar(AWAR_XFERSET_FTSNAME) ->add_callback(fts_filesel_changed_cb);
1021        awr->awar(AWAR_XFERSET_SELECTED)->add_callback(selected_fts_changed_cb);
1022
1023        awr->awar(AWAR_XFERSET_COMMENT)  ->add_callback(makeRootCallback(ruleset_awar_changed_cb, AWAR_XFERSET_COMMENT));
1024        awr->awar(AWAR_XFERSET_UNDEFINED)->add_callback(makeRootCallback(ruleset_awar_changed_cb, AWAR_XFERSET_UNDEFINED));
1025
1026        init_rule_definition_awars(awr);
1027
1028        initialized = true;
1029    }
1030}
1031
1032static void popup_ruleset_admin_window(AW_root *awr) {
1033    static AW_window *aw_select = NULp;
1034    if (!aw_select) {
1035        AW_window_simple *aws = new AW_window_simple;
1036        aws->init(awr, "SELECT_FTS", "Select field transfer set (FTS)");
1037        aws->load_xfig("fts_select.fig");
1038
1039        aws->button_length(8);
1040
1041        aws->at("close");
1042        aws->callback(AW_POPDOWN);
1043        aws->create_button("CLOSE", "CLOSE", "C");
1044
1045        aws->at("help");
1046        aws->callback(makeHelpCallback("xferset.hlp"));
1047        aws->create_button("HELP", "HELP", "H");
1048
1049        aws->at("name");
1050        aws->create_input_field(AWAR_XFERSET_SELECTED);
1051
1052        aws->at("comment");
1053        aws->create_text_field(AWAR_XFERSET_COMMENT);
1054
1055        AW_create_fileselection(aws, AWAR_XFERSET_FTSBASE, "fts", ".", MULTI_DIRS, false);
1056
1057        aws->at("create");
1058        aws->callback(makeWindowCallback(createRuleset_cb));
1059        aws->create_button("CREATE", "CREATE", "N");
1060
1061        aws->at("edit");
1062        aws->callback(makeWindowCallback(editRuleset_cb));
1063        aws->create_button("EDIT", "EDIT", "E");
1064
1065        aws->at("copy");
1066        aws->callback(makeWindowCallback(askCopyMoveRuleset_cb, COPY_RULESET));
1067        aws->create_button("COPY", "COPY", "Y");
1068
1069        aws->at("delete");
1070        aws->callback(makeWindowCallback(deleteRuleset_cb));
1071        aws->create_button("DELETE", "DELETE", "D");
1072
1073        aws->at("rename");
1074        aws->callback(makeWindowCallback(askCopyMoveRuleset_cb, MOVE_RULESET));
1075        aws->create_button("RENAME", "RENAME", "R");
1076
1077        aws->at("none");
1078        aws->callback(makeWindowCallback(noRuleset_cb));
1079        aws->create_button("NONE", "NONE", "O");
1080
1081        aw_select = aws;
1082    }
1083    aw_select->activate();
1084}
1085
1086void XFER_select_RuleSet(AW_window *aww, const char *awar_selected_fts, const AvailableFieldScanner *fieldScanner) {
1087    /*! initializes ruleset GUI to choose/edit 'awar_selected_fts'.
1088     * May be bound via multiple callbacks; will always edit the awar of the last triggered callback.
1089     */
1090
1091    AW_root *awr = aww->get_root();
1092    initXferAwars(awr);
1093    awr->awar(AWAR_XFERSET_SELECTED)->map(awar_selected_fts);
1094
1095    if (fieldScanner != currentFieldScanner) {
1096        currentFieldScanner = fieldScanner;
1097        XFER_refresh_available_fields(awr, currentFieldScanner, SCAN_ALL_FIELDS);
1098    }
1099
1100    popup_ruleset_admin_window(awr);
1101}
1102
1103// --------------------------------------------------------------------------------
1104
1105#ifdef UNIT_TESTS
1106#ifndef TEST_UNIT_H
1107#include <test_unit.h>
1108#endif
1109
1110void TEST_StrUniquifier() { // @@@ move to CORE later
1111    // tests class defined in ../../CORE/StrUniquifier.h
1112
1113    StrUniquifier uniq("->");
1114    TEST_EXPECT_EQUAL(uniq.make_unique_key("hey"), "hey");
1115    TEST_EXPECT_EQUAL(uniq.make_unique_key("hey"), "hey->2");
1116    TEST_EXPECT_EQUAL(uniq.make_unique_key("Hey"), "Hey");
1117    TEST_EXPECT_EQUAL(uniq.make_unique_key("Hey"), "Hey->2");
1118
1119    TEST_EXPECT_EQUAL(uniq.make_unique_key(""), "");
1120    TEST_EXPECT_EQUAL(uniq.make_unique_key(""), "->2");
1121
1122    StrUniquifier fresh(".");
1123    TEST_EXPECT_EQUAL(fresh.make_unique_key(""), "");
1124    TEST_EXPECT_EQUAL(fresh.make_unique_key(""), ".2");
1125}
1126
1127#endif // UNIT_TESTS
1128
1129// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.