source: tags/ms_r16q3/WINDOW/AW_preset.cxx

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