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

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