NOOP, remove trailing tabs/whitespace.
[ardour.git] / libs / gtkmm2ext / motionfeedback.cc
1 /*
2     Copyright (C) 2010-2011 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18     $Id: motionfeedback.cc,v 1.5 2004/03/01 03:44:19 pauld Exp $
19 */
20
21 #include <iostream>
22 #include <cmath>
23 #include <cstdlib>
24 #include <algorithm>
25 #include <unistd.h>
26 #include <stdio.h> /* for snprintf, grrr */
27
28 #include <pbd/gstdio_compat.h>
29
30 #include <gdk/gdkkeysyms.h>
31 #include <gtkmm.h>
32
33 #include "pbd/controllable.h"
34 #include "pbd/compose.h"
35 #include "pbd/error.h"
36
37 #include "gtkmm2ext/motionfeedback.h"
38 #include "gtkmm2ext/keyboard.h"
39 #include "gtkmm2ext/prolooks-helpers.h"
40 #include "gtkmm2ext/gui_thread.h"
41
42 #include "i18n.h"
43
44 using namespace Gtk;
45 using namespace Gtkmm2ext;
46 using namespace sigc;
47
48 using PBD::error;
49
50 Gdk::Color* MotionFeedback::base_color;
51
52 MotionFeedback::MotionFeedback (Glib::RefPtr<Gdk::Pixbuf> pix,
53                                 Type t,
54                                 boost::shared_ptr<PBD::Controllable> c,
55                                 double default_val,
56                                 double step_increment,
57                                 double page_increment,
58                                 const char *widget_name,
59                                 bool with_numeric_display,
60                                 int subw,
61                                 int subh)
62         : _controllable (c)
63         , value (0)
64         , default_value (default_val)
65         , step_inc (step_increment)
66         , page_inc (page_increment)
67         , type (t)
68         , value_packer (0)
69         , pixbuf (pix)
70         , subwidth (subw)
71         , subheight (subh)
72 {
73         if (!base_color) {
74                 base_color = new Gdk::Color ("#1a5274");
75         }
76
77         char value_name[1024];
78
79         print_func = default_printer;
80         print_arg = 0;
81
82
83         HBox* hpacker = manage (new HBox);
84         hpacker->pack_start (pixwin, true, true);
85         hpacker->show ();
86         pack_start (*hpacker, false, false);
87         pixwin.show ();
88
89         if (with_numeric_display) {
90
91                 value_packer = new EventBox;
92                 value_packer->set_name ("MotionControllerValue");
93                 value_packer->show ();
94                 value_packer->set_border_width (6);
95
96                 value = new Label;
97                 value->set_justify (Gtk::JUSTIFY_RIGHT);
98                 value->show ();
99
100                 value_packer->add (*value);
101
102                 hpacker = manage (new HBox);
103                 hpacker->pack_start (*value_packer, true, false);
104                 hpacker->show ();
105                 hpacker->set_border_width (6);
106
107                 pack_start (*hpacker, false, false);
108
109                 if (widget_name) {
110                         snprintf (value_name, sizeof(value_name), "%sValue", widget_name);
111                         value->set_name (value_name);
112                 }
113
114                 if (_controllable) {
115                         char buf[32];
116                         print_func (buf, _controllable, print_arg);
117                         value->set_text (buf);
118                 }
119         }
120
121         pixwin.set_events (Gdk::BUTTON_PRESS_MASK|
122                            Gdk::BUTTON_RELEASE_MASK|
123                            Gdk::POINTER_MOTION_MASK|
124                            Gdk::ENTER_NOTIFY_MASK|
125                            Gdk::LEAVE_NOTIFY_MASK|
126                            Gdk::SCROLL_MASK|
127                            Gdk::KEY_PRESS_MASK|
128                            Gdk::KEY_RELEASE_MASK);
129
130         pixwin.set_flags (CAN_FOCUS);
131
132         /* Proxy all important events on the pixwin to ourselves */
133
134         pixwin.signal_button_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_press_event));
135         pixwin.signal_button_release_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_release_event));
136         pixwin.signal_motion_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_motion_notify_event));
137         pixwin.signal_enter_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_enter_notify_event));
138         pixwin.signal_leave_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_leave_notify_event));
139         pixwin.signal_key_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_key_press_event));
140         pixwin.signal_scroll_event().connect(mem_fun (*this,&MotionFeedback::pixwin_scroll_event));
141         pixwin.signal_expose_event().connect(mem_fun (*this,&MotionFeedback::pixwin_expose_event), true);
142         pixwin.signal_size_request().connect(mem_fun (*this,&MotionFeedback::pixwin_size_request));
143 }
144
145 MotionFeedback::~MotionFeedback()
146 {
147         delete value;
148         delete value_packer;
149 }
150
151 bool
152 MotionFeedback::pixwin_button_press_event (GdkEventButton *ev)
153 {
154         if (binding_proxy.button_press_handler (ev)) {
155                 return true;
156         }
157
158         switch (ev->button) {
159         case 1:
160                 grab_is_fine = false;
161                 break;
162         case 2:
163                 grab_is_fine = true;
164                 break;
165         case 3:
166                 return false;
167         }
168
169         gtk_grab_add(GTK_WIDGET(pixwin.gobj()));
170
171         grabbed_y = ev->y_root;
172         grabbed_x = ev->x_root;
173
174         return false;
175 }
176
177 bool
178 MotionFeedback::pixwin_button_release_event (GdkEventButton *ev)
179 {
180         if (!_controllable) {
181                 return false;
182         }
183
184         switch (ev->button) {
185         case 1:
186                 if (pixwin.has_grab()) {
187                         if (!grab_is_fine) {
188                                 gtk_grab_remove
189                                         (GTK_WIDGET(pixwin.gobj()));
190                         }
191                 }
192                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
193                         /* shift click back to the default */
194                         _controllable->set_value (default_value);
195                         return true;
196                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
197                         /* ctrl click back to the minimum value */
198                         _controllable->set_value (_controllable->lower ());
199                 }
200                 break;
201
202         case 3:
203                 if (pixwin.has_grab()) {
204                         if (grab_is_fine) {
205                                 gtk_grab_remove
206                                         (GTK_WIDGET(pixwin.gobj()));
207                         }
208                 }
209                 break;
210         }
211
212         return VBox::on_button_release_event (ev);
213 }
214
215 bool
216 MotionFeedback::pixwin_motion_notify_event (GdkEventMotion *ev)
217 {
218         if (!_controllable) {
219                 return false;
220         }
221
222         gfloat multiplier;
223         gfloat x_delta;
224         gfloat y_delta;
225
226         if (!pixwin.has_grab()) {
227                 return VBox::on_motion_notify_event (ev);
228         }
229
230         multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100 : 1) *
231                 ((ev->state & Keyboard::PrimaryModifier) ? 10 : 1) *
232                 ((ev->state & Keyboard::SecondaryModifier) ? 0.1 : 1);
233
234         if (ev->state & Gdk::BUTTON1_MASK) {
235
236                 /* vertical control */
237
238                 y_delta = grabbed_y - ev->y_root;
239                 grabbed_y = ev->y_root;
240
241                 x_delta = ev->x_root - grabbed_x;
242
243                 if (y_delta == 0) return TRUE;
244
245                 y_delta *= 1 + (x_delta/100);
246                 y_delta *= multiplier;
247                 y_delta /= 10;
248
249                 _controllable->set_value (adjust ((grab_is_fine ? step_inc : page_inc) * y_delta));
250
251         } else if (ev->state & Gdk::BUTTON2_MASK) {
252
253                 /* rotary control */
254
255                 double x = ev->x - subwidth/2;
256                 double y = - ev->y + subwidth/2;
257                 double angle = std::atan2 (y, x) / M_PI;
258
259                 if (angle < -0.5) {
260                         angle += 2.0;
261                 }
262
263                 angle = -(2.0/3.0) * (angle - 1.25);
264                 angle *= multiplier;
265
266                 _controllable->set_value (to_control_value (angle));
267         }
268
269
270         return true;
271 }
272
273 bool
274 MotionFeedback::pixwin_enter_notify_event (GdkEventCrossing*)
275 {
276         pixwin.grab_focus();
277         return false;
278 }
279
280 bool
281 MotionFeedback::pixwin_leave_notify_event (GdkEventCrossing*)
282 {
283         pixwin.unset_flags (HAS_FOCUS);
284         return false;
285 }
286
287 bool
288 MotionFeedback::pixwin_key_press_event (GdkEventKey *ev)
289 {
290         if (!_controllable) {
291                 return false;
292         }
293
294         bool retval = false;
295         double multiplier;
296
297         multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100.0 : 1.0) *
298                 ((ev->state & Keyboard::SecondaryModifier) ? 10.0 : 1.0) *
299                 ((ev->state & Keyboard::PrimaryModifier) ? 2.0 : 1.0);
300
301         switch (ev->keyval) {
302         case GDK_Page_Up:
303                 retval = true;
304                 _controllable->set_value (adjust (multiplier * page_inc));
305                 break;
306
307         case GDK_Page_Down:
308                 retval = true;
309                 _controllable->set_value (adjust (-multiplier * page_inc));
310                 break;
311
312         case GDK_Up:
313                 retval = true;
314                 _controllable->set_value (adjust (multiplier * step_inc));
315                 break;
316
317         case GDK_Down:
318                 retval = true;
319                 _controllable->set_value (adjust (-multiplier * step_inc));
320                 break;
321
322         case GDK_Home:
323                 retval = true;
324                 _controllable->set_value (_controllable->lower());
325                 break;
326
327         case GDK_End:
328                 retval = true;
329                 _controllable->set_value (_controllable->upper());
330                 break;
331         }
332
333         return retval;
334 }
335
336 bool
337 MotionFeedback::pixwin_expose_event (GdkEventExpose*)
338 {
339         if (!_controllable) {
340                 return true;
341         }
342
343         GdkWindow *window = pixwin.get_window()->gobj();
344         double display_val = to_display_value (_controllable->get_value());
345         int32_t phase = lrint (display_val * 64.0);
346
347         // skip middle phase except for true middle value
348
349         if (type == Rotary && phase == 32) {
350                 double pt = (display_val * 2.0) - 1.0;
351                 if (pt < 0)
352                         phase = 31;
353                 if (pt > 0)
354                         phase = 33;
355         }
356
357         // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
358
359         if (type == Endless && !(phase % 16)) {
360                 if (phase == 64) {
361                         phase = 0;
362                 }
363
364                 double nom = phase / 64.0;
365                 double diff = display_val - nom;
366
367                 if (diff > 0.0001)
368                         phase = (phase + 1) % 64;
369                 if (diff < -0.0001)
370                         phase = (phase + 63) % 64;
371         }
372
373         phase = std::min (phase, (int32_t) 63);
374
375         GtkWidget* widget = GTK_WIDGET(pixwin.gobj());
376         gdk_draw_pixbuf (GDK_DRAWABLE(window), widget->style->fg_gc[0],
377                          pixbuf->gobj(),
378                          phase * subwidth, type * subheight,
379                          /* center image in allocated area */
380                          (get_width() - subwidth)/2,
381                          0,
382                          subwidth, subheight, GDK_RGB_DITHER_NORMAL, 0, 0);
383
384         return true;
385 }
386
387 bool
388 MotionFeedback::pixwin_scroll_event (GdkEventScroll* ev)
389 {
390         double scale;
391
392         if (!_controllable) {
393                 return false;
394         }
395
396         if (ev->state & Keyboard::GainFineScaleModifier) {
397                 if (ev->state & Keyboard::GainExtraFineScaleModifier) {
398                         scale = 0.01;
399                 } else {
400                         scale = 0.10;
401                 }
402         } else {
403                 scale = 0.20;
404         }
405
406         switch (ev->direction) {
407         case GDK_SCROLL_UP:
408         case GDK_SCROLL_RIGHT:
409                 _controllable->set_value (adjust (scale * page_inc));
410                 break;
411
412         case GDK_SCROLL_DOWN:
413         case GDK_SCROLL_LEFT:
414                 _controllable->set_value (adjust (-scale * page_inc));
415                 break;
416         }
417
418         return true;
419 }
420
421 void
422 MotionFeedback::pixwin_size_request (GtkRequisition* req)
423 {
424         req->width = subwidth;
425         req->height = subheight;
426 }
427
428
429 void
430 MotionFeedback::controllable_value_changed ()
431 {
432         if (value) {
433                 char buf[32];
434                 print_func (buf, _controllable, print_arg);
435                 value->set_text (buf);
436         }
437
438         pixwin.queue_draw ();
439 }
440
441 void
442 MotionFeedback::set_controllable (boost::shared_ptr<PBD::Controllable> c)
443 {
444         _controllable = c;
445         binding_proxy.set_controllable (c);
446         controller_connection.disconnect ();
447
448         if (c) {
449                 c->Changed.connect (controller_connection, MISSING_INVALIDATOR, boost::bind (&MotionFeedback::controllable_value_changed, this), gui_context());
450
451                 char buf[32];
452                 print_func (buf, _controllable, print_arg);
453                 value->set_text (buf);
454         }
455
456         pixwin.queue_draw ();
457 }
458
459 boost::shared_ptr<PBD::Controllable>
460 MotionFeedback::controllable () const
461 {
462         return _controllable;
463 }
464
465 void
466 MotionFeedback::default_printer (char buf[32], const boost::shared_ptr<PBD::Controllable>& c, void *)
467 {
468         if (c) {
469                 sprintf (buf, "%.2f", c->get_value());
470         } else {
471                 buf[0] = '\0';
472         }
473 }
474
475 Glib::RefPtr<Gdk::Pixbuf>
476 MotionFeedback::render_pixbuf (int size)
477 {
478         Glib::RefPtr<Gdk::Pixbuf> pixbuf;
479         char *path;
480         int fd;
481         GError *gerror = NULL;
482
483         fd = g_file_open_tmp ("mfimgXXXXXX", &path, &gerror);
484
485         if (gerror) {
486                 error << string_compose (_("motionfeedback: failed to open a temporary file for writing: %1"), gerror->message) << endmsg;
487                 g_error_free (gerror);
488                 return pixbuf;
489         } else {
490                 ::close (fd);
491         }
492
493
494         GdkColor col2 = {0,0,0,0};
495         GdkColor col3 = {0,0,0,0};
496         GdkColor dark;
497         GdkColor bright;
498         ProlooksHSV* hsv;
499
500         hsv = prolooks_hsv_new_for_gdk_color (base_color->gobj());
501         bright = (prolooks_hsv_to_gdk_color (hsv, &col2), col2);
502         prolooks_hsv_set_saturation (hsv, 0.66);
503         prolooks_hsv_set_value (hsv, 0.67);
504         dark = (prolooks_hsv_to_gdk_color (hsv, &col3), col3);
505
506         cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size * 64, size);
507         cairo_t* cr = cairo_create (surface);
508
509         for (int i = 0; i < 64; ++i) {
510                 cairo_save (cr);
511                 core_draw (cr, i, size, 20, size*i, 0, &bright, &dark);
512                 cairo_restore (cr);
513         }
514
515         if (cairo_surface_write_to_png (surface, path) != CAIRO_STATUS_SUCCESS) {
516                 error << string_compose (_("motionfeedback: could not save image set to %1"), path) << endmsg;
517                 return pixbuf;
518         }
519
520         cairo_destroy (cr);
521         cairo_surface_destroy (surface);
522
523         try {
524                 pixbuf = Gdk::Pixbuf::create_from_file (path);
525         } catch (const Gdk::PixbufError &e) {
526                 error << string_compose (_("motionfeedback: caught PixbufError: %1"), e.what()) << endmsg;
527         } catch (...) {
528                 error << _("motionfeedback: unknown exception") << endmsg;
529         }
530
531         g_unlink (path);
532         g_free (path);
533
534         return pixbuf;
535 }
536
537 void
538 MotionFeedback::core_draw (cairo_t* cr, int phase, double size, double progress_width, double xorigin, double yorigin,
539                            const GdkColor* bright, const GdkColor* dark)
540 {
541         double xc;
542         double yc;
543         double start_angle;
544         double end_angle;
545         double value_angle;
546         double value;
547         double value_x;
548         double value_y;
549         double start_angle_x;
550         double start_angle_y;
551         double end_angle_x;
552         double end_angle_y;
553         double progress_radius;
554         double progress_radius_inner;
555         double progress_radius_outer;
556
557         g_return_if_fail (cr != NULL);
558
559         progress_radius = 40.0;
560         progress_radius_inner = progress_radius - (progress_width / 2.0);
561         progress_radius_outer = progress_radius + (progress_width / 2.0);
562
563         const double pad = 2.0; /* line width for boundary of progress ring */
564         const double actual_width = ((2.0 * pad) + (2.0 * progress_radius_outer));
565         const double scale_factor = size / actual_width;
566
567         /* knob center is at middle of the area bounded by (xorigin,yorigin) and (xorigin+size, yorigin+size)
568            but the coordinates will be scaled by the scale factor when cairo uses them so first
569            adjust them by the reciprocal of the scale factor.
570         */
571
572         xc = (xorigin + (size / 2.0)) * (1.0/scale_factor);
573         yc = (yorigin + (size / 2.0)) * (1.0/scale_factor);
574
575         value = (phase * 1.0) / (65 - 1);
576
577         start_angle = ((180 - 65) * G_PI) / 180;
578         end_angle = ((360 + 65) * G_PI) / 180;
579
580         value_angle = start_angle + (value * (end_angle - start_angle));
581         value_x = cos (value_angle);
582         value_y = sin (value_angle);
583         start_angle_x = cos (start_angle);
584         start_angle_y = sin (start_angle);
585         end_angle_x = cos (end_angle);
586         end_angle_y = sin (end_angle);
587
588         cairo_scale (cr, scale_factor, scale_factor);
589
590         //dark arc background
591         cairo_set_source_rgb (cr, 0.3, 0.3, 0.3 );
592         cairo_set_line_width (cr, progress_width);
593         cairo_arc (cr, xc, yc, progress_radius, start_angle, end_angle);
594         cairo_stroke (cr);
595
596
597         float r = (value) * (((float)bright->red)/G_MAXUINT16) + (1.0-value)*(((float)dark->red)/G_MAXUINT16);
598         float g = (value) * (((float)bright->green)/G_MAXUINT16) + (1.0-value)*(((float)dark->green)/G_MAXUINT16);
599         float b = (value) * (((float)bright->blue)/G_MAXUINT16) + (1.0-value)*(((float)dark->blue)/G_MAXUINT16);
600
601         //colored arc
602         cairo_set_source_rgb (cr, r,g,b);
603         cairo_set_line_width (cr, progress_width);
604         cairo_arc (cr, xc, yc, progress_radius, start_angle, value_angle);
605         cairo_stroke (cr);
606
607         //overall shade
608         cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, progress_radius_outer);
609         cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1,1,1, 0.3);
610         cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 1,1,1, 0.0);
611         cairo_set_source (cr, shade_pattern);
612         cairo_arc (cr, xc, yc, progress_radius_outer-1, 0, 2.0*G_PI);
613         cairo_fill (cr);
614         cairo_pattern_destroy (shade_pattern);
615
616         //black border
617         cairo_set_source_rgb (cr, 0, 0, 0 );
618         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
619         cairo_set_line_width (cr, 1.0/scale_factor);
620         cairo_move_to (cr, xc + (progress_radius_outer * start_angle_x), yc + (progress_radius_outer * start_angle_y));
621         cairo_line_to (cr, xc + (progress_radius_inner * start_angle_x), yc + (progress_radius_inner * start_angle_y));
622         cairo_stroke (cr);
623         cairo_move_to (cr, xc + (progress_radius_outer * end_angle_x), yc + (progress_radius_outer * end_angle_y));
624         cairo_line_to (cr, xc + (progress_radius_inner * end_angle_x), yc + (progress_radius_inner * end_angle_y));
625         cairo_stroke (cr);
626         cairo_arc (cr, xc, yc, progress_radius_outer, start_angle, end_angle);
627         cairo_stroke (cr);
628         cairo_arc (cr, xc, yc, progress_radius_inner, start_angle, end_angle);
629         cairo_stroke (cr);
630
631         //knob shadow
632         cairo_save(cr);
633         cairo_translate(cr, 6, 6 );
634         cairo_set_source_rgba (cr, 0,0,0,0.1 );
635         cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
636         cairo_fill (cr);
637         cairo_restore(cr);
638
639         //inner circle
640         cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 1 );
641         cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
642         cairo_fill (cr);
643
644         //knob shade
645         shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, progress_radius_outer);
646         cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 1,1,1, 0.5);
647         cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0,0,0, 0.3);
648         cairo_set_source (cr, shade_pattern);
649         cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
650         cairo_fill (cr);
651         cairo_pattern_destroy (shade_pattern);
652
653         //inner circle
654         cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 0.5 );
655         cairo_arc (cr, xc, yc, progress_radius_inner-5, 0, 2.0*G_PI);
656         cairo_fill (cr);
657
658         //line
659         cairo_save(cr);
660         cairo_translate(cr, 2, 2 );
661         cairo_set_source_rgba (cr, 0,0,0,0.5 );
662         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
663         cairo_set_line_width (cr, 4);
664         cairo_move_to (cr, xc + (progress_radius_inner * value_x), yc + (progress_radius_inner * value_y));
665         cairo_line_to (cr, xc + ((progress_radius_inner*0.4) * value_x), yc + ((progress_radius_inner*0.4) * value_y));
666         cairo_stroke (cr);
667         cairo_restore(cr);
668         cairo_set_source_rgba (cr, 1,1,1,0.7 );
669         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
670         cairo_set_line_width (cr, 4.0);
671         cairo_move_to (cr, xc + (progress_radius_inner * value_x), yc + (progress_radius_inner * value_y));
672         cairo_line_to (cr, xc + ((progress_radius_inner*0.4) * value_x), yc + ((progress_radius_inner*0.4) * value_y));
673         cairo_stroke (cr);
674
675
676         //highlight if focused  (damn, this is a cached image which doesn't (yet) have a "focused" state
677 //      if (pixwin.has_focus()) {
678 //              cairo_set_source_rgba (cr, 1,1,1, 0.5 );
679 //              cairo_arc (cr, xc, yc, progress_radius_inner-1, 0, 2.0*G_PI);
680 //              cairo_fill (cr);
681 //      }
682 }
683
684 void
685 MotionFeedback::set_lamp_color (const std::string& str)
686 {
687         if (base_color) {
688                 *base_color = Gdk::Color (str);
689         } else {
690                 base_color = new Gdk::Color (str);
691         }
692 }