source: branches/stable/WINDOW/AW_preset.cxx

Last change on this file was 18686, checked in by westram, 3 years ago
  • change references to (never released) arb-6.1 → arb-7.0
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 72.7 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 aw_create_color_chooser_window(AW_window *aww, const char *awar_name, const char *color_description);
57static void aw_create_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_gc_manager::add_color_groups(int& gc) {
958    AW_root *awr = AW_root::SINGLETON;
959    for (int i = 1; i <= AW_COLOR_GROUPS; ++i) {
960        awr->awar_string(colorgroupname_awarname(i), default_colorgroup_name(i))->add_callback(color_group_name_changed_cb);
961    }
962
963    const char **color_group_gc_default = AW_gc_manager::color_group_defaults;
964    while (*color_group_gc_default) {
965        add_gc(*color_group_gc_default++, gc, GC_TYPE_GROUP);
966    }
967}
968
969AW_gc_manager *AW_manage_GC(AW_window                *aww,
970                            const char               *gc_base_name,
971                            AW_device                *device,
972                            int                       base_drag,
973                            AW_GCM_AREA               area,
974                            const GcChangedCallback&  changecb,
975                            const char               *default_background_color,
976                            ...)
977{
978    /*!
979     * creates some GC pairs:
980     * - one for normal operation,
981     * - the other for drag mode
982     * eg.
983     *     AW_manage_GC(aww, "ARB_NT", device, 0, 1, AW_GCM_DATA_AREA, my_expose_cb, "white","#sequence", NULp);
984     *     (see implementation for more details on parameter strings)
985     *     will create 2 GCs:
986     *      GC 0 (normal)
987     *      GC 1 (drag)
988     *  Don't forget the NULp sentinel at the end of the GC definition list.
989     *
990     *  When the GCs are modified the 'changecb' is called
991     *
992     * @param aww          base window
993     * @param gc_base_name (usually the window_id, prefixed to awars)
994     * @param device       screen device
995     * @param base_drag    one after last gc
996     * @param area         AW_GCM_DATA_AREA or AW_GCM_WINDOW_AREA (motif only)
997     * @param changecb     cb if changed
998     * @param define_color_groups  true -> add colors for color groups
999     * @param ...                  NULp terminated list of \0 terminated <definition>s:
1000     *
1001     *   <definition>::= <gcdef>|<reserve>|<rangedef>|<groupdef>
1002     *   <reserve>   ::= !<num>                                     "reserves <num> gc-numbers; used eg. in arb_pars to sync GCs with arb_ntree"
1003     *   <gcdef>     ::= [<option>+]<descript>['$'<default>]        "defines a GC"
1004     *   <option>    ::= '#'|'+'|'-'                                "'-'=no font; '#'=only fixed fonts; '+'=append next GC on same line"
1005     *   <descript>  ::=                                            "description of GC; shown as label; has to be unique when converted to key"
1006     *   <default>   ::= <xcolor>|<gcref>                           "default color of GC"
1007     *   <xcolor>    ::=                                            "accepts any valid X-colorname (e.g. 'white', '#c0ff40')"
1008     *   <gcref>     ::= '{'<descript>'}'                           "reference to another earlier defined gc"
1009     *   <rangedef>  ::= '*'<name>','<type>':'<gcdef>[','<gcdef>]+  "defines a GC-range (with one <gcdef> for each support color)"
1010     *   <name>      ::=                                            "description of range"
1011     *   <type>      ::= 'linear'|'cyclic'|'planar'|'spatial'       "rangetype; implies number of required support colors: linear=2 cyclic=3-N planar=3 spatial=4"
1012     *   <groupdef>  ::= '&color_groups'                            "insert color-groups here"
1013     */
1014
1015    aw_assert(default_background_color[0]);
1016
1017#if defined(ASSERTION_USED)
1018    int base_drag_given = base_drag;
1019#endif
1020
1021    AW_root *aw_root = AW_root::SINGLETON;
1022
1023    int            colidx_base = area == AW_GCM_DATA_AREA ? AW_DATA_BG : AW_WINDOW_BG;
1024    AW_gc_manager *gcmgr       = new AW_gc_manager(gc_base_name, device, base_drag, aww, colidx_base);
1025
1026    int  gc = GC_BACKGROUND; // gets incremented by add_gc
1027    char background[50];
1028    sprintf(background, "-background$%s", default_background_color);
1029    gcmgr->add_gc(background, gc, GC_TYPE_NORMAL);
1030
1031    va_list parg;
1032    va_start(parg, default_background_color);
1033
1034    bool defined_color_groups = false;
1035
1036    const char *id;
1037    while ( (id = va_arg(parg, char*)) ) {
1038        switch (id[0]) {
1039            case '!': gcmgr->reserve_gcs(id, gc); break;
1040            case '*': gcmgr->add_gc_range(id); break;
1041            case '&':
1042                if (strcmp(id, "&color_groups") == 0) { // trigger use of color groups
1043                    aw_assert(!defined_color_groups); // color-groups trigger specified twice!
1044                    if (!defined_color_groups) {
1045                        gcmgr->add_color_groups(gc);
1046                        defined_color_groups = true;
1047                    }
1048                }
1049                else { aw_assert(0); } // unknown trigger
1050                break;
1051            default: gcmgr->add_gc(id, gc, GC_TYPE_NORMAL); break;
1052        }
1053    }
1054    va_end(parg);
1055
1056    {
1057        AW_awar *awar_useGroups = aw_root->awar_int(AWAR_COLOR_GROUPS_USE, 1);
1058
1059        awar_useGroups->add_callback(makeRootCallback(color_group_use_changed_cb, gcmgr));
1060        AW_gc_manager::use_color_groups = awar_useGroups->read_int();
1061    }
1062
1063    aw_root->awar_int(AWAR_RANGE_OVERLAY, 0)->add_callback(makeRootCallback(range_overlay_changed_cb, gcmgr));
1064    aw_root->awar_int(gcmgr->awarname_active_range(), 0)->add_callback(makeRootCallback(active_range_changed_cb, gcmgr));
1065
1066    gcmgr->init_color_ranges(gc);
1067    gcmgr->init_all_fonts();
1068
1069    // installing changed callback here avoids that it gets triggered by initializing GCs
1070    gcmgr->set_changed_cb(changecb);
1071
1072#if defined(ASSERTION_USED)
1073    if (strcmp(gc_base_name, "ARB_PARSIMONY") == 0) {
1074        // ARB_PARSIMONY does not define color-ranges, but uses same GCs as ARB_NTREE
1075        // => accept weird 'base_drag_given'
1076        aw_assert(gc == (base_drag_given-AW_RANGE_COLORS));
1077    }
1078    else {
1079        aw_assert(gc == base_drag_given); // parameter 'base_drag' has wrong value
1080                                          // (has to be next value after last GC or after last color-group-GC)
1081    }
1082#endif
1083
1084    // @@@ add check: 1. all range IDs have to be unique
1085    // @@@ add check: 2. all GC IDs have to be unique
1086
1087    return gcmgr;
1088}
1089
1090void AW_copy_GC_colors(AW_root *aw_root, const char *source_gcman, const char *dest_gcman, const char *id0, ...) {
1091    // read the color values of the specified GCs from 'source_gcman'
1092    // and write the values into same-named GCs of 'dest_gcman'.
1093    //
1094    // 'id0' is the first of a list of color ids.
1095    // Notes:
1096    // - a NULp sentinel has to be passed after the last color
1097    // - the ids (may) differ from the descriptions passed to AW_manage_GC (ids are keys!)
1098
1099    va_list parg;
1100    va_start(parg, id0);
1101
1102    const char *id = id0;
1103    while (id) {
1104        const char *value = aw_root->awar(color_awarname(source_gcman, id))->read_char_pntr();
1105        aw_root->awar(color_awarname(dest_gcman, id))->write_string(value);
1106
1107        id = va_arg(parg, const char*); // another argument?
1108    }
1109
1110    va_end(parg);
1111}
1112
1113void AW_init_color_group_defaults(const char *for_program) {
1114    // if for_program == NULp defaults of arb_ntree are silently used
1115    // if for_program is unknown a warning is shown
1116
1117    aw_assert(!AW_gc_manager::color_group_defaults);
1118
1119    bool for_edit4 = false;
1120    if (for_program) {
1121        if (strcmp(for_program, "arb_edit4") == 0) {
1122            for_edit4 = true;
1123        }
1124#if defined(ASSERTION_USED)
1125        else {
1126            aw_assert(strcmp(for_program, "arb_ntree") == 0); // you might want to add specific defaults for_program
1127                                                              // alternatively pass NULp to use ARB_NTREE_color_group
1128        }
1129#endif
1130    }
1131
1132    const int     BUFSIZE = AW_COLOR_GROUPS*40;
1133    const char    SEP     = '\1';
1134    GBS_strstruct printBuffer(BUFSIZE);
1135
1136    for (int i = 0; i<AW_COLOR_GROUPS; ++i) {
1137        if (!(i&1)) printBuffer.put('+'); // newline after every 2nd color
1138        printBuffer.put('-');
1139        printBuffer.cat(default_colorgroup_name(i+1));
1140        printBuffer.put('$');
1141        printBuffer.put('#');
1142        printBuffer.cat(color_group_def[i].rgbValue(for_edit4));
1143        printBuffer.put(SEP);
1144    }
1145
1146    aw_assert(printBuffer.get_position()<BUFSIZE); // avoid need to resize buffer
1147
1148    static ConstStrArray singleDefs;
1149    {
1150        char *alldefs = printBuffer.release();
1151        GBT_splitNdestroy_string(singleDefs, alldefs, SEP);
1152    }
1153
1154    static const char *cg_def[AW_COLOR_GROUPS+1];
1155    for (int i = 0; i<AW_COLOR_GROUPS; ++i) {
1156        cg_def[i] = singleDefs[i];
1157    }
1158    cg_def[AW_COLOR_GROUPS] = NULp;
1159
1160    AW_gc_manager::color_group_defaults = cg_def;
1161}
1162
1163bool AW_color_groups_active() {
1164    /*! returns true if color groups are active */
1165    aw_assert(AW_gc_manager::color_groups_initialized());
1166    return AW_gc_manager::use_color_groups;
1167}
1168const char *AW_get_color_groups_active_awarname() {
1169    aw_assert(AW_gc_manager::color_groups_initialized());
1170    return AWAR_COLOR_GROUPS_USE;
1171}
1172
1173char *AW_get_color_group_name(AW_root *awr, int color_group) {
1174    aw_assert(AW_gc_manager::color_groups_initialized());
1175    aw_assert(valid_color_group(color_group));
1176    return awr->awar(colorgroupname_awarname(color_group))->read_string();
1177}
1178
1179static void create_color_button(AW_window *aws, const char *awar_name, const char *color_description) {
1180    const char *color     = aws->get_root()->awar(awar_name)->read_char_pntr();
1181    char       *button_id = GBS_global_string_copy("sel_color[%s]", awar_name);
1182
1183    aws->callback(makeWindowCallback(aw_create_color_chooser_window, strdup(awar_name), strdup(color_description)));
1184    aws->create_button(button_id, " ", NULp, color);
1185
1186    free(button_id);
1187}
1188
1189static void create_font_button(AW_window *aws, const char *gc_base_name, const gc_desc& gcd) {
1190    char *button_id = GBS_global_string_copy("sel_font_%s", gcd.key.c_str());
1191
1192    aws->callback(makeWindowCallback(aw_create_font_chooser_window, gc_base_name, &gcd));
1193    aws->create_button(button_id, fontinfo_awarname(gc_base_name, gcd.key));
1194
1195    free(button_id);
1196}
1197
1198static const int STD_LABEL_LEN    = 15;
1199static const int COLOR_BUTTON_LEN = 10;
1200static const int FONT_BUTTON_LEN  = COLOR_BUTTON_LEN+STD_LABEL_LEN+1;
1201// => color+font has ~same length as 2 colors (does not work for color groups)
1202
1203void AW_gc_manager::create_gc_buttons(AW_window *aws, gc_type for_gc_type) {
1204    for (int idx = 0; idx<int(GCs.size()); ++idx) {
1205        const gc_desc& gcd = GCs[idx];
1206
1207        if (gcd.is_color_group())        { if (for_gc_type != GC_TYPE_GROUP) continue; }
1208        else if (gcd.belongs_to_range()) { if (for_gc_type != GC_TYPE_RANGE) continue; }
1209        else                             { if (for_gc_type != GC_TYPE_NORMAL) continue; }
1210
1211        if (for_gc_type == GC_TYPE_RANGE) {
1212            if (gcd.get_color_index() == 0) { // first color of range
1213                const gc_range& range = color_ranges[gcd.get_range_index()];
1214
1215                const char *type_info = NULp;
1216                switch (range.get_type()) {
1217                    case GC_RANGE_LINEAR:  type_info  = "linear 1D range"; break;
1218                    case GC_RANGE_CYCLIC:  type_info  = "cyclic 1D range"; break;
1219                    case GC_RANGE_PLANAR:  type_info  = "planar 2D range"; break;
1220                    case GC_RANGE_SPATIAL: type_info  = "spatial 3D range"; break;
1221                    case GC_RANGE_INVALID: type_info = "invalid range "; aw_assert(0); break;
1222                }
1223
1224                const char *range_headline = GBS_global_string("%s (%s)", range.get_name().c_str(), type_info);
1225                aws->button_length(60);
1226                aws->create_button(NULp, range_headline, NULp, "+");
1227                aws->at_newline();
1228            }
1229        }
1230
1231        if (for_gc_type == GC_TYPE_GROUP) {
1232            int color_group_no = idx-first_colorgroup_idx+1;
1233            char buf[13];
1234            sprintf(buf, "%2i:", color_group_no); // @@@ shall this short label be stored in gc_desc?
1235            aws->label_length(3);
1236            aws->label(buf);
1237            aws->create_input_field(colorgroupname_awarname(color_group_no), 21);
1238        }
1239        else {
1240            aws->label_length(STD_LABEL_LEN);
1241            aws->label(gcd.colorlabel.c_str());
1242        }
1243
1244        aws->button_length(COLOR_BUTTON_LEN);
1245        create_color_button(aws, color_awarname(gc_base_name, gcd.key), gcd.colorlabel.c_str());
1246        if (gcd.has_font)   {
1247            aws->button_length(FONT_BUTTON_LEN);
1248            create_font_button(aws, gc_base_name, gcd);
1249        }
1250        if (!gcd.same_line) aws->at_newline();
1251    }
1252}
1253
1254typedef std::map<AW_gc_manager*, AW_window_simple*> GroupWindowRegistry;
1255
1256static void AW_popup_gc_color_groups_window(AW_window *aww, AW_gc_manager *gcmgr) {
1257    aw_assert(AW_gc_manager::color_groups_initialized());
1258
1259    static GroupWindowRegistry     existing_window;
1260    GroupWindowRegistry::iterator  found = existing_window.find(gcmgr);
1261    AW_window_simple              *aws   = found == existing_window.end() ? NULp : found->second;
1262
1263    if (!aws) {
1264        aws = new AW_window_simple;
1265
1266        aws->init(aww->get_root(), "COLOR_GROUP_DEF2", "Define color groups");
1267
1268        aws->at(10, 10);
1269        aws->auto_space(1, 1);
1270
1271        aws->callback(AW_POPDOWN);
1272        aws->create_button("CLOSE", "CLOSE", "C");
1273        aws->callback(makeHelpCallback("color_props_groups.hlp"));
1274        aws->create_button("HELP", "HELP", "H");
1275        aws->at_newline();
1276
1277        aws->label_length(20);
1278        aws->label("Enable color groups:");
1279        aws->create_toggle(AWAR_COLOR_GROUPS_USE);
1280        aws->at_newline();
1281
1282        gcmgr->create_gc_buttons(aws, GC_TYPE_GROUP);
1283
1284        aws->window_fit();
1285        existing_window[gcmgr] = aws;
1286    }
1287
1288    aws->activate();
1289}
1290
1291void AW_popup_gc_color_range_window(AW_window *aww, AW_gc_manager *gcmgr) {
1292    static GroupWindowRegistry     existing_window;
1293    GroupWindowRegistry::iterator  found = existing_window.find(gcmgr);
1294    AW_window_simple              *aws   = found == existing_window.end() ? NULp : found->second;
1295
1296    if (!aws) {
1297        aws = new AW_window_simple;
1298
1299        aws->init(aww->get_root(), "COLOR_RANGE_EDIT", "Edit color ranges");
1300
1301        aws->at(10, 10);
1302        aws->auto_space(5, 5);
1303
1304        aws->callback(AW_POPDOWN);
1305        aws->create_button("CLOSE", "CLOSE", "C");
1306        aws->callback(makeHelpCallback("color_ranges.hlp"));
1307        aws->create_button("HELP", "HELP", "H");
1308        aws->at_newline();
1309
1310        aws->label("Overlay active range");
1311        aws->create_toggle(AWAR_RANGE_OVERLAY);
1312        aws->at_newline();
1313
1314        gcmgr->create_gc_buttons(aws, GC_TYPE_RANGE);
1315
1316        aws->window_fit();
1317        existing_window[gcmgr] = aws;
1318    }
1319
1320    aws->activate();
1321}
1322
1323AW_window *AW_create_gc_window_named(AW_root *aw_root, AW_gc_manager *gcman, const char *wid, const char *windowname) {
1324    // same as AW_create_gc_window, but uses different window id and name
1325    // (use if if there are two or more color def windows in one application,
1326    // otherwise they save the same window properties)
1327
1328    AW_window_simple *aws = new AW_window_simple;
1329
1330    aws->init(aw_root, wid, windowname);
1331
1332    aws->at(10, 10);
1333    aws->auto_space(2, 2);
1334
1335    aws->callback(AW_POPDOWN);
1336    aws->create_button("CLOSE", "CLOSE", "C");
1337    aws->callback(makeHelpCallback("color_props.hlp"));
1338    aws->create_button("HELP", "HELP", "H");
1339    aws->at_newline();
1340
1341    // select all fonts:
1342    {
1343        static gc_desc allFont_fake_desc(-1, GC_TYPE_NORMAL);
1344        allFont_fake_desc.colorlabel       = "<all fonts>";
1345        allFont_fake_desc.key              = ALL_FONTS_ID;
1346        allFont_fake_desc.fixed_width_font = !gcman->has_variable_width_font();
1347        aws->callback(makeWindowCallback(aw_create_font_chooser_window, gcman->get_base_name(), &allFont_fake_desc));
1348    }
1349    aws->label_length(STD_LABEL_LEN);
1350    aws->label("All fonts:");
1351    aws->button_length(COLOR_BUTTON_LEN);
1352    aws->create_button("select_all_fonts", "Select", "s");
1353    aws->at_newline();
1354
1355    gcman->create_gc_buttons(aws, GC_TYPE_NORMAL);
1356
1357    bool groups_or_range = false;
1358    if (gcman->has_color_groups()) {
1359        aws->callback(makeWindowCallback(AW_popup_gc_color_groups_window, gcman));
1360        aws->create_autosize_button("EDIT_COLOR_GROUP", "Edit color groups", "E");
1361        groups_or_range = true;
1362    }
1363    if (gcman->has_color_range()) {
1364        aws->callback(makeWindowCallback(AW_popup_gc_color_range_window, gcman));
1365        aws->create_autosize_button("EDIT_COLOR_RANGE", "Edit color ranges", "r");
1366        groups_or_range = true;
1367    }
1368    if (groups_or_range) aws->at_newline();
1369
1370    aws->window_fit();
1371    return aws;
1372}
1373AW_window *AW_create_gc_window(AW_root *aw_root, AW_gc_manager *gcman) {
1374    return AW_create_gc_window_named(aw_root, gcman, "COLOR_DEF2", "Colors and Fonts");
1375}
1376
1377int AW_get_drag_gc(AW_gc_manager *gcman) {
1378    return gcman->get_drag_gc();
1379}
1380
1381void AW_displayColorRange(AW_device *device, int first_range_gc, AW::Position start, AW_pos xsize, AW_pos ysize) {
1382    /*! display active color range
1383     * @param device          output device
1384     * @param first_range_gc  first color range gc
1385     * @param start           position of upper left corner
1386     * @param xsize           xsize used per color
1387     * @param ysize           xsize used per color
1388     *
1389     * displays AW_PLANAR_COLORS rows and AW_PLANAR_COLORS columns
1390     */
1391    using namespace AW;
1392
1393    if (AW_gc_manager::show_range_overlay) {
1394        Vector size(xsize, ysize);
1395        for (int x = 0; x<AW_PLANAR_COLORS; ++x) {
1396            for (int y = 0; y<AW_PLANAR_COLORS; ++y) {
1397                int      gc     = first_range_gc + y*AW_PLANAR_COLORS+x;
1398                Vector   toCorner(x*xsize, y*ysize);
1399                Position corner = start+toCorner;
1400                device->box(gc, FillStyle::SOLID, Rectangle(corner, size));
1401            }
1402        }
1403    }
1404}
1405
1406void AW_getColorRangeNames(const AW_gc_manager *gcman, int dimension, ConstStrArray& ids, ConstStrArray& names) {
1407    /*! retrieve selected color-range IDs
1408     * @param gcman      the gc-manager defining the color-ranges
1409     * @param dimension  the wanted dimension of the color-ranges
1410     * @param ids        array where range-IDs will be stored
1411     * @param names      array where corresponding range-names will be stored (same index as 'ids')
1412     */
1413    ids.clear();
1414    names.clear();
1415    gcman->getColorRangeNames(dimension, ids, names);
1416}
1417
1418int AW_getFirstRangeGC(AW_gc_manager *gcman) { return gcman->first_range_gc(); }
1419void AW_activateColorRange(AW_gc_manager *gcman, const char *id) { gcman->activateColorRange(id); }
1420
1421const char *AW_getActiveColorRangeID(AW_gc_manager *gcman, int *dimension) {
1422    /*! @return the ID of the active color range
1423     * @param gcman      of this gc-manager
1424     * @param dimension  if !NULp -> is set to dimension of range
1425     */
1426    return gcman->getActiveColorRangeID(dimension);
1427}
1428
1429#if defined(UNIT_TESTS)
1430void fake_AW_init_color_groups() {
1431    if (!AW_gc_manager::color_groups_initialized()) {
1432        AW_init_color_group_defaults(NULp);
1433    }
1434    AW_gc_manager::use_color_groups = true;
1435}
1436#endif
1437
1438// @@@ move code below somewhere more sensible
1439
1440static void add_common_property_menu_entries(AW_window *aw) {
1441    aw->insert_menu_topic("enable_advices",   "Reactivate advices",   "R", "advice.hlp",    AWM_ALL, AW_reactivate_all_advices);
1442    aw->insert_menu_topic("enable_questions", "Reactivate questions", "q", "questions.hlp", AWM_ALL, AW_reactivate_all_questions);
1443    aw->insert_menu_topic("reset_win_layout", "Reset window layout",  "w", "reset_win_layout.hlp", AWM_ALL, AW_forget_all_window_geometry);
1444}
1445void AW_insert_common_property_menu_entries(AW_window_menu_modes *awmm) { add_common_property_menu_entries(awmm); }
1446void AW_insert_common_property_menu_entries(AW_window_simple_menu *awsm) { add_common_property_menu_entries(awsm); }
1447
1448void AW_save_specific_properties(AW_window *aw, const char *filename) {  // special version for EDIT4
1449    GB_ERROR error = aw->get_root()->save_properties(filename);
1450    if (error) aw_message(error);
1451}
1452void AW_save_properties(AW_window *aw) {
1453    AW_save_specific_properties(aw, NULp);
1454}
1455
1456// --------------------------------
1457//      RGB <-> HSV conversion
1458
1459class AW_hsv {
1460    float H, S, V;
1461public:
1462    AW_hsv(float hue, float saturation, float value) : H(hue), S(saturation), V(value) {
1463        aw_assert(H>=0.0 && H<360.0);
1464        aw_assert(S>=0.0 && S<=1.0);
1465        aw_assert(V>=0.0 && V<=1.0);
1466    }
1467    AW_hsv(const AW_rgb_normalized& col) {
1468        float R = col.r();
1469        float G = col.g();
1470        float B = col.b();
1471
1472        float min = std::min(std::min(R, G), B);
1473        float max = std::max(std::max(R, G), B);
1474
1475        if (min == max) {
1476            H = 0;
1477        }
1478        else {
1479            H = 60;
1480
1481            if      (max == R) { H *= 0 + (G-B)/(max-min); }
1482            else if (max == G) { H *= 2 + (B-R)/(max-min); }
1483            else               { H *= 4 + (R-G)/(max-min); }
1484
1485            if (H<0) H += 360;
1486        }
1487
1488        S = max ? (max-min)/max : 0;
1489        V = max;
1490    }
1491#if defined(Cxx11)
1492    AW_hsv(const AW_rgb16& col) : AW_hsv(AW_rgb_normalized(col)) {}
1493#else // !defined(Cxx11)
1494    AW_hsv(const AW_rgb16& col) { *this = AW_rgb_normalized(col); }
1495#endif
1496
1497    AW_rgb_normalized rgb() const {
1498        int   hi = int(H/60);
1499        float f  = H/60-hi;
1500
1501        float p = V*(1-S);
1502        float q = V*(1-S*f);
1503        float t = V*(1-S*(1-f));
1504
1505        switch (hi) {
1506            case 0: return AW_rgb_normalized(V, t, p); //   0 <= H <  60 (deg)
1507            case 1: return AW_rgb_normalized(q, V, p); //  60 <= H < 120
1508            case 2: return AW_rgb_normalized(p, V, t); // 120 <= H < 180
1509            case 3: return AW_rgb_normalized(p, q, V); // 180 <= H < 240
1510            case 4: return AW_rgb_normalized(t, p, V); // 240 <= H < 300
1511            case 5: return AW_rgb_normalized(V, p, q); // 300 <= H < 360
1512        }
1513        aw_assert(0);
1514        return AW_rgb_normalized(0, 0, 0);
1515    }
1516
1517    float h() const { return H; }
1518    float s() const { return S; }
1519    float v() const { return V; }
1520};
1521
1522
1523// --------------------------------------------------------------------------------
1524
1525#ifdef UNIT_TESTS
1526#ifndef TEST_UNIT_H
1527#include <test_unit.h>
1528#endif
1529
1530void TEST_rgb_hsv_conversion() {
1531    // Note: more related tests in AW_rgb.cxx@RGB_TESTS
1532
1533    const int tested[] = {
1534        // testing full rgb space takes too long
1535        // just test all combinations of these:
1536
1537        0, 1, 2, 3, 4, 5,
1538        58, 59, 60, 61, 62,
1539        998, 999, 1000, 1001, 1002,
1540        32766, 32767, 32768, 32769, 32770,
1541        39998, 39999, 40000, 40001, 40002,
1542        65531, 65532, 65533, 65534, 65535
1543    };
1544
1545    for (unsigned i = 0; i<ARRAY_ELEMS(tested); ++i) {
1546        int r = tested[i];
1547        for (unsigned j = 0; j<ARRAY_ELEMS(tested); ++j) {
1548            int g = tested[j];
1549            for (unsigned k = 0; k<ARRAY_ELEMS(tested); ++k) {
1550                int b = tested[k];
1551
1552                TEST_ANNOTATE(GBS_global_string("rgb=%i/%i/%i", r, g, b));
1553
1554                AW_hsv hsv(AW_rgb16(r, g, b));
1555
1556                // check range overflow
1557                TEST_EXPECT(hsv.h()>=0.0 && hsv.h()<360.0);
1558                TEST_EXPECT(hsv.s()>=0.0 && hsv.s()<=1.0);
1559                TEST_EXPECT(hsv.v()>=0.0 && hsv.v()<=1.0);
1560
1561                AW_rgb16 RGB(hsv.rgb());
1562
1563                // fprintf(stderr, "rgb=%i/%i/%i hsv=%i/%i/%i RGB=%i/%i/%i\n", r, g, b, h, s, v, R, G, B);
1564
1565                // check that rgb->hsv->RGB produces a similar color
1566                const int MAXDIFF    = 1; // less than .0015% difference per channel
1567                const int MAXDIFFSUM = 2; // less than .003% difference overall
1568
1569                TEST_EXPECT(abs(r-RGB.r()) <= MAXDIFF);
1570                TEST_EXPECT(abs(g-RGB.g()) <= MAXDIFF);
1571                TEST_EXPECT(abs(b-RGB.b()) <= MAXDIFF);
1572
1573                TEST_EXPECT((abs(r-RGB.r())+abs(g-RGB.g())+abs(b-RGB.b())) <= MAXDIFFSUM);
1574            }
1575        }
1576    }
1577
1578    for (unsigned i = 0; i<ARRAY_ELEMS(tested); ++i) {
1579        int h = tested[i]*320/65535;
1580        for (unsigned j = 0; j<ARRAY_ELEMS(tested); ++j) {
1581            float s = tested[j]/65535.0;
1582            for (unsigned k = 0; k<ARRAY_ELEMS(tested); ++k) {
1583                float v = tested[k]/65535.0;
1584
1585                TEST_ANNOTATE(GBS_global_string("hsv=%i/%.3f/%.3f", h, s, v));
1586
1587                AW_rgb16 rgb(AW_hsv(h, s, v).rgb());
1588                AW_rgb16 RGB(AW_hsv(rgb).rgb());
1589
1590                // 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);
1591
1592                // check that hsv->rgb->HSV->RGB produces a similar color (comparing hsv makes no sense)
1593                const int MAXDIFF    = 1; // less than .0015% difference per channel
1594                const int MAXDIFFSUM = 2; // less than .003% difference overall
1595
1596                TEST_EXPECT(abs(rgb.r()-RGB.r()) <= MAXDIFF);
1597                TEST_EXPECT(abs(rgb.g()-RGB.g()) <= MAXDIFF);
1598                TEST_EXPECT(abs(rgb.b()-RGB.b()) <= MAXDIFF);
1599
1600                TEST_EXPECT((abs(rgb.r()-RGB.r())+abs(rgb.g()-RGB.g())+abs(rgb.b()-RGB.b())) <= MAXDIFFSUM);
1601            }
1602        }
1603    }
1604
1605    // specific conversion (showed wrong 'hue' and 'saturation' until [14899])
1606    {
1607        AW_hsv hsv(AW_rgb16(0, 0, 14906));
1608
1609        TEST_EXPECT_SIMILAR(hsv.h(), 240.0, 0.001); //= ~ 240 deg
1610        TEST_EXPECT_SIMILAR(hsv.s(), 1.0,   0.001); //= 100%
1611        TEST_EXPECT_SIMILAR(hsv.v(), 0.227, 0.001); //= ~ 22.7%
1612
1613        AW_rgb16 rgb(hsv.rgb());
1614
1615        TEST_EXPECT_EQUAL(rgb.r(), 0);
1616        TEST_EXPECT_EQUAL(rgb.g(), 0);
1617        TEST_EXPECT_EQUAL(rgb.b(), 14906);
1618    }
1619}
1620
1621#endif // UNIT_TESTS
1622
1623// --------------------------------------------------------------------------------
1624
1625
1626// ------------------------------
1627//      motif color selector
1628
1629#define AWAR_SELECTOR_COLOR_LABEL "tmp/aw/color_label"
1630
1631#define AWAR_CV_R "tmp/aw/color_r" // rgb..
1632#define AWAR_CV_G "tmp/aw/color_g"
1633#define AWAR_CV_B "tmp/aw/color_b"
1634#define AWAR_CV_H "tmp/aw/color_h" // hsv..
1635#define AWAR_CV_S "tmp/aw/color_s"
1636#define AWAR_CV_V "tmp/aw/color_v"
1637
1638static char *current_color_awarname         = NULp; // name of the currently modified color-awar
1639static bool  ignore_color_value_change      = false;
1640static bool  color_value_change_was_ignored = false;
1641
1642static void aw_set_rgb_sliders(AW_root *awr, const AW_rgb_normalized& col) {
1643    color_value_change_was_ignored = false;
1644    {
1645        LocallyModify<bool> delayUpdate(ignore_color_value_change, true);
1646        awr->awar(AWAR_CV_R)->write_float(col.r());
1647        awr->awar(AWAR_CV_G)->write_float(col.g());
1648        awr->awar(AWAR_CV_B)->write_float(col.b());
1649    }
1650    if (color_value_change_was_ignored) awr->awar(AWAR_CV_B)->touch();
1651}
1652
1653static void aw_set_sliders_from_color(AW_root *awr) {
1654    const char *color = awr->awar(current_color_awarname)->read_char_pntr();
1655    aw_set_rgb_sliders(awr, AW_rgb_normalized(color));
1656}
1657
1658inline void aw_set_color(AW_root *awr, const char *color_name) {
1659    awr->awar(current_color_awarname)->write_string(color_name);
1660    aw_set_sliders_from_color(awr);
1661}
1662static void aw_set_color(AW_window *aww, const char *color_name) {
1663    aw_set_color(aww->get_root(), color_name);
1664}
1665
1666static void colorslider_changed_cb(AW_root *awr, bool hsv_changed) {
1667    if (ignore_color_value_change) {
1668        color_value_change_was_ignored = true;
1669    }
1670    else {
1671        LocallyModify<bool> noRecursion(ignore_color_value_change, true);
1672
1673        if (hsv_changed) {
1674            float h = awr->awar(AWAR_CV_H)->read_float();
1675            float s = awr->awar(AWAR_CV_S)->read_float();
1676            float v = awr->awar(AWAR_CV_V)->read_float();
1677
1678            if (h>=360.0) h -= 360;
1679
1680            AW_rgb_normalized col(AW_hsv(h, s, v).rgb());
1681            aw_set_color(awr, AW_rgb16(col).ascii());
1682
1683            awr->awar(AWAR_CV_R)->write_float(col.r());
1684            awr->awar(AWAR_CV_G)->write_float(col.g());
1685            awr->awar(AWAR_CV_B)->write_float(col.b());
1686        }
1687        else {
1688            AW_rgb_normalized col(awr->awar(AWAR_CV_R)->read_float(),
1689                                  awr->awar(AWAR_CV_G)->read_float(),
1690                                  awr->awar(AWAR_CV_B)->read_float());
1691
1692            aw_set_color(awr, AW_rgb16(col).ascii());
1693
1694            AW_hsv hsv(col);
1695
1696            awr->awar(AWAR_CV_H)->write_float(hsv.h());
1697            awr->awar(AWAR_CV_S)->write_float(hsv.s());
1698            awr->awar(AWAR_CV_V)->write_float(hsv.v());
1699        }
1700    }
1701}
1702static void aw_create_colorslider_awars(AW_root *awr) {
1703    awr->awar_string(AWAR_SELECTOR_COLOR_LABEL);
1704    static const char *colorValueAwars[] = {
1705        AWAR_CV_R, AWAR_CV_H,
1706        AWAR_CV_G, AWAR_CV_S,
1707        AWAR_CV_B, AWAR_CV_V,
1708    };
1709
1710    for (int cv = 0; cv<6; ++cv) {
1711        awr->awar_float(colorValueAwars[cv])
1712            ->set_minmax(0.0, cv == 1 ? 360.0 : 1.0)
1713            ->add_callback(makeRootCallback(colorslider_changed_cb, bool(cv%2)));
1714    }
1715}
1716static void aw_create_color_chooser_window(AW_window *aww, const char *awar_name, const char *color_description) {
1717    AW_root *awr = aww->get_root();
1718    static AW_window_simple *aws = NULp;
1719    if (!aws) {
1720        aw_create_colorslider_awars(awr);
1721
1722        aws = new AW_window_simple;
1723        aws->init(awr, "COLORS", "Select color");
1724
1725        int x1 = 10;
1726        int y1 = 10;
1727
1728        aws->at(x1, y1);
1729        aws->auto_space(3, 3);
1730        aws->callback(AW_POPDOWN);
1731        aws->create_button("CLOSE", "CLOSE", "C");
1732
1733        aws->button_length(20);
1734        aws->create_button(NULp, AWAR_SELECTOR_COLOR_LABEL);
1735        aws->at_newline();
1736
1737        int x2, y2;
1738        aws->get_at_position(&x2, &y2);
1739        y2 += 3;
1740
1741        struct ColorValue {
1742            const char *label;
1743            const char *awar;
1744        } colorValue[] = {
1745            { "R", AWAR_CV_R }, { "H", AWAR_CV_H },
1746            { "G", AWAR_CV_G }, { "S", AWAR_CV_S },
1747            { "B", AWAR_CV_B }, { "V", AWAR_CV_V },
1748        };
1749
1750        const int INPUTFIELD_WIDTH = 12;
1751        const int SCALERLENGTH     = 320;
1752
1753        for (int row = 0; row<3; ++row) {
1754            aws->at(x1, y1+(row+1)*(y2-y1));
1755            const ColorValue *vc = &colorValue[row*2];
1756            aws->label(vc->label);
1757            aws->create_input_field_with_scaler(vc->awar, INPUTFIELD_WIDTH, SCALERLENGTH, AW_SCALER_LINEAR);
1758            ++vc;
1759            aws->label(vc->label);
1760            aws->create_input_field_with_scaler(vc->awar, INPUTFIELD_WIDTH, SCALERLENGTH, AW_SCALER_LINEAR);
1761        }
1762
1763        aws->button_length(1);
1764        aws->at_newline();
1765
1766        const float SATVAL_INCREMENT = 0.2;
1767        const int   HUE_INCREMENT    = 10;
1768        const int   COLORS_PER_ROW   = 360/HUE_INCREMENT;
1769
1770        for (int v = 5; v>=2; --v) {
1771            float val = v*SATVAL_INCREMENT;
1772            bool  rev = !(v%2);
1773            for (int s = rev ? 2 : 5; rev ? s<=5 : s>=2; s = rev ? s+1 : s-1) {
1774                float sat = s*SATVAL_INCREMENT;
1775                for (int hue = 0; hue<360;  hue += HUE_INCREMENT) {
1776                    const char *color_name = AW_rgb16(AW_hsv(hue, sat, val).rgb()).ascii();
1777                    aws->callback(makeWindowCallback(aw_set_color, strdup(color_name)));
1778                    aws->create_button(color_name, "", NULp, color_name);
1779                }
1780                aws->at_newline();
1781            }
1782        }
1783
1784        for (int p = 0; p<COLORS_PER_ROW; ++p) {
1785            float       grey       = (1.0 * p) / (COLORS_PER_ROW-1);
1786            const char *color_name = AW_rgb16(AW_rgb_normalized(grey, grey, grey)).ascii();
1787
1788            aws->callback(makeWindowCallback(aw_set_color, strdup(color_name)));
1789            aws->create_button(color_name, "=", NULp, color_name);
1790        }
1791        aws->at_newline();
1792
1793        aws->window_fit();
1794    }
1795    awr->awar(AWAR_SELECTOR_COLOR_LABEL)->write_string(color_description);
1796    freedup(current_color_awarname, awar_name);
1797    aw_set_sliders_from_color(awr);
1798    aws->activate();
1799}
1800
1801// ----------------------------
1802//      motif font chooser
1803
1804#define AWAR_SELECTOR_FONT_LABEL "tmp/aw/font_label"
1805#define AWAR_SELECTOR_FONT_NAME  "tmp/aw/font_name"
1806#define AWAR_SELECTOR_FONT_SIZE  "tmp/aw/font_size"
1807
1808static void aw_create_font_chooser_awars(AW_root *awr) {
1809    awr->awar_string(AWAR_SELECTOR_FONT_LABEL, "<invalid>");
1810    awr->awar_int(AWAR_SELECTOR_FONT_NAME, NO_FONT);
1811    awr->awar_int(AWAR_SELECTOR_FONT_SIZE, NO_SIZE)->set_minmax(MIN_FONTSIZE, MAX_FONTSIZE);
1812}
1813
1814static void aw_create_font_chooser_window(AW_window *aww, const char *gc_base_name, const gc_desc *gcd) {
1815    AW_root *awr = aww->get_root();
1816
1817    static AW_window_simple *aw_fontChoose[2] = { NULp, NULp }; // one for fixed-width font; one general
1818
1819    bool fixed_width_only = gcd->fixed_width_font;
1820
1821    AW_window_simple*& aws = aw_fontChoose[fixed_width_only];
1822    AW_window_simple*& awo = aw_fontChoose[!fixed_width_only];
1823
1824    if (!aws) {
1825        aw_create_font_chooser_awars(awr);
1826
1827        aws = new AW_window_simple;
1828        aws->init(awr, "FONT", fixed_width_only ? "Select fixed width font" : "Select font");
1829
1830        aws->auto_space(10, 10);
1831
1832        aws->callback(AW_POPDOWN);
1833        aws->create_button("CLOSE", "CLOSE", "C");
1834
1835        aws->button_length(20);
1836        aws->create_button(NULp, AWAR_SELECTOR_FONT_LABEL);
1837        aws->at_newline();
1838
1839        aws->label("Font:");
1840        aws->create_option_menu(AWAR_SELECTOR_FONT_NAME, true);
1841        {
1842            int fonts_inserted = 0;
1843            for (int order = 1; order>=0; order--) {
1844                for (int font_nr = 0; ; font_nr++) {
1845                    AW_font     aw_font_nr  = font_nr;
1846                    bool        found;
1847                    const char *font_string = AW_get_font_specification(aw_font_nr, found);
1848
1849                    if (!font_string) {
1850                        fprintf(stderr, "[Font detection: tried=%i, found=%i]\n", font_nr, fonts_inserted);
1851                        break;
1852                    }
1853
1854                    if (found != bool(order)) continue; // display found fonts at top
1855                    if (fixed_width_only && !font_has_fixed_width(aw_font_nr)) continue;
1856
1857                    aws->insert_option(font_string, NULp, font_nr);
1858                    ++fonts_inserted;
1859                }
1860            }
1861            if (!fonts_inserted) aws->insert_option("No suitable fonts detected", NULp, 0);
1862            aws->insert_default_option("<no font selected>", NULp, NO_FONT);
1863            aws->update_option_menu();
1864        }
1865
1866        aws->at_newline();
1867
1868        aws->label("Size:");
1869        aws->create_input_field_with_scaler(AWAR_SELECTOR_FONT_SIZE, 3, 330);
1870        aws->at_newline();
1871
1872        aws->window_fit();
1873    }
1874
1875    awr->awar(AWAR_SELECTOR_FONT_LABEL)->write_string(gcd->colorlabel.c_str());
1876    awr->awar(AWAR_SELECTOR_FONT_NAME)->map(awr->awar(fontname_awarname(gc_base_name, gcd->key)));
1877    awr->awar(AWAR_SELECTOR_FONT_SIZE)->map(awr->awar(fontsize_awarname(gc_base_name, gcd->key)));
1878
1879    if (awo) awo->hide(); // both windows use same awars -> hide the other window to avoid chaos
1880    aws->activate();
1881}
1882
1883// -----------------------------------
1884//      frame colors (motif only)
1885
1886static void aw_message_reload(AW_root *) {
1887    aw_message("Sorry, to activate new colors:\n"
1888                "   save properties\n"
1889                "   and restart application");
1890}
1891
1892static void AW_preset_create_font_chooser(AW_window *aws, const char *awar, const char *label, bool message_reload) {
1893    if (message_reload) aws->get_root()->awar(awar)->add_callback(aw_message_reload);
1894
1895    aws->label(label);
1896    aws->create_option_menu(awar, true);
1897
1898    aws->insert_option("5x8",               "5", "5x8");
1899    aws->insert_option("6x10",              "6", "6x10");
1900    aws->insert_option("7x13",              "7", "7x13");
1901    aws->insert_option("7x13bold",          "7", "7x13bold");
1902    aws->insert_option("8x13",              "8", "8x13");
1903    aws->insert_option("8x13bold",          "8", "8x13bold");
1904    aws->insert_option("9x15",              "9", "9x15");
1905    aws->insert_option("9x15bold",          "9", "9x15bold");
1906    aws->insert_option("helvetica-12",      "9", "helvetica-12");
1907    aws->insert_option("helvetica-bold-12", "9", "helvetica-bold-12");
1908    aws->insert_option("helvetica-13",      "9", "helvetica-13");
1909    aws->insert_option("helvetica-bold-13", "9", "helvetica-bold-13");
1910
1911    aws->insert_default_option("other", "o", "");
1912    aws->update_option_menu();
1913}
1914static void AW_preset_create_color_button(AW_window *aws, const char *awar_name, const char *label) {
1915    aws->get_root()->awar(awar_name)->add_callback(aw_message_reload);
1916    aws->label(label);
1917    create_color_button(aws, awar_name, label);
1918}
1919
1920AW_window *AW_preset_window(AW_root *root) {
1921    AW_window_simple *aws = new AW_window_simple;
1922    const int   tabstop = 400;
1923    aws->init(root, "PROPS_FRAME", "WINDOW_PROPERTIES");
1924
1925    aws->label_length(25);
1926    aws->button_length(20);
1927
1928    aws->at           (10, 10);
1929    aws->auto_space(10, 10);
1930
1931    aws->callback     (AW_POPDOWN);
1932    aws->create_button("CLOSE", "CLOSE", "C");
1933
1934    aws->callback(makeHelpCallback("props_frame.hlp"));
1935    aws->create_button("HELP", "HELP", "H");
1936
1937    aws->at_newline();
1938
1939    AW_preset_create_font_chooser(aws, "window/font", "Main Menu Font", 1);
1940    aws->at_x(tabstop);
1941    aws->create_input_field("window/font", 12);
1942
1943    aws->at_newline();
1944
1945    aws->button_length(10);
1946    AW_preset_create_color_button(aws, "window/background", "Application Background");
1947    aws->at_x(tabstop);
1948    aws->create_input_field("window/background", 12);
1949
1950    aws->at_newline();
1951
1952    AW_preset_create_color_button(aws, "window/foreground", "Application Foreground");
1953    aws->at_x(tabstop);
1954    aws->create_input_field("window/foreground", 12);
1955
1956    aws->at_newline();
1957
1958    AW_preset_create_color_button(aws, "window/color_1", "Color 1");
1959    aws->at_x(tabstop);
1960    aws->create_input_field("window/color_1", 12);
1961
1962    aws->at_newline();
1963
1964    AW_preset_create_color_button(aws, "window/color_2", "Color 2");
1965    aws->at_x(tabstop);
1966    aws->create_input_field("window/color_2", 12);
1967
1968    aws->at_newline();
1969
1970    AW_preset_create_color_button(aws, "window/color_3", "Color 3");
1971
1972    aws->at_x(tabstop);
1973    aws->create_input_field("window/color_3", 12);
1974
1975    aws->at_newline();
1976
1977    aws->window_fit();
1978    return aws;
1979}
1980
Note: See TracBrowser for help on using the repository browser.