Files
evolution/modules/calendar/e-task-shell-content.c
Milan Crha 8752647eca Make Calendar, Memos and Tasks views non-UI-blocking
The Calendar, Memos and Tasks views use to do D-Bus calls to
the backends on the main (UI) thread, which could result in UI
freezes, until the operation was done on the backend (and server)
side. This commit fixes that by invoking the operations in
a dedicated thread. It has few additional advantages too:
- operations can be cancelled
- proper error reporting to a user
- less code duplication between the views for common operations

There had been fixed some performance issues when selecting/unselecting
sources in the source selector as well.
2014-10-06 12:33:03 +02:00

720 lines
21 KiB
C

/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
* Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <glib/gi18n.h>
#include <calendar/gui/comp-util.h>
#include <calendar/gui/e-cal-component-preview.h>
#include <calendar/gui/e-cal-model-tasks.h>
#include "e-cal-base-shell-sidebar.h"
#include "e-task-shell-content.h"
#define E_TASK_SHELL_CONTENT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_TASK_SHELL_CONTENT, ETaskShellContentPrivate))
struct _ETaskShellContentPrivate {
GtkWidget *paned;
GtkWidget *task_table;
GtkWidget *preview_pane;
GtkOrientation orientation;
gchar *current_uid;
guint preview_visible : 1;
};
enum {
PROP_0,
PROP_ORIENTATION,
PROP_PREVIEW_VISIBLE
};
G_DEFINE_DYNAMIC_TYPE_EXTENDED (ETaskShellContent, e_task_shell_content, E_TYPE_CAL_BASE_SHELL_CONTENT, 0,
G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_ORIENTABLE, NULL))
static void
task_shell_content_display_view_cb (ETaskShellContent *task_shell_content,
GalView *gal_view)
{
ETaskTable *task_table;
if (!GAL_IS_VIEW_ETABLE (gal_view))
return;
task_table = e_task_shell_content_get_task_table (task_shell_content);
gal_view_etable_attach_table (
GAL_VIEW_ETABLE (gal_view), E_TABLE (task_table));
}
static void
task_shell_content_table_foreach_cb (gint model_row,
gpointer user_data)
{
ECalModelComponent *comp_data;
icalcomponent *clone;
icalcomponent *vcal;
gchar *string;
struct {
ECalModel *model;
GSList *list;
} *foreach_data = user_data;
comp_data = e_cal_model_get_component_at (
foreach_data->model, model_row);
vcal = e_cal_util_new_top_level ();
clone = icalcomponent_new_clone (comp_data->icalcomp);
e_cal_util_add_timezones_from_component (vcal, comp_data->icalcomp);
icalcomponent_add_component (vcal, clone);
/* String is owned by libical; do not free. */
string = icalcomponent_as_ical_string (vcal);
if (string != NULL) {
ESource *source;
const gchar *source_uid;
source = e_client_get_source (E_CLIENT (comp_data->client));
source_uid = e_source_get_uid (source);
foreach_data->list = g_slist_prepend (
foreach_data->list,
g_strdup_printf ("%s\n%s", source_uid, string));
}
icalcomponent_free (vcal);
}
static void
task_shell_content_table_drag_data_get_cb (ETaskShellContent *task_shell_content,
gint row,
gint col,
GdkDragContext *context,
GtkSelectionData *selection_data,
guint info,
guint time)
{
ETaskTable *task_table;
GdkAtom target;
struct {
ECalModel *model;
GSList *list;
} foreach_data;
/* Sanity check the selection target. */
target = gtk_selection_data_get_target (selection_data);
if (!e_targets_include_calendar (&target, 1))
return;
task_table = e_task_shell_content_get_task_table (task_shell_content);
foreach_data.model = e_task_table_get_model (task_table);
foreach_data.list = NULL;
e_table_selected_row_foreach (
E_TABLE (task_table),
task_shell_content_table_foreach_cb,
&foreach_data);
if (foreach_data.list != NULL) {
cal_comp_selection_set_string_list (
selection_data, foreach_data.list);
g_slist_foreach (foreach_data.list, (GFunc) g_free, NULL);
g_slist_free (foreach_data.list);
}
}
static void
task_shell_content_table_drag_data_delete_cb (ETaskShellContent *task_shell_content,
gint row,
gint col,
GdkDragContext *context)
{
/* Moved components are deleted from source immediately when moved,
* because some of them can be part of destination source, and we
* don't want to delete not-moved tasks. There is no such information
* which event has been moved and which not, so skip this method. */
}
static void
task_shell_content_cursor_change_cb (ETaskShellContent *task_shell_content,
gint row,
ETable *table)
{
ECalComponentPreview *task_preview;
ECalModel *task_model;
ECalModelComponent *comp_data;
EPreviewPane *preview_pane;
EWebView *web_view;
const gchar *uid;
task_model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (task_shell_content));
preview_pane = e_task_shell_content_get_preview_pane (task_shell_content);
web_view = e_preview_pane_get_web_view (preview_pane);
task_preview = E_CAL_COMPONENT_PREVIEW (web_view);
if (e_table_selected_count (table) != 1) {
if (task_shell_content->priv->preview_visible)
e_cal_component_preview_clear (task_preview);
return;
}
row = e_table_get_cursor_row (table);
comp_data = e_cal_model_get_component_at (task_model, row);
if (task_shell_content->priv->preview_visible) {
ECalComponent *comp;
comp = e_cal_component_new_from_icalcomponent (
icalcomponent_new_clone (comp_data->icalcomp));
e_cal_component_preview_display (
task_preview, comp_data->client, comp,
e_cal_model_get_timezone (task_model),
e_cal_model_get_use_24_hour_format (task_model));
g_object_unref (comp);
}
uid = icalcomponent_get_uid (comp_data->icalcomp);
g_free (task_shell_content->priv->current_uid);
task_shell_content->priv->current_uid = g_strdup (uid);
}
static void
task_shell_content_selection_change_cb (ETaskShellContent *task_shell_content,
ETable *table)
{
ECalComponentPreview *task_preview;
EPreviewPane *preview_pane;
EWebView *web_view;
preview_pane = e_task_shell_content_get_preview_pane (task_shell_content);
web_view = e_preview_pane_get_web_view (preview_pane);
task_preview = E_CAL_COMPONENT_PREVIEW (web_view);
if (e_table_selected_count (table) != 1)
e_cal_component_preview_clear (task_preview);
}
static void
task_shell_content_model_row_changed_cb (ETaskShellContent *task_shell_content,
gint row,
ETableModel *model)
{
ECalModelComponent *comp_data;
ETaskTable *task_table;
const gchar *current_uid;
const gchar *uid;
current_uid = task_shell_content->priv->current_uid;
if (current_uid == NULL)
return;
comp_data = e_cal_model_get_component_at (E_CAL_MODEL (model), row);
if (comp_data == NULL)
return;
uid = icalcomponent_get_uid (comp_data->icalcomp);
if (g_strcmp0 (uid, current_uid) != 0)
return;
task_table = e_task_shell_content_get_task_table (task_shell_content);
task_shell_content_cursor_change_cb (
task_shell_content, 0, E_TABLE (task_table));
}
static void
task_shell_content_is_editing_changed_cb (ETaskTable *task_table,
GParamSpec *param,
EShellView *shell_view)
{
g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
e_shell_view_update_actions (shell_view);
}
static guint32
task_shell_content_check_state (EShellContent *shell_content)
{
ETaskShellContent *task_shell_content;
ETaskTable *task_table;
GSList *list, *iter;
gboolean assignable = TRUE;
gboolean editable = TRUE;
gboolean has_url = FALSE;
gint n_selected;
gint n_complete = 0;
gint n_incomplete = 0;
guint32 state = 0;
task_shell_content = E_TASK_SHELL_CONTENT (shell_content);
task_table = e_task_shell_content_get_task_table (task_shell_content);
n_selected = e_table_selected_count (E_TABLE (task_table));
list = e_task_table_get_selected (task_table);
for (iter = list; iter != NULL; iter = iter->next) {
ECalModelComponent *comp_data = iter->data;
icalproperty *prop;
const gchar *cap;
gboolean read_only;
if (!comp_data)
continue;
read_only = e_client_is_readonly (E_CLIENT (comp_data->client));
editable &= !read_only;
cap = CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT;
if (e_client_check_capability (E_CLIENT (comp_data->client), cap))
assignable = FALSE;
cap = CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK;
if (e_client_check_capability (E_CLIENT (comp_data->client), cap))
assignable = FALSE;
prop = icalcomponent_get_first_property (
comp_data->icalcomp, ICAL_URL_PROPERTY);
has_url |= (prop != NULL);
prop = icalcomponent_get_first_property (
comp_data->icalcomp, ICAL_COMPLETED_PROPERTY);
if (prop != NULL)
n_complete++;
else
n_incomplete++;
}
g_slist_free (list);
if (n_selected == 1)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_SINGLE;
if (n_selected > 1)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_MULTIPLE;
if (assignable)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_CAN_ASSIGN;
if (editable)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_IS_EDITABLE;
if (n_complete > 0)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_HAS_COMPLETE;
if (n_incomplete > 0)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_HAS_INCOMPLETE;
if (has_url)
state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_HAS_URL;
return state;
}
static void
task_shell_content_focus_search_results (EShellContent *shell_content)
{
ETaskShellContent *task_shell_content;
task_shell_content = E_TASK_SHELL_CONTENT (shell_content);
gtk_widget_grab_focus (task_shell_content->priv->task_table);
}
static GtkOrientation
task_shell_content_get_orientation (ETaskShellContent *task_shell_content)
{
return task_shell_content->priv->orientation;
}
static void
task_shell_content_set_orientation (ETaskShellContent *task_shell_content,
GtkOrientation orientation)
{
if (task_shell_content->priv->orientation == orientation)
return;
task_shell_content->priv->orientation = orientation;
g_object_notify (G_OBJECT (task_shell_content), "orientation");
}
static void
task_shell_content_view_created (ECalBaseShellContent *cal_base_shell_content)
{
ETaskShellContent *task_shell_content;
EShellView *shell_view;
GalViewInstance *view_instance;
GSettings *settings;
task_shell_content = E_TASK_SHELL_CONTENT (cal_base_shell_content);
shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (task_shell_content));
/* Bind GObject properties to settings keys. */
settings = g_settings_new ("org.gnome.evolution.calendar");
g_settings_bind (
settings, "task-hpane-position",
task_shell_content->priv->paned, "hposition",
G_SETTINGS_BIND_DEFAULT);
g_settings_bind (
settings, "task-vpane-position",
task_shell_content->priv->paned, "vposition",
G_SETTINGS_BIND_DEFAULT);
g_object_unref (settings);
/* Finally load the view instance */
view_instance = e_shell_view_get_view_instance (shell_view);
gal_view_instance_load (view_instance);
/* Show everything known by default */
e_cal_model_set_time_range (e_cal_base_shell_content_get_model (cal_base_shell_content), 0, 0);
}
static void
task_shell_content_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ORIENTATION:
task_shell_content_set_orientation (
E_TASK_SHELL_CONTENT (object),
g_value_get_enum (value));
return;
case PROP_PREVIEW_VISIBLE:
e_task_shell_content_set_preview_visible (
E_TASK_SHELL_CONTENT (object),
g_value_get_boolean (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
task_shell_content_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ORIENTATION:
g_value_set_enum (
value,
task_shell_content_get_orientation (
E_TASK_SHELL_CONTENT (object)));
return;
case PROP_PREVIEW_VISIBLE:
g_value_set_boolean (
value,
e_task_shell_content_get_preview_visible (
E_TASK_SHELL_CONTENT (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
task_shell_content_dispose (GObject *object)
{
ETaskShellContent *task_shell_content = E_TASK_SHELL_CONTENT (object);
g_clear_object (&task_shell_content->priv->paned);
g_clear_object (&task_shell_content->priv->task_table);
g_clear_object (&task_shell_content->priv->preview_pane);
g_free (task_shell_content->priv->current_uid);
task_shell_content->priv->current_uid = NULL;
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_task_shell_content_parent_class)->dispose (object);
}
static void
task_shell_content_constructed (GObject *object)
{
ETaskShellContent *task_shell_content;
EShellView *shell_view;
EShellContent *shell_content;
EShellTaskbar *shell_taskbar;
ECalModel *model;
GalViewInstance *view_instance;
GtkTargetList *target_list;
GtkTargetEntry *targets;
GtkWidget *container;
GtkWidget *widget;
gint n_targets;
task_shell_content = E_TASK_SHELL_CONTENT (object);
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_task_shell_content_parent_class)->constructed (object);
model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (task_shell_content));
shell_content = E_SHELL_CONTENT (object);
shell_view = e_shell_content_get_shell_view (shell_content);
shell_taskbar = e_shell_view_get_shell_taskbar (shell_view);
/* Build content widgets. */
container = GTK_WIDGET (object);
widget = e_paned_new (GTK_ORIENTATION_VERTICAL);
gtk_container_add (GTK_CONTAINER (container), widget);
task_shell_content->priv->paned = g_object_ref (widget);
gtk_widget_show (widget);
g_object_bind_property (
object, "orientation",
widget, "orientation",
G_BINDING_SYNC_CREATE);
container = task_shell_content->priv->paned;
widget = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (
GTK_SCROLLED_WINDOW (widget),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (
GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
gtk_paned_pack1 (GTK_PANED (container), widget, TRUE, FALSE);
gtk_widget_show (widget);
container = widget;
widget = e_task_table_new (shell_view, model);
gtk_container_add (GTK_CONTAINER (container), widget);
task_shell_content->priv->task_table = g_object_ref (widget);
gtk_widget_show (widget);
container = task_shell_content->priv->paned;
widget = e_cal_component_preview_new ();
gtk_widget_show (widget);
g_signal_connect_swapped (
widget, "status-message",
G_CALLBACK (e_shell_taskbar_set_message),
shell_taskbar);
widget = e_preview_pane_new (E_WEB_VIEW (widget));
gtk_paned_pack2 (GTK_PANED (container), widget, FALSE, FALSE);
task_shell_content->priv->preview_pane = g_object_ref (widget);
gtk_widget_show (widget);
g_object_bind_property (
object, "preview-visible",
widget, "visible",
G_BINDING_SYNC_CREATE);
target_list = gtk_target_list_new (NULL, 0);
e_target_list_add_calendar_targets (target_list, 0);
targets = gtk_target_table_new_from_list (target_list, &n_targets);
e_table_drag_source_set (
E_TABLE (task_shell_content->priv->task_table),
GDK_BUTTON1_MASK, targets, n_targets,
GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_ASK);
gtk_target_table_free (targets, n_targets);
gtk_target_list_unref (target_list);
g_signal_connect_swapped (
task_shell_content->priv->task_table, "table-drag-data-get",
G_CALLBACK (task_shell_content_table_drag_data_get_cb),
object);
g_signal_connect_swapped (
task_shell_content->priv->task_table, "table-drag-data-delete",
G_CALLBACK (task_shell_content_table_drag_data_delete_cb),
object);
g_signal_connect_swapped (
task_shell_content->priv->task_table, "cursor-change",
G_CALLBACK (task_shell_content_cursor_change_cb),
object);
g_signal_connect_swapped (
task_shell_content->priv->task_table, "selection-change",
G_CALLBACK (task_shell_content_selection_change_cb),
object);
e_signal_connect_notify (
task_shell_content->priv->task_table, "notify::is-editing",
G_CALLBACK (task_shell_content_is_editing_changed_cb), shell_view);
g_signal_connect_swapped (
model, "model-row-changed",
G_CALLBACK (task_shell_content_model_row_changed_cb), object);
/* Prepare the view instance. */
view_instance = e_shell_view_new_view_instance (shell_view, NULL);
g_signal_connect_swapped (
view_instance, "display-view",
G_CALLBACK (task_shell_content_display_view_cb),
object);
e_shell_view_set_view_instance (shell_view, view_instance);
g_object_unref (view_instance);
}
static void
e_task_shell_content_class_init (ETaskShellContentClass *class)
{
GObjectClass *object_class;
EShellContentClass *shell_content_class;
ECalBaseShellContentClass *cal_base_shell_content_class;
g_type_class_add_private (class, sizeof (ETaskShellContentPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = task_shell_content_set_property;
object_class->get_property = task_shell_content_get_property;
object_class->dispose = task_shell_content_dispose;
object_class->constructed = task_shell_content_constructed;
shell_content_class = E_SHELL_CONTENT_CLASS (class);
shell_content_class->check_state = task_shell_content_check_state;
shell_content_class->focus_search_results = task_shell_content_focus_search_results;
cal_base_shell_content_class = E_CAL_BASE_SHELL_CONTENT_CLASS (class);
cal_base_shell_content_class->new_cal_model = e_cal_model_tasks_new;
cal_base_shell_content_class->view_created = task_shell_content_view_created;
g_object_class_install_property (
object_class,
PROP_PREVIEW_VISIBLE,
g_param_spec_boolean (
"preview-visible",
"Preview is Visible",
"Whether the preview pane is visible",
TRUE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_override_property (
object_class, PROP_ORIENTATION, "orientation");
}
static void
e_task_shell_content_class_finalize (ETaskShellContentClass *class)
{
}
static void
e_task_shell_content_init (ETaskShellContent *task_shell_content)
{
task_shell_content->priv = E_TASK_SHELL_CONTENT_GET_PRIVATE (task_shell_content);
/* Postpone widget construction until we have a shell view. */
}
void
e_task_shell_content_type_register (GTypeModule *type_module)
{
/* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
* function, so we have to wrap it with a public function in
* order to register types from a separate compilation unit. */
e_task_shell_content_register_type (type_module);
}
GtkWidget *
e_task_shell_content_new (EShellView *shell_view)
{
g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
return g_object_new (
E_TYPE_TASK_SHELL_CONTENT,
"shell-view", shell_view, NULL);
}
ETaskTable *
e_task_shell_content_get_task_table (ETaskShellContent *task_shell_content)
{
g_return_val_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
return E_TASK_TABLE (task_shell_content->priv->task_table);
}
EPreviewPane *
e_task_shell_content_get_preview_pane (ETaskShellContent *task_shell_content)
{
g_return_val_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
return E_PREVIEW_PANE (task_shell_content->priv->preview_pane);
}
gboolean
e_task_shell_content_get_preview_visible (ETaskShellContent *task_shell_content)
{
g_return_val_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content), FALSE);
return task_shell_content->priv->preview_visible;
}
void
e_task_shell_content_set_preview_visible (ETaskShellContent *task_shell_content,
gboolean preview_visible)
{
g_return_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content));
if (task_shell_content->priv->preview_visible == preview_visible)
return;
task_shell_content->priv->preview_visible = preview_visible;
if (preview_visible && task_shell_content->priv->preview_pane) {
task_shell_content_cursor_change_cb (
task_shell_content, 0,
E_TABLE (task_shell_content->priv->task_table));
}
g_object_notify (G_OBJECT (task_shell_content), "preview-visible");
}
EShellSearchbar *
e_task_shell_content_get_searchbar (ETaskShellContent *task_shell_content)
{
EShellView *shell_view;
EShellContent *shell_content;
GtkWidget *widget;
g_return_val_if_fail (E_IS_TASK_SHELL_CONTENT (task_shell_content), NULL);
shell_content = E_SHELL_CONTENT (task_shell_content);
shell_view = e_shell_content_get_shell_view (shell_content);
widget = e_shell_view_get_searchbar (shell_view);
return E_SHELL_SEARCHBAR (widget);
}