[california] Migrate to GTK+ 3.12.2: Bug #732129
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california] Migrate to GTK+ 3.12.2: Bug #732129
- Date: Thu, 21 Aug 2014 01:03:05 +0000 (UTC)
commit 4e3b6d002fc306b00edfd3bcfd9ceac7ac1403c4
Author: Jim Nelson <jim yorba org>
Date: Wed Aug 20 14:34:07 2014 -0700
Migrate to GTK+ 3.12.2: Bug #732129
This moves California back onto GTK+ 3.12. Gtk.Popover use is more
spare than initially planned. In particular, there is no popover
used that is attached to a header bar button due to a Unity bug.
Aesthetic issues remain, but to keep this patch manageable, they will
be attacked in future commits.
configure.ac | 5 +-
debian/control | 2 +-
src/Makefile.am | 5 +-
src/collection/collection-iterable.vala | 4 +-
src/host/host-date-time-widget.vala | 4 -
src/host/host-main-window.vala | 116 ++++++++++++++++++-------
src/host/host-quick-create-event.vala | 10 +-
src/host/host-show-event.vala | 36 ++++++---
src/manager/manager-window.vala | 18 +---
src/rc/create-update-recurring.ui | 6 +-
src/rc/show-event.ui | 5 +-
src/toolkit/toolkit-deck-popover.vala | 102 ++++++++++++++++++++++
src/toolkit/toolkit-deck-window.vala | 4 +-
src/toolkit/toolkit-deck.vala | 31 ++++++--
src/toolkit/toolkit-rotating-button-box.vala | 61 +++++++++++++-
src/toolkit/toolkit.vala | 29 +++++++
src/view/month/month-controller.vala | 16 ++++
src/view/view-controllable.vala | 45 ++++++++++
src/view/view.vala | 4 +
src/view/week/week-controller.vala | 15 ++++
src/view/week/week-day-pane.vala | 11 +++
src/view/week/week-grid.vala | 4 +-
22 files changed, 435 insertions(+), 98 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index ad884c8..f2e142d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -25,7 +25,7 @@ AC_SUBST(CPPFLAGS)
AC_SUBST(LDFLAGS)
GLIB_REQUIRED=2.38.0
-GTK_REQUIRED=3.10.7
+GTK_REQUIRED=3.12.2
GEE_REQUIRED=0.10.5
ECAL_REQUIRED=3.8.5
LIBSOUP_REQUIRED=2.44
@@ -45,9 +45,6 @@ PKG_CHECK_MODULES(CALIFORNIA, [
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_REQUIRED
])
-# Look for GTK+ 3.12 or better
-AM_CONDITIONAL(IS_GTK_312, pkg-config --exists 'gtk+-3.0 >= 3.12')
-
AC_SUBST(CALIFORNIA_CFLAGS)
AC_SUBST(CALIFORNIA_LIBS)
diff --git a/debian/control b/debian/control
index aeaf13a..c0234b6 100644
--- a/debian/control
+++ b/debian/control
@@ -5,7 +5,7 @@ Maintainer: Jim Nelson <jim yorba org>
Build-Depends: debhelper (>= 8),
libgee-0.8-dev (>= 0.10.5),
libglib2.0-dev (>= 2.38.0),
- libgtk-3-dev (>= 3.10.7),
+ libgtk-3-dev (>= 3.12.2),
valac (>= 0.22.1),
intltool,
libecal1.2-dev (>= 3.8.5),
diff --git a/src/Makefile.am b/src/Makefile.am
index 8a99e77..f11b6a9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -133,6 +133,7 @@ california_VALASOURCES = \
toolkit/toolkit-card.vala \
toolkit/toolkit-combo-box-text-model.vala \
toolkit/toolkit-deck.vala \
+ toolkit/toolkit-deck-popover.vala \
toolkit/toolkit-deck-window.vala \
toolkit/toolkit-editable-label.vala \
toolkit/toolkit-entry-clear-text-connector.vala \
@@ -213,10 +214,6 @@ if ENABLE_UNITY
california_OPTIONAL_VALAFLAGS += --define ENABLE_UNITY
endif
-if IS_GTK_312
-california_OPTIONAL_VALAFLAGS += --define GTK_312
-endif
-
if HAVE__NL_TIME_FIRST_WEEKDAY
california_OPTIONAL_VALAFLAGS += --define HAVE__NL_TIME_FIRST_WEEKDAY
endif
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 66a1e79..d51e82d 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -22,7 +22,9 @@ public California.Iterable<G> traverse<G>(Gee.Iterable<G>? gee_iterable) {
* it.
*
* "Safe iteration" means later operations that remove elements while iterating do not cause an
- * assertion.
+ * assertion. This involves creating a copy of the supplied Gee.Iterable, meaning that any changes
+ * made in subsequence operations (i.e. { link California.Iterable.filter} are not reflected in
+ * the passed-in collection.
*
* An empty Gee.Iterable is created and used if null is passed in.
*/
diff --git a/src/host/host-date-time-widget.vala b/src/host/host-date-time-widget.vala
index 4b2a804..3f43a56 100644
--- a/src/host/host-date-time-widget.vala
+++ b/src/host/host-date-time-widget.vala
@@ -118,11 +118,7 @@ public class DateTimeWidget : Gtk.Box {
Calendar.System.instance.is_24hr_changed.connect(system_24hr_changed);
system_24hr_changed();
- // GTK 3.12 requires this in order to constrain GtkEntry width, older versions were happy
- // with width_chars alone
-#if GTK_312
hour_entry.max_width_chars = minutes_entry.max_width_chars = 2;
-#endif
}
~DateTimeWidget() {
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index 26dd5fd..d8643b5 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -83,6 +83,8 @@ public class MainWindow : Gtk.ApplicationWindow {
{ ACTION_RESET_FONT, on_reset_font }
};
+ public Gtk.Button calendar_button { get; private set; }
+
private Gtk.Button quick_add_button;
private View.Palette palette;
private View.Controllable month_view;
@@ -171,7 +173,7 @@ public class MainWindow : Gtk.ApplicationWindow {
view_switcher.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED);
view_switcher.get_style_context().add_class(Gtk.STYLE_CLASS_RAISED);
- // pack left-side of window
+ // pack left-side of header bar
headerbar.pack_start(today);
headerbar.pack_start(view_switcher);
@@ -180,11 +182,11 @@ public class MainWindow : Gtk.ApplicationWindow {
quick_add_button.tooltip_text = _("Quick add event (Ctrl+N)");
quick_add_button.set_action_name(DETAILED_ACTION_QUICK_CREATE_EVENT);
- Gtk.Button calendars = new Gtk.Button.from_icon_name("x-office-calendar-symbolic",
+ calendar_button = new Gtk.Button.from_icon_name("x-office-calendar-symbolic",
Gtk.IconSize.MENU);
- calendars.valign = Gtk.Align.CENTER;
- calendars.tooltip_text = _("Calendars (Ctrl+L)");
- calendars.set_action_name(Application.DETAILED_ACTION_CALENDAR_MANAGER);
+ calendar_button.valign = Gtk.Align.CENTER;
+ calendar_button.tooltip_text = _("Calendars (Ctrl+L)");
+ calendar_button.set_action_name(Application.DETAILED_ACTION_CALENDAR_MANAGER);
Gtk.MenuButton window_menu = new Gtk.MenuButton();
window_menu.valign = Gtk.Align.CENTER;
@@ -199,20 +201,13 @@ public class MainWindow : Gtk.ApplicationWindow {
size.add_widget(custom_title.next_button);
size.add_widget(custom_title.prev_button);
size.add_widget(quick_add_button);
- size.add_widget(calendars);
+ size.add_widget(calendar_button);
size.add_widget(window_menu);
- // pack right-side of window ... note that this was fixed in 3.12, reversing the order of
- // how widgets need to be packed at the end
-#if GTK_312
+ // pack right-side of header bar
headerbar.pack_end(window_menu);
- headerbar.pack_end(calendars);
- headerbar.pack_end(quick_add_button);
-#else
+ headerbar.pack_end(calendar_button);
headerbar.pack_end(quick_add_button);
- headerbar.pack_end(calendars);
- headerbar.pack_end(window_menu);
-#endif
Gtk.Box layout = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
// if on Unity, since headerbar is not the titlebar, need to pack it like any other widget
@@ -323,7 +318,7 @@ public class MainWindow : Gtk.ApplicationWindow {
}
}
- private void show_deck(Gtk.Widget relative_to, Gdk.Point? for_location, Toolkit.Deck deck) {
+ private void show_deck_window(Toolkit.Deck deck) {
Toolkit.DeckWindow deck_window = new Toolkit.DeckWindow(this, deck);
// when the dialog closes, reset View.Controllable state (selection is maintained while
@@ -345,8 +340,34 @@ public class MainWindow : Gtk.ApplicationWindow {
deck_window.destroy();
}
+ private void show_deck_popover(Gtk.Widget relative_to, Gdk.Point? for_location, Toolkit.Deck deck) {
+ Toolkit.DeckPopover deck_popover = new Toolkit.DeckPopover(relative_to, for_location, deck);
+
+ // when the popover closes, reset View.Controllable state (selection is maintained while
+ // use is viewing/editing interaction) and destroy widgets
+ deck_popover.dismiss.connect(() => {
+ current_controller.unselect_all();
+ Toolkit.destroy_later(deck_popover);
+ });
+
+ deck_popover.deck.failure.connect((msg) => {
+ Application.instance.error_message(msg);
+ });
+
+ deck_popover.show_all();
+ }
+
private void on_quick_create_event() {
- quick_create_event(null, quick_add_button, null);
+ // switch to Today and execute Quick Add when transition is complete
+ current_controller.today();
+ current_controller.execute_when_not_transitioning(do_quick_create_event);
+ }
+
+ private void do_quick_create_event(View.Controllable controller) {
+ Gtk.Widget? today_widget = controller.get_widget_for_date(Calendar.System.today);
+ assert(today_widget != null);
+
+ on_request_create_all_day_event(Calendar.System.today.to_date_span(), today_widget, null);
}
private void on_jump_to_today() {
@@ -415,29 +436,58 @@ public class MainWindow : Gtk.ApplicationWindow {
private void quick_create_event(Component.Event? initial, Gtk.Widget relative_to, Gdk.Point?
for_location) {
QuickCreateEvent quick_create = new QuickCreateEvent();
- CreateUpdateEvent create_update = new CreateUpdateEvent();
- create_update.is_update = false;
-
- CreateUpdateRecurring create_update_recurring = new CreateUpdateRecurring();
-
- EventTimeSettings event_time_settings = new EventTimeSettings();
-
Toolkit.Deck deck = new Toolkit.Deck();
- deck.add_cards(
- iterate<Toolkit.Card>(quick_create, create_update, create_update_recurring, event_time_settings)
- .to_array_list()
- );
+ deck.add_cards(iterate<Toolkit.Card>(quick_create).to_array_list());
- // initialize the Deck with the initial event (if any)
deck.go_home(initial);
- show_deck(relative_to, for_location, deck);
+ deck.dismiss.connect(() => {
+ if (quick_create.edit_required)
+ edit_event(quick_create.event);
+ });
+
+ show_deck_popover(relative_to, for_location, deck);
}
private void on_request_display_event(Component.Event event, Gtk.Widget relative_to,
Gdk.Point? for_location) {
ShowEvent show_event = new ShowEvent();
+ Toolkit.Deck deck = new Toolkit.Deck();
+ deck.add_cards(iterate<Toolkit.Card>(show_event).to_array_list());
+
+ deck.go_home(event);
+
+ deck.dismiss.connect(() => {
+ if (!show_event.edit_requested)
+ return;
+
+ // pass a clone of the existing event for editing
+ Component.Event clone;
+ try {
+ clone = event.clone() as Component.Event;
+ } catch (Error err) {
+ Application.instance.error_message(_("Unable to edit event: %s").printf(err.message));
+
+ return;
+ }
+
+ edit_event(clone);
+ });
+
+ show_deck_popover(relative_to, for_location, deck);
+ }
+
+ private void edit_event(Component.Event event) {
+ // use Idle loop so popovers have a chance to hide before bringing up DeckWindow
+ Idle.add(() => {
+ on_edit_event(event);
+
+ return false;
+ }, Priority.LOW + 100);
+ }
+
+ private void on_edit_event(Component.Event event) {
CreateUpdateEvent create_update = new CreateUpdateEvent();
create_update.is_update = true;
@@ -447,14 +497,14 @@ public class MainWindow : Gtk.ApplicationWindow {
Toolkit.Deck deck = new Toolkit.Deck();
deck.add_cards(
- iterate<Toolkit.Card>(show_event, create_update, create_update_recurring, event_time_settings)
+ iterate<Toolkit.Card>(create_update, create_update_recurring, event_time_settings)
.to_array_list()
);
- // "initialize" the Deck with the requested Event (because ShowEvent is first, it's home)
+ // "initialize" the Deck with the requested Event
deck.go_home(event);
- show_deck(relative_to, for_location, deck);
+ show_deck_window(deck);
}
}
diff --git a/src/host/host-quick-create-event.vala b/src/host/host-quick-create-event.vala
index 7b35170..bcde5e4 100644
--- a/src/host/host-quick-create-event.vala
+++ b/src/host/host-quick-create-event.vala
@@ -16,6 +16,8 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
public new Component.Event? event { get; private set; default = null; }
+ public bool edit_required { get; private set; default = false; }
+
public Gtk.Widget? default_widget { get { return create_button; } }
public Gtk.Widget? initial_focus { get { return details_entry; } }
@@ -143,11 +145,9 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
if (!event.is_valid(false))
event.set_event_date_span(Calendar.System.today.to_date_span());
- // jump to Create/Update dialog and remove this Card from the Deck ... this ensures
- // that if the user presses Cancel in the Create/Update dialog the Deck exits rather
- // than returns here (via jump_home_or_user_closed())
- jump_to_card_by_name(CreateUpdateEvent.ID, event);
- deck.remove_cards(iterate<Toolkit.Card>(this).to_array_list());
+ edit_required = true;
+
+ notify_user_closed();
}
private async void create_event_async(Cancellable? cancellable) {
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index d5cd3f4..9179a95 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -25,6 +25,8 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
public Gtk.Widget? initial_focus { get { return close_button; } }
+ public bool edit_requested { get; private set; default = false; }
+
[GtkChild]
private Gtk.Label summary_text;
@@ -47,6 +49,9 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
private Gtk.Label calendar_text;
[GtkChild]
+ private Gtk.ScrolledWindow description_text_window;
+
+ [GtkChild]
private Gtk.Label description_text;
[GtkChild]
@@ -59,10 +64,11 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
private Gtk.Button close_button = new Gtk.Button.with_mnemonic(_("_Close"));
private Gtk.Button update_button = new Gtk.Button.with_mnemonic(_("_Edit"));
private Gtk.Button remove_button = new Gtk.Button.with_mnemonic(_("_Delete"));
- private Gtk.Button remove_all_button = new Gtk.Button.with_mnemonic(_("Delete A_ll Events"));
- private Gtk.Button remove_this_button = new Gtk.Button.with_mnemonic(_("Delete _This Event"));
+ private Gtk.Label delete_label = new Gtk.Label(_("Delete"));
+ private Gtk.Button remove_all_button = new Gtk.Button.with_mnemonic(_("A_ll Events"));
+ private Gtk.Button remove_this_button = new Gtk.Button.with_mnemonic(_("_This Event"));
private Gtk.Button remove_this_future_button = new Gtk.Button.with_mnemonic(
- _("Delete This and _Future Events"));
+ _("This & _Future Events"));
private Gtk.Button cancel_remove_button = new Gtk.Button.with_mnemonic(_("_Cancel"));
public ShowEvent() {
@@ -89,11 +95,16 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
rotating_button_box.pack_end(FAMILY_NORMAL, update_button);
rotating_button_box.pack_end(FAMILY_NORMAL, close_button);
+ delete_label.xalign = 1.0f;
+ delete_label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
+ rotating_button_box.pack_start(FAMILY_REMOVING, delete_label);
rotating_button_box.pack_end(FAMILY_REMOVING, remove_this_button);
rotating_button_box.pack_end(FAMILY_REMOVING, remove_this_future_button);
rotating_button_box.pack_end(FAMILY_REMOVING, remove_all_button);
rotating_button_box.pack_end(FAMILY_REMOVING, cancel_remove_button);
+ rotating_button_box.get_family_container(FAMILY_REMOVING).homogeneous = false;
+
rotating_button_box.expand = true;
rotating_button_box.halign = Gtk.Align.FILL;
rotating_button_box.valign = Gtk.Align.END;
@@ -113,6 +124,13 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
event = message as Component.Event;
assert(event != null);
+ description_text.bind_property("visible", description_text_window, "visible",
+ BindingFlags.SYNC_CREATE);
+ description_text.bind_property("no-show-all", description_text_window, "no-show-all",
+ BindingFlags.SYNC_CREATE);
+
+ rotating_button_box.show_hide_family(FAMILY_REMOVING, event.is_generated_instance);
+
build_display();
}
@@ -182,9 +200,6 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
private void on_remove_button_clicked() {
// If recurring (and so this is a generated instance of the VEVENT, not the VEVENT itself),
// reveal additional remove buttons
- //
- // TODO: Gtk.Stack would be a better widget for this animation, but it's unavailable in
- // Glade as of GTK+ 3.12.
if (event.is_generated_instance) {
rotating_button_box.family = FAMILY_REMOVING;
@@ -211,12 +226,9 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
}
private void on_update_button_clicked() {
- // pass a clone of the existing event for editing
- try {
- jump_to_card_by_name(CreateUpdateEvent.ID, event.clone() as Component.Event);
- } catch (Error err) {
- notify_failure(_("Unable to update event: %s").printf(err.message));
- }
+ edit_requested = true;
+
+ notify_user_closed();
}
private void on_close_button_clicked() {
diff --git a/src/manager/manager-window.vala b/src/manager/manager-window.vala
index fe61b5e..08e3546 100644
--- a/src/manager/manager-window.vala
+++ b/src/manager/manager-window.vala
@@ -11,31 +11,21 @@ namespace California.Manager {
*/
public class Window : Toolkit.DeckWindow {
- private static Manager.Window? instance = null;
-
private CalendarList calendar_list = new CalendarList();
- private Window(Gtk.Window? parent) {
- base (parent, null);
+ private Window(Gtk.Window? window) {
+ base (window, null);
deck.add_cards(iterate<Toolkit.Card>(calendar_list).to_array_list());
Activator.prepare_deck(deck, null);
}
- public static void display(Gtk.Window? parent) {
- // only allow one instance at a time
- if (instance != null) {
- instance.present_with_time(Gdk.CURRENT_TIME);
-
- return;
- }
+ public static void display(Gtk.Window? window) {
+ Manager.Window instance = new Manager.Window(window);
- instance = new Manager.Window(parent);
instance.show_all();
instance.run();
instance.destroy();
-
- instance = null;
}
public override bool key_release_event(Gdk.EventKey event) {
diff --git a/src/rc/create-update-recurring.ui b/src/rc/create-update-recurring.ui
index dccd51d..1e52fc5 100644
--- a/src/rc/create-update-recurring.ui
+++ b/src/rc/create-update-recurring.ui
@@ -5,10 +5,8 @@
<template class="CaliforniaHostCreateUpdateRecurring" parent="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="margin_left">8</property>
- <property name="margin_right">8</property>
- <property name="margin_top">8</property>
- <property name="margin_bottom">8</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkCheckButton" id="make_recurring_checkbutton">
diff --git a/src/rc/show-event.ui b/src/rc/show-event.ui
index dd031f7..29dd4e8 100644
--- a/src/rc/show-event.ui
+++ b/src/rc/show-event.ui
@@ -5,6 +5,8 @@
<template class="CaliforniaHostShowEvent" parent="GtkGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="summary_text">
@@ -27,7 +29,7 @@
</packing>
</child>
<child>
- <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <object class="GtkScrolledWindow" id="description_text_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_top">6</property>
@@ -187,6 +189,7 @@
<object class="GtkBox" id="rotating_button_box_container">
<property name="visible">True</property>
<property name="can_focus">False</property>
+ <property name="margin_top">8</property>
<child>
<placeholder/>
</child>
diff --git a/src/toolkit/toolkit-deck-popover.vala b/src/toolkit/toolkit-deck-popover.vala
new file mode 100644
index 0000000..8f8da88
--- /dev/null
+++ b/src/toolkit/toolkit-deck-popover.vala
@@ -0,0 +1,102 @@
+/* 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 GtkPopover with special support for { link Deck}s.
+ */
+
+public class DeckPopover : Gtk.Popover {
+ public Deck deck { get; private set; }
+
+ /**
+ * See { link Card.dismiss}
+ */
+ public signal void dismiss(bool user_request, bool final);
+
+ private bool preserve_mode;
+ private bool forcing_mode = false;
+
+ public DeckPopover(Gtk.Widget rel_to, Gdk.Point? for_location, Deck? starter_deck) {
+ Object (relative_to: rel_to);
+
+ preserve_mode = modal;
+
+ // treat "closed" signal as dismissal by user request
+ closed.connect(() => {
+ dismiss(true, true);
+ });
+
+ notify["modal"].connect(on_modal_changed);
+
+ this.deck = starter_deck ?? new Deck();
+
+ if (for_location != null) {
+ Gdk.Rectangle for_location_rect = Gdk.Rectangle() { x = for_location.x, y = for_location.y,
+ width = 1, height = 1 };
+ pointing_to = for_location_rect;
+ }
+
+ // because adding/removing cards can cause deep in Gtk.Widget the Popover to lose focus,
+ // those operations can prematurely close the Popover. Catching these signals allow for
+ // DeckWindow to go modeless during the operation and not close. (RotatingButtonBox has a
+ // similar issue.)
+ //
+ // TODO: This is fixed in GTK+ 3.13.6. When 3.14 is baseline requirement, this code can
+ // be removed.
+ deck.notify["transition-running"].connect(on_transition_running_changed);
+ deck.adding_removing_cards.connect(on_adding_removing_cards);
+ deck.added_removed_cards.connect(on_added_removed_cards);
+ deck.dismiss.connect(on_deck_dismissed);
+
+ // store Deck in box so margin can be applied
+ Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+ box.margin = 4;
+ box.add(deck);
+
+ add(box);
+ }
+
+ ~DeckPopover() {
+ deck.notify["transition-running"].disconnect(on_transition_running_changed);
+ deck.adding_removing_cards.disconnect(on_adding_removing_cards);
+ deck.added_removed_cards.disconnect(on_added_removed_cards);
+ deck.dismiss.disconnect(on_deck_dismissed);
+ }
+
+ // if the modal value changes, preserve it (unless it's changing because we're forcing it to
+ // go modal/modeless during transitions we're attempting to work around)
+ private void on_modal_changed() {
+ if (!forcing_mode)
+ preserve_mode = modal;
+ }
+
+ private void force_mode(bool modal) {
+ forcing_mode = true;
+ this.modal = modal;
+ forcing_mode = false;
+ }
+
+ private void on_transition_running_changed() {
+ force_mode(deck.transition_running ? false : preserve_mode);
+ }
+
+ private void on_adding_removing_cards() {
+ force_mode(false);
+ }
+
+ private void on_added_removed_cards() {
+ force_mode(preserve_mode);
+ }
+
+ private void on_deck_dismissed(bool user_request, bool final) {
+ dismiss(user_request, final);
+ }
+}
+
+}
+
diff --git a/src/toolkit/toolkit-deck-window.vala b/src/toolkit/toolkit-deck-window.vala
index 44d1872..08c4331 100644
--- a/src/toolkit/toolkit-deck-window.vala
+++ b/src/toolkit/toolkit-deck-window.vala
@@ -7,13 +7,11 @@
namespace California.Toolkit {
/**
- * A GtkDialog with no visible action area.
+ * A GtkDialog with no visible action area that holds { link Deck}s.
*
* This is designed for UI panes that want to control their own interaction with the user (in
* particular, button placement) but need all the benefits interaction-wise of GtkDialog.
*
- * It's expected this will go away when we move to GTK+ 3.12 and can use GtkPopovers for these
- * interactions.
*/
public class DeckWindow : Gtk.Dialog {
diff --git a/src/toolkit/toolkit-deck.vala b/src/toolkit/toolkit-deck.vala
index 6592690..c640b7b 100644
--- a/src/toolkit/toolkit-deck.vala
+++ b/src/toolkit/toolkit-deck.vala
@@ -44,6 +44,16 @@ public class Deck : Gtk.Stack {
private Gee.HashMap<string, Card> names = new Gee.HashMap<string, Card>();
/**
+ * Fired before { link Card}s are added or removed.
+ */
+ public signal void adding_removing_cards(Gee.List<Card>? adding, Gee.Collection<Card>? removing);
+
+ /**
+ * Fired after { link Card}s are added or removed.
+ */
+ public signal void added_removed_cards(Gee.List<Card>? added, Gee.Collection<Card>? removed);
+
+ /**
* @see Card.dismiss
*/
public signal void dismiss(bool user_request, bool final);
@@ -124,6 +134,8 @@ public class Deck : Gtk.Stack {
if (cards.size == 0)
return;
+ adding_removing_cards(cards, null);
+
// if empty, first card is home and should be made visible when added
bool set_home_visible = size == 0;
@@ -153,6 +165,8 @@ public class Deck : Gtk.Stack {
set_visible_child(home);
home.jumped_to(null, Card.Jump.HOME, null);
}
+
+ added_removed_cards(cards, null);
}
/**
@@ -161,9 +175,11 @@ public class Deck : Gtk.Stack {
* If the { link top} card is removed, the Deck will return { link home}, clearing the
* navigation stack in the process.
*/
- public void remove_cards(Gee.Iterable<Card> cards) {
+ public void remove_cards(Gee.Collection<Card> cards) {
bool displaying = top != null;
+ adding_removing_cards(null, cards);
+
foreach (Card card in cards) {
if (!names.has_key(card.card_id)) {
message("Card %s not found in Deck", card.card_id);
@@ -189,6 +205,8 @@ public class Deck : Gtk.Stack {
set_visible_child(home);
home.jumped_to(null, Card.Jump.HOME, null);
}
+
+ added_removed_cards(null, cards);
}
private Value? strip_null_value(Value? message) {
@@ -288,11 +306,12 @@ public class Deck : Gtk.Stack {
private void on_card_mapped(Gtk.Widget widget) {
Card card = (Card) widget;
- if (card.default_widget != null && card.default_widget.can_default)
- card.default_widget.grab_default();
-
- if (card.initial_focus != null && card.initial_focus.can_focus)
- card.initial_focus.grab_focus();
+ if (card.default_widget != null) {
+ if (card.default_widget.can_default)
+ card.default_widget.grab_default();
+ else
+ message("Card %s specifies default widget that cannot be default", card.card_id);
+ }
}
}
diff --git a/src/toolkit/toolkit-rotating-button-box.vala b/src/toolkit/toolkit-rotating-button-box.vala
index 13778f7..07a5f53 100644
--- a/src/toolkit/toolkit-rotating-button-box.vala
+++ b/src/toolkit/toolkit-rotating-button-box.vala
@@ -33,23 +33,49 @@ public class RotatingButtonBox : Gtk.Stack {
public string? family { get; set; }
private Gee.HashMap<string, Gtk.ButtonBox> button_boxes = new Gee.HashMap<string, Gtk.ButtonBox>();
+ private Gtk.Popover? parent_popover = null;
+ private bool parent_popover_modal = false;
public RotatingButtonBox() {
homogeneous = true;
transition_duration = SLOW_STACK_TRANSITION_DURATION_MSEC;
transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
+ notify["transition-running"].connect(on_transition_running);
+
bind_property("visible-child-name", this, PROP_FAMILY,
BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
}
+ // unfortunately, RotatingButtonBox can cause modal Popovers to close because the focus
+ // changes from one button box to another, triggering a situation in GtkWidget where the
+ // Popover thinks it has lost focus ... this hacks around the problem by setting the popover
+ // to modeless until the transition is complete
+ //
+ // TODO: This is fixed in GTK+ 3.13.6. When 3.14 is baseline requirement, this code can
+ // be removed.
+ private void on_transition_running() {
+ if (transition_running && parent_popover == null) {
+ // set to modeless to hack around problem
+ parent_popover = get_ancestor(typeof (Gtk.Popover)) as Gtk.Popover;
+ if (parent_popover != null) {
+ parent_popover_modal = parent_popover.modal;
+ parent_popover.modal = false;
+ }
+ } else if (!transition_running && parent_popover != null) {
+ // reset to original mode
+ parent_popover.modal = parent_popover_modal;
+ parent_popover = null;
+ }
+ }
+
/**
* Pack a Gtk.Button at the start of a particular family, creating the family if necessary.
*
* See Gtk.Box.pack_start().
*/
- public void pack_start(string family, Gtk.Button button) {
- get_family_container(family).pack_start(button);
+ public void pack_start(string family, Gtk.Widget widget) {
+ get_family_container(family).pack_start(widget);
}
/**
@@ -57,8 +83,8 @@ public class RotatingButtonBox : Gtk.Stack {
*
* See Gtk.Box.pack_end().
*/
- public void pack_end(string family, Gtk.Button button) {
- get_family_container(family).pack_end(button);
+ public void pack_end(string family, Gtk.Widget widget) {
+ get_family_container(family).pack_end(widget);
}
/**
@@ -83,6 +109,33 @@ public class RotatingButtonBox : Gtk.Stack {
return button_box;
}
+
+ /**
+ * Removes (or adds back) a family from the { link RotatingButtonBox}.
+ *
+ * The family remains under the RotatingButtonBox's control, it's simply removed from the
+ * widget heirarchy. This is useful for sizing purposes.
+ */
+ public void show_hide_family(string family, bool show) {
+ if (!button_boxes.has_key(family))
+ return;
+
+ Gtk.ButtonBox button_box = button_boxes.get(family);
+
+ bool shown = false;
+ foreach (Gtk.Widget widget in get_children()) {
+ if (widget == button_box) {
+ shown = true;
+
+ break;
+ }
+ }
+
+ if (show && !shown)
+ add_named(button_box, family);
+ else if (!show && shown)
+ remove(button_box);
+ }
}
}
diff --git a/src/toolkit/toolkit.vala b/src/toolkit/toolkit.vala
index 2630ed5..ca38290 100644
--- a/src/toolkit/toolkit.vala
+++ b/src/toolkit/toolkit.vala
@@ -33,19 +33,28 @@ public const bool STOP = true;
public const bool PROPAGATE = false;
private int init_count = 0;
+private Gee.Set<Gtk.Widget>? dead_pool = null;
+private Scheduled? dead_pool_gc = null;
public void init() throws Error {
if (!Unit.do_init(ref init_count))
return;
+ dead_pool = new Gee.HashSet<Gtk.Widget>();
+
Calendar.init();
+ Collection.init();
}
public void terminate() {
if (!Unit.do_terminate(ref init_count))
return;
+ Collection.terminate();
Calendar.terminate();
+
+ dead_pool = null;
+ dead_pool_gc = null;
}
/**
@@ -94,4 +103,24 @@ public void set_unbusy(Gtk.Widget widget, Gdk.Cursor? unbusy_cursor) {
toplevel.get_window().set_cursor(unbusy_cursor);
}
+/**
+ * Destroy a Gtk.Widget when the event loop is idle.
+ */
+public void destroy_later(Gtk.Widget widget) {
+ if (!dead_pool.add(widget))
+ return;
+
+ // always reschedule
+ dead_pool_gc = new Scheduled.once_at_idle(() => {
+ // traverse_safely makes a copy of the dead_pool, so filter() won't work to remove destroyed
+ // elements, but that also means we can safely remove elements from it in the iterate()
+ // handler ... need to jump through these hoops because the widget.destroy() signal handlers
+ // may turn around and add more widgets to the dead pool during traversal
+ traverse_safely<Gtk.Widget>(dead_pool).iterate((widget) => {
+ widget.destroy();
+ dead_pool.remove(widget);
+ });
+ }, Priority.LOW);
+}
+
}
diff --git a/src/view/month/month-controller.vala b/src/view/month/month-controller.vala
index 78a47cf..12463e9 100644
--- a/src/view/month/month-controller.vala
+++ b/src/view/month/month-controller.vala
@@ -66,6 +66,11 @@ public class Controller : BaseObject, View.Controllable {
public Calendar.Date default_date { get; protected set; }
/**
+ * @inheritDoc
+ */
+ public bool in_transition { get; protected set; }
+
+ /**
* { link View.Palette} for the entire view.
*/
public View.Palette palette { get; private set; }
@@ -92,6 +97,8 @@ public class Controller : BaseObject, View.Controllable {
Toolkit.StackModel.OrderedTransitionType.SLIDE_LEFT_RIGHT, model_presentation,
trim_presentation_from_cache, ensure_presentation_in_cache);
+ stack.bind_property("transition-running", this, PROP_IN_TRANSITION, BindingFlags.SYNC_CREATE);
+
// insert labels for days of the week across top of master grid
for (int col = 0; col < Grid.COLS; col++) {
Gtk.Label dow_label = new Gtk.Label(null);
@@ -189,6 +196,15 @@ public class Controller : BaseObject, View.Controllable {
/**
* @inheritDoc
*/
+ public Gtk.Widget? get_widget_for_date(Calendar.Date date) {
+ Grid? current_grid = get_current_month_grid();
+
+ return current_grid != null ? current_grid.get_cell_for_date(date) : null;
+ }
+
+ /**
+ * @inheritDoc
+ */
public View.Container get_container() {
return master_grid;
}
diff --git a/src/view/view-controllable.vala b/src/view/view-controllable.vala
index 0251c9c..299df7d 100644
--- a/src/view/view-controllable.vala
+++ b/src/view/view-controllable.vala
@@ -18,6 +18,12 @@ public interface Controllable : Object {
public const string PROP_CURRENT_LABEL = "current-label";
public const string PROP_IS_VIEWING_TODAY = "is-viewing-today";
public const string PROP_DEFAULT_DATE = "default-date";
+ public const string PROP_IN_TRANSITION = "in-transition";
+
+ /**
+ * For { link execute_when_not_in_transition}.
+ */
+ public delegate void Callback(View.Controllable controller);
/**
* A short string uniquely identifying this view.
@@ -46,6 +52,11 @@ public interface Controllable : Object {
public abstract bool is_viewing_today { get; protected set; }
/**
+ * Indicates when a transition is running (such as when moving between dates).
+ */
+ public abstract bool in_transition { get; protected set; }
+
+ /**
* Signal from the { link Controllable} that a DATE-TIME { link Component.Event} should be
* created with the specified initial parameters.
*/
@@ -96,6 +107,40 @@ public interface Controllable : Object {
* when creating or displaying events).
*/
public abstract void unselect_all();
+
+ /**
+ * Return the GtkWidget that represents the specified { link Calendar.Date}, if available.
+ */
+ public abstract Gtk.Widget? get_widget_for_date(Calendar.Date date);
+
+ /**
+ * Execute a { link Callback} only when the { link Controllable} is not performing a transition.
+ *
+ * If the Controllable is not in transition when called, the Callback is executed immediately.
+ * Otherwise, execution is delayed until the transition completes.
+ */
+ public void execute_when_not_transitioning(Callback callback) {
+ if (!in_transition) {
+ callback(this);
+
+ return;
+ }
+
+ ulong handler_id = 0;
+ handler_id = notify[PROP_IN_TRANSITION].connect(() => {
+ // watch for spurious notifications
+ if (in_transition)
+ return;
+
+ // disable this signal
+ if (handler_id > 0)
+ disconnect(handler_id);
+ else
+ debug("Unable to disconnect in-transition notify signal handler");
+
+ callback(this);
+ });
+ }
}
}
diff --git a/src/view/view.vala b/src/view/view.vala
index 57e3e19..584bfaa 100644
--- a/src/view/view.vala
+++ b/src/view/view.vala
@@ -18,6 +18,8 @@ public void init() throws Error {
if (!Unit.do_init(ref init_count))
return;
+ Calendar.init();
+
// subunit initialization
View.Common.init();
View.Month.init();
@@ -31,6 +33,8 @@ public void terminate() {
View.Week.terminate();
View.Month.terminate();
View.Common.terminate();
+
+ Calendar.terminate();
}
}
diff --git a/src/view/week/week-controller.vala b/src/view/week/week-controller.vala
index d3496d6..adbb00d 100644
--- a/src/view/week/week-controller.vala
+++ b/src/view/week/week-controller.vala
@@ -52,6 +52,11 @@ public class Controller : BaseObject, View.Controllable {
public bool is_viewing_today { get; protected set; }
/**
+ * @inheritDoc
+ */
+ public bool in_transition { get; protected set; }
+
+ /**
* { link View.Palette} for the entire hosted view.
*/
public View.Palette palette { get; private set; }
@@ -66,6 +71,7 @@ public class Controller : BaseObject, View.Controllable {
stack = new ViewContainer(this);
stack.homogeneous = true;
stack.transition_duration = Toolkit.SLOW_STACK_TRANSITION_DURATION_MSEC;
+ stack.bind_property("transition-running", this, PROP_IN_TRANSITION, BindingFlags.SYNC_CREATE);
stack_model = new Toolkit.StackModel<Calendar.Week>(stack,
Toolkit.StackModel.OrderedTransitionType.SLIDE_LEFT_RIGHT, model_presentation,
@@ -122,6 +128,15 @@ public class Controller : BaseObject, View.Controllable {
current.unselect_all();
}
+ /**
+ * @inheritDoc
+ */
+ public Gtk.Widget? get_widget_for_date(Calendar.Date date) {
+ Grid? current_grid = get_current_grid();
+
+ return current_grid != null ? current_grid.get_all_day_cell_for_date(date) : null;
+ }
+
private Grid? get_current_grid() {
return stack.get_visible_child() as Grid;
}
diff --git a/src/view/week/week-day-pane.vala b/src/view/week/week-day-pane.vala
index e71790a..0298618 100644
--- a/src/view/week/week-day-pane.vala
+++ b/src/view/week/week-day-pane.vala
@@ -38,6 +38,11 @@ internal class DayPane : Pane, Common.InstanceContainer {
public Calendar.WallTime? selection_end { get; private set; }
/**
+ * The center point of the current selection.
+ */
+ public Gdk.Point? selection_point { get; private set; default = null; }
+
+ /**
* @inheritDoc
*/
public int event_count { get { return days_events.size; } }
@@ -306,6 +311,12 @@ internal class DayPane : Pane, Common.InstanceContainer {
ctx.rectangle(0, y, get_allocated_width(), height);
Gdk.cairo_set_source_rgba(ctx, palette.selection);
ctx.fill();
+
+ selection_point = Gdk.Point();
+ selection_point.x = get_allocated_width() / 2;
+ selection_point.y = y + (height / 2);
+ } else {
+ selection_point = null;
}
return true;
diff --git a/src/view/week/week-grid.vala b/src/view/week/week-grid.vala
index 6556a02..c2141f4 100644
--- a/src/view/week/week-grid.vala
+++ b/src/view/week/week-grid.vala
@@ -383,10 +383,10 @@ internal class Grid : Gtk.Box {
DayPane day_pane = (DayPane) widget;
Calendar.ExactTimeSpan? selection_span = day_pane.get_selection_span();
- if (selection_span == null)
+ if (selection_span == null || day_pane.selection_point == null)
return Toolkit.PROPAGATE;
- owner.request_create_timed_event(selection_span, widget, point);
+ owner.request_create_timed_event(selection_span, widget, day_pane.selection_point);
return Toolkit.STOP;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]