[california] Rename calendars in Calendar Manager: Closes bug #726809



commit c9a2fd0effd029b5715ef0671b92f496a79f2b94
Author: Jim Nelson <jim yorba org>
Date:   Wed Apr 30 22:04:49 2014 -0700

    Rename calendars in Calendar Manager: Closes bug #726809
    
    When a selected calendar is clicked on, the calendar title can be
    renamed by the user.

 src/Makefile.am                             |    1 +
 src/manager/manager-calendar-list-item.vala |   59 ++++++++++++++++++++++++++-
 src/manager/manager-calendar-list.vala      |   21 ++++++++-
 src/rc/calendar-manager-list-item.ui        |   47 ++++++++++++---------
 src/rc/calendar-manager-list.ui             |    1 +
 src/toolkit/toolkit-calendar-popup.vala     |    2 +-
 src/toolkit/toolkit-editable-label.vala     |   60 +++++++++++++++++++++++++++
 src/toolkit/toolkit-popup.vala              |   34 +++++++++++++--
 8 files changed, 195 insertions(+), 30 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 99df41d..30fa900 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -111,6 +111,7 @@ california_VALASOURCES = \
        toolkit/toolkit-combo-box-text-model.vala \
        toolkit/toolkit-deck.vala \
        toolkit/toolkit-deck-window.vala \
+       toolkit/toolkit-editable-label.vala \
        toolkit/toolkit-listbox-model.vala \
        toolkit/toolkit-mutable-widget.vala \
        toolkit/toolkit-popup.vala \
diff --git a/src/manager/manager-calendar-list-item.vala b/src/manager/manager-calendar-list-item.vala
index 977453d..fd62bde 100644
--- a/src/manager/manager-calendar-list-item.vala
+++ b/src/manager/manager-calendar-list-item.vala
@@ -11,11 +11,16 @@ namespace California.Manager {
  */
 
 [GtkTemplate (ui = "/org/yorba/california/rc/calendar-manager-list-item.ui")]
-public class CalendarListItem : Gtk.Grid {
+internal class CalendarListItem : Gtk.Grid, Toolkit.MutableWidget {
     private const int COLOR_DIM = 16;
     
     public Backing.CalendarSource source { get; private set; }
     
+    /**
+     * Set by { link CalendarList}.
+     */
+    public bool is_selected { get; set; default = false; }
+    
     [GtkChild]
     private Gtk.Image readonly_icon;
     
@@ -23,16 +28,23 @@ public class CalendarListItem : Gtk.Grid {
     private Gtk.CheckButton visible_check_button;
     
     [GtkChild]
+    private Gtk.EventBox title_eventbox;
+    
+    [GtkChild]
     private Gtk.Label title_label;
     
     [GtkChild]
     private Gtk.ColorButton color_button;
     
+    private Toolkit.EditableLabel? editable_label = null;
+    
     public CalendarListItem(Backing.CalendarSource source) {
         this.source = source;
         
         has_tooltip = true;
         
+        source.notify[Backing.Source.PROP_TITLE].connect(on_title_changed);
+        
         source.bind_property(Backing.Source.PROP_TITLE, title_label, "label",
             BindingFlags.SYNC_CREATE);
         source.bind_property(Backing.Source.PROP_VISIBLE, visible_check_button, "active",
@@ -43,6 +55,17 @@ public class CalendarListItem : Gtk.Grid {
             () => source.read_only ? "changes-prevent-symbolic" : "");
         Properties.xform_to_string(source, Backing.Source.PROP_READONLY, readonly_icon, "tooltip-text",
             () => source.read_only ? _("Calendar is read-only") : null);
+        
+        title_eventbox.button_release_event.connect(on_title_button_release);
+    }
+    
+    ~CalendarListItem() {
+        source.notify[Backing.Source.PROP_TITLE].disconnect(on_title_changed);
+    }
+    
+    private void on_title_changed() {
+        // title determines sort order, so this is important
+        mutated();
     }
     
     public override bool query_tooltip(int x, int y, bool keyboard_mode, Gtk.Tooltip tooltip) {
@@ -67,6 +90,40 @@ public class CalendarListItem : Gtk.Grid {
         
         return true;
     }
+    
+    private void activate_editable_label() {
+        assert(editable_label == null);
+        
+        editable_label = new Toolkit.EditableLabel(title_label);
+        editable_label.accepted.connect(on_title_edit_accepted);
+        editable_label.dismissed.connect(remove_editable_label);
+        
+        editable_label.show_all();
+    }
+    
+    private void remove_editable_label() {
+        assert(editable_label != null);
+        
+        editable_label.destroy();
+        editable_label = null;
+    }
+    
+    private bool on_title_button_release(Gdk.EventButton event) {
+        // if already accepting input or not selected, don't activate text entry for rename (but
+        // allow signal to propagate further)
+        if (editable_label != null || !is_selected)
+            return false;
+        
+        activate_editable_label();
+        
+        // don't propagate
+        return true;
+    }
+    
+    private void on_title_edit_accepted(string text) {
+        if (!String.is_empty(text))
+            source.title = text;
+    }
 }
 
 }
diff --git a/src/manager/manager-calendar-list.vala b/src/manager/manager-calendar-list.vala
index 48246b0..8287e7d 100644
--- a/src/manager/manager-calendar-list.vala
+++ b/src/manager/manager-calendar-list.vala
@@ -11,7 +11,9 @@ namespace California.Manager {
  */
 
 [GtkTemplate (ui = "/org/yorba/california/rc/calendar-manager-list.ui")]
-public class CalendarList : Gtk.Grid, Toolkit.Card {
+internal class CalendarList : Gtk.Grid, Toolkit.Card {
+    public const string PROP_SELECTED = "selected";
+    
     public const string ID = "CalendarList";
     
     public string card_id { get { return ID; } }
@@ -22,6 +24,8 @@ public class CalendarList : Gtk.Grid, Toolkit.Card {
     
     public Gtk.Widget? initial_focus { get { return calendar_list_box; } }
     
+    public CalendarListItem? selected { get; private set; default = null; }
+    
     [GtkChild]
     private Gtk.ListBox calendar_list_box;
     
@@ -84,8 +88,19 @@ public class CalendarList : Gtk.Grid, Toolkit.Card {
     
     [GtkCallback]
     private void on_calendar_list_box_row_activated(Gtk.ListBoxRow row) {
-        CalendarListItem item = (CalendarListItem) row.get_child();
-        debug("activated %s", item.source.to_string());
+    }
+    
+    [GtkCallback]
+    private void on_calendar_list_box_row_selected(Gtk.ListBoxRow? row) {
+        if (selected != null)
+            selected.is_selected = false;
+        
+        if (row != null) {
+            selected = (CalendarListItem) row.get_child();
+            selected.is_selected = true;
+        } else {
+            selected = null;
+        }
     }
     
     [GtkCallback]
diff --git a/src/rc/calendar-manager-list-item.ui b/src/rc/calendar-manager-list-item.ui
index 2b756cf..6b5f2d7 100644
--- a/src/rc/calendar-manager-list-item.ui
+++ b/src/rc/calendar-manager-list-item.ui
@@ -7,26 +7,6 @@
     <property name="can_focus">False</property>
     <property name="column_spacing">4</property>
     <child>
-      <object class="GtkLabel" id="title_label">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="valign">center</property>
-        <property name="hexpand">True</property>
-        <property name="vexpand">True</property>
-        <property name="xalign">0</property>
-        <property name="yalign">0</property>
-        <property name="label">calendar name</property>
-        <property name="ellipsize">end</property>
-        <property name="single_line_mode">True</property>
-      </object>
-      <packing>
-        <property name="left_attach">3</property>
-        <property name="top_attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
-      </packing>
-    </child>
-    <child>
       <object class="GtkColorButton" id="color_button">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
@@ -79,5 +59,32 @@
         <property name="height">1</property>
       </packing>
     </child>
+    <child>
+      <object class="GtkEventBox" id="title_eventbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="events">GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
+        <child>
+          <object class="GtkLabel" id="title_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label">calendar name</property>
+            <property name="ellipsize">end</property>
+            <property name="single_line_mode">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">3</property>
+        <property name="top_attach">0</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
   </template>
 </interface>
diff --git a/src/rc/calendar-manager-list.ui b/src/rc/calendar-manager-list.ui
index c61bc11..65d2a74 100644
--- a/src/rc/calendar-manager-list.ui
+++ b/src/rc/calendar-manager-list.ui
@@ -61,6 +61,7 @@
                 <property name="vexpand">True</property>
                 <property name="activate_on_single_click">False</property>
                 <signal name="row-activated" handler="on_calendar_list_box_row_activated" 
object="CaliforniaManagerCalendarList" swapped="no"/>
+                <signal name="row-selected" handler="on_calendar_list_box_row_selected" 
object="CaliforniaManagerCalendarList" swapped="no"/>
               </object>
             </child>
           </object>
diff --git a/src/toolkit/toolkit-calendar-popup.vala b/src/toolkit/toolkit-calendar-popup.vala
index 391dab0..0c0633c 100644
--- a/src/toolkit/toolkit-calendar-popup.vala
+++ b/src/toolkit/toolkit-calendar-popup.vala
@@ -35,7 +35,7 @@ public class CalendarPopup : Popup {
      * inheritDoc
      */
     public CalendarPopup(Gtk.Widget relative_to, Calendar.Date initial_date) {
-        base (relative_to);
+        base (relative_to, Popup.Position.BELOW);
         
         calendar.day = initial_date.day_of_month.value;
         calendar.month = initial_date.month.value - 1;
diff --git a/src/toolkit/toolkit-editable-label.vala b/src/toolkit/toolkit-editable-label.vala
new file mode 100644
index 0000000..46bd05f
--- /dev/null
+++ b/src/toolkit/toolkit-editable-label.vala
@@ -0,0 +1,60 @@
+/* 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.Toolkit {
+
+/**
+ * Uses a { link Popup} to place a Gtk.Entry over a Gtk.Label, creating the illusion that the user
+ * can "edit" the label.
+ *
+ * If the user presses Enter, { link accepted} will fire.  It is up to the caller to set the
+ * Gtk.Label's text to this field.
+ *
+ * Callers should subscribe to the { link dismissed} label to destroy this widget.
+ *
+ * This currently doesn't deal with font issues (i.e. the editable field will use the system editing
+ * font).
+ */
+
+public class EditableLabel : Popup {
+    /**
+     * The Gtk.Label being "edited".
+     */
+    public Gtk.Label label { get; private set; }
+    
+    private Gtk.Entry entry = new Gtk.Entry();
+    
+    /**
+     * Fired when the user presses Enter indicating the text should be accepted.
+     *
+     * It is up to the caller to set the Gtk.Label's text to this text (if so desired).  The
+     * { link EditableLabel} will be dismissed after this signal completes.
+     */
+    public signal void accepted(string text);
+    
+    public EditableLabel(Gtk.Label label) {
+        base (label, Popup.Position.VERTICAL_CENTER);
+        
+        // set up Gtk.Entry to look and be sized exactly like the Gtk.Label
+        entry.text = label.label;
+        entry.width_chars = label.width_chars;
+        add(entry);
+        
+        // make sure the Popup window is hugging close to label as well
+        margin = 0;
+        
+        // Enter accepts
+        entry.activate.connect(on_entry_accepted);
+    }
+    
+    private void on_entry_accepted() {
+        accepted(entry.text);
+        dismissed();
+    }
+}
+
+}
+
diff --git a/src/toolkit/toolkit-popup.vala b/src/toolkit/toolkit-popup.vala
index beb76b8..fcee4f5 100644
--- a/src/toolkit/toolkit-popup.vala
+++ b/src/toolkit/toolkit-popup.vala
@@ -14,8 +14,16 @@ namespace California.Toolkit {
  */
 
 public class Popup : Gtk.Window {
+    public enum Position {
+        BELOW,
+        VERTICAL_CENTER
+    }
+    
     private Gtk.Widget relative_to;
+    private Position position;
     private Gtk.Widget? prev_focus = null;
+    private int relative_x = 0;
+    private int relative_y = 0;
     
     /**
      * Fired when the { link Popup} is hidden, either due to user interaction (losing focus) or
@@ -29,17 +37,16 @@ public class Popup : Gtk.Window {
      *
      * The GtkWidget must be realized when this is invoked.
      */
-    public Popup(Gtk.Widget relative_to) {
+    public Popup(Gtk.Widget relative_to, Position position) {
         Object(type:Gtk.WindowType.TOPLEVEL);
         
         assert(relative_to.get_realized());
         this.relative_to = relative_to;
+        this.position = position;
         
         set_screen(relative_to.get_screen());
         
-        // move Popup window directly below relative_to widget aligned on the left-hand side
-        // TODO: RTL support
-        // TODO: Better detection to ensure Popup is always fully mapped onto the screen
+        // get coordinates of relative_to widget
         Gtk.Window? relative_to_win = relative_to.get_ancestor(typeof (Gtk.Window)) as Gtk.Window;
         if (relative_to_win != null && relative_to_win.is_toplevel()) {
             int gtk_x, gtk_y;
@@ -50,7 +57,8 @@ public class Popup : Gtk.Window {
             int gdk_x, gdk_y;
             gdk_win.get_position(out gdk_x, out gdk_y);
             
-            move(gtk_x + gdk_x, gtk_y + gdk_y + relative_to.get_allocated_height());
+            relative_x = gtk_x + gdk_x;
+            relative_y = gtk_y + gdk_y;
         }
         
         decorated = false;
@@ -82,6 +90,22 @@ public class Popup : Gtk.Window {
     public override void map() {
         base.map();
         
+        switch (position) {
+            case Position.BELOW:
+                // move Popup window directly below relative_to widget aligned on the left-hand side
+                // TODO: RTL support
+                // TODO: Better detection to ensure Popup is always fully mapped onto the same screen
+                move(relative_x, relative_y + relative_to.get_allocated_height());
+            break;
+            
+            case Position.VERTICAL_CENTER:
+                move(relative_x, relative_y + ((relative_to.get_allocated_height() - get_allocated_height()) 
/ 2));
+            break;
+            
+            default:
+                assert_not_reached();
+        }
+        
         prev_focus = get_focus();
         
         Gtk.grab_add(this);


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