calendars-page: Introduce GcalCalendarsPage

A page dedicated to show a list of calendars and add
new ones. Other pages will come in soon.
parent b2d27671
......@@ -5,6 +5,7 @@
<file alias="calendar-management-dialog.ui" compressed="true">ui/calendar-management-dialog.ui</file>
<file alias="calendar-popover.ui" compressed="true">ui/calendar-popover.ui</file>
<file alias="calendar-row.ui" compressed="true">ui/calendar-row.ui</file>
<file alias="calendars-page.ui" compressed="true">ui/calendars-page.ui</file>
<file alias="date-chooser.ui" compressed="true">ui/date-chooser.ui</file>
<file alias="date-selector.ui" compressed="true">ui/date-selector.ui</file>
<file alias="edit-dialog.ui" compressed="true">ui/edit-dialog.ui</file>
......
......@@ -85,6 +85,7 @@ resource_data = files(
'ui/alarm-row.ui',
'ui/calendar-management-dialog.ui',
'ui/calendar-row.ui',
'ui/calendars-page.ui',
'ui/date-chooser.ui',
'ui/date-selector.ui',
'ui/edit-dialog.ui',
......
......@@ -20,84 +20,6 @@
<child>
<object class="GtkOverlay" id="overlay">
<property name="visible">True</property>
<child type="overlay">
<object class="GtkRevealer" id="notification">
<property name="visible">True</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="transition_duration">100</property>
<signal name="notify::child-revealed" handler="notification_child_revealed_changed" object="GcalCalendarManagementDialog" swapped="no"/>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<child>
<object class="GtkGrid">
<property name="visible">True</property>
<property name="margin_start">12</property>
<property name="margin_end">12</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="notification_label">
<property name="visible">True</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="notification_action_button">
<property name="label" translatable="yes">Undo</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="no_show_all">True</property>
<signal name="clicked" handler="undo_remove_action" object="GcalCalendarManagementDialog" swapped="no"/>
<style>
<class name="text-button"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="notification_close_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<signal name="clicked" handler="hide_notification" object="GcalCalendarManagementDialog" swapped="yes"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon_name">window-close-symbolic</property>
<property name="icon_size">2</property>
</object>
</child>
<style>
<class name="image-button"/>
</style>
</object>
<packing>
<property name="left_attach">2</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
<style>
<class name="app-notification"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
......@@ -106,110 +28,6 @@
<property name="vhomogeneous">False</property>
<property name="transition_type">crossfade</property>
<signal name="notify::visible-child-name" handler="stack_visible_child_name_changed" object="GcalCalendarManagementDialog" swapped="no"/>
<child>
<object class="GtkScrolledWindow" id="main_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<child>
<object class="GtkViewport" id="main_viewport">
<property name="visible">True</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkBox" id="main_box">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="border_width">18</property>
<property name="orientation">vertical</property>
<property name="spacing">18</property>
<child>
<object class="GtkGrid" id="other_calendars_main_grid">
<property name="visible">True</property>
<property name="row_spacing">12</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="calendars_header_label">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Calendars</property>
<property name="xalign">0</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="other_calendars_main_frame">
<property name="visible">True</property>
<child>
<object class="GtkListBox" id="calendars_listbox">
<property name="visible">True</property>
<property name="selection_mode">none</property>
<signal name="row-activated" handler="calendar_listbox_row_activated" object="GcalCalendarManagementDialog" swapped="no"/>
<style>
<class name="calendars-list"/>
</style>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkMenuButton" id="add_calendar_menu_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<child>
<object class="GtkBox" id="add_calendar_button_box">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="add_calendar_button">
<property name="visible">True</property>
<property name="label" translatable="yes">Add</property>
</object>
</child>
<child>
<object class="GtkImage" id="add_calendar_button_image">
<property name="visible">True</property>
<property name="icon_name">pan-down-symbolic</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">main</property>
<property name="title" translatable="yes">Overview</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="edit_grid">
<property name="visible">True</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GcalCalendarsPage" parent="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkOverlay">
<property name="visible">True</property>
<child type="overlay">
<object class="GtkRevealer" id="notification_revealer">
<property name="visible">True</property>
<property name="halign">center</property>
<property name="valign">start</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="notification_label">
<property name="visible">True</property>
<property name="use_markup">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="notification_action_button">
<property name="visible">True</property>
<property name="label" translatable="yes">Undo</property>
<signal name="clicked" handler="on_undo_remove_button_clicked_cb" object="GcalCalendarsPage" swapped="no"/>
</object>
</child>
<child>
<object class="GtkButton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<signal name="clicked" handler="on_close_notification_button_clicked_cb" object="GcalCalendarsPage" swapped="no"/>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon_name">window-close-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
<style>
<class name="app-notification"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="hscrollbar_policy">never</property>
<property name="max_content_height">600</property>
<property name="propagate_natural_height">True</property>
<child>
<object class="GtkFrame">
<property name="visible">True</property>
<property name="margin-top">24</property>
<property name="margin-bottom">24</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<child>
<object class="GtkListBox" id="listbox">
<property name="visible">True</property>
<property name="selection-mode">none</property>
<signal name="row-activated" handler="on_listbox_row_activated_cb" object="GcalCalendarsPage" swapped="no" />
<style>
<class name="calendars-list"/>
</style>
<child>
<object class="GtkListBoxRow" id="add_calendar_row">
<property name="visible">True</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="expand">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="label" translatable="yes">Add Calendar…</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
<!-- Size group for the listbox rows -->
<object class="GtkSizeGroup" id="sizegroup">
<property name="mode">vertical</property>
<widgets>
<widget name="add_calendar_row" />
</widgets>
</object>
</interface>
/* gcal-calendars-page.c
*
* Copyright 2019 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "GcalCalendarsPage"
#include <glib/gi18n.h>
#include "gcal-context.h"
#include "gcal-calendar-management-page.h"
#include "gcal-calendars-page.h"
#include "gcal-debug.h"
#include "gcal-utils.h"
struct _GcalCalendarsPage
{
GtkBox parent;
GtkListBoxRow *add_calendar_row;
GtkListBox *listbox;
GtkLabel *notification_label;
GtkRevealer *notification_revealer;
GtkSizeGroup *sizegroup;
GcalCalendar *removed_calendar;
gint notification_timeout_id;
GcalContext *context;
};
static void gcal_calendar_management_page_iface_init (GcalCalendarManagementPageInterface *iface);
static void on_calendar_color_changed_cb (GcalCalendar *calendar,
GParamSpec *pspec,
GtkImage *icon);
G_DEFINE_TYPE_WITH_CODE (GcalCalendarsPage, gcal_calendars_page, GTK_TYPE_BOX,
G_IMPLEMENT_INTERFACE (GCAL_TYPE_CALENDAR_MANAGEMENT_PAGE,
gcal_calendar_management_page_iface_init))
enum
{
PROP_0,
PROP_CONTEXT,
N_PROPS
};
/*
* Auxiliary methods
*/
static GtkWidget*
make_calendar_row (GcalCalendarsPage *self,
GcalCalendar *calendar)
{
g_autofree gchar *parent_name = NULL;
g_autoptr (GtkBuilder) builder = NULL;
cairo_surface_t *surface;
const GdkRGBA *color;
GcalManager *manager;
GtkWidget *bottom_label;
GtkWidget *top_label;
GtkWidget *icon;
GtkWidget *row;
manager = gcal_context_get_manager (self->context);
get_source_parent_name_color (manager, gcal_calendar_get_source (calendar), &parent_name, NULL);
builder = gtk_builder_new_from_resource ("/org/gnome/calendar/calendar-row.ui");
/*
* Since we're destroying the builder instance before adding
* the row to the listbox, it should be referenced here so
* it isn't destroyed with the GtkBuilder.
*/
row = g_object_ref (GTK_WIDGET (gtk_builder_get_object (builder, "row")));
/* source color icon */
color = gcal_calendar_get_color (calendar);
surface = get_circle_surface_from_color (color, 24);
icon = GTK_WIDGET (gtk_builder_get_object (builder, "icon"));
gtk_image_set_from_surface (GTK_IMAGE (icon), surface);
/* source name label */
top_label = GTK_WIDGET (gtk_builder_get_object (builder, "title"));
gtk_label_set_label (GTK_LABEL (top_label), gcal_calendar_get_name (calendar));
g_object_bind_property (calendar, "name", top_label, "label", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (calendar,
"notify::name",
G_CALLBACK (gtk_list_box_invalidate_sort),
self->listbox);
g_signal_connect_object (calendar,
"notify::color",
G_CALLBACK (on_calendar_color_changed_cb),
icon,
0);
/* parent source name label */
bottom_label = GTK_WIDGET (gtk_builder_get_object (builder, "subtitle"));
gtk_label_set_label (GTK_LABEL (bottom_label), parent_name);
g_clear_pointer (&surface, cairo_surface_destroy);
gtk_size_group_add_widget (self->sizegroup, row);
return row;
}
static void
add_calendar (GcalCalendarsPage *self,
GcalCalendar *calendar)
{
g_autoptr (GList) children = NULL;
GtkWidget *row;
ESource *source;
GList *l;
children = gtk_container_get_children (GTK_CONTAINER (self->listbox));
for (l = children; l; l = l->next)
{
if (g_object_get_data (l->data, "calendar") == calendar)
return;
}
source = gcal_calendar_get_source (calendar);
row = make_calendar_row (self, calendar);
g_object_set_data (G_OBJECT (row), "source", source);
g_object_set_data (G_OBJECT (row), "calendar", calendar);
gtk_container_add (GTK_CONTAINER (self->listbox), row);
}
static void
remove_calendar (GcalCalendarsPage *self,
GcalCalendar *calendar)
{
g_autoptr (GList) children = NULL;
GList *aux;
children = gtk_container_get_children (GTK_CONTAINER (self->listbox));
for (aux = children; aux != NULL; aux = aux->next)
{
GcalCalendar *row_calendar = g_object_get_data (G_OBJECT (aux->data), "calendar");
if (row_calendar && row_calendar == calendar)
{
gtk_widget_destroy (aux->data);
break;
}
}
}
static void
delete_calendar (GcalCalendarsPage *self,
GcalCalendar *calendar)
{
g_autoptr (GError) error = NULL;
ESource *removed_source;
g_assert (calendar != NULL);
removed_source = gcal_calendar_get_source (self->removed_calendar);
/* We don't really want to remove non-removable sources */
if (!e_source_get_removable (removed_source))
return;
/* Enable the source again to remove it's name from disabled list */
gcal_calendar_set_visible (self->removed_calendar, TRUE);
e_source_remove_sync (removed_source, NULL, &error);
if (error != NULL)
{
g_warning ("[source-dialog] Error removing source: %s", error->message);
add_calendar (self, self->removed_calendar);
}
gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
g_clear_handle_id (&self->notification_timeout_id, g_source_remove);
}
/*
* Callbacks
*/
static gint
listbox_sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data)
{
g_autofree gchar *parent_name1 = NULL;
g_autofree gchar *parent_name2 = NULL;
GcalCalendarsPage *self;
GcalManager *manager;
ESource *source1;
ESource *source2;
gint retval;
self = GCAL_CALENDARS_PAGE (user_data);
/* Keep "Add Calendar" row always at the bottom */
if (row1 == self->add_calendar_row)
return 1;
else if (row2 == self->add_calendar_row)
return -1;
manager = gcal_context_get_manager (self->context);
source1 = g_object_get_data (G_OBJECT (row1), "source");
source2 = g_object_get_data (G_OBJECT (row2), "source");
retval = g_ascii_strcasecmp (e_source_get_display_name (source1), e_source_get_display_name (source2));
if (retval != 0)
return retval;
get_source_parent_name_color (manager, source1, &parent_name1, NULL);
get_source_parent_name_color (manager, source2, &parent_name2, NULL);
return g_strcmp0 (parent_name1, parent_name2);
}
static gboolean
remove_calendar_after_delay_cb (gpointer data)
{
GcalCalendarsPage *self = GCAL_CALENDARS_PAGE (data);
g_assert (self->removed_calendar != NULL);
delete_calendar (self, self->removed_calendar);
g_clear_object (&self->removed_calendar);
gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
self->notification_timeout_id = 0;
return G_SOURCE_REMOVE;
}
static void
on_calendar_color_changed_cb (GcalCalendar *calendar,
GParamSpec *pspec,
GtkImage *icon)
{
cairo_surface_t *surface;
const GdkRGBA *color;
color = gcal_calendar_get_color (calendar);
surface = get_circle_surface_from_color (color, 24);
gtk_image_set_from_surface (GTK_IMAGE (icon), surface);
g_clear_pointer (&surface, cairo_surface_destroy);
}
static void
on_close_notification_button_clicked_cb (GcalCalendarsPage *self,
GtkWidget *button)
{
g_assert (self->removed_calendar != NULL);
delete_calendar (self, self->removed_calendar);
g_clear_object (&self->removed_calendar);
gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
g_clear_handle_id (&self->notification_timeout_id, g_source_remove);
}
static void
on_listbox_row_activated_cb (GtkListBox *listbox,
GtkListBoxRow *row,
GcalCalendarsPage *self)
{
GcalCalendarManagementPage *page = GCAL_CALENDAR_MANAGEMENT_PAGE (self);
if (row == self->add_calendar_row)
{
gcal_calendar_management_page_switch_page (page, "new-calendar", NULL);
}
else
{
GcalCalendar *calendar = g_object_get_data (G_OBJECT (row), "calendar");
gcal_calendar_management_page_switch_page (page, "edit-calendar", calendar);
}
}
static void
on_manager_calendar_added_cb (GcalManager *manager,
GcalCalendar *calendar,
GcalCalendarsPage *self)
{
add_calendar (self, calendar);
}
static void
on_manager_calendar_removed_cb (GcalManager *manager,
GcalCalendar *calendar,
GcalCalendarsPage *self)
{
remove_calendar (self, calendar);
}
static void
on_undo_remove_button_clicked_cb (GtkButton *button,
GcalCalendarsPage *self)
{
if (!self->removed_calendar)
return;
gcal_calendar_set_visible (self->removed_calendar, TRUE);
add_calendar (self, self->removed_calendar);
g_clear_object (&self->removed_calendar);
gtk_revealer_set_reveal_child (self->notification_revealer, FALSE);
g_clear_handle_id (&self->notification_timeout_id, g_source_remove);
}
/*
* GcalCalendarManagementPage iface
*/
static const gchar*
gcal_calendars_page_get_name (GcalCalendarManagementPage *page)
{
return "calendars";
}
static const gchar*
gcal_calendars_page_get_title (GcalCalendarManagementPage *page)
{
return _("Manage Calendars");
}
static void
gcal_calendars_page_activate (GcalCalendarManagementPage *page,
gpointer page_data)
{
g_autofree gchar *new_string = NULL;
GcalCalendarsPage *self;
GcalCalendar *calendar;
GCAL_ENTRY;
g_assert (!page_data || GCAL_IS_CALENDAR (page_data));
if (!page_data)
GCAL_RETURN ();
self = GCAL_CALENDARS_PAGE (page);
calendar = GCAL_CALENDAR (page_data);
self->removed_calendar = g_object_ref (calendar);
/* Remove the listbox entry (if any) */
remove_calendar (self, calendar);
/* Update notification label */
new_string = g_markup_printf_escaped (_("Calendar <b>%s</b> removed"),
gcal_calendar_get_name (self->removed_calendar));
gtk_label_set_markup (self->notification_label, new_string);