[california] Use calendar colors: Closes bgo#725768



commit 3c945c11d93ef688d1cedea890a3f7722907c5c4
Author: Jim Nelson <jim yorba org>
Date:   Thu Mar 20 15:11:50 2014 -0700

    Use calendar colors: Closes bgo#725768
    
    Colors are used to display events and can be changed from the
    Calendar Manager.

 src/Makefile.am                                  |    6 +
 src/backing/backing-source.vala                  |   33 ++++++
 src/backing/eds/backing-eds-calendar-source.vala |   11 ++
 src/host/host-color-chooser-popup.vala           |   36 +++++++
 src/manager/manager-calendar-list-item.vala      |   30 ++++++
 src/rc/calendar-manager-list-item.ui             |   20 ++++
 src/util/util-gfx.vala                           |  114 ++++++++++++++++++++++
 src/view/month/month-cell.vala                   |   13 ++-
 8 files changed, 257 insertions(+), 6 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 4c6ac3b..f523b5a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,6 +62,7 @@ california_VALASOURCES = \
        \
        host/host.vala \
        host/host-calendar-popup.vala \
+       host/host-color-chooser-popup.vala \
        host/host-create-update-event.vala \
        host/host-interaction.vala \
        host/host-main-window.vala \
@@ -74,6 +75,7 @@ california_VALASOURCES = \
        manager/manager-calendar-list-item.vala \
        manager/manager-window.vala \
        \
+       util/util-gfx.vala \
        util/util-memory.vala \
        util/util-string.vala \
        \
@@ -123,6 +125,10 @@ california_CFLAGS = \
        -DPREFIX=\"$(prefix)\" \
        $(NULL)
 
+LIBS = \
+       -lm \
+       $(NULL)
+
 california-resources.c: $(california_RC) california-resources.xml
        $(GLIB_COMPILE_RESOURCES) --target="$@" --generate-source california-resources.xml
 
diff --git a/src/backing/backing-source.vala b/src/backing/backing-source.vala
index 022c9c6..d985b93 100644
--- a/src/backing/backing-source.vala
+++ b/src/backing/backing-source.vala
@@ -20,6 +20,7 @@ public abstract class Source : BaseObject {
     public const string PROP_IS_AVAILABLE = "is-available";
     public const string PROP_TITLE = "title";
     public const string PROP_VISIBLE = "visible";
+    public const string PROP_COLOR = "color";
     
     /**
      * True if the { link Source} is unavailable for use due to being removed from it's
@@ -49,6 +50,12 @@ public abstract class Source : BaseObject {
      */
     public bool visible { get; set; }
     
+    /**
+     * The suggested color to use when displaying the { link Source} or information about or from
+     * it.
+     */
+    public string color { get; set; }
+    
     protected Source(string title) {
         this.title = title;
     }
@@ -65,6 +72,32 @@ public abstract class Source : BaseObject {
         is_available = false;
     }
     
+    /**
+     * Returns the current { link color} setting as a Gdk.Color.
+     *
+     * If the color string is unparseable, returns a Gdk.Color that corresponds to "dressy-black".
+     */
+    public Gdk.Color color_as_rgb() {
+        return Gfx.rgb_string_to_rgb(color, Gdk.Color() { red = 0, green = 0, blue = 0 }, null);
+    }
+    
+    /**
+     * Returns the current { link color} setting as a Gdk.RGBA.
+     *
+     * If the color string is unparseable, returns a Gdk.RGBA that corresponds to "dressy-black".
+     */
+    public Gdk.RGBA color_as_rgba() {
+        return Gfx.rgb_string_to_rgba(color,
+            Gdk.RGBA() { red = 0.0, green = 0.0, blue = 0.0, alpha = 1.0 },  null);
+    }
+    
+    /**
+     * Set the { link color} property to the string representation of the Gdk.RGBA structure.
+     */
+    public void set_color_to_rgba(Gdk.RGBA rgba) {
+        color = Gfx.rgb_to_uint8_rgb_string(Gfx.rgba_to_rgb(rgba));
+    }
+    
     public override string to_string() {
         return title;
     }
diff --git a/src/backing/eds/backing-eds-calendar-source.vala 
b/src/backing/eds/backing-eds-calendar-source.vala
index 8b31f90..0c9f9bb 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -28,9 +28,11 @@ internal class EdsCalendarSource : CalendarSource {
         // use unidirectional bindings so source updates (writing) only occurs when changed from
         // within the app
         eds_calendar.bind_property("selected", this, PROP_VISIBLE, BindingFlags.SYNC_CREATE);
+        eds_calendar.bind_property("color", this, PROP_COLOR, BindingFlags.SYNC_CREATE);
         
         // when changed within the app, need to write it back out
         notify[PROP_VISIBLE].connect(on_visible_changed);
+        notify[PROP_COLOR].connect(on_color_changed);
     }
     
     ~EdsCalendarSource() {
@@ -46,6 +48,15 @@ internal class EdsCalendarSource : CalendarSource {
         schedule_source_write("visible=%s".printf(visible.to_string()));
     }
     
+    private void on_color_changed() {
+        // only schedule writes if something changed
+        if (eds_calendar.color == color)
+            return;
+        
+        eds_calendar.color = color;
+        schedule_source_write("color=%s".printf(color));
+    }
+    
     private void schedule_source_write(string reason) {
         cancel_source_write();
         
diff --git a/src/host/host-color-chooser-popup.vala b/src/host/host-color-chooser-popup.vala
new file mode 100644
index 0000000..dc76f85
--- /dev/null
+++ b/src/host/host-color-chooser-popup.vala
@@ -0,0 +1,36 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Host {
+
+/**
+ * A simple { link Popup} window featuring only a GtkColorChooser.
+ */
+
+public class ColorChooserPopup : Popup {
+    private Gtk.ColorChooserWidget color_chooser = new Gtk.ColorChooserWidget();
+    
+    public signal void selected(Gdk.RGBA rgba);
+    
+    public ColorChooserPopup(Gtk.Widget relative_to, Gdk.RGBA initial_rgba) {
+        base (relative_to);
+        
+        color_chooser.rgba = initial_rgba;
+        color_chooser.use_alpha = false;
+        color_chooser.show_editor = false;
+        color_chooser.margin = 8;
+        
+        color_chooser.color_activated.connect((rgba) => {
+            selected(rgba);
+            dismissed();
+        });
+        
+        add(color_chooser);
+    }
+}
+
+}
+
diff --git a/src/manager/manager-calendar-list-item.vala b/src/manager/manager-calendar-list-item.vala
index 87c1e6c..11a3bb2 100644
--- a/src/manager/manager-calendar-list-item.vala
+++ b/src/manager/manager-calendar-list-item.vala
@@ -12,6 +12,8 @@ namespace California.Manager {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/calendar-manager-list-item.ui")]
 public class CalendarListItem : Gtk.Grid {
+    private const int COLOR_DIM = 16;
+    
     public Backing.CalendarSource source { get; private set; }
     
     [GtkChild]
@@ -20,6 +22,9 @@ public class CalendarListItem : Gtk.Grid {
     [GtkChild]
     private Gtk.Label title_label;
     
+    [GtkChild]
+    private Gtk.Button color_button;
+    
     public CalendarListItem(Backing.CalendarSource source) {
         this.source = source;
         
@@ -27,6 +32,31 @@ public class CalendarListItem : Gtk.Grid {
             BindingFlags.SYNC_CREATE);
         source.bind_property(Backing.Source.PROP_VISIBLE, visible_check_button, "active",
             BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+        
+        on_color_changed();
+        source.notify[Backing.Source.PROP_COLOR].connect(on_color_changed);
+    }
+    
+    private void on_color_changed() {
+        Gdk.Pixbuf pixbuf = new Gdk.Pixbuf(Gdk.Colorspace.RGB, false, 8, COLOR_DIM, COLOR_DIM);
+        pixbuf.fill(Gfx.rgba_to_pixel(source.color_as_rgba()));
+        
+        color_button.set_image(new Gtk.Image.from_pixbuf(pixbuf));
+    }
+    
+    [GtkCallback]
+    private void on_color_button_clicked() {
+        Host.ColorChooserPopup popup = new Host.ColorChooserPopup(color_button, source.color_as_rgba());
+        
+        popup.selected.connect((rgba) => {
+            source.set_color_to_rgba(rgba);
+        });
+        
+        popup.dismissed.connect(() => {
+            popup.destroy();
+        });
+        
+        popup.show_all();
     }
 }
 
diff --git a/src/rc/calendar-manager-list-item.ui b/src/rc/calendar-manager-list-item.ui
index b081b42..0a8647e 100644
--- a/src/rc/calendar-manager-list-item.ui
+++ b/src/rc/calendar-manager-list-item.ui
@@ -40,6 +40,26 @@
         <property name="single_line_mode">True</property>
       </object>
       <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">0</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="color_button">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="tooltip_text" translatable="yes">Calendar color</property>
+        <property name="halign">center</property>
+        <property name="valign">center</property>
+        <signal name="clicked" handler="on_color_button_clicked" object="CaliforniaManagerCalendarListItem" 
swapped="no"/>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">0</property>
         <property name="width">1</property>
diff --git a/src/util/util-gfx.vala b/src/util/util-gfx.vala
new file mode 100644
index 0000000..2225df4
--- /dev/null
+++ b/src/util/util-gfx.vala
@@ -0,0 +1,114 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Gfx {
+
+/**
+ * Convert an RGB string into an RGB structure.
+ *
+ * The string can be in any of the forms that Gdk.Color.parse accepts.  If unable to parse the
+ * string, the { link default_rgb} is returned and { link used_default} is set to true.
+ */
+public Gdk.Color rgb_string_to_rgb(string rgb_string, Gdk.Color default_rgb, out bool used_default) {
+    Gdk.Color rgb;
+    if (!Gdk.Color.parse(rgb_string, out rgb)) {
+        debug("Unable to parse RGB color \"%s\"", rgb_string);
+        
+        used_default = true;
+        
+        return default_rgb;
+    }
+    
+    used_default = false;
+    
+    return rgb;
+}
+
+/**
+ * Convert an RGB string into an RGBA structure.
+ *
+ * The string can be in any of the forms that Gdk.Color.parse accepts.  If unable to parse the
+ * string, the { link default_rgba} is returned and { link used_default} is set to true.
+ */
+public Gdk.RGBA rgb_string_to_rgba(string rgb_string, Gdk.RGBA default_rgba, out bool used_default) {
+    Gdk.Color rgb;
+    if (!Gdk.Color.parse(rgb_string, out rgb)) {
+        debug("Unable to parse RGB color \"%s\"", rgb_string);
+        
+        used_default = true;
+        
+        return default_rgba;
+    }
+    
+    Gdk.RGBA rgba = Gdk.RGBA();
+    rgba.red = uint16_to_fp(rgb.red);
+    rgba.green = uint16_to_fp(rgb.green);
+    rgba.blue = uint16_to_fp(rgb.blue);
+    rgba.alpha = 1.0;
+    
+    used_default = false;
+    
+    return rgba;
+}
+
+// compiler error if this calculation is done inline when initializing struct
+private inline double uint16_to_fp(uint16 value) {
+    return (double) value / (double) uint16.MAX;
+}
+
+/**
+ * Converts the Gdk.RGBA into a 32-bit pixel representation.
+ */
+public uint32 rgba_to_pixel(Gdk.RGBA rgba) {
+    return (uint32) fp_to_uint8(rgba.red) << 24
+        | (uint32) fp_to_uint8(rgba.green) << 16
+        | (uint32) fp_to_uint8(rgba.blue) << 8
+        | (uint32) fp_to_uint8(rgba.alpha);
+}
+
+private inline uint8 fp_to_uint8(double value) {
+    return (uint8) Math.round(value * (double) uint8.MAX);
+}
+
+/**
+ * Converts a Gdk.RGBA structure into an RGB (Gdk.Color) structure.
+ *
+ * The alpha channel is necessarily stripped in this conversion.
+ */
+public Gdk.Color rgba_to_rgb(Gdk.RGBA rgba) {
+    Gdk.Color rgb = Gdk.Color();
+    rgb.red = fp_to_uint16(rgba.red);
+    rgb.green = fp_to_uint16(rgba.green);
+    rgb.blue = fp_to_uint16(rgba.blue);
+    
+    return rgb;
+}
+
+private inline uint16 fp_to_uint16(double value) {
+    return (uint16) Math.round(value * (double) uint16.MAX);
+}
+
+public string rgb_to_uint8_rgb_string(Gdk.Color rgb) {
+    return "#%02x%02x%02x".printf(
+        uint16_to_uint8(rgb.red),
+        uint16_to_uint8(rgb.green),
+        uint16_to_uint8(rgb.blue)
+    );
+}
+
+private inline uint8 uint16_to_uint8(uint16 value) {
+    return (uint8) (value / (uint8.MAX + 1));
+}
+
+public string rgb_to_string(Gdk.Color rgb) {
+    return "(%d,%d,%d)".printf(rgb.red, rgb.green, rgb.blue);
+}
+
+public string rgba_to_string(Gdk.RGBA rgba) {
+    return "(%lf,%lf,%lf,%lf)".printf(rgba.red, rgba.green, rgba.blue, rgba.alpha);
+}
+
+}
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index 80e01de..2aa450f 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -255,7 +255,8 @@ public class Cell : Gtk.EventBox {
                 text = "%s %s".printf(local_start.to_pretty_time_string(PRETTY_TIME_FLAGS), event.summary);
             }
             
-            Pango.Layout layout = draw_line_of_text(ctx, line_number, RGBA_DAY_OF_MONTH, text);
+            Pango.Layout layout = draw_line_of_text(ctx, line_number, event.calendar_source.color_as_rgba(),
+                text);
             line_to_event.set(line_number++, event);
             event.set_data<string?>(KEY_TOOLTIP, layout.is_ellipsized() ? text : null);
         }
@@ -314,12 +315,12 @@ public class Cell : Gtk.EventBox {
      * The Gdk.Point must be relative to the widget's coordinate system.
      */
     public Component.Event? get_event_at(Gdk.Point point) {
-        int line_number = 0;
-        foreach (Component.Event event in days_events) {
-            int y = get_line_top_y(line_number++);
-            
+        for (int line_number = 0; line_number < line_to_event.size; line_number++) {
+            int y = get_line_top_y(line_number);
             if (point.y >= y && point.y < (y + line_height_px))
-                return event;
+                return line_to_event.get(line_number);
+            
+            line_number++;
         }
         
         return null;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]