source: tags/ms_r16q2/WINDOW/AW_preset.cxx

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