source: branches/species/WINDOW/AW_preset.cxx

Last change on this file was 19691, checked in by westram, 2 weeks ago
  • reintegrates 'macros' into 'trunk'
    • fixes macro IDs for mergetool (completing #870).
      • most common problem:
        • several modules were reused (twice) for items of same type, but in different databases.
        • auto-generated IDs were identical.
  • adds: log:branches/macros@19667:19690
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.0 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : AW_preset.cxx                                      //
4//   Purpose   :                                                    //
5//                                                                  //
6//   Institute of Microbiology (Technical University Munich)        //
7//   http://www.arb-home.de/                                        //
8//                                                                  //
9// ================================================================ //
10
11#ifndef IN_ARB_WINDOW
12#error MODULE_... is not known
13#endif
14
15#include <aw_color_groups.hxx>
16#include "aw_preset.hxx"
17#include "aw_root.hxx"
18#include "aw_awar.hxx"
19#include "aw_device.hxx"
20#include "aw_advice.hxx"
21#include "aw_question.hxx"
22#include "aw_msg.hxx"
23
24#include "aw_def.hxx"
25#include "aw_nawar.hxx"
26#include "aw_xfont.hxx"
27#include "aw_rgb.hxx"
28
29#include <arbdbt.h>
30#include <arb_strbuf.h>
31
32#include <vector>
33#include <map>
34#include <string>
35
36#if defined(DEBUG)
37#include <ctime>
38#endif
39
40
41using std::string;
42
43#define AWAR_RANGE_OVERLAY       "tmp/GCS/range/overlay"          // global toggle (used for all gc-managers!)
44#define AWAR_COLOR_GROUPS_PREFIX "colorGroups"                    // [Note: before arb-7.0 the prefix was "color_groups"]
45#define AWAR_COLOR_GROUPS_USE    AWAR_COLOR_GROUPS_PREFIX "/use"  // int : whether to use the colors in display or not
46
47#define ATPL_GCMAN_LOCAL "GCS/%s"                          // awar prefix for awars local to gc-manager
48#define ATPL_GC_LOCAL    ATPL_GCMAN_LOCAL "/MANAGE_GCS/%s" // awar prefix for awars local to a single gc
49
50#define ALL_FONTS_ID "all_fonts"
51
52#define NO_FONT -1
53#define NO_SIZE -2
54
55// prototypes for motif-only section at bottom of this file:
56static void popup_color_chooser_window(AW_window *aww, const char *awar_name, const char *color_description);
57static void popup_font_chooser_window(AW_window *aww, const char *gc_base_name, const struct gc_desc *gcd);
58
59CONSTEXPR_INLINE bool valid_color_group(int color_group) {
60    return color_group>0 && color_group<=AW_COLOR_GROUPS;
61}
62
63inline const char* gcman_specific_awarname(const char *tpl, const char *gcman_id, const char *localpart) {
64    aw_assert(!GB_check_key(gcman_id));   // has to be a key
65    aw_assert(!GB_check_hkey(localpart)); // has to be a key or hierarchical key
66
67    static SmartCharPtr awar_name;
68    awar_name = GBS_global_string_copy(tpl, gcman_id, localpart);
69    return &*awar_name;
70}
71inline const char* gc_awarname(const char *tpl, const char *gcman_id, const string& colname) {
72    return gcman_specific_awarname(tpl, gcman_id, colname.c_str());
73}
74inline const char* gcman_awarname(const char* gcman_id, const char *localpart) {
75    return gcman_specific_awarname(ATPL_GCMAN_LOCAL "/%s", gcman_id, localpart);
76}
77
78inline const char* color_awarname   (const char* gcman_id, const string& colname) { return gc_awarname(ATPL_GC_LOCAL "/colorname", gcman_id, colname); }
79inline const char* fontname_awarname(const char* gcman_id, const string& colname) { return gc_awarname(ATPL_GC_LOCAL "/font",      gcman_id, colname); }
80inline const char* fontsize_awarname(const char* gcman_id, const string& colname) { return gc_awarname(ATPL_GC_LOCAL "/size",      gcman_id, colname); }
81inline const char* fontinfo_awarname(const char* gcman_id, const string& colname) { return gc_awarname(ATPL_GC_LOCAL "/info",      gcman_id, colname); }
82
83// ----------------------
84//      color groups
85
86struct ColorGroupDef {
87    const char *description;
88    const char *rgb_nt;
89    const char *rgb_e4;
90
91    const char *defaultColorgroupName(int color_group) {
92        const char *result = NULp;
93        if (valid_color_group(color_group)) {
94            if (color_group<10) {
95                result = GBS_global_string(AW_COLOR_GROUP_PREFIX "_%i_%s", color_group, description);
96            }
97            else {
98                result = GBS_global_string(AW_COLOR_GROUP_PREFIX "%2i_%s", color_group, description);
99            }
100        }
101        return result;
102    }
103
104    const char *rgbValue(bool for_edit4) {
105        return for_edit4 ? rgb_e4 : rgb_nt;
106    }
107};
108
109static ColorGroupDef color_group_def[AW_COLOR_GROUPS+1] = {
110    // name           NTREE     EDIT4
111    //                rrggbb    rrggbb
112    { "red",         "D50000", "FFDCDC" }, // 01
113    { "cyan",        "00ffff", "CFFFFF" }, // 02
114    { "green",       "00FF77", "B1FFD6" }, // 03
115    { "purple",      "c700c7", "F1BFF1" }, // 04
116    { "blue",        "0000ff", "D7D7FF" }, // 05
117    { "orange",      "FFCE5B", "FFEAB9" }, // 06
118    { "dark_red",    "AB2323", "DCB8B8" }, // 07
119    { "petrol",      "008888", "8BBBBB" }, // 08
120    { "dark_green",  "008800", "97CB97" }, // 09
121    { "dark_purple", "880088", "B177B1" }, // 10
122    { "dark_blue",   "000088", "9898E2" }, // 11
123    { "olive",       "888800", "C8D98F" }, // 12
124};
125
126inline const char *colorgroupname_awarname(int color_group) {
127    if (!valid_color_group(color_group)) return NULp;
128    return GBS_global_string(AWAR_COLOR_GROUPS_PREFIX "/name%i", color_group);
129}
130inline const char* default_colorgroup_name(int color_group) {
131    return color_group_def[color_group-1].defaultColorgroupName(color_group);
132}
133
134
135const int GC_BACKGROUND = -1;
136const int GC_INVALID    = -2;
137
138const int NO_INDEX = -1;
139
140enum gc_type {
141    GC_TYPE_NORMAL,
142    GC_TYPE_GROUP,
143    GC_TYPE_RANGE,
144};
145
146#define RANGE_INDEX_BITS 4
147#define RANGE_INDEX_MASK ((1<<RANGE_INDEX_BITS)-1)
148
149inline int build_range_gc_number(int range_idx, int color_idx) {
150    aw_assert(range_idx == (range_idx & RANGE_INDEX_MASK));
151    return (color_idx << RANGE_INDEX_BITS) | range_idx;
152}
153
154inline string name2ID(const char *name) { // does not test uniqueness!
155    char   *keyCopy = GBS_string_2_key(name);
156    string  id      = keyCopy;
157    free(keyCopy);
158    return id;
159}
160inline string name2ID(const string& name) { return name2ID(name.c_str()); }
161
162struct gc_desc {
163    // data for one GC
164    // - used to populate color config windows and
165    // - in change-callbacks
166
167    int     gc;              // -1 = background;
168                             // if type!=GC_TYPE_RANGE: [0..n-1] (where n=AW_gc_manager::drag_gc_offset if no gc_ranges defined)
169                             // if type==GC_TYPE_RANGE: contains range- and color-index
170    string  colorlabel;      // label to appear next to chooser
171    string  key;             // key (normally build from colorlabel)
172    bool    has_font;        // show font selector
173    bool    fixed_width_font; // only allow fixed width fonts
174    bool    same_line;       // no line break after this
175    gc_type type;
176
177    gc_desc(int gc_, gc_type type_) :
178        gc(gc_),
179        has_font(true),
180        fixed_width_font(false),
181        same_line(false),
182        type(type_)
183    {}
184
185    bool is_color_group()   const { return type == GC_TYPE_GROUP; }
186    bool belongs_to_range() const { return type == GC_TYPE_RANGE; }
187
188    int get_range_index() const { aw_assert(belongs_to_range()); return gc  & RANGE_INDEX_MASK; }
189    int get_color_index() const { aw_assert(belongs_to_range()); return gc >> RANGE_INDEX_BITS; }
190
191private:
192    bool parse_char(char c) {
193        switch (c) {
194            case '#': fixed_width_font = true; break;
195            case '+': same_line        = true; break;
196            case '-': has_font         = false; break;
197            default : return false;
198        }
199        return true;
200    }
201
202    void correct() {
203        if (same_line && has_font)   same_line = false; // GC needs full line if defining both color and font
204    }
205public:
206
207    const char *parse_decl(const char *decl, const char *id_prefix) {
208        // returns default color
209        int offset = 0;
210        while (decl[offset]) {
211            if (!parse_char(decl[offset])) break;
212            offset++;
213        }
214        correct();
215
216        decl += offset;
217
218        const char *split         = strchr(decl, '$');
219        const char *default_color = NULp;
220        if (split) { // defines a default color
221            colorlabel    = string(decl, split-decl);
222            default_color = split+1;
223        }
224        else {
225            colorlabel    = decl;
226            default_color = "black";
227        }
228
229        key = string(null2empty(id_prefix)) + name2ID(colorlabel);
230
231        return default_color;
232    }
233};
234
235// --------------------------------
236//      types for color-ranges
237
238enum gc_range_type {
239    GC_RANGE_INVALID,
240    GC_RANGE_LINEAR,
241    GC_RANGE_CYCLIC,
242    GC_RANGE_PLANAR,
243    GC_RANGE_SPATIAL,
244};
245
246class gc_range {
247    string        name; // name of range shown in config window
248    string        id;
249    gc_range_type type;
250
251    int index;       // in range-array of AW_gc_manager
252    int color_count; // number of support colors (ie. customizable colors)
253    int gc_index;    // of first support color in GCs-array of AW_gc_manager
254
255public:
256    gc_range(const string& name_, gc_range_type type_, int index_, int gc_index_) :
257        name(name_),
258        id(name2ID(name)),
259        type(type_),
260        index(index_),
261        color_count(0),
262        gc_index(gc_index_)
263    {}
264
265    void add_color(const string& colordef, AW_gc_manager *gcman);
266    void update_colors(const AW_gc_manager *gcman) const;
267
268    AW_rgb_normalized get_color(int idx, const AW_gc_manager *gcman) const;
269
270    const string& get_name() const { return name; }
271    const string& get_id() const { return id; }
272    gc_range_type get_type() const { return type; }
273    int get_dimension() const {
274        switch (type) {
275            case GC_RANGE_LINEAR:
276            case GC_RANGE_CYCLIC:  return 1;
277
278            case GC_RANGE_PLANAR:  return 2;
279            case GC_RANGE_SPATIAL: return 3;
280            case GC_RANGE_INVALID: aw_assert(0); break;
281        }
282        return 0;
283    }
284};
285
286// --------------------
287//      GC manager
288
289class AW_gc_manager : virtual Noncopyable {
290    const char *gc_base_name;
291    AW_device  *device;
292    int         drag_gc_offset; // = drag_gc (as used by clients)
293
294    int first_colorgroup_idx; // index into 'GCs' or NO_INDEX (if no color groups defined)
295
296    AW_window *aww;             // motif only (colors get allocated in window)
297    int        colorindex_base; // motif-only (colorindex of background-color)
298
299    typedef std::vector<gc_desc>  gc_container;
300    typedef std::vector<gc_range> gc_range_container;
301
302    gc_container       GCs;
303    gc_range_container color_ranges;
304    unsigned           active_range_number; // offset into 'color_ranges'
305
306    GcChangedCallback changed_cb;
307
308    mutable bool          suppress_changed_cb;  // if true -> collect cb-trigger in 'did_suppress_change'
309    mutable GcChange      did_suppress_change;  // "max" change suppressed so far (will be triggered by delay_changed_callbacks())
310    static const GcChange GC_NOTHING_CHANGED = GcChange(0);
311
312#if defined(ASSERTION_USED)
313    bool valid_idx(int idx) const { return idx>=0 && idx<int(GCs.size()); }
314    bool valid_gc(int gc) const {
315        // does not test gc is really valid, just tests whether it is completely out-of-bounds
316        return gc>=GC_BACKGROUND && gc < drag_gc_offset;
317    }
318#endif
319
320    AW_color_idx colorindex(int gc) const {
321        aw_assert(valid_gc(gc));
322        return AW_color_idx(colorindex_base+gc+1);
323    }
324    static void ignore_change_cb(GcChange) {}
325
326    void allocate_gc(int gc) const;
327
328    void update_gc_color_internal(int gc, const char *color) const;
329    void update_range_colors(const gc_desc& gcd) const;
330    void update_range_font(int fname, int fsize) const;
331
332public:
333    static const char **color_group_defaults;
334
335    static bool use_color_groups;
336    static bool show_range_overlay;
337
338    static bool color_groups_initialized() { return color_group_defaults; }
339
340    AW_gc_manager(const char*  name, AW_device* device_, int drag_gc_offset_,
341                  AW_window   *aww_, int colorindex_base_)
342        : gc_base_name(name),
343          device(device_),
344          drag_gc_offset(drag_gc_offset_),
345          first_colorgroup_idx(NO_INDEX),
346          aww(aww_),
347          colorindex_base(colorindex_base_),
348          active_range_number(-1), // => will be initialized from init_color_ranges
349          changed_cb(makeGcChangedCallback(ignore_change_cb)),
350          suppress_changed_cb(false),
351          did_suppress_change(GC_NOTHING_CHANGED)
352    {}
353
354    void init_all_fonts() const;
355    void update_all_fonts(bool sizeChanged) const;
356
357    void init_color_ranges(int& gc);
358    bool has_color_range() const { return !color_ranges.empty(); }
359    int first_range_gc() const { return drag_gc_offset - AW_RANGE_COLORS; }
360
361    bool has_color_groups() const { return first_colorgroup_idx != NO_INDEX; }
362    bool has_variable_width_font() const;
363
364    int size() const { return GCs.size(); }
365    const gc_desc& get_gc_desc(int idx) const {
366        aw_assert(valid_idx(idx));
367        return GCs[idx];
368    }
369    const char *get_base_name() const { return gc_base_name; }
370    int get_drag_gc() const { return drag_gc_offset; }
371
372    void add_gc      (const char *gc_description, int& gc, gc_type type, const char *id_prefix = NULp);
373    void add_gc_range(const char *gc_description);
374    void reserve_gcs (const char *gc_description, int& gc);
375    void add_color_groups(int& gc);
376
377    void update_gc_color(int idx) const;
378    void update_gc_font(int idx) const;
379
380    void update_range_gc_color(int idx, const char *color) const {
381        update_gc_color_internal(first_range_gc()+idx, color);
382    }
383
384    void create_gc_buttons(AW_window *aww, gc_type for_gc_type);
385
386    void set_changed_cb(const GcChangedCallback& ccb) { changed_cb = ccb; }
387    void trigger_changed_cb(GcChange whatChanged) const {
388        if (suppress_changed_cb) {
389            did_suppress_change = GcChange(std::max(whatChanged, did_suppress_change));
390        }
391        else {
392#if defined(DEBUG) && 0
393            fprintf(stderr, "[changed_cb] @ %zu\n", clock());
394#endif
395            changed_cb(whatChanged);
396        }
397    }
398
399    void delay_changed_callbacks(bool suppress) const {
400        aw_assert(suppress != suppress_changed_cb);
401
402        suppress_changed_cb = suppress;
403        if (suppress) {
404            did_suppress_change = GC_NOTHING_CHANGED;
405        }
406        else if (did_suppress_change>GC_NOTHING_CHANGED) {
407            trigger_changed_cb(did_suppress_change);
408        }
409    }
410
411    const char *get_current_color(int idx) const {
412        return AW_root::SINGLETON->awar(color_awarname(get_base_name(), get_gc_desc(idx).key))->read_char_pntr();
413    }
414
415    void getColorRangeNames(int dimension, ConstStrArray& ids, ConstStrArray& names) const;
416    void activateColorRange(const char *id);
417    const char *getActiveColorRangeID(int *dimension) const;
418
419    const char *awarname_active_range() const { return gcman_awarname(get_base_name(), "range/active"); }
420    void active_range_changed_cb(AW_root *awr);
421};
422
423const char **AW_gc_manager::color_group_defaults = NULp;
424
425bool AW_gc_manager::use_color_groups   = false;
426bool AW_gc_manager::show_range_overlay = false;
427
428// ---------------------------
429//      GC awar callbacks
430
431void AW_gc_manager::update_gc_font(int idx) const {
432    aw_assert(valid_idx(idx));
433
434    static bool avoid_recursion = false;
435    if (avoid_recursion) return;
436    LocallyModify<bool> flag(avoid_recursion, true);
437
438    const gc_desc& gcd0 = GCs[idx];
439    aw_assert(gcd0.gc != GC_BACKGROUND); // background has no font
440
441    AW_awar *awar_fontname = AW_root::SINGLETON->awar(fontname_awarname(gc_base_name, gcd0.key));
442    AW_awar *awar_fontsize = AW_root::SINGLETON->awar(fontsize_awarname(gc_base_name, gcd0.key));
443    AW_awar *awar_fontinfo = AW_root::SINGLETON->awar(fontinfo_awarname(gc_base_name, gcd0.key));
444
445    int fname = awar_fontname->read_int();
446    int fsize = awar_fontsize->read_int();
447
448    if (fname == NO_FONT) return;
449
450    int found_font_size;
451    device->set_font(gcd0.gc,                fname, fsize, &found_font_size);
452    device->set_font(gcd0.gc+drag_gc_offset, fname, fsize, NULp);
453
454    bool autocorrect_fontsize = (found_font_size != fsize) && (found_font_size != -1);
455    if (autocorrect_fontsize) {
456        awar_fontsize->write_int(found_font_size);
457        fsize = found_font_size;
458    }
459
460    // set font of all following GCs which do NOT define a font themselves
461    for (int i = idx+1; i<int(GCs.size()); ++i) {
462        const gc_desc& gcd = GCs[i];
463        if (gcd.has_font) break; // abort if GC defines its own font
464        if (gcd.belongs_to_range()) {
465            update_range_font(fname, fsize);
466            break; // all leftover GCs belong to ranges = > stop here
467        }
468
469        device->set_font(gcd.gc,                fname, fsize, NULp);
470        device->set_font(gcd.gc+drag_gc_offset, fname, fsize, NULp);
471    }
472
473    awar_fontinfo->write_string(GBS_global_string("%s | %i", AW_get_font_shortname(fname), fsize));
474
475    trigger_changed_cb(GC_FONT_CHANGED);
476}
477static void gc_fontOrSize_changed_cb(AW_root*, AW_gc_manager *mgr, int idx) {
478    mgr->update_gc_font(idx);
479}
480inline bool font_has_fixed_width(AW_font aw_font_nr) {
481    return AW_font_2_xfig(aw_font_nr) < 0;
482}
483void AW_gc_manager::update_all_fonts(bool sizeChanged) const {
484    AW_root *awr = AW_root::SINGLETON;
485
486    int fname = awr->awar(fontname_awarname(gc_base_name, ALL_FONTS_ID))->read_int();
487    int fsize = awr->awar(fontsize_awarname(gc_base_name, ALL_FONTS_ID))->read_int();
488
489    if (fname == NO_FONT) return;
490
491    delay_changed_callbacks(true); // temp. disable callbacks
492    for (gc_container::const_iterator g = GCs.begin(); g != GCs.end(); ++g) {
493        if (g->has_font) {
494            if (sizeChanged) {
495                awr->awar(fontsize_awarname(gc_base_name, g->key))->write_int(fsize);
496            }
497            else {
498                bool update = !g->fixed_width_font || font_has_fixed_width(fname);
499                if (update) awr->awar(fontname_awarname(gc_base_name, g->key))->write_int(fname);
500            }
501        }
502    }
503    delay_changed_callbacks(false);
504}
505static void all_fontsOrSizes_changed_cb(AW_root*, const AW_gc_manager *mgr, bool sizeChanged) {
506    mgr->update_all_fonts(sizeChanged);
507}
508
509void AW_gc_manager::update_gc_color_internal(int gc, const char *color) const {
510    AW_color_idx colorIdx = colorindex(gc);
511    aww->alloc_named_data_color(colorIdx, color);
512
513    if (gc == GC_BACKGROUND && colorIdx == AW_DATA_BG) {
514        // if background color changes, all drag-gc colors need to be updated
515        // (did not understand why, just refactored existing code --ralf)
516
517        for (int i = 1; i<size(); ++i) {
518            const gc_desc& gcd = GCs[i];
519            if (gcd.belongs_to_range()) break; // do not update range-GCs here
520
521            int g    = gcd.gc;
522            colorIdx = colorindex(g);
523            device->set_foreground_color(g + drag_gc_offset, colorIdx);
524        }
525        if (has_color_range()) { // update drag GCs of all range GCs
526            for (int i = 0; i<AW_RANGE_COLORS; ++i) {
527                int g = first_range_gc()+i;
528                colorIdx = colorindex(g);
529                device->set_foreground_color(g + drag_gc_offset, colorIdx);
530            }
531        }
532    }
533    else {
534        if (gc == GC_BACKGROUND) gc = 0; // special case: background color of bottom-area (only used by arb_phylo)
535
536        device->set_foreground_color(gc,                  colorIdx);
537        device->set_foreground_color(gc + drag_gc_offset, colorIdx);
538    }
539}
540
541AW_rgb_normalized gc_range::get_color(int idx, const AW_gc_manager *gcman) const {
542    aw_assert(idx>=0 && idx<color_count);
543    return AW_rgb_normalized(gcman->get_current_color(gc_index+idx));
544}
545
546STATIC_ASSERT(AW_PLANAR_COLORS*AW_PLANAR_COLORS == AW_RANGE_COLORS); // Note: very strong assertion (could also use less than AW_RANGE_COLORS)
547STATIC_ASSERT(AW_SPATIAL_COLORS*AW_SPATIAL_COLORS*AW_SPATIAL_COLORS == AW_RANGE_COLORS);
548
549void gc_range::update_colors(const AW_gc_manager *gcman) const {
550    /*! recalculate colors of a range (called after one color changed)
551     * @param gcman    the GC manager
552     */
553
554    // @@@ try HSV color blending as alternative
555
556    if (type == GC_RANGE_LINEAR) {
557        aw_assert(color_count == 2); // currently exactly 2 support-colors are required for linear ranges
558
559        AW_rgb_normalized low  = get_color(0, gcman);
560        AW_rgb_normalized high = get_color(1, gcman);
561
562        AW_rgb_diff low2high = high-low;
563        for (int i = 0; i<AW_RANGE_COLORS; ++i) { // blend colors
564            float factor = i/float(AW_RANGE_COLORS-1);
565            gcman->update_range_gc_color(i, AW_rgb16(low + factor*low2high).ascii());
566        }
567    }
568    else if (type == GC_RANGE_CYCLIC) {
569        aw_assert(color_count >= 3); // less than 3 colors does not make sense for cyclic ranges!
570
571        AW_rgb_normalized low = get_color(0, gcman);
572        int               i1  = 0;
573        for (int part = 0; part<color_count; ++part) {
574            AW_rgb_normalized high     = get_color((part+1)%color_count, gcman);
575            AW_rgb_diff       low2high = high-low;
576
577            int i2 = AW_RANGE_COLORS * (float(part+1)/color_count);
578            aw_assert(implicated((part+1) == color_count, i2 == AW_RANGE_COLORS));
579
580            for (int i = i1; i<i2; ++i) { // blend colors
581                int   o      = i-i1;
582                float factor = o/float(i2-i1-1);
583                gcman->update_range_gc_color(i, AW_rgb16(low + factor*low2high).ascii());
584            }
585
586            low = high;
587            i1  = i2;
588        }
589    }
590    else if (type == GC_RANGE_PLANAR) {
591        aw_assert(color_count == 3); // currently exactly 3 support-colors are required for planar ranges
592
593        AW_rgb_normalized low  = get_color(0, gcman);
594        AW_rgb_normalized dim1 = get_color(1, gcman);
595        AW_rgb_normalized dim2 = get_color(2, gcman);
596
597        AW_rgb_diff low2dim1 = dim1-low;
598        AW_rgb_diff low2dim2 = dim2-low;
599
600        for (int i1 = 0; i1<AW_PLANAR_COLORS; ++i1) {
601            float       fact1 = i1/float(AW_PLANAR_COLORS-1);
602            AW_rgb_diff diff1 = fact1*low2dim1;
603
604            for (int i2 = 0; i2<AW_PLANAR_COLORS; ++i2) {
605                float       fact2 = i2/float(AW_PLANAR_COLORS-1);
606                AW_rgb_diff diff2 = fact2*low2dim2;
607
608                AW_rgb_normalized mixcol = low + (diff1+diff2);
609
610                gcman->update_range_gc_color(i1*AW_PLANAR_COLORS+i2, AW_rgb16(mixcol).ascii());
611            }
612        }
613    }
614    else if (type == GC_RANGE_SPATIAL) {
615        aw_assert(color_count == 4); // currently exactly 4 support-colors are required for planar ranges
616
617        AW_rgb_normalized low  = get_color(0, gcman);
618        AW_rgb_normalized dim1 = get_color(1, gcman);
619        AW_rgb_normalized dim2 = get_color(2, gcman);
620        AW_rgb_normalized dim3 = get_color(3, gcman);
621
622        AW_rgb_diff low2dim1 = dim1-low;
623        AW_rgb_diff low2dim2 = dim2-low;
624        AW_rgb_diff low2dim3 = dim3-low;
625
626        for (int i1 = 0; i1<AW_SPATIAL_COLORS; ++i1) {
627            float       fact1 = i1/float(AW_SPATIAL_COLORS-1);
628            AW_rgb_diff diff1 = fact1*low2dim1;
629
630            for (int i2 = 0; i2<AW_SPATIAL_COLORS; ++i2) {
631                float       fact2 = i2/float(AW_SPATIAL_COLORS-1);
632                AW_rgb_diff diff2 = fact2*low2dim2;
633
634                for (int i3 = 0; i3<AW_SPATIAL_COLORS; ++i3) {
635                    float       fact3 = i3/float(AW_SPATIAL_COLORS-1);
636                    AW_rgb_diff diff3 = fact3*low2dim3;
637
638                    AW_rgb_normalized mixcol = low + (diff1+diff2+diff3);
639                    gcman->update_range_gc_color((i1*AW_SPATIAL_COLORS+i2)*AW_SPATIAL_COLORS+i3, AW_rgb16(mixcol).ascii());
640                }
641            }
642        }
643    }
644    else {
645        aw_assert(0); // unsupported range-type
646    }
647}
648void AW_gc_manager::update_range_colors(const gc_desc& gcd) const {
649    int defined_ranges = color_ranges.size();
650    int range_idx      = gcd.get_range_index();
651
652    if (range_idx<defined_ranges) {
653        const gc_range& gcr = color_ranges[range_idx];
654        gcr.update_colors(this);
655    }
656}
657void AW_gc_manager::update_range_font(int fname, int fsize) const {
658    // set font for all GCs belonging to color-range
659    int first_gc = first_range_gc();
660    for (int i = 0; i<AW_RANGE_COLORS; ++i) {
661        int gc = first_gc+i;
662        device->set_font(gc,                fname, fsize, NULp);
663        device->set_font(gc+drag_gc_offset, fname, fsize, NULp);
664    }
665}
666
667void AW_gc_manager::update_gc_color(int idx) const {
668    aw_assert(valid_idx(idx));
669
670    const gc_desc&  gcd   = GCs[idx];
671    const char     *color = AW_root::SINGLETON->awar(color_awarname(gc_base_name, gcd.key))->read_char_pntr();
672
673    if (gcd.belongs_to_range()) {
674        update_range_colors(gcd); // @@@ should not happen during startup and only if affected range is the active range
675    }
676    else {
677        update_gc_color_internal(gcd.gc, color);
678    }
679
680    trigger_changed_cb(GC_COLOR_CHANGED);
681}
682static void gc_color_changed_cb(AW_root*, AW_gc_manager *mgr, int idx) {
683    mgr->update_gc_color(idx);
684}
685
686static void color_group_name_changed_cb(AW_root *awr) {
687    static bool warned = false;
688    if (!warned) {
689        AW_advice("Color group names are used at various places of the interface.\n"
690                  "To activate the changed names everywhere, you have to\n"
691                  "save properties and restart the program.",
692                  AW_ADVICE_TOGGLE, "Color group name has been changed", NULp);
693        warned = true;
694    }
695
696    for (int i = 1; i <= AW_COLOR_GROUPS; ++i) {
697        const char *awarname            = colorgroupname_awarname(i);
698        AW_awar    *awar_colorgroupname = awr->awar(awarname);
699        char       *content             = awar_colorgroupname->read_string();
700        size_t      len                 = strlen(content);
701        if (len>AW_COLOR_GROUP_NAME_LEN) {
702            content[AW_COLOR_GROUP_NAME_LEN] = 0;
703            awar_colorgroupname->write_string(content);
704            aw_message("the colorgroup name was too long and has been truncated.");
705        }
706        free(content);
707    }
708}
709
710static void color_group_use_changed_cb(AW_root *awr, AW_gc_manager *gcmgr) {
711    AW_gc_manager::use_color_groups = awr->awar(AWAR_COLOR_GROUPS_USE)->read_int();
712    gcmgr->trigger_changed_cb(GC_COLOR_GROUP_USE_CHANGED);
713}
714static void range_overlay_changed_cb(AW_root *awr, AW_gc_manager *gcmgr) {
715    AW_gc_manager::show_range_overlay = awr->awar(AWAR_RANGE_OVERLAY)->read_int();
716    gcmgr->trigger_changed_cb(GC_COLOR_GROUP_USE_CHANGED);
717}
718
719// ----------------------------
720//      define color-ranges
721
722void gc_range::add_color(const string& colordef, AW_gc_manager *gcman) {
723    int gc = build_range_gc_number(index, color_count);
724    gcman->add_gc(colordef.c_str(), gc, GC_TYPE_RANGE, get_id().c_str());
725    color_count++;
726}
727
728void AW_gc_manager::add_gc_range(const char *gc_description) {
729    GB_ERROR    error = NULp;
730    const char *comma = strchr(gc_description+1, ',');
731    if (!comma) error = "',' expected";
732    else {
733        string      range_name(gc_description+1, comma-gc_description-1);
734        const char *colon = strchr(comma+1, ':');
735        if (!colon) error = "':' expected";
736        else {
737            gc_range_type rtype = GC_RANGE_INVALID;
738            string        range_type(comma+1, colon-comma-1);
739
740            if      (range_type == "linear")  rtype = GC_RANGE_LINEAR;
741            else if (range_type == "planar")  rtype = GC_RANGE_PLANAR;
742            else if (range_type == "spatial") rtype = GC_RANGE_SPATIAL;
743            else if (range_type == "cyclic")  rtype = GC_RANGE_CYCLIC;
744
745            if (rtype == GC_RANGE_INVALID) {
746                error = GBS_global_string("invalid range-type '%s'", range_type.c_str());
747            }
748
749            if (!error) {
750                gc_range range(range_name, rtype, color_ranges.size(), GCs.size());
751
752                const char *color_start = colon+1;
753                comma                   = strchr(color_start, ',');
754                while (comma) {
755                    range.add_color(string(color_start, comma-color_start), this);
756                    color_start = comma+1;
757                    comma       = strchr(color_start, ',');
758                }
759                range.add_color(string(color_start), this);
760
761                color_ranges.push_back(range); // add to manager
762            }
763        }
764    }
765
766    if (error) {
767        GBK_terminatef("Failed to parse color range specification '%s'\n(Reason: %s)", gc_description, error);
768    }
769}
770
771void AW_gc_manager::active_range_changed_cb(AW_root *awr) {
772    // read AWAR (awarname_active_range), compare with 'active_range_number', if differs = > update colors
773    // (triggered by AWAR)
774
775    unsigned wanted_range_number = awr->awar(awarname_active_range())->read_int();
776    if (wanted_range_number != active_range_number) {
777        aw_assert(wanted_range_number<color_ranges.size());
778
779        active_range_number = wanted_range_number;
780        const gc_range& active_range = color_ranges[active_range_number];
781        active_range.update_colors(this);
782    }
783}
784static void active_range_changed_cb(AW_root *awr, AW_gc_manager *gcman) { gcman->active_range_changed_cb(awr); }
785
786void AW_gc_manager::init_color_ranges(int& gc) {
787    if (has_color_range()) {
788        aw_assert(gc == first_range_gc()); // 'drag_gc_offset' is wrong (is passed as 'base_drag' to AW_manage_GC)
789        int last_gc = gc + AW_RANGE_COLORS-1;
790        while (gc<=last_gc) allocate_gc(gc++);
791
792        active_range_changed_cb(AW_root::SINGLETON); // either initializes default-range (=0) or the range-in-use when saving props
793    }
794}
795void AW_gc_manager::activateColorRange(const char *id) {
796    unsigned wanted_range_number = 0;
797    for (gc_range_container::const_iterator r = color_ranges.begin(); r != color_ranges.end(); ++r) {
798        const gc_range& range = *r;
799        if (range.get_id() == id) {
800            aw_assert(wanted_range_number<color_ranges.size());
801            AW_root::SINGLETON->awar(awarname_active_range())->write_int(wanted_range_number); // => will update color range of all identical gc-managers
802            break;
803        }
804        ++wanted_range_number;
805    }
806}
807const char *AW_gc_manager::getActiveColorRangeID(int *dimension) const {
808    const gc_range& range     = color_ranges[active_range_number];
809    if (dimension) *dimension = range.get_dimension();
810    return range.get_id().c_str();
811}
812
813
814void AW_gc_manager::getColorRangeNames(int dimension, ConstStrArray& ids, ConstStrArray& names) const {
815    for (gc_range_container::const_iterator r = color_ranges.begin(); r != color_ranges.end(); ++r) {
816        const gc_range& range = *r;
817        if (range.get_dimension() == dimension) {
818            ids.put(range.get_id().c_str());
819            names.put(range.get_name().c_str());
820        }
821    }
822}
823
824// -------------------------
825//      reserve/add GCs
826
827void AW_gc_manager::reserve_gcs(const char *gc_description, int& gc) {
828    aw_assert(gc_description[0] == '!');
829
830    // just reserve one or several GCs (eg. done in arb_pars)
831    int amount = atoi(gc_description+1);
832    aw_assert(amount>=1);
833
834    gc += amount;
835}
836
837void AW_gc_manager::allocate_gc(int gc) const {
838    device->new_gc(gc);
839    device->set_line_attributes(gc, 1, AW_SOLID);
840    device->set_function(gc, AW_COPY);
841
842    int gc_drag = gc + drag_gc_offset;
843    device->new_gc(gc_drag);
844    device->set_line_attributes(gc_drag, 1, AW_SOLID);
845    device->set_function(gc_drag, AW_XOR);
846    device->establish_default(gc_drag);
847}
848
849void AW_gc_manager::add_gc(const char *gc_description, int& gc, gc_type type, const char *id_prefix) {
850    aw_assert(!strchr("*!&", gc_description[0]));
851    aw_assert(implicated(type != GC_TYPE_RANGE, !has_color_range())); // color ranges have to be specified AFTER all other color definition strings (in call to AW_manage_GC)
852
853    GCs.push_back(gc_desc(gc, type));
854    gc_desc &gcd = GCs.back();
855    int      idx = int(GCs.size()-1); // index position where new GC has been added
856
857    if (gcd.is_color_group() && first_colorgroup_idx == NO_INDEX) {
858        first_colorgroup_idx = idx; // remember index of first color-group
859    }
860
861    bool is_background = gc == GC_BACKGROUND;
862    bool alloc_gc      = (!is_background || colorindex_base != AW_DATA_BG) && !gcd.belongs_to_range();
863    if (alloc_gc) {
864        allocate_gc(gc + is_background); // increment only happens for AW_BOTTOM_AREA defining GCs
865    }
866
867    const char *default_color = gcd.parse_decl(gc_description, id_prefix);
868
869    aw_assert(strcmp(gcd.key.c_str(), ALL_FONTS_ID) != 0); // id is reserved
870#if defined(ASSERTION_USED)
871    {
872        const string& lastId = gcd.key;
873        for (int i = 0; i<idx; ++i) {
874            const gc_desc& check = GCs[i];
875            aw_assert(check.key != lastId); // duplicate GC-ID!
876        }
877    }
878#endif
879
880#if !defined(ARB_OPENGL)
881    // normally the first GC should define a font (wrong for RNA3D)
882    aw_assert(implicated(gc == 0 && type != GC_TYPE_RANGE, gcd.has_font));
883#endif
884
885    if (default_color[0] == '{') {
886        // use current value of an already defined color as default for current color:
887        // (e.g. used in SECEDIT)
888        const char *close_brace = strchr(default_color+1, '}');
889        aw_assert(close_brace); // missing '}' in reference!
890        char *referenced_colorlabel = ARB_strpartdup(default_color+1, close_brace-1);
891
892        IF_ASSERTION_USED(bool found = false);
893
894        for (gc_container::iterator g = GCs.begin(); g != GCs.end(); ++g) {
895            if (strcmp(g->colorlabel.c_str(), referenced_colorlabel) == 0) {
896                default_color = AW_root::SINGLETON->awar(color_awarname(gc_base_name, g->key))->read_char_pntr(); // @@@ should use default value (not current value)
897                IF_ASSERTION_USED(found = true);
898                break;
899            }
900        }
901
902        aw_assert(found); // unknown default color!
903        free(referenced_colorlabel);
904    }
905
906    // @@@ add assertion vs duplicate ids here?
907    AW_root::SINGLETON->awar_string(color_awarname(gc_base_name, gcd.key), default_color)
908        ->add_callback(makeRootCallback(gc_color_changed_cb, this, idx));
909    gc_color_changed_cb(NULp, this, idx);
910
911    if (!is_background) { // no font for background
912        if (gcd.has_font) {
913            aw_assert(!gcd.belongs_to_range()); // no fonts supported for ranges
914
915            AW_font default_font = gcd.fixed_width_font ? AW_DEFAULT_FIXED_FONT : AW_DEFAULT_NORMAL_FONT;
916
917            RootCallback fontChange_cb = makeRootCallback(gc_fontOrSize_changed_cb, this, idx);
918            AW_root::SINGLETON->awar_int(fontname_awarname(gc_base_name, gcd.key), default_font)->add_callback(fontChange_cb);
919            AW_root::SINGLETON->awar_int(fontsize_awarname(gc_base_name, gcd.key), DEF_FONTSIZE)->add_callback(fontChange_cb);
920            AW_root::SINGLETON->awar_string(fontinfo_awarname(gc_base_name, gcd.key), "<select font>");
921        }
922        // Note: fonts are not initialized here. This is done in init_all_fonts() after all GCs have been defined.
923    }
924
925    gc++;
926}
927void AW_gc_manager::init_all_fonts() const {
928    int      ad_font = -1;
929    int      ad_size = -1;
930    AW_root *awr     = AW_root::SINGLETON;
931
932    // initialize fonts of all defined GCs:
933    for (int idx = 0; idx<int(GCs.size()); ++idx) {
934        const gc_desc& gcd = GCs[idx];
935        if (gcd.has_font) {
936            update_gc_font(idx);
937
938            if (ad_font == -1) {
939                ad_font = awr->awar(fontname_awarname(gc_base_name, gcd.key))->read_int();
940                ad_size = awr->awar(fontsize_awarname(gc_base_name, gcd.key))->read_int();
941            }
942        }
943    }
944
945    // init global font awars (used to set ALL fonts)
946    AW_root::SINGLETON->awar_int(fontname_awarname(gc_base_name, ALL_FONTS_ID), ad_font)->add_callback(makeRootCallback(all_fontsOrSizes_changed_cb, this, false));
947    AW_root::SINGLETON->awar_int(fontsize_awarname(gc_base_name, ALL_FONTS_ID), ad_size)->add_callback(makeRootCallback(all_fontsOrSizes_changed_cb, this, true));
948}
949
950bool AW_gc_manager::has_variable_width_font() const {
951    for (gc_container::const_iterator g = GCs.begin(); g != GCs.end(); ++g) {
952        if (g->has_font && !g->fixed_width_font) return true;
953    }
954    return false;
955}
956
957void AW_init_colorgroup_name_awars(AW_root *awr) {
958    for (int i = 1; i <= AW_COLOR_GROUPS; ++i) {
959        awr->awar_string(colorgroupname_awarname(i), default_colorgroup_name(i))->add_callback(color_group_name_changed_cb);
960    }
961}
962
963void AW_gc_manager::add_color_groups(int& gc) {
964    aw_assert(AW_gc_manager::color_groups_initialized());
965    const char **color_group_gc_default = AW_gc_manager::color_group_defaults;
966    while (*color_group_gc_default) {
967        add_gc(*color_group_gc_default++, gc, GC_TYPE_GROUP);
968    }
969}
970
971AW_gc_manager *AW_manage_GC(AW_window                *aww,
972                            const char               *gc_base_name,
973                            AW_device                *device,
974                            int                       base_drag,
975                            AW_GCM_AREA               area,
976                            const GcChangedCallback&  changecb,
977                            const char               *default_background_color,
978                            ...)
979{
980    /*!
981     * creates some GC pairs:
982     * - one for normal operation,
983     * - the other for drag mode
984     * eg.
985     *     AW_manage_GC(aww, "ARB_NT", device, 0, 1, AW_GCM_DATA_AREA, my_expose_cb, "white","#sequence", NULp);
986     *     (see implementation for more details on parameter strings)
987     *     will create 2 GCs:
988     *      GC 0 (normal)
989     *      GC 1 (drag)
990     *  Don't forget the NULp sentinel at the end of the GC definition list.
991     *
992     *  When the GCs are modified the 'changecb' is called
993     *
994     * @param aww          base window
995     * @param gc_base_name (usually the window_id, prefixed to awars)
996     * @param device       screen device
997     * @param base_drag    one after last gc
998     * @param area         AW_GCM_DATA_AREA or AW_GCM_WINDOW_AREA (motif only)
999     * @param changecb     cb if changed
1000     * @param define_color_groups  true -> add colors for color groups
1001     * @param ...                  NULp terminated list of \0 terminated <definition>s:
1002     *
1003     *   <definition>::= <gcdef>|<reserve>|<rangedef>|<groupdef>
1004     *   <reserve>   ::= !<num>                                     "reserves <num> gc-numbers; used eg. in arb_pars to sync GCs with arb_ntree"
1005     *   <gcdef>     ::= [<option>+]<descript>['$'<default>]        "defines a GC"
1006     *   <option>    ::= '#'|'+'|'-'                                "'-'=no font; '#'=only fixed fonts; '+'=append next GC on same line"
1007     *   <descript>  ::=                                            "description of GC; shown as label; has to be unique when converted to key"
1008     *   <default>   ::= <xcolor>|<gcref>                           "default color of GC"
1009     *   <xcolor>    ::=                                            "accepts any valid X-colorname (e.g. 'white', '#c0ff40')"
1010     *   <gcref>     ::= '{'<descript>'}'                           "reference to another earlier defined gc"
1011     *   <rangedef>  ::= '*'<name>','<type>':'<gcdef>[','<gcdef>]+  "defines a GC-range (with one <gcdef> for each support color)"
1012     *   <name>      ::=                                            "description of range"
1013     *   <type>      ::= 'linear'|'cyclic'|'planar'|'spatial'       "rangetype; implies number of required support colors: linear=2 cyclic=3-N planar=3 spatial=4"
1014     *   <groupdef>  ::= '&color_groups'                            "insert color-groups here"
1015     */
1016
1017    aw_assert(default_background_color[0]);
1018
1019#if defined(ASSERTION_USED)
1020    int base_drag_given = base_drag;
1021#endif
1022
1023    AW_root *aw_root = AW_root::SINGLETON;
1024
1025    int            colidx_base = area == AW_GCM_DATA_AREA ? AW_DATA_BG : AW_WINDOW_BG;
1026    AW_gc_manager *gcmgr       = new AW_gc_manager(gc_base_name, device, base_drag, aww, colidx_base);
1027
1028    int  gc = GC_BACKGROUND; // gets incremented by add_gc
1029    char background[50];
1030    sprintf(background, "-background$%s", default_background_color);
1031    gcmgr->add_gc(background, gc, GC_TYPE_NORMAL);
1032
1033    va_list parg;
1034    va_start(parg, default_background_color);
1035
1036    bool defined_color_groups = false;
1037
1038    const char *id;
1039    while ( (id = va_arg(parg, char*)) ) {
1040        switch (id[0]) {
1041            case '!': gcmgr->reserve_gcs(id, gc); break;
1042            case '*': gcmgr->add_gc_range(id); break;
1043            case '&':
1044                if (strcmp(id, "&color_groups") == 0) { // trigger use of color groups
1045                    aw_assert(!defined_color_groups); // color-groups trigger specified twice!
1046                    if (!defined_color_groups) {
1047                        gcmgr->add_color_groups(gc);
1048                        defined_color_groups = true;
1049                    }
1050                }
1051                else { aw_assert(0); } // unknown trigger
1052                break;
1053            default: gcmgr->add_gc(id, gc, GC_TYPE_NORMAL); break;
1054        }
1055    }
1056    va_end(parg);
1057
1058    {
1059        AW_awar *awar_useGroups = aw_root->awar_int(AWAR_COLOR_GROUPS_USE, 1);
1060
1061        awar_useGroups->add_callback(makeRootCallback(color_group_use_changed_cb, gcmgr));
1062        AW_gc_manager::use_color_groups = awar_useGroups->read_int();
1063    }
1064
1065    aw_root->awar_int(AWAR_RANGE_OVERLAY, 0)->add_callback(makeRootCallback(range_overlay_changed_cb, gcmgr));
1066    aw_root->awar_int(gcmgr->awarname_active_range(), 0)->add_callback(makeRootCallback(active_range_changed_cb, gcmgr));
1067
1068    gcmgr->init_color_ranges(gc);
1069    gcmgr->init_all_fonts();
1070
1071    // installing changed callback here avoids that it gets triggered by initializing GCs
1072    gcmgr->set_changed_cb(changecb);
1073
1074#if defined(ASSERTION_USED)
1075    if (strcmp(gc_base_name, "ARB_PARSIMONY") == 0) {
1076        // ARB_PARSIMONY does not define color-ranges, but uses same GCs as ARB_NTREE
1077        // => accept weird 'base_drag_given'
1078        aw_assert(gc == (base_drag_given-AW_RANGE_COLORS));
1079    }
1080    else {
1081        aw_assert(gc == base_drag_given); // parameter 'base_drag' has wrong value
1082                                          // (has to be next value after last GC or after last color-group-GC)
1083    }
1084#endif
1085
1086    // @@@ add check: 1. all range IDs have to be unique
1087    // @@@ add check: 2. all GC IDs have to be unique
1088
1089    return gcmgr;
1090}
1091
1092void AW_copy_GC_colors(AW_root *aw_root, const char *source_gcman, const char *dest_gcman, const char *id0, ...) {
1093    // read the color values of the specified GCs from 'source_gcman'
1094    // and write the values into same-named GCs of 'dest_gcman'.
1095    //
1096    // 'id0' is the first of a list of color ids.
1097    // Notes:
1098    // - a NULp sentinel has to be passed after the last color
1099    // - the ids (may) differ from the descriptions passed to AW_manage_GC (ids are keys!)
1100
1101    va_list parg;
1102    va_start(parg, id0);
1103
1104    const char *id = id0;
1105    while (id) {
1106        const char *value = aw_root->awar(color_awarname(source_gcman, id))->read_char_pntr();
1107        aw_root->awar(color_awarname(dest_gcman, id))->write_string(value);
1108
1109        id = va_arg(parg, const char*); // another argument?
1110    }
1111
1112    va_end(parg);
1113}
1114
1115void AW_init_color_group_defaults(const char *for_program) {
1116    // if for_program == NULp defaults of arb_ntree are silently used + awars are not generated
1117    // if for_program is unknown a warning is shown
1118
1119    aw_assert(!AW_gc_manager::color_group_defaults);
1120
1121    bool for_edit4 = false;
1122    if (for_program) {
1123        if (strcmp(for_program, "arb_edit4") == 0) {
1124            for_edit4 = true;
1125        }
1126#if defined(ASSERTION_USED)
1127        else {
1128            aw_assert(strcmp(for_program, "arb_ntree") == 0); // you might want to add specific defaults for_program
1129                                                              // alternatively pass NULp to use ARB_NTREE_color_group
1130        }
1131#endif
1132    }
1133
1134    const int     BUFSIZE = AW_COLOR_GROUPS*40;
1135    const char    SEP     = '\1';
1136    GBS_strstruct printBuffer(BUFSIZE);
1137
1138    for (int i = 0; i<AW_COLOR_GROUPS; ++i) {
1139        if (!(i&1)) printBuffer.put('+'); // newline after every 2nd color
1140        printBuffer.put('-');
1141        printBuffer.cat(default_colorgroup_name(i+1));
1142        printBuffer.put('$');
1143        printBuffer.put('#');
1144        printBuffer.cat(color_group_def[i].rgbValue(for_edit4));
1145        printBuffer.put(SEP);
1146    }
1147
1148    aw_assert(printBuffer.get_position()<BUFSIZE); // avoid need to resize buffer
1149
1150    static ConstStrArray singleDefs;
1151    {
1152        char *alldefs = printBuffer.release();
1153        arb_assert(alldefs[0]); // check whether using SPLIT_DROPEMPTY would be better here. Currently will generate an array containing one empty string.
1154        GBT_splitNdestroy_string(singleDefs, alldefs, SEP);
1155    }
1156
1157    static const char *cg_def[AW_COLOR_GROUPS+1];
1158    for (int i = 0; i<AW_COLOR_GROUPS; ++i) {
1159        cg_def[i] = singleDefs[i];
1160    }
1161    cg_def[AW_COLOR_GROUPS] = NULp;
1162
1163    AW_gc_manager::color_group_defaults = cg_def;
1164
1165    if (for_program) AW_init_colorgroup_name_awars(AW_root::SINGLETON);
1166}
1167
1168bool AW_color_groups_active() {
1169    /*! returns true if color groups are active */
1170    aw_assert(AW_gc_manager::color_groups_initialized());
1171    return AW_gc_manager::use_color_groups;
1172}
1173const char *AW_get_color_groups_active_awarname() {
1174    aw_assert(AW_gc_manager::color_groups_initialized());
1175    return AWAR_COLOR_GROUPS_USE;
1176}
1177
1178char *AW_get_color_group_name(AW_root *awr, int color_group) {
1179    aw_assert(AW_gc_manager::color_groups_initialized());
1180    aw_assert(valid_color_group(color_group));
1181    return awr->awar(colorgroupname_awarname(color_group))->read_string();
1182}
1183
1184static void create_color_button(AW_window *aws, const char *awar_name, const char *color_description) {
1185    const char *color     = aws->get_root()->awar(awar_name)->read_char_pntr();
1186    char       *button_id = GBS_global_string_copy("sel_color[%s]", awar_name);
1187
1188    aws->callback(makeWindowCallback(popup_color_chooser_window, strdup(awar_name), strdup(color_description)));
1189    aws->create_button(button_id, " ", NULp, color);
1190
1191    free(button_id);
1192}
1193
1194static void create_font_button(AW_window *aws, const char *gc_base_name, const gc_desc& gcd) {
1195    char *button_id = GBS_global_string_copy("sel_font_%s", gcd.key.c_str());
1196
1197    aws->callback(makeWindowCallback(popup_font_chooser_window, gc_base_name, &gcd));
1198    aws->create_button(button_id, fontinfo_awarname(gc_base_name, gcd.key));
1199
1200    free(button_id);
1201}
1202
1203static const int STD_LABEL_LEN    = 15;
1204static const int COLOR_BUTTON_LEN = 10;
1205static const int FONT_BUTTON_LEN  = COLOR_BUTTON_LEN+STD_LABEL_LEN+1;
1206// => color+font has ~same length as 2 colors (does not work for color groups)
1207
1208void AW_gc_manager::create_gc_buttons(AW_window *aws, gc_type for_gc_type) {
1209    for (int idx = 0; idx<int(GCs.size()); ++idx) {
1210        const gc_desc& gcd = GCs[idx];
1211
1212        if (gcd.is_color_group())        { if (for_gc_type != GC_TYPE_GROUP) continue; }
1213        else if (gcd.belongs_to_range()) { if (for_gc_type != GC_TYPE_RANGE) continue; }
1214        else                             { if (for_gc_type != GC_TYPE_NORMAL) continue; }
1215
1216        if (for_gc_type == GC_TYPE_RANGE) {
1217            if (gcd.get_color_index() == 0) { // first color of range
1218                const gc_range& range = color_ranges[gcd.get_range_index()];
1219
1220                const char *type_info = NULp;
1221                switch (range.get_type()) {
1222                    case GC_RANGE_LINEAR:  type_info  = "linear 1D range"; break;
1223                    case GC_RANGE_CYCLIC:  type_info  = "cyclic 1D range"; break;
1224                    case GC_RANGE_PLANAR:  type_info  = "planar 2D range"; break;
1225                    case GC_RANGE_SPATIAL: type_info  = "spatial 3D range"; break;
1226                    case GC_RANGE_INVALID: type_info = "invalid range "; aw_assert(0); break;
1227                }
1228
1229                const char *range_headline = GBS_global_string("%s (%s)", range.get_name().c_str(), type_info);
1230                aws->button_length(60);
1231                aws->create_button(NULp, range_headline, NULp, "+");
1232                aws->at_newline();
1233            }
1234        }
1235
1236        if (for_gc_type == GC_TYPE_GROUP) {
1237            int color_group_no = idx-first_colorgroup_idx+1;
1238            char buf[13];
1239            sprintf(buf, "%2i:", color_group_no); // @@@ shall this short label be stored in gc_desc?
1240            aws->label_length(3);
1241            aws->label(buf);
1242            aws->create_input_field(colorgroupname_awarname(color_group_no), 21);
1243        }
1244        else {
1245            aws->label_length(STD_LABEL_LEN);
1246            aws->label(gcd.colorlabel.c_str());
1247        }
1248
1249        aws->button_length(COLOR_BUTTON_LEN);
1250        create_color_button(aws, color_awarname(gc_base_name, gcd.key), gcd.colorlabel.c_str());
1251        if (gcd.has_font)   {
1252            aws->button_length(FONT_BUTTON_LEN);
1253            create_font_button(aws, gc_base_name, gcd);
1254        }
1255        if (!gcd.same_line) aws->at_newline();
1256    }
1257}
1258
1259typedef std::map<AW_gc_manager*, AW_window_simple*> GroupWindowRegistry;
1260
1261static void AW_popup_gc_color_groups_window(AW_window *aww, AW_gc_manager *gcmgr) {
1262    aw_assert(AW_gc_manager::color_groups_initialized());
1263
1264    static GroupWindowRegistry     existing_window;
1265    GroupWindowRegistry::iterator  found = existing_window.find(gcmgr);
1266    AW_window_simple              *aws   = found == existing_window.end() ? NULp : found->second;
1267
1268    if (!aws) {
1269        aws = new AW_window_simple;
1270
1271        aws->init(aww->get_root(), "COLOR_GROUP_DEF2", "Define color groups");
1272
1273        aws->at(10, 10);
1274        aws->auto_space(1, 1);
1275
1276        aws->callback(AW_POPDOWN);
1277        aws->create_button("CLOSE", "CLOSE", "C");
1278        aws->callback(makeHelpCallback("color_props_groups.hlp"));
1279        aws->create_button("HELP", "HELP", "H");
1280        aws->at_newline();
1281
1282        aws->label_length(20);
1283        aws->label("Enable color groups:");
1284        aws->create_toggle(AWAR_COLOR_GROUPS_USE);
1285        aws->at_newline();
1286
1287        gcmgr->create_gc_buttons(aws, GC_TYPE_GROUP);
1288
1289        aws->window_fit();
1290        existing_window[gcmgr] = aws;
1291    }
1292
1293    aws->activate();
1294}
1295
1296void AW_popup_gc_color_range_window(AW_window *aww, AW_gc_manager *gcmgr) {
1297    static GroupWindowRegistry     existing_window;
1298    GroupWindowRegistry::iterator  found = existing_window.find(gcmgr);
1299    AW_window_simple              *aws   = found == existing_window.end() ? NULp : found->second;
1300
1301    if (!aws) {
1302        aws = new AW_window_simple;
1303
1304        aws->init(aww->get_root(), "COLOR_RANGE_EDIT", "Edit color ranges");
1305
1306        aws->at(10, 10);
1307        aws->auto_space(5, 5);
1308
1309        aws->callback(AW_POPDOWN);
1310        aws->create_button("CLOSE", "CLOSE", "C");
1311        aws->callback(makeHelpCallback("color_ranges.hlp"));
1312        aws->create_button("HELP", "HELP", "H");
1313        aws->at_newline();
1314
1315        aws->label("Overlay active range");
1316        aws->create_toggle(AWAR_RANGE_OVERLAY);
1317        aws->at_newline();
1318
1319        gcmgr->create_gc_buttons(aws, GC_TYPE_RANGE);
1320
1321        aws->window_fit();
1322        existing_window[gcmgr] = aws;
1323    }
1324
1325    aws->activate();
1326}
1327
1328AW_window *AW_create_gc_window_named(AW_root *aw_root, AW_gc_manager *gcman, const char *wid, const char *windowname) {
1329    // same as AW_create_gc_window, but uses different window id and name
1330    // (use if if there are two or more color def windows in one application,
1331    // otherwise they save the same window properties)
1332
1333    AW_window_simple *aws = new AW_window_simple;
1334
1335    aws->init(aw_root, wid, windowname);
1336
1337    aws->at(10, 10);
1338    aws->auto_space(2, 2);
1339
1340    aws->callback(AW_POPDOWN);
1341    aws->create_button("CLOSE", "CLOSE", "C");
1342    aws->callback(makeHelpCallback("color_props.hlp"));
1343    aws->create_button("HELP", "HELP", "H");
1344    aws->at_newline();
1345
1346    // select all fonts:
1347    {
1348        static gc_desc allFont_fake_desc(-1, GC_TYPE_NORMAL);
1349        allFont_fake_desc.colorlabel       = "<all fonts>";
1350        allFont_fake_desc.key              = ALL_FONTS_ID;
1351        allFont_fake_desc.fixed_width_font = !gcman->has_variable_width_font();
1352        aws->callback(makeWindowCallback(popup_font_chooser_window, gcman->get_base_name(), &allFont_fake_desc));
1353    }
1354    aws->label_length(STD_LABEL_LEN);
1355    aws->label("All fonts:");
1356    aws->button_length(COLOR_BUTTON_LEN);
1357    aws->create_button("select_all_fonts", "Select", "s");
1358    aws->at_newline();
1359
1360    gcman->create_gc_buttons(aws, GC_TYPE_NORMAL);
1361
1362    bool groups_or_range = false;
1363    if (gcman->has_color_groups()) {
1364        aws->callback(makeWindowCallback(AW_popup_gc_color_groups_window, gcman));
1365        aws->create_autosize_button("EDIT_COLOR_GROUP", "Edit color groups", "E");
1366        groups_or_range = true;
1367    }
1368    if (gcman->has_color_range()) {
1369        aws->callback(makeWindowCallback(AW_popup_gc_color_range_window, gcman));
1370        aws->create_autosize_button("EDIT_COLOR_RANGE", "Edit color ranges", "r");
1371        groups_or_range = true;
1372    }
1373    if (groups_or_range) aws->at_newline();
1374
1375    aws->window_fit();
1376    return aws;
1377}
1378AW_window *AW_create_gc_window(AW_root *aw_root, AW_gc_manager *gcman) {
1379    return AW_create_gc_window_named(aw_root, gcman, "COLOR_DEF2", "Colors and Fonts");
1380}
1381
1382int AW_get_drag_gc(AW_gc_manager *gcman) {
1383    return gcman->get_drag_gc();
1384}
1385
1386void AW_displayColorRange(AW_device *device, int first_range_gc, AW::Position start, AW_pos xsize, AW_pos ysize) {
1387    /*! display active color range
1388     * @param device          output device
1389     * @param first_range_gc  first color range gc
1390     * @param start           position of upper left corner
1391     * @param xsize           xsize used per color
1392     * @param ysize           xsize used per color
1393     *
1394     * displays AW_PLANAR_COLORS rows and AW_PLANAR_COLORS columns
1395     */
1396    using namespace AW;
1397
1398    if (AW_gc_manager::show_range_overlay) {
1399        Vector size(xsize, ysize);
1400        for (int x = 0; x<AW_PLANAR_COLORS; ++x) {
1401            for (int y = 0; y<AW_PLANAR_COLORS; ++y) {
1402                int      gc     = first_range_gc + y*AW_PLANAR_COLORS+x;
1403                Vector   toCorner(x*xsize, y*ysize);
1404                Position corner = start+toCorner;
1405                device->box(gc, FillStyle::SOLID, Rectangle(corner, size));
1406            }
1407        }
1408    }
1409}
1410
1411void AW_getColorRangeNames(const AW_gc_manager *gcman, int dimension, ConstStrArray& ids, ConstStrArray& names) {
1412    /*! retrieve selected color-range IDs
1413     * @param gcman      the gc-manager defining the color-ranges
1414     * @param dimension  the wanted dimension of the color-ranges
1415     * @param ids        array where range-IDs will be stored
1416     * @param names      array where corresponding range-names will be stored (same index as 'ids')
1417     */
1418    ids.clear();
1419    names.clear();
1420    gcman->getColorRangeNames(dimension, ids, names);
1421}
1422
1423int AW_getFirstRangeGC(AW_gc_manager *gcman) { return gcman->first_range_gc(); }
1424void AW_activateColorRange(AW_gc_manager *gcman, const char *id) { gcman->activateColorRange(id); }
1425
1426const char *AW_getActiveColorRangeID(AW_gc_manager *gcman, int *dimension) {
1427    /*! @return the ID of the active color range
1428     * @param gcman      of this gc-manager
1429     * @param dimension  if !NULp -> is set to dimension of range
1430     */
1431    return gcman->getActiveColorRangeID(dimension);
1432}
1433
1434#if defined(UNIT_TESTS)
1435void fake_AW_init_color_groups() {
1436    if (!AW_gc_manager::color_groups_initialized()) {
1437        AW_init_color_group_defaults(NULp);
1438    }
1439    AW_gc_manager::use_color_groups = true;
1440}
1441#endif
1442
1443// @@@ move code below somewhere more sensible
1444
1445static void add_common_property_menu_entries(AW_window *aw) {
1446    aw->insert_menu_topic("enable_advices",   "Reactivate advices",   "R", "advice.hlp",    AWM_ALL, AW_reactivate_all_advices);
1447    aw->insert_menu_topic("enable_questions", "Reactivate questions", "q", "questions.hlp", AWM_ALL, AW_reactivate_all_questions);
1448    aw->insert_menu_topic("reset_win_layout", "Reset window layout",  "w", "reset_win_layout.hlp", AWM_ALL, AW_forget_all_window_geometry);
1449}
1450void AW_insert_common_property_menu_entries(AW_window_menu_modes *awmm) { add_common_property_menu_entries(awmm); }
1451void AW_insert_common_property_menu_entries(AW_window_simple_menu *awsm) { add_common_property_menu_entries(awsm); }
1452
1453void AW_save_specific_properties(AW_window *aw, const char *filename) {  // special version for EDIT4
1454    GB_ERROR error = aw->get_root()->save_properties(filename);
1455    if (error) aw_message(error);
1456}
1457void AW_save_properties(AW_window *aw) {
1458    AW_save_specific_properties(aw, NULp);
1459}
1460
1461// --------------------------------
1462//      RGB <-> HSV conversion
1463
1464class AW_hsv {
1465    float H, S, V;
1466public:
1467    AW_hsv(float hue, float saturation, float value) : H(hue), S(saturation), V(value) {
1468        aw_assert(H>=0.0 && H<360.0);
1469        aw_assert(S>=0.0 && S<=1.0);
1470        aw_assert(V>=0.0 && V<=1.0);
1471    }
1472    AW_hsv(const AW_rgb_normalized& col) {
1473        float R = col.r();
1474        float G = col.g();
1475        float B = col.b();
1476
1477        float min = std::min(std::min(R, G), B);
1478        float max = std::max(std::max(R, G), B);
1479
1480        if (min == max) {
1481            H = 0;
1482        }
1483        else {
1484            H = 60;
1485
1486            if      (max == R) { H *= 0 + (G-B)/(max-min); }
1487            else if (max == G) { H *= 2 + (B-R)/(max-min); }
1488            else               { H *= 4 + (R-G)/(max-min); }
1489
1490            if (H<0) H += 360;
1491        }
1492
1493        S = max ? (max-min)/max : 0;
1494        V = max;
1495    }
1496#if defined(Cxx11)
1497    AW_hsv(const AW_rgb16& col) : AW_hsv(AW_rgb_normalized(col)) {}
1498#else // !defined(Cxx11)
1499    AW_hsv(const AW_rgb16& col) { *this = AW_rgb_normalized(col); }
1500#endif
1501
1502    AW_rgb_normalized rgb() const {
1503        int   hi = int(H/60);
1504        float f  = H/60-hi;
1505
1506        float p = V*(1-S);
1507        float q = V*(1-S*f);
1508        float t = V*(1-S*(1-f));
1509
1510        switch (hi) {
1511            case 0: return AW_rgb_normalized(V, t, p); //   0 <= H <  60 (deg)
1512            case 1: return AW_rgb_normalized(q, V, p); //  60 <= H < 120
1513            case 2: return AW_rgb_normalized(p, V, t); // 120 <= H < 180
1514            case 3: return AW_rgb_normalized(p, q, V); // 180 <= H < 240
1515            case 4: return AW_rgb_normalized(t, p, V); // 240 <= H < 300
1516            case 5: return AW_rgb_normalized(V, p, q); // 300 <= H < 360
1517        }
1518        aw_assert(0);
1519        return AW_rgb_normalized(0, 0, 0);
1520    }
1521
1522    float h() const { return H; }
1523    float s() const { return S; }
1524    float v() const { return V; }
1525};
1526
1527
1528// --------------------------------------------------------------------------------
1529
1530#ifdef UNIT_TESTS
1531#ifndef TEST_UNIT_H
1532#include <test_unit.h>
1533#endif
1534
1535void TEST_rgb_hsv_conversion() {
1536    // Note: more related tests in AW_rgb.cxx@RGB_TESTS
1537
1538    const int tested[] = {
1539        // testing full rgb space takes too long
1540        // just test all combinations of these:
1541
1542        0, 1, 2, 3, 4, 5,
1543        58, 59, 60, 61, 62,
1544        998, 999, 1000, 1001, 1002,
1545        32766, 32767, 32768, 32769, 32770,
1546        39998, 39999, 40000, 40001, 40002,
1547        65531, 65532, 65533, 65534, 65535
1548    };
1549
1550    for (unsigned i = 0; i<ARRAY_ELEMS(tested); ++i) {
1551        int r = tested[i];
1552        for (unsigned j = 0; j<ARRAY_ELEMS(tested); ++j) {
1553            int g = tested[j];
1554            for (unsigned k = 0; k<ARRAY_ELEMS(tested); ++k) {
1555                int b = tested[k];
1556
1557                TEST_ANNOTATE(GBS_global_string("rgb=%i/%i/%i", r, g, b));
1558
1559                AW_hsv hsv(AW_rgb16(r, g, b));
1560
1561                // check range overflow
1562                TEST_EXPECT(hsv.h()>=0.0 && hsv.h()<360.0);
1563                TEST_EXPECT(hsv.s()>=0.0 && hsv.s()<=1.0);
1564                TEST_EXPECT(hsv.v()>=0.0 && hsv.v()<=1.0);
1565
1566                AW_rgb16 RGB(hsv.rgb());
1567
1568                // fprintf(stderr, "rgb=%i/%i/%i hsv=%i/%i/%i RGB=%i/%i/%i\n", r, g, b, h, s, v, R, G, B);
1569
1570                // check that rgb->hsv->RGB produces a similar color
1571                const int MAXDIFF    = 1; // less than .0015% difference per channel
1572                const int MAXDIFFSUM = 2; // less than .003% difference overall
1573
1574                TEST_EXPECT(abs(r-RGB.r()) <= MAXDIFF);
1575                TEST_EXPECT(abs(g-RGB.g()) <= MAXDIFF);
1576                TEST_EXPECT(abs(b-RGB.b()) <= MAXDIFF);
1577
1578                TEST_EXPECT((abs(r-RGB.r())+abs(g-RGB.g())+abs(b-RGB.b())) <= MAXDIFFSUM);
1579            }
1580        }
1581    }
1582
1583    for (unsigned i = 0; i<ARRAY_ELEMS(tested); ++i) {
1584        int h = tested[i]*320/65535;
1585        for (unsigned j = 0; j<ARRAY_ELEMS(tested); ++j) {
1586            float s = tested[j]/65535.0;
1587            for (unsigned k = 0; k<ARRAY_ELEMS(tested); ++k) {
1588                float v = tested[k]/65535.0;
1589
1590                TEST_ANNOTATE(GBS_global_string("hsv=%i/%.3f/%.3f", h, s, v));
1591
1592                AW_rgb16 rgb(AW_hsv(h, s, v).rgb());
1593                AW_rgb16 RGB(AW_hsv(rgb).rgb());
1594
1595                // fprintf(stderr, "hsv=%i/%i/%i rgb=%i/%i/%i HSV=%i/%i/%i RGB=%i/%i/%i\n", h, s, v, r, g, b, H, S, V, R, G, B);
1596
1597                // check that hsv->rgb->HSV->RGB produces a similar color (comparing hsv makes no sense)
1598                const int MAXDIFF    = 1; // less than .0015% difference per channel
1599                const int MAXDIFFSUM = 2; // less than .003% difference overall
1600
1601                TEST_EXPECT(abs(rgb.r()-RGB.r()) <= MAXDIFF);
1602                TEST_EXPECT(abs(rgb.g()-RGB.g()) <= MAXDIFF);
1603                TEST_EXPECT(abs(rgb.b()-RGB.b()) <= MAXDIFF);
1604
1605                TEST_EXPECT((abs(rgb.r()-RGB.r())+abs(rgb.g()-RGB.g())+abs(rgb.b()-RGB.b())) <= MAXDIFFSUM);
1606            }
1607        }
1608    }
1609
1610    // specific conversion (showed wrong 'hue' and 'saturation' until [14899])
1611    {
1612        AW_hsv hsv(AW_rgb16(0, 0, 14906));
1613
1614        TEST_EXPECT_SIMILAR(hsv.h(), 240.0, 0.001); //= ~ 240 deg
1615        TEST_EXPECT_SIMILAR(hsv.s(), 1.0,   0.001); //= 100%
1616        TEST_EXPECT_SIMILAR(hsv.v(), 0.227, 0.001); //= ~ 22.7%
1617
1618        AW_rgb16 rgb(hsv.rgb());
1619
1620        TEST_EXPECT_EQUAL(rgb.r(), 0);
1621        TEST_EXPECT_EQUAL(rgb.g(), 0);
1622        TEST_EXPECT_EQUAL(rgb.b(), 14906);
1623    }
1624}
1625
1626#endif // UNIT_TESTS
1627
1628// --------------------------------------------------------------------------------
1629
1630
1631// ------------------------------
1632//      motif color selector
1633
1634#define AWAR_SELECTOR_COLOR_LABEL "tmp/aw/color_label"
1635
1636#define AWAR_CV_R "tmp/aw/color_r" // rgb..
1637#define AWAR_CV_G "tmp/aw/color_g"
1638#define AWAR_CV_B "tmp/aw/color_b"
1639#define AWAR_CV_H "tmp/aw/color_h" // hsv..
1640#define AWAR_CV_S "tmp/aw/color_s"
1641#define AWAR_CV_V "tmp/aw/color_v"
1642
1643static char *current_color_awarname         = NULp; // name of the currently modified color-awar
1644static bool  ignore_color_value_change      = false;
1645static bool  color_value_change_was_ignored = false;
1646
1647static void aw_set_rgb_sliders(AW_root *awr, const AW_rgb_normalized& col) {
1648    color_value_change_was_ignored = false;
1649    {
1650        LocallyModify<bool> delayUpdate(ignore_color_value_change, true);
1651        awr->awar(AWAR_CV_R)->write_float(col.r());
1652        awr->awar(AWAR_CV_G)->write_float(col.g());
1653        awr->awar(AWAR_CV_B)->write_float(col.b());
1654    }
1655    if (color_value_change_was_ignored) awr->awar(AWAR_CV_B)->touch();
1656}
1657
1658static void aw_set_sliders_from_color(AW_root *awr) {
1659    const char *color = awr->awar(current_color_awarname)->read_char_pntr();
1660    aw_set_rgb_sliders(awr, AW_rgb_normalized(color));
1661}
1662
1663inline void aw_set_color(AW_root *awr, const char *color_name) {
1664    awr->awar(current_color_awarname)->write_string(color_name);
1665    aw_set_sliders_from_color(awr);
1666}
1667static void aw_set_color(AW_window *aww, const char *color_name) {
1668    aw_set_color(aww->get_root(), color_name);
1669}
1670
1671static void colorslider_changed_cb(AW_root *awr, bool hsv_changed) {
1672    if (ignore_color_value_change) {
1673        color_value_change_was_ignored = true;
1674    }
1675    else {
1676        LocallyModify<bool> noRecursion(ignore_color_value_change, true);
1677
1678        if (hsv_changed) {
1679            float h = awr->awar(AWAR_CV_H)->read_float();
1680            float s = awr->awar(AWAR_CV_S)->read_float();
1681            float v = awr->awar(AWAR_CV_V)->read_float();
1682
1683            if (h>=360.0) h -= 360;
1684
1685            AW_rgb_normalized col(AW_hsv(h, s, v).rgb());
1686            aw_set_color(awr, AW_rgb16(col).ascii());
1687
1688            awr->awar(AWAR_CV_R)->write_float(col.r());
1689            awr->awar(AWAR_CV_G)->write_float(col.g());
1690            awr->awar(AWAR_CV_B)->write_float(col.b());
1691        }
1692        else {
1693            AW_rgb_normalized col(awr->awar(AWAR_CV_R)->read_float(),
1694                                  awr->awar(AWAR_CV_G)->read_float(),
1695                                  awr->awar(AWAR_CV_B)->read_float());
1696
1697            aw_set_color(awr, AW_rgb16(col).ascii());
1698
1699            AW_hsv hsv(col);
1700
1701            awr->awar(AWAR_CV_H)->write_float(hsv.h());
1702            awr->awar(AWAR_CV_S)->write_float(hsv.s());
1703            awr->awar(AWAR_CV_V)->write_float(hsv.v());
1704        }
1705    }
1706}
1707static void aw_create_colorslider_awars(AW_root *awr) {
1708    awr->awar_string(AWAR_SELECTOR_COLOR_LABEL);
1709    static const char *colorValueAwars[] = {
1710        AWAR_CV_R, AWAR_CV_H,
1711        AWAR_CV_G, AWAR_CV_S,
1712        AWAR_CV_B, AWAR_CV_V,
1713    };
1714
1715    for (int cv = 0; cv<6; ++cv) {
1716        awr->awar_float(colorValueAwars[cv])
1717            ->set_minmax(0.0, cv == 1 ? 360.0 : 1.0)
1718            ->add_callback(makeRootCallback(colorslider_changed_cb, bool(cv%2)));
1719    }
1720}
1721static void popup_color_chooser_window(AW_window *aww, const char *awar_name, const char *color_description) {
1722    AW_root *awr = aww->get_root();
1723    static AW_window_simple *aws = NULp;
1724    if (!aws) {
1725        aw_create_colorslider_awars(awr);
1726
1727        aws = new AW_window_simple;
1728        aws->init(awr, "COLORS", "Select color");
1729
1730        int x1 = 10;
1731        int y1 = 10;
1732
1733        aws->at(x1, y1);
1734        aws->auto_space(3, 3);
1735        aws->callback(AW_POPDOWN);
1736        aws->create_button("CLOSE", "CLOSE", "C");
1737
1738        aws->button_length(20);
1739        aws->create_button(NULp, AWAR_SELECTOR_COLOR_LABEL);
1740        aws->at_newline();
1741
1742        int x2, y2;
1743        aws->get_at_position(&x2, &y2);
1744        y2 += 3;
1745
1746        struct ColorValue {
1747            const char *label;
1748            const char *awar;
1749        } colorValue[] = {
1750            { "R", AWAR_CV_R }, { "H", AWAR_CV_H },
1751            { "G", AWAR_CV_G }, { "S", AWAR_CV_S },
1752            { "B", AWAR_CV_B }, { "V", AWAR_CV_V },
1753        };
1754
1755        const int INPUTFIELD_WIDTH = 12;
1756        const int SCALERLENGTH     = 320;
1757
1758        for (int row = 0; row<3; ++row) {
1759            aws->at(x1, y1+(row+1)*(y2-y1));
1760            const ColorValue *vc = &colorValue[row*2];
1761            aws->label(vc->label);
1762            aws->create_input_field_with_scaler(vc->awar, INPUTFIELD_WIDTH, SCALERLENGTH, AW_SCALER_LINEAR);
1763            ++vc;
1764            aws->label(vc->label);
1765            aws->create_input_field_with_scaler(vc->awar, INPUTFIELD_WIDTH, SCALERLENGTH, AW_SCALER_LINEAR);
1766        }
1767
1768        aws->button_length(1);
1769        aws->at_newline();
1770
1771        const float SATVAL_INCREMENT = 0.2;
1772        const int   HUE_INCREMENT    = 10;
1773        const int   COLORS_PER_ROW   = 360/HUE_INCREMENT;
1774
1775        for (int v = 5; v>=2; --v) {
1776            float val = v*SATVAL_INCREMENT;
1777            bool  rev = !(v%2);
1778            for (int s = rev ? 2 : 5; rev ? s<=5 : s>=2; s = rev ? s+1 : s-1) {
1779                float sat = s*SATVAL_INCREMENT;
1780                for (int hue = 0; hue<360;  hue += HUE_INCREMENT) {
1781                    const char *color_name = AW_rgb16(AW_hsv(hue, sat, val).rgb()).ascii();
1782                    aws->callback(makeWindowCallback(aw_set_color, strdup(color_name)));
1783                    aws->create_button(color_name, "", NULp, color_name);
1784                }
1785                aws->at_newline();
1786            }
1787        }
1788
1789        for (int p = 0; p<COLORS_PER_ROW; ++p) {
1790            float       grey       = (1.0 * p) / (COLORS_PER_ROW-1);
1791            const char *color_name = AW_rgb16(AW_rgb_normalized(grey, grey, grey)).ascii();
1792
1793            aws->callback(makeWindowCallback(aw_set_color, strdup(color_name)));
1794            aws->create_button(color_name, "=", NULp, color_name);
1795        }
1796        aws->at_newline();
1797
1798        aws->window_fit();
1799    }
1800    awr->awar(AWAR_SELECTOR_COLOR_LABEL)->write_string(color_description);
1801    freedup(current_color_awarname, awar_name);
1802    aw_set_sliders_from_color(awr);
1803    aws->activate();
1804}
1805
1806// ----------------------------
1807//      motif font chooser
1808
1809#define AWAR_SELECTOR_FONT_LABEL "tmp/aw/font_label"
1810#define AWAR_SELECTOR_FONT_NAME  "tmp/aw/font_name"
1811#define AWAR_SELECTOR_FONT_SIZE  "tmp/aw/font_size"
1812
1813static void create_font_chooser_awars(AW_root *awr) {
1814    awr->awar_string(AWAR_SELECTOR_FONT_LABEL, "<invalid>");
1815    awr->awar_int(AWAR_SELECTOR_FONT_NAME, NO_FONT);
1816    awr->awar_int(AWAR_SELECTOR_FONT_SIZE, NO_SIZE)->set_minmax(MIN_FONTSIZE, MAX_FONTSIZE);
1817}
1818
1819static void popup_font_chooser_window(AW_window *aww, const char *gc_base_name, const gc_desc *gcd) {
1820    AW_root *awr = aww->get_root();
1821
1822    static AW_window_simple *aw_fontChoose[2] = { NULp, NULp }; // one for fixed-width font; one general
1823
1824    bool fixed_width_only = gcd->fixed_width_font;
1825
1826    AW_window_simple*& aws = aw_fontChoose[fixed_width_only];
1827    AW_window_simple*& awo = aw_fontChoose[!fixed_width_only];
1828
1829    if (!aws) {
1830        if (!awo) create_font_chooser_awars(awr); // only called once
1831
1832        aws = new AW_window_simple;
1833        aws->init(awr, "FONT", fixed_width_only ? "Select fixed width font" : "Select font");
1834
1835        aws->auto_space(10, 10);
1836
1837        aws->callback(AW_POPDOWN);
1838        aws->create_button("CLOSE", "CLOSE", "C");
1839
1840        aws->button_length(20);
1841        aws->create_button(NULp, AWAR_SELECTOR_FONT_LABEL);
1842        aws->at_newline();
1843
1844        aws->label("Font:");
1845        aws->create_option_menu(AWAR_SELECTOR_FONT_NAME);
1846        {
1847            int fonts_inserted = 0;
1848            for (int order = 1; order>=0; order--) {
1849                for (int font_nr = 0; ; font_nr++) {
1850                    AW_font     aw_font_nr  = font_nr;
1851                    bool        found;
1852                    const char *font_string = AW_get_font_specification(aw_font_nr, found);
1853
1854                    if (!font_string) {
1855                        fprintf(stderr, "[Font detection: tried=%i, found=%i]\n", font_nr, fonts_inserted);
1856                        break;
1857                    }
1858
1859                    if (found != bool(order)) continue; // display found fonts at top
1860                    if (fixed_width_only && !font_has_fixed_width(aw_font_nr)) continue;
1861
1862                    aws->insert_option(font_string, NULp, font_nr);
1863                    ++fonts_inserted;
1864                }
1865            }
1866            if (!fonts_inserted) aws->insert_option("No suitable fonts detected", NULp, 0);
1867            aws->insert_default_option("<no font selected>", NULp, NO_FONT);
1868            aws->update_option_menu();
1869        }
1870
1871        aws->at_newline();
1872
1873        aws->label("Size:");
1874        aws->create_input_field_with_scaler(AWAR_SELECTOR_FONT_SIZE, 3, 330);
1875        aws->at_newline();
1876
1877        aws->window_fit();
1878    }
1879
1880    awr->awar(AWAR_SELECTOR_FONT_LABEL)->write_string(gcd->colorlabel.c_str());
1881    awr->awar(AWAR_SELECTOR_FONT_NAME)->map(awr->awar(fontname_awarname(gc_base_name, gcd->key)));
1882    awr->awar(AWAR_SELECTOR_FONT_SIZE)->map(awr->awar(fontsize_awarname(gc_base_name, gcd->key)));
1883
1884    if (awo) awo->hide(); // both windows use same awars -> hide the other window to avoid chaos
1885    aws->activate();
1886}
1887
1888// -----------------------------------
1889//      frame colors (motif only)
1890
1891static void aw_message_reload(AW_root *) {
1892    aw_message("Sorry, to activate new colors:\n"
1893                "   save properties\n"
1894                "   and restart application");
1895}
1896
1897static void AW_preset_create_font_chooser(AW_window *aws, const char *awar, const char *label, bool message_reload) {
1898    if (message_reload) aws->get_root()->awar(awar)->add_callback(aw_message_reload);
1899
1900    aws->label(label);
1901    aws->create_option_menu(awar);
1902
1903    aws->insert_option("5x8",               "5", "5x8");
1904    aws->insert_option("6x10",              "6", "6x10");
1905    aws->insert_option("7x13",              "7", "7x13");
1906    aws->insert_option("7x13bold",          "7", "7x13bold");
1907    aws->insert_option("8x13",              "8", "8x13");
1908    aws->insert_option("8x13bold",          "8", "8x13bold");
1909    aws->insert_option("9x15",              "9", "9x15");
1910    aws->insert_option("9x15bold",          "9", "9x15bold");
1911    aws->insert_option("helvetica-12",      "9", "helvetica-12");
1912    aws->insert_option("helvetica-bold-12", "9", "helvetica-bold-12");
1913    aws->insert_option("helvetica-13",      "9", "helvetica-13");
1914    aws->insert_option("helvetica-bold-13", "9", "helvetica-bold-13");
1915
1916    aws->insert_default_option("other", "o", "");
1917    aws->update_option_menu();
1918}
1919static void AW_preset_create_color_button(AW_window *aws, const char *awar_name, const char *label) {
1920    aws->get_root()->awar(awar_name)->add_callback(aw_message_reload);
1921    aws->label(label);
1922    create_color_button(aws, awar_name, label);
1923}
1924
1925AW_window *AW_preset_window(AW_root *root) {
1926    AW_window_simple *aws = new AW_window_simple;
1927    const int   tabstop = 400;
1928    aws->init(root, "PROPS_FRAME", "WINDOW_PROPERTIES");
1929
1930    aws->label_length(25);
1931    aws->button_length(20);
1932
1933    aws->at           (10, 10);
1934    aws->auto_space(10, 10);
1935
1936    aws->callback     (AW_POPDOWN);
1937    aws->create_button("CLOSE", "CLOSE", "C");
1938
1939    aws->callback(makeHelpCallback("props_frame.hlp"));
1940    aws->create_button("HELP", "HELP", "H");
1941
1942    aws->at_newline();
1943
1944    AW_preset_create_font_chooser(aws, "window/font", "Main Menu Font", 1);
1945    aws->at_x(tabstop);
1946    aws->create_input_field("window/font", 12);
1947
1948    aws->at_newline();
1949
1950    aws->button_length(10);
1951    AW_preset_create_color_button(aws, "window/background", "Application Background");
1952    aws->at_x(tabstop);
1953    aws->create_input_field("window/background", 12);
1954
1955    aws->at_newline();
1956
1957    AW_preset_create_color_button(aws, "window/foreground", "Application Foreground");
1958    aws->at_x(tabstop);
1959    aws->create_input_field("window/foreground", 12);
1960
1961    aws->at_newline();
1962
1963    AW_preset_create_color_button(aws, "window/color_1", "Color 1");
1964    aws->at_x(tabstop);
1965    aws->create_input_field("window/color_1", 12);
1966
1967    aws->at_newline();
1968
1969    AW_preset_create_color_button(aws, "window/color_2", "Color 2");
1970    aws->at_x(tabstop);
1971    aws->create_input_field("window/color_2", 12);
1972
1973    aws->at_newline();
1974
1975    AW_preset_create_color_button(aws, "window/color_3", "Color 3");
1976
1977    aws->at_x(tabstop);
1978    aws->create_input_field("window/color_3", 12);
1979
1980    aws->at_newline();
1981
1982    aws->window_fit();
1983    return aws;
1984}
1985
Note: See TracBrowser for help on using the repository browser.