[california] Click+Drag to create event in week view: Bug #730601
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california] Click+Drag to create event in week view: Bug #730601
- Date: Wed, 28 May 2014 23:56:11 +0000 (UTC)
commit 20c5d78eccb0d45473f6428cbd965fe0d34d11b4
Author: Jim Nelson <jim yorba org>
Date: Wed May 28 16:55:20 2014 -0700
Click+Drag to create event in week view: Bug #730601
Introduces MotionConnector, operating on much the same principles as
ButtonConnector.
src/Makefile.am | 2 +
src/toolkit/toolkit-button-connector.vala | 52 +++++-------
src/toolkit/toolkit-button-event.vala | 33 +++++++-
src/toolkit/toolkit-event-connector.vala | 4 -
src/toolkit/toolkit-motion-connector.vala | 117 ++++++++++++++++++++++++++
src/toolkit/toolkit-motion-event.vala | 69 ++++++++++++++++
src/toolkit/toolkit.vala | 10 +++
src/view/month/month-grid.vala | 11 +--
src/view/week/week-controller.vala | 7 ++
src/view/week/week-day-pane.vala | 77 ++++++++++++++++--
src/view/week/week-grid.vala | 126 ++++++++++++++++++++++++++++-
11 files changed, 450 insertions(+), 58 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 0999a5c..67612c2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -118,6 +118,8 @@ california_VALASOURCES = \
toolkit/toolkit-editable-label.vala \
toolkit/toolkit-event-connector.vala \
toolkit/toolkit-listbox-model.vala \
+ toolkit/toolkit-motion-connector.vala \
+ toolkit/toolkit-motion-event.vala \
toolkit/toolkit-mutable-widget.vala \
toolkit/toolkit-popup.vala \
toolkit/toolkit-stack-model.vala \
diff --git a/src/toolkit/toolkit-button-connector.vala b/src/toolkit/toolkit-button-connector.vala
index 915134d..19ce292 100644
--- a/src/toolkit/toolkit-button-connector.vala
+++ b/src/toolkit/toolkit-button-connector.vala
@@ -75,23 +75,22 @@ public class ButtonConnector : EventConnector {
Gtk.Widget, InternalButtonEvent>();
private Gee.HashMap<Gtk.Widget, InternalButtonEvent> tertiary_states = new Gee.HashMap<
Gtk.Widget, InternalButtonEvent>();
- private Cancellable cancellable = new Cancellable();
/**
* The "raw" "button-pressed" signal received by { link ButtonConnector}.
*
- * Signal subscribers should cancel the Cancellable to prevent propagation of the event.
- * This will prevent the various "clicked" signals from firing.
+ * Return { link STOP} to prevent further propagation of the event. This will prevent firing
+ * of synthesized signals, i.e. { link clicked} and { link double_clicked}.
*/
- public signal void pressed(Gtk.Widget widget, Gdk.EventButton event, Cancellable cancellable);
+ public signal bool pressed(Gtk.Widget widget, Button button, Gdk.Point point, Gdk.EventType event_type);
/**
* The "raw" "button-released" signal received by { link ButtonConnector}.
*
- * Signal subscribers should cancel the Cancellable to prevent propagation of the event.
- * This will prevent the various "clicked" signals from firing.
+ * Return { link STOP} to prevent further propagation of the event. This will prevent firing
+ * of synthesized signals, i.e. { link clicked} and { link double_clicked}.
*/
- public signal void released(Gtk.Widget widget, Gdk.EventButton event, Cancellable cancellable);
+ public signal bool released(Gtk.Widget widget, Button button, Gdk.Point point, Gdk.EventType event_type);
/**
* Fired when a button is pressed and released once.
@@ -137,10 +136,9 @@ public class ButtonConnector : EventConnector {
*
* @return { link EVENT_STOP} or { link EVENT_PROPAGATE}.
*/
- protected virtual bool notify_pressed(Gtk.Widget widget, Gdk.EventButton event) {
- pressed(widget, event, cancellable);
-
- return stop_propagation();
+ protected virtual bool notify_pressed(Gtk.Widget widget, Button button, Gdk.Point point,
+ Gdk.EventType event_type) {
+ return pressed(widget, button, point, event_type);
}
/**
@@ -149,10 +147,9 @@ public class ButtonConnector : EventConnector {
*
* @return { link EVENT_STOP} or { link EVENT_PROPAGATE}.
*/
- protected virtual bool notify_released(Gtk.Widget widget, Gdk.EventButton event) {
- released(widget, event, cancellable);
-
- return stop_propagation();
+ protected virtual bool notify_released(Gtk.Widget widget, Button button, Gdk.Point point,
+ Gdk.EventType event_type) {
+ return released(widget, button, point, event_type);
}
/**
@@ -200,17 +197,6 @@ public class ButtonConnector : EventConnector {
tertiary_states.unset(widget);
}
- // Checks if the Cancellable has been cancelled, in which case return EVENT_STOP and replaces
- // the Cancellable
- private bool stop_propagation() {
- if (!cancellable.is_cancelled())
- return EVENT_PROPAGATE;
-
- cancellable = new Cancellable();
-
- return EVENT_STOP;
- }
-
private Gee.HashMap<Gtk.Widget, InternalButtonEvent>? get_states_map(Button button) {
switch (button) {
case Button.PRIMARY:
@@ -231,7 +217,7 @@ public class ButtonConnector : EventConnector {
}
private bool on_button_event(Gtk.Widget widget, Gdk.EventButton event) {
- Button button = Button.from_event(event);
+ Button button = Button.from_button_event(event);
return process_button_event(widget, event, button, get_states_map(button));
}
@@ -243,12 +229,13 @@ public class ButtonConnector : EventConnector {
case Gdk.EventType.2BUTTON_PRESS:
case Gdk.EventType.3BUTTON_PRESS:
// notify of raw event
- if (notify_pressed(widget, event) == EVENT_STOP) {
+ Gdk.Point point = Gdk.Point() { x = (int) event.x, y = (int) event.y };
+ if (notify_pressed(widget, button, point, event.type) == Toolkit.STOP) {
// drop any lingering state
if (button_states != null)
button_states.unset(widget);
- return EVENT_STOP;
+ return Toolkit.STOP;
}
// save state for the release event, potentially updating existing state from
@@ -268,12 +255,13 @@ public class ButtonConnector : EventConnector {
case Gdk.EventType.BUTTON_RELEASE:
// notify of raw event
- if (notify_released(widget, event) == EVENT_STOP) {
+ Gdk.Point point = Gdk.Point() { x = (int) event.x, y = (int) event.y };
+ if (notify_released(widget, button, point, event.type) == Toolkit.STOP) {
// release lingering state
if (button_states != null)
button_states.unset(widget);
- return EVENT_STOP;
+ return Toolkit.STOP;
}
// update saved state (if any) with release info and start timer
@@ -303,7 +291,7 @@ public class ButtonConnector : EventConnector {
break;
}
- return EVENT_PROPAGATE;
+ return Toolkit.PROPAGATE;
}
private void on_release_timeout(InternalButtonEvent details) {
diff --git a/src/toolkit/toolkit-button-event.vala b/src/toolkit/toolkit-button-event.vala
index 917a54f..775267e 100644
--- a/src/toolkit/toolkit-button-event.vala
+++ b/src/toolkit/toolkit-button-event.vala
@@ -20,7 +20,7 @@ public enum Button {
/**
* Converts the button field of a Gdk.EventButton to a { link Button} enumeration.
*/
- public static Button from_event(Gdk.EventButton event) {
+ public static Button from_button_event(Gdk.EventButton event) {
switch (event.button) {
case 1:
return PRIMARY;
@@ -35,6 +35,31 @@ public enum Button {
return OTHER;
}
}
+
+ /**
+ * Returns the Gdk.ModifierType corresponding to this { link Button}.
+ *
+ * { link OTHER} merely means any button not { link PRIMARY}, { link SECONDARY}, or
+ * { link TERTIARY}.
+ */
+ public Gdk.ModifierType get_modifier_mask() {
+ switch (this) {
+ case PRIMARY:
+ return Gdk.ModifierType.BUTTON1_MASK;
+
+ case SECONDARY:
+ return Gdk.ModifierType.BUTTON2_MASK;
+
+ case TERTIARY:
+ return Gdk.ModifierType.BUTTON3_MASK;
+
+ case OTHER:
+ return Gdk.ModifierType.BUTTON4_MASK | Gdk.ModifierType.BUTTON5_MASK;
+
+ default:
+ assert_not_reached();
+ }
+ }
}
/**
@@ -74,7 +99,7 @@ public class ButtonEvent : BaseObject {
internal ButtonEvent(Gtk.Widget widget, Gdk.EventButton press_event) {
this.widget = widget;
- button = Button.from_event(press_event);
+ button = Button.from_button_event(press_event);
press_type = press_event.type;
_press_point.x = (int) press_event.x;
_press_point.y = (int) press_event.y;
@@ -83,7 +108,7 @@ public class ButtonEvent : BaseObject {
// Update state with the next button press
internal virtual void update_press(Gtk.Widget widget, Gdk.EventButton press_event) {
assert(this.widget == widget);
- assert(Button.from_event(press_event) == button);
+ assert(Button.from_button_event(press_event) == button);
press_type = press_event.type;
_press_point.x = (int) press_event.x;
@@ -93,7 +118,7 @@ public class ButtonEvent : BaseObject {
// Update state with the next button release and start the release timer
internal virtual void update_release(Gtk.Widget widget, Gdk.EventButton release_event) {
assert(this.widget == widget);
- assert(Button.from_event(release_event) == button);
+ assert(Button.from_button_event(release_event) == button);
_release_point.x = (int) release_event.x;
_release_point.y = (int) release_event.y;
diff --git a/src/toolkit/toolkit-event-connector.vala b/src/toolkit/toolkit-event-connector.vala
index 66fa695..484bce9 100644
--- a/src/toolkit/toolkit-event-connector.vala
+++ b/src/toolkit/toolkit-event-connector.vala
@@ -20,10 +20,6 @@ namespace California.Toolkit {
*/
public abstract class EventConnector : BaseObject {
- // helper consts for subclasses
- protected const bool EVENT_PROPAGATE = false;
- protected const bool EVENT_STOP = true;
-
private Gdk.EventMask event_mask;
private Gee.HashSet<Gtk.Widget> widgets = new Gee.HashSet<Gtk.Widget>();
diff --git a/src/toolkit/toolkit-motion-connector.vala b/src/toolkit/toolkit-motion-connector.vala
new file mode 100644
index 0000000..7915b34
--- /dev/null
+++ b/src/toolkit/toolkit-motion-connector.vala
@@ -0,0 +1,117 @@
+/* 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 {
+
+/**
+ * A { link EventConnector} for pointer (mouse) motion events, including the pointer entering and
+ * exiting the widget's space.
+ */
+
+public class MotionConnector : EventConnector {
+ /**
+ * Fired when the pointer (mouse cursor) enters the Gtk.Widget.
+ */
+ public signal void entered(MotionEvent event);
+
+ /**
+ * Fired when the pointer (mouse cursor) leaves the Gtk.Widget.
+ */
+ public signal void exited(MotionEvent event);
+
+ /**
+ * Fired when the pointer (mouse cursor) moves across the Gtk.Widget.
+ *
+ * @see button_motion
+ */
+ public signal void motion(MotionEvent event);
+
+ /**
+ * Fired when the pointer (mouse cursor) moves across the Gtk.Widget while a button is pressed.
+ *
+ * @see motion
+ */
+ public signal void button_motion(MotionEvent event);
+
+ public MotionConnector() {
+ base (Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK |
Gdk.EventMask.LEAVE_NOTIFY_MASK);
+ }
+
+ /**
+ * Create a { link MotionConnector} that only reports motion when a button is depressed.
+ *
+ * This generates fewer events and should be used if the subscribers signal handlers are only
+ * interested in motion while a button is depressed.
+ */
+ public MotionConnector.button_only() {
+ base (Gdk.EventMask.BUTTON_MOTION_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK |
Gdk.EventMask.LEAVE_NOTIFY_MASK);
+ }
+
+ /**
+ * Subclasses may override this call to update state before or after the signal fires.
+ */
+ protected void notify_entered(MotionEvent event) {
+ entered(event);
+ }
+
+ /**
+ * Subclasses may override this call to update state before or after the signal fires.
+ */
+ protected void notify_exited(MotionEvent event) {
+ exited(event);
+ }
+
+ /**
+ * Subclasses may override this call to update state before or after the signal fires.
+ */
+ protected void notify_motion(MotionEvent event) {
+ motion(event);
+ }
+
+ /**
+ * Subclasses may override this call to update state before or after the signal fires.
+ */
+ protected void notify_button_motion(MotionEvent event) {
+ button_motion(event);
+ }
+
+ protected override void connect_signals(Gtk.Widget widget) {
+ widget.motion_notify_event.connect(on_motion_notify_event);
+ widget.enter_notify_event.connect(on_enter_notify_event);
+ widget.leave_notify_event.connect(on_leave_notify_event);
+ }
+
+ protected override void disconnect_signals(Gtk.Widget widget) {
+ widget.motion_notify_event.disconnect(on_motion_notify_event);
+ widget.enter_notify_event.disconnect(on_enter_notify_event);
+ widget.leave_notify_event.disconnect(on_leave_notify_event);
+ }
+
+ private bool on_motion_notify_event(Gtk.Widget widget, Gdk.EventMotion event) {
+ MotionEvent motion_event = new MotionEvent(widget, event);
+
+ notify_motion(motion_event);
+ if (motion_event.is_any_button_pressed())
+ notify_button_motion(motion_event);
+
+ return Toolkit.PROPAGATE;
+ }
+
+ private bool on_enter_notify_event(Gtk.Widget widget, Gdk.EventCrossing event) {
+ notify_entered(new MotionEvent.for_crossing(widget, event));
+
+ return Toolkit.PROPAGATE;
+ }
+
+ private bool on_leave_notify_event(Gtk.Widget widget, Gdk.EventCrossing event) {
+ notify_entered(new MotionEvent.for_crossing(widget, event));
+
+ return Toolkit.PROPAGATE;
+ }
+}
+
+}
+
diff --git a/src/toolkit/toolkit-motion-event.vala b/src/toolkit/toolkit-motion-event.vala
new file mode 100644
index 0000000..697b64e
--- /dev/null
+++ b/src/toolkit/toolkit-motion-event.vala
@@ -0,0 +1,69 @@
+/* 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 {
+
+/**
+ * Details of a pointer (mouse) motion event, including entering and leaving a widget.
+ */
+
+public class MotionEvent : BaseObject {
+ /**
+ * The Gtk.Widget in question.
+ */
+ public Gtk.Widget widget { get; private set; }
+
+ /**
+ * The pointer location at the time of the event.
+ */
+ private Gdk.Point _point = Gdk.Point();
+ public Gdk.Point point { get { return _point; } }
+
+ /**
+ * The state of the modifier keys at the time of the event.
+ */
+ public Gdk.ModifierType modifiers { get; private set; }
+
+ internal MotionEvent(Gtk.Widget widget, Gdk.EventMotion event) {
+ this.widget = widget;
+ _point.x = (int) event.x;
+ _point.y = (int) event.y;
+ modifiers = event.state;
+ }
+
+ internal MotionEvent.for_crossing(Gtk.Widget widget, Gdk.EventCrossing event) {
+ this.widget = widget;
+ _point.x = (int) event.x;
+ _point.y = (int) event.y;
+ modifiers = event.state;
+ }
+
+ /**
+ * Returns true if the { link Button} is pressed at the time of this event.
+ */
+ public bool is_button_pressed(Button button) {
+ return (modifiers & button.get_modifier_mask()) != 0;
+ }
+
+ /**
+ * Returns true if any button is pressed at the time of this event.
+ */
+ public bool is_any_button_pressed() {
+ return (modifiers &
+ (Gdk.ModifierType.BUTTON1_MASK
+ | Gdk.ModifierType.BUTTON1_MASK
+ | Gdk.ModifierType.BUTTON1_MASK
+ | Gdk.ModifierType.BUTTON1_MASK
+ | Gdk.ModifierType.BUTTON1_MASK)) != 0;
+ }
+
+ public override string to_string() {
+ return "MotionEvent %d,%d".printf(point.x, point.y);
+ }
+}
+
+}
+
diff --git a/src/toolkit/toolkit.vala b/src/toolkit/toolkit.vala
index ec84471..41c3030 100644
--- a/src/toolkit/toolkit.vala
+++ b/src/toolkit/toolkit.vala
@@ -22,6 +22,16 @@ public const int DEFAULT_STACK_TRANSITION_DURATION_MSEC = 300;
*/
public const int SLOW_STACK_TRANSITION_DURATION_MSEC = 500;
+/**
+ * Indicates a GTK/GDK event should not be propagated further (no processing by other handlers).
+ */
+public const bool STOP = true;
+
+/**
+ * Indicates a GTK/GDK event should be propagated (continue processing by other handlers).
+ */
+public const bool PROPAGATE = false;
+
private int init_count = 0;
public void init() throws Error {
diff --git a/src/view/month/month-grid.vala b/src/view/month/month-grid.vala
index 80ec831..497e4c9 100644
--- a/src/view/month/month-grid.vala
+++ b/src/view/month/month-grid.vala
@@ -380,14 +380,11 @@ private class Grid : Gtk.Grid {
owner.request_display_event(event, release_cell, release_point);
stop_propagation = true;
}
- } else if (press_cell.date != null && release_cell.date != null) {
+ } else {
// create multi-day event
owner.request_create_all_day_event(new Calendar.DateSpan(press_cell.date, release_cell.date),
release_cell, release_point);
stop_propagation = true;
- } else {
- // make sure to clear selections if no action is taken
- unselect_all();
}
return stop_propagation;
@@ -441,15 +438,11 @@ private class Grid : Gtk.Grid {
if (hover_cell == null || hover_cell == press_cell)
return false;
- // both must have dates as well
- if (press_cell.date == null || hover_cell.date == null)
- return false;
-
// mark two cells and all in-between as selected, being sure to mark any previous selected
// as unselected
Calendar.DateSpan span = new Calendar.DateSpan(press_cell.date, hover_cell.date);
foreach_cell((cell) => {
- cell.selected = (cell.date != null) ? cell.date in span : false;
+ cell.selected = cell.date in span;
return true;
});
diff --git a/src/view/week/week-controller.vala b/src/view/week/week-controller.vala
index 4a4789c..fbba969 100644
--- a/src/view/week/week-controller.vala
+++ b/src/view/week/week-controller.vala
@@ -108,6 +108,13 @@ public class Controller : BaseObject, View.Controllable {
* @inheritDoc
*/
public void unselect_all() {
+ Grid? current = get_current_grid();
+ if (current != null)
+ current.unselect_all();
+ }
+
+ private Grid? get_current_grid() {
+ return stack.get_visible_child() as Grid;
}
private Gtk.Widget model_presentation(Calendar.Week week, out string? id) {
diff --git a/src/view/week/week-day-pane.vala b/src/view/week/week-day-pane.vala
index 6a29494..a0d7954 100644
--- a/src/view/week/week-day-pane.vala
+++ b/src/view/week/week-day-pane.vala
@@ -16,7 +16,8 @@ namespace California.View.Week {
internal class DayPane : Pane, Common.InstanceContainer {
public const string PROP_OWNER = "owner";
public const string PROP_DATE = "date";
- public const string PROP_SELECTED = "selected";
+ public const string PROP_SELECTION_STATE = "selection-start";
+ public const string PROP_SELECTION_END = "selection-end";
// No matter how wide the event is in the day, always leave a little peeking out so the hour/min
// lines are visible
@@ -24,7 +25,15 @@ internal class DayPane : Pane, Common.InstanceContainer {
public Calendar.Date date { get; set; }
- public bool selected { get; set; default = false; }
+ /**
+ * Where the current selection starts, if any.
+ */
+ public Calendar.WallTime? selection_start { get; private set; }
+
+ /**
+ * Where the current selection ends, if any.
+ */
+ public Calendar.WallTime? selection_end { get; private set; }
/**
* @inheritDoc
@@ -45,7 +54,7 @@ internal class DayPane : Pane, Common.InstanceContainer {
this.date = date;
notify[PROP_DATE].connect(queue_draw);
- notify[PROP_SELECTED].connect(queue_draw);
+
Calendar.System.instance.is_24hr_changed.connect(queue_draw);
Calendar.System.instance.today_changed.connect(on_today_changed);
@@ -136,14 +145,53 @@ internal class DayPane : Pane, Common.InstanceContainer {
return null;
}
+ public void update_selection(Calendar.WallTime wall_time) {
+ // round down to the nearest 15-minute mark
+ Calendar.WallTime rounded_time = wall_time.round_down(15, Calendar.TimeUnit.MINUTE);
+
+ // assign start first, end second (ordering doesn't matter, possible to select upwards)
+ if (selection_start == null) {
+ selection_start = rounded_time;
+ selection_end = null;
+ } else {
+ selection_end = rounded_time;
+ }
+
+ // if same, treat as unselected
+ if (selection_start != null && selection_end != null && selection_start.equal_to(selection_end)) {
+ clear_selection();
+
+ return;
+ }
+
+ queue_draw();
+ }
+
+ public Calendar.ExactTimeSpan? get_selection_span() {
+ if (selection_start == null || selection_end == null)
+ return null;
+
+ return new Calendar.ExactTimeSpan(
+ new Calendar.ExactTime(Calendar.Timezone.local, date, selection_start),
+ new Calendar.ExactTime(Calendar.Timezone.local, date, selection_end)
+ );
+ }
+
+ public void clear_selection() {
+ if (selection_start == null && selection_end == null)
+ return;
+
+ selection_start = null;
+ selection_end = null;
+
+ queue_draw();
+ }
+
// note that a painter's algorithm should be used here: background should be painted before
// calling base method, and foreground afterward
protected override bool on_draw(Cairo.Context ctx) {
- // shade background color if this is current day or selected
- if (selected) {
- Gdk.cairo_set_source_rgba(ctx, Palette.instance.selection);
- ctx.paint();
- } else if (date.equal_to(Calendar.System.today)) {
+ // shade background color if this is current day
+ if (date.equal_to(Calendar.System.today)) {
Gdk.cairo_set_source_rgba(ctx, Palette.instance.current_day);
ctx.paint();
}
@@ -211,6 +259,19 @@ internal class DayPane : Pane, Common.InstanceContainer {
ctx.stroke();
}
+ // draw selection rectangle
+ if (selection_start != null && selection_end != null) {
+ int start_y = get_line_y(selection_start);
+ int end_y = get_line_y(selection_end);
+
+ int y = int.min(start_y, end_y);
+ int height = int.max(start_y, end_y) - y;
+
+ ctx.rectangle(0, y, get_allocated_width(), height);
+ Gdk.cairo_set_source_rgba(ctx, Palette.instance.selection);
+ ctx.fill();
+ }
+
return true;
}
diff --git a/src/view/week/week-grid.vala b/src/view/week/week-grid.vala
index bf546f1..b570327 100644
--- a/src/view/week/week-grid.vala
+++ b/src/view/week/week-grid.vala
@@ -44,6 +44,10 @@ internal class Grid : Gtk.Box {
private Gee.HashMap<Calendar.Date, AllDayCell> date_to_all_day = new Gee.HashMap<Calendar.Date,
AllDayCell>();
private Toolkit.ButtonConnector instance_container_button_connector = new Toolkit.ButtonConnector();
+ private Toolkit.ButtonConnector all_day_button_connector = new Toolkit.ButtonConnector();
+ private Toolkit.ButtonConnector day_pane_button_connector = new Toolkit.ButtonConnector();
+ private Toolkit.MotionConnector day_pane_motion_connector = new Toolkit.MotionConnector.button_only();
+ private Toolkit.MotionConnector all_day_cell_motion_connector = new
Toolkit.MotionConnector.button_only();
private Gtk.ScrolledWindow scrolled_panes;
private Gtk.Widget right_spacer;
private bool vadj_init = false;
@@ -104,6 +108,8 @@ internal class Grid : Gtk.Box {
// label and the day panes
AllDayCell all_day_cell = new AllDayCell(this, date);
instance_container_button_connector.connect_to(all_day_cell);
+ all_day_button_connector.connect_to(all_day_cell);
+ all_day_cell_motion_connector.connect_to(all_day_cell);
top_grid.attach(all_day_cell, col, 1, 1, 1);
// save mapping
@@ -112,6 +118,8 @@ internal class Grid : Gtk.Box {
DayPane pane = new DayPane(this, date);
pane.expand = true;
instance_container_button_connector.connect_to(pane);
+ day_pane_button_connector.connect_to(pane);
+ day_pane_motion_connector.connect_to(pane);
pane_grid.attach(pane, col, 1, 1, 1);
// save mapping
@@ -133,10 +141,18 @@ internal class Grid : Gtk.Box {
scrolled_panes.get_vscrollbar().realize.connect(on_realloc_right_spacer);
scrolled_panes.get_vscrollbar().size_allocate.connect(on_realloc_right_spacer);
- // connect panes' event signal handlers
+ // connect instance connectors button event signal handlers for click/double-clicked
instance_container_button_connector.clicked.connect(on_instance_container_clicked);
instance_container_button_connector.double_clicked.connect(on_instance_container_double_clicked);
+ // connect to individual motion event handlers for different types of instance containers
+ all_day_cell_motion_connector.button_motion.connect(on_all_day_cell_button_motion);
+ day_pane_motion_connector.button_motion.connect(on_day_pane_motion);
+
+ // connect to individual button released handlers for different types of instance containers
+ all_day_button_connector.released.connect(on_all_day_cell_button_released);
+ day_pane_button_connector.released.connect(on_day_pane_button_released);
+
// set up calendar subscriptions for the week
subscriptions = new Backing.CalendarSubscriptionManager(
new Calendar.ExactTimeSpan.from_span(week, Calendar.Timezone.local));
@@ -167,6 +183,14 @@ internal class Grid : Gtk.Box {
scrolled_panes.vadjustment.changed.connect(on_vadjustment_changed);
}
+ public void unselect_all() {
+ foreach (AllDayCell day_cell in date_to_all_day.values)
+ day_cell.selected = false;
+
+ foreach (DayPane day_pane in date_to_panes.values)
+ day_pane.clear_selection();
+ }
+
private void on_vadjustment_changed(Gtk.Adjustment vadj) {
// wait for vadjustment to look like something reasonable; also, only do this once
if (vadj.upper <= 1.0 || vadj_init)
@@ -338,6 +362,106 @@ internal class Grid : Gtk.Box {
owner.request_create_all_day_event(instance_container.contained_span, instance_container,
details.press_point);
}
+
+ private void on_day_pane_motion(Toolkit.MotionEvent details) {
+ DayPane day_pane = (DayPane) details.widget;
+
+ // only update selection as long as button is depressed
+ if (details.is_button_pressed(Toolkit.Button.PRIMARY))
+ day_pane.update_selection(day_pane.get_wall_time(details.point.y));
+ else
+ day_pane.clear_selection();
+ }
+
+ private bool on_day_pane_button_released(Gtk.Widget widget, Toolkit.Button button, Gdk.Point point,
+ Gdk.EventType event_type) {
+ if (button != Toolkit.Button.PRIMARY)
+ return Toolkit.PROPAGATE;
+
+ DayPane day_pane = (DayPane) widget;
+
+ Calendar.ExactTimeSpan? selection_span = day_pane.get_selection_span();
+ if (selection_span == null)
+ return Toolkit.PROPAGATE;
+
+ owner.request_create_timed_event(selection_span, widget, point);
+
+ return Toolkit.STOP;
+ }
+
+ private AllDayCell? get_cell_at(AllDayCell widget, Gdk.Point widget_location) {
+ // convert widget's coordinates into grid coordinates
+ int grid_x, grid_y;
+ if (!widget.translate_coordinates(this, widget_location.x, widget_location.y,
+ out grid_x, out grid_y)) {
+ return null;
+ }
+
+ // convert those coordinates into the day cell now being hovered over
+ // TODO: Obviously a better hit-test could be done here
+ foreach (AllDayCell day_cell in date_to_all_day.values) {
+ int cell_x, cell_y;
+ if (!translate_coordinates(day_cell, grid_x, grid_y, out cell_x, out cell_y))
+ continue;
+
+ if (day_cell.is_hit(cell_x, cell_y))
+ return day_cell;
+ }
+
+ return null;
+ }
+
+ private void on_all_day_cell_button_motion(Toolkit.MotionEvent details) {
+ if (!details.is_button_pressed(Toolkit.Button.PRIMARY))
+ return;
+
+ // widget is always the cell where the drag began, not ends
+ AllDayCell start_cell = (AllDayCell) details.widget;
+
+ // get the widget now being hovered over
+ AllDayCell? hit_cell = get_cell_at(start_cell, details.point);
+ if (hit_cell == null)
+ return;
+
+ // select everything from the start cell to the hit cell
+ Calendar.DateSpan span = new Calendar.DateSpan(start_cell.date, hit_cell.date);
+ foreach (AllDayCell day_cell in date_to_all_day.values)
+ day_cell.selected = day_cell.date in span;
+ }
+
+ private bool on_all_day_cell_button_released(Gtk.Widget widget, Toolkit.Button button, Gdk.Point point,
+ Gdk.EventType event_type) {
+ if (button != Toolkit.Button.PRIMARY) {
+ unselect_all();
+
+ return Toolkit.PROPAGATE;
+ }
+
+ AllDayCell start_cell = (AllDayCell) widget;
+
+ // only convert drag-and-release to new event if start is selected (this prevents single-clicks
+ // from being turned into new events)
+ if (!start_cell.selected) {
+ unselect_all();
+
+ return Toolkit.PROPAGATE;
+ }
+
+ // get widget button was released over
+ AllDayCell? release_cell = get_cell_at(start_cell, point);
+ if (release_cell == null) {
+ unselect_all();
+
+ return Toolkit.PROPAGATE;
+ }
+
+ // let the host unselect all once the event has been created, this keeps the selection on
+ // the display until the user has completed
+ owner.request_create_all_day_event(new Calendar.DateSpan(start_cell.date, release_cell.date),
+ widget, point);
+
+ return Toolkit.STOP;
+ }
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]