source: branches/alilink/WINDOW/AW_preset.cxx

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