blob: 23fa8c74d9cb22b61ad5db5273ffb2e475cddcf4 [file] [log] [blame]
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001/*
Stepan Salenikovichbe87d2c2016-01-25 14:14:34 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040018 */
19
20#include "accountview.h"
21
22#include <gtk/gtk.h>
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -040023#include <glib/gi18n.h>
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040024#include <accountmodel.h>
Edric Milaret58e71072016-03-21 12:16:37 -040025#include <codecmodel.h>
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040026#include <protocolmodel.h>
27#include <QtCore/QItemSelectionModel>
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040028#include "models/gtkqtreemodel.h"
Stepan Salenikovichf6078222016-10-03 17:31:16 -040029#include "models/gtkqtreemodel.h"
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040030#include "models/activeitemproxymodel.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040031#include "accountgeneraltab.h"
aviau6aeb4852016-08-18 16:01:09 -040032#include "accountcreationwizard.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040033#include "accountaudiotab.h"
34#include "accountvideotab.h"
Stepan Salenikovichb7e6a732015-05-04 17:13:22 -040035#include "accountadvancedtab.h"
Stepan Salenikovich7a1e71c2015-05-07 11:14:48 -040036#include "accountsecuritytab.h"
aviau6aeb4852016-08-18 16:01:09 -040037#include "accountdevicestab.h"
Stepan Salenikovichbd029582015-03-24 11:00:56 -040038#include "dialogs.h"
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -040039#include <glib/gprintf.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040040#include "utils/models.h"
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -040041#include "accountimportexportview.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040042
aviau6aeb4852016-08-18 16:01:09 -040043static constexpr const char* ACCOUNT_CREATION_WIZARD_VIEW_NAME = "account-creation-wizard";
44
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040045struct _AccountView
46{
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -050047 GtkPaned parent;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040048};
49
50struct _AccountViewClass
51{
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -050052 GtkPanedClass parent_class;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040053};
54
55typedef struct _AccountViewPrivate AccountViewPrivate;
56
57struct _AccountViewPrivate
58{
59 GtkWidget *treeview_account_list;
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -040060 GtkWidget *account_import_view;
61 GtkWidget *account_export_view;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040062 GtkWidget *stack_account;
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040063 GtkWidget *button_remove_account;
64 GtkWidget *button_add_account;
65 GtkWidget *combobox_account_type;
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -040066 GtkWidget *button_import_account;
67 GtkWidget *button_export_account;
aviau69081842016-10-14 14:51:54 -040068 GtkWidget *account_creation_wizard;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040069
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040070 gint current_page; /* keeps track of current notebook page displayed */
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040071
72 ActiveItemProxyModel *active_protocols;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040073 QMetaObject::Connection protocol_selection_changed;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040074};
75
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -050076G_DEFINE_TYPE_WITH_PRIVATE(AccountView, account_view, GTK_TYPE_PANED);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040077
78#define ACCOUNT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ACCOUNT_VIEW_TYPE, AccountViewPrivate))
79
80static void
81account_view_dispose(GObject *object)
82{
83 AccountView *view = ACCOUNT_VIEW(object);
84 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
85
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040086 QObject::disconnect(priv->protocol_selection_changed);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040087
88 G_OBJECT_CLASS(account_view_parent_class)->dispose(object);
89}
90
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040091static void
92account_view_finalize(GObject *object)
93{
94 AccountView *view = ACCOUNT_VIEW(object);
95 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
96
97 delete priv->active_protocols;
98
99 G_OBJECT_CLASS(account_view_parent_class)->finalize(object);
100}
101
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400102static void
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400103update_account_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
104{
105 QModelIndex current = get_index_from_selection(selection);
106 if (current.isValid())
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400107 AccountModel::instance().selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400108 else
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400109 AccountModel::instance().selectionModel()->clearCurrentIndex();
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400110}
111
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400112static GtkWidget *
113create_scrolled_account_view(GtkWidget *account_view)
114{
115 auto scrolled = gtk_scrolled_window_new(NULL, NULL);
116 gtk_container_add(GTK_CONTAINER(scrolled), account_view);
117 return scrolled;
118}
119
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400120static void
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400121tab_selection_changed(G_GNUC_UNUSED GtkNotebook *notebook,
122 G_GNUC_UNUSED GtkWidget *page,
123 guint page_num,
124 AccountView *self)
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400125{
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400126 g_return_if_fail(IS_ACCOUNT_VIEW(self));
127 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400128
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400129 priv->current_page = page_num;
130}
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400131
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400132static void
133account_selection_changed(GtkTreeSelection *selection, AccountView *self)
134{
135 g_return_if_fail(IS_ACCOUNT_VIEW(self));
136 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400137
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400138 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
aviau6aeb4852016-08-18 16:01:09 -0400139 if(IS_ACCOUNT_CREATION_WIZARD(old_view))
140 {
141 /* When using the account creation wizard, The user might create
142 * accounts that will be deleted by the daemon. The fact that the
143 * selection has changed should be ignored. The account creation wizard
144 * should be responsible to remove itself of the stack and then call
145 * account_selection_changed.
146 */
147 return;
148 }
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400149
150 QModelIndex account_idx = get_index_from_selection(selection);
151 if (!account_idx.isValid()) {
152 /* it nothing is slected, simply display something empty */
153 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
154 gtk_widget_show(empty_box);
155 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
156 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400157
158 /* cannot delete nor export accounts */
159 gtk_widget_set_sensitive(priv->button_remove_account, FALSE);
160 gtk_widget_set_sensitive(priv->button_export_account, FALSE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400161 } else {
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400162 Account *account = AccountModel::instance().getAccountByModelIndex(account_idx);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400163
164 /* build new account view */
165 GtkWidget *hbox_account = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
166
167 /* create account notebook */
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400168 auto account_notebook = gtk_notebook_new();
169 gtk_notebook_set_scrollable(GTK_NOTEBOOK(account_notebook), TRUE);
170 gtk_notebook_set_show_border(GTK_NOTEBOOK(account_notebook), FALSE);
171 gtk_box_pack_start(GTK_BOX(hbox_account), account_notebook, TRUE, TRUE, 0);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400172
173 /* customize account view based on account */
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400174 auto general_tab = create_scrolled_account_view(account_general_tab_new(account));
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400175 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400176 general_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400177 gtk_label_new(C_("Account settings", "General")));
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400178 auto audio_tab = create_scrolled_account_view(account_audio_tab_new(account));
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400179 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400180 audio_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400181 gtk_label_new(C_("Account settings", "Audio")));
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400182 auto video_tab = create_scrolled_account_view(account_video_tab_new(account));
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400183 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400184 video_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400185 gtk_label_new(C_("Account settings", "Video")));
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400186 auto advanced_tab = create_scrolled_account_view(account_advanced_tab_new(account));
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400187 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovichb7e6a732015-05-04 17:13:22 -0400188 advanced_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400189 gtk_label_new(C_("Account settings", "Advanced")));
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400190 auto security_tab = create_scrolled_account_view(account_security_tab_new(account));
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400191 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovich7a1e71c2015-05-07 11:14:48 -0400192 security_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400193 gtk_label_new(C_("Account settings", "Security")));
aviau6aeb4852016-08-18 16:01:09 -0400194 auto devices_tab = create_scrolled_account_view(account_devices_tab_new(account));
195 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
196 devices_tab,
197 gtk_label_new(C_("Account settings", "Devices")));
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400198
Stepan Salenikovichad589082016-05-18 13:22:52 -0400199 gtk_widget_show_all(hbox_account);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400200 /* set the tab displayed to the same as the prev account selected */
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400201 gtk_notebook_set_current_page(GTK_NOTEBOOK(account_notebook), priv->current_page);
202 /* now connect to the tab changed signal */
203 g_signal_connect(account_notebook, "switch-page", G_CALLBACK(tab_selection_changed), self);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400204
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400205 /* set the new account view as visible */
206 char *account_view_name = g_strdup_printf("%p_account", account);
207 gtk_stack_add_named(GTK_STACK(priv->stack_account), hbox_account, account_view_name);
208 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), hbox_account);
209 g_free(account_view_name);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400210
211 /* can delete and export accounts */
212 gtk_widget_set_sensitive(priv->button_remove_account, TRUE);
213 gtk_widget_set_sensitive(priv->button_export_account, TRUE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400214 }
215
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400216 /* remove the old view */
217 if (old_view)
218 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400219}
220
221static void
222account_active_toggled(GtkCellRendererToggle *renderer, gchar *path, AccountView *view)
223{
224 g_return_if_fail(IS_ACCOUNT_VIEW(view));
225 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
226
227 /* we want to set it to the opposite of the current value */
228 gboolean toggle = !gtk_cell_renderer_toggle_get_active(renderer);
229
230 /* get iter which was clicked */
231 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
232 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
233 GtkTreeIter iter;
234 gtk_tree_model_get_iter(model, &iter, tree_path);
235
236 /* get qmodelindex from iter and set the model data */
237 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
238 if (idx.isValid()) {
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400239 QVariant alias = idx.data(static_cast<int>(Account::Role::Alias));
Stepan Salenikovichfea84052016-05-13 16:32:51 -0400240 AccountModel::instance().setData(idx, QVariant(toggle), Qt::CheckStateRole);
241 /* save the account to apply the changed state right away */
242 AccountModel::instance().getAccountByModelIndex(idx)->performAction(Account::EditAction::SAVE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400243 }
244}
245
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400246static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400247remove_account_dialog(AccountView *view, Account *account)
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400248{
249 gboolean response = FALSE;
250 GtkWidget *dialog = gtk_message_dialog_new(NULL,
251 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
252 GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400253 _("Are you sure you want to delete account \"%s\"?"),
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400254 account->alias().toLocal8Bit().constData());
255
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400256 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
257
258 /* get parent window so we can center on it */
259 GtkWidget *parent = gtk_widget_get_toplevel(GTK_WIDGET(view));
260 if (gtk_widget_is_toplevel(parent)) {
261 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
262 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
263 }
264
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400265 switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
266 case GTK_RESPONSE_OK:
267 response = TRUE;
268 break;
269 default:
270 response = FALSE;
271 break;
272 }
273
274 gtk_widget_destroy(dialog);
275
276 return response;
277}
278
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400279static gboolean
280save_account(GtkWidget *working_dialog)
281{
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400282 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400283 if (working_dialog)
284 gtk_widget_destroy(working_dialog);
285
286 return G_SOURCE_REMOVE;
287}
288
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400289static void
290remove_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
291{
292 g_return_if_fail(IS_ACCOUNT_VIEW(view));
293 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
294
295 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
296 QModelIndex idx = get_index_from_selection(selection);
297
298 if (idx.isValid()) {
299 /* this is a destructive operation, ask the user if they are sure */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400300 Account *account = AccountModel::instance().getAccountByModelIndex(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400301 if (remove_account_dialog(view, account)) {
302 /* show working dialog in case save operation takes time */
303 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
304 gtk_window_present(GTK_WINDOW(working));
305
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400306 AccountModel::instance().remove(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400307
308 /* now save the time it takes to transition the account view to the new account (300ms)
309 * the save doesn't happen before the "working" dialog is presented
310 * the timeout function should destroy the "working" dialog when done saving
311 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400312 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400313 }
314 }
315}
316
317static void
aviau6aeb4852016-08-18 16:01:09 -0400318hide_account_creation_wizard(AccountView *view)
319{
320 g_return_if_fail(IS_ACCOUNT_VIEW(view));
321 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
322
323 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
324 if (IS_ACCOUNT_CREATION_WIZARD(old_view))
325 {
326 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
327 }
328
329 auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
330 account_selection_changed(selection, view);
331}
332
333static void
334show_account_creation_wizard(AccountView *view)
335{
336 g_return_if_fail(IS_ACCOUNT_VIEW(view));
337 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
338
339 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
340
aviau69081842016-10-14 14:51:54 -0400341 if (!priv->account_creation_wizard)
aviau6aeb4852016-08-18 16:01:09 -0400342 {
aviau69081842016-10-14 14:51:54 -0400343 priv->account_creation_wizard = account_creation_wizard_new(true);
344 g_object_add_weak_pointer(G_OBJECT(priv->account_creation_wizard), (gpointer *)&priv->account_creation_wizard);
aviau6aeb4852016-08-18 16:01:09 -0400345 gtk_stack_add_named(GTK_STACK(priv->stack_account),
aviau69081842016-10-14 14:51:54 -0400346 priv->account_creation_wizard,
aviau6aeb4852016-08-18 16:01:09 -0400347 ACCOUNT_CREATION_WIZARD_VIEW_NAME);
aviau69081842016-10-14 14:51:54 -0400348 g_signal_connect_swapped(priv->account_creation_wizard, "account-creation-completed", G_CALLBACK(hide_account_creation_wizard), view);
349 g_signal_connect_swapped(priv->account_creation_wizard, "account-creation-canceled", G_CALLBACK(hide_account_creation_wizard), view);
aviau6aeb4852016-08-18 16:01:09 -0400350 }
351
aviau69081842016-10-14 14:51:54 -0400352 gtk_widget_show(priv->account_creation_wizard);
aviau6aeb4852016-08-18 16:01:09 -0400353
354 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_account), ACCOUNT_CREATION_WIZARD_VIEW_NAME);
355
356 /* remove the old view */
357 if (old_view)
358 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
359}
360
361static void
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400362add_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
363{
364 g_return_if_fail(IS_ACCOUNT_VIEW(view));
365 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
366
367 GtkTreeIter protocol_iter;
368 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->combobox_account_type), &protocol_iter)) {
369 /* get the qmodelindex of the protocol */
370 GtkTreeModel *protocol_model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->combobox_account_type));
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400371 QModelIndex protocol_idx = gtk_q_tree_model_get_source_idx(
372 GTK_Q_TREE_MODEL(protocol_model),
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400373 &protocol_iter);
374 if (protocol_idx.isValid()) {
375 protocol_idx = priv->active_protocols->mapToSource(protocol_idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400376
aviau6aeb4852016-08-18 16:01:09 -0400377 Account::Protocol protocol = qvariant_cast<Account::Protocol>(protocol_idx.data((int)ProtocolModel::Role::Protocol));
378 if (protocol == Account::Protocol::RING)
379 {
380 show_account_creation_wizard(view);
381 }
382 else
383 {
384 /* show working dialog in case save operation takes time */
385 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
386 gtk_window_present(GTK_WINDOW(working));
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400387
aviau6aeb4852016-08-18 16:01:09 -0400388 AccountModel::instance().add(QString(_("New Account")), protocol_idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400389
aviau6aeb4852016-08-18 16:01:09 -0400390 /* now save after a short timeout to make sure that
391 * the save doesn't happen before the "working" dialog is presented
392 * the timeout function should destroy the "working" dialog when done saving
393 */
394 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)save_account, working, NULL);
395 }
396
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400397 }
398 }
399}
400
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400401static void
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400402state_to_string(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
403 GtkCellRenderer *cell,
404 GtkTreeModel *tree_model,
405 GtkTreeIter *iter,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400406 GtkTreeView *treeview)
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400407{
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400408 // check if this iter is selected
409 gboolean is_selected = FALSE;
410 if (GTK_IS_TREE_VIEW(treeview)) {
411 auto selection = gtk_tree_view_get_selection(treeview);
412 is_selected = gtk_tree_selection_iter_is_selected(selection, iter);
413 }
414
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400415 gchar *display_state = NULL;
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400416
417 /* get account */
418 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
419 if (idx.isValid()) {
420
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400421 auto account = AccountModel::instance().getAccountByModelIndex(idx);
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400422 auto humanState = account->toHumanStateName();
423
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400424 auto escaped_state = g_markup_escape_text(humanState.toUtf8().constData(), -1);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400425 /* we want the color of the status text to be the default color if this iter is
426 * selected so that the treeview is able to invert it against the selection color */
427 if (is_selected) {
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400428 display_state = escaped_state;
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400429 } else {
430 switch (account->registrationState()) {
431 case Account::RegistrationState::READY:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400432 display_state = g_strdup_printf("<span fgcolor=\"green\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400433 break;
434 case Account::RegistrationState::UNREGISTERED:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400435 display_state = g_strdup_printf("<span fgcolor=\"gray\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400436 break;
437 case Account::RegistrationState::TRYING:
aviau6aeb4852016-08-18 16:01:09 -0400438 case Account::RegistrationState::INITIALIZING:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400439 display_state = g_strdup_printf("<span fgcolor=\"orange\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400440 break;
441 case Account::RegistrationState::ERROR:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400442 display_state = g_strdup_printf("<span fgcolor=\"red\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400443 break;
444 case Account::RegistrationState::COUNT__:
445 g_warning("registration state should never be \"count\"");
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400446 display_state = g_strdup_printf("<span fgcolor=\"red\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400447 break;
448 }
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400449 g_free(escaped_state);
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400450 }
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400451 }
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400452
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400453 g_object_set(G_OBJECT(cell), "markup", display_state, NULL);
454 g_free(display_state);
455}
456
457static void
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400458close_import_export_view(AccountView *self)
459{
460 g_return_if_fail(IS_ACCOUNT_VIEW(self));
461 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
462
463 auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
464 account_selection_changed(selection, self);
465}
466
467static void
468import_account(AccountView* self)
469{
470 g_return_if_fail(IS_ACCOUNT_VIEW(self));
471 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
472
473 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
474
475 auto import_account = account_import_view_new();
476 g_signal_connect_swapped(import_account, "import-export-canceled", G_CALLBACK(close_import_export_view), self);
477 g_signal_connect_swapped(import_account, "import-export-completed", G_CALLBACK(close_import_export_view), self);
478 auto scrolled_view = create_scrolled_account_view(import_account);
479 gtk_widget_show_all(scrolled_view);
480 gtk_stack_add_named(GTK_STACK(priv->stack_account), scrolled_view, "import_account");
481 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), scrolled_view);
482
483 /* remove the old view */
484 if (old_view)
485 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
486}
487
488static void
489export_account(AccountView* self)
490{
491 g_return_if_fail(IS_ACCOUNT_VIEW(self));
492 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
493
494 auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
495 auto idx = get_index_from_selection(selection);
496 if (idx.isValid()) {
497 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
498
499 auto account = AccountModel::instance().getAccountByModelIndex(idx);
500 auto export_account = account_export_view_new();
501 g_signal_connect_swapped(export_account, "import-export-canceled", G_CALLBACK(close_import_export_view), self);
502 g_signal_connect_swapped(export_account, "import-export-completed", G_CALLBACK(close_import_export_view), self);
503 GList *account_list = nullptr;
504 account_list = g_list_append(account_list, account);
505 account_export_view_set_accounts(ACCOUNT_IMPORTEXPORT_VIEW(export_account), account_list);
506 g_list_free(account_list);
507 auto scrolled_view = create_scrolled_account_view(export_account);
508 gtk_widget_show_all(scrolled_view);
509 gtk_stack_add_named(GTK_STACK(priv->stack_account), scrolled_view, "export_account");
510 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), scrolled_view);
511
512 /* remove the old view */
513 if (old_view)
514 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
515
516 }
517
518}
519
520static void
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400521account_view_init(AccountView *view)
522{
523 gtk_widget_init_template(GTK_WIDGET(view));
524
525 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
526
527 /* account model */
528 GtkQTreeModel *account_model;
529 GtkCellRenderer *renderer;
530 GtkTreeViewColumn *column;
531
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400532 account_model = gtk_q_tree_model_new(&AccountModel::instance(), 4,
aviau9bbb19b2016-05-16 15:53:44 -0400533 0, Account::Role::Enabled, G_TYPE_BOOLEAN,
534 0, Account::Role::Alias, G_TYPE_STRING,
535 0, Account::Role::Proto, G_TYPE_STRING,
536 0, Account::Role::RegistrationState, G_TYPE_UINT);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400537 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_account_list), GTK_TREE_MODEL(account_model));
538
539 renderer = gtk_cell_renderer_toggle_new();
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400540 column = gtk_tree_view_column_new_with_attributes(C_("Account state column", "Enabled"), renderer, "active", 0, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400541 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
542
543 g_signal_connect(renderer, "toggled", G_CALLBACK(account_active_toggled), view);
544
545 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400546 column = gtk_tree_view_column_new_with_attributes(C_("Account alias (name) column", "Alias"), renderer, "text", 1, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400547 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -0500548 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
549 gtk_tree_view_column_set_expand(column, TRUE);
550 // set a min width so most of the account name is visible
551 g_object_set(G_OBJECT(renderer), "width", 75, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400552
553 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400554 column = gtk_tree_view_column_new_with_attributes(C_("Account status column", "Status"), renderer, "text", 3, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400555 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -0500556 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
557 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400558
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400559 /* the registration state is an enum, we want to display it as a string */
560 gtk_tree_view_column_set_cell_data_func(
561 column,
562 renderer,
563 (GtkTreeCellDataFunc)state_to_string,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400564 priv->treeview_account_list,
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400565 NULL);
566
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400567 /* add an empty box to the account stack initially, otherwise there will
568 * be no cool animation when the first account is selected */
569 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
570 gtk_widget_show(empty_box);
571 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
572 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400573
574 /* populate account type combo box */
575 /* TODO: when to delete this model? */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400576 priv->active_protocols = new ActiveItemProxyModel((QAbstractItemModel *)AccountModel::instance().protocolModel());
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400577
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400578 GtkQTreeModel *protocol_model = gtk_q_tree_model_new(
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400579 (QSortFilterProxyModel *)priv->active_protocols,
580 1,
aviau271bcc22016-05-27 17:25:19 -0400581 0, Qt::DisplayRole, G_TYPE_STRING);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400582
583 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_account_type), GTK_TREE_MODEL(protocol_model));
584
585 renderer = gtk_cell_renderer_text_new();
586 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_account_type), renderer, FALSE);
587 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(priv->combobox_account_type), renderer,
588 "text", 0, NULL);
589
590 /* connect signals to and from the selection model of the account model */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400591 priv->protocol_selection_changed = QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400592 AccountModel::instance().selectionModel(),
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400593 &QItemSelectionModel::currentChanged,
Stepan Salenikovich825bb0a2016-03-11 15:33:52 -0500594 [=](const QModelIndex & current, G_GNUC_UNUSED const QModelIndex & previous) {
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400595 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
596
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400597 /* select the current */
598 if (current.isValid()) {
599 GtkTreeIter new_iter;
600 if (gtk_q_tree_model_source_index_to_iter(account_model, current, &new_iter)) {
601 gtk_tree_selection_select_iter(selection, &new_iter);
602 } else {
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400603 g_warning("SelectionModel of AccountModel changed to invalid QModelIndex?");
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400604 }
605 }
606 }
607 );
608
609 GtkTreeSelection *account_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
610 g_signal_connect(account_selection, "changed", G_CALLBACK(update_account_model_selection), NULL);
611 g_signal_connect(account_selection, "changed", G_CALLBACK(account_selection_changed), view);
612
613 /* select the default protocol */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400614 QModelIndex protocol_idx = AccountModel::instance().protocolModel()->selectionModel()->currentIndex();
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400615 if (protocol_idx.isValid()) {
616 protocol_idx = priv->active_protocols->mapFromSource(protocol_idx);
617 GtkTreeIter protocol_iter;
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400618 if (gtk_q_tree_model_source_index_to_iter(
619 (GtkQTreeModel *)protocol_model,
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400620 protocol_idx,
621 &protocol_iter)) {
622 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(priv->combobox_account_type), &protocol_iter);
623 }
624 }
625
626 /* connect signals to add/remove accounts */
627 g_signal_connect(priv->button_remove_account, "clicked", G_CALLBACK(remove_account), view);
628 g_signal_connect(priv->button_add_account, "clicked", G_CALLBACK(add_account), view);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400629
630 /* signals to import/export acounts */
631 g_signal_connect_swapped(priv->button_import_account, "clicked", G_CALLBACK(import_account), view);
632 g_signal_connect_swapped(priv->button_export_account, "clicked", G_CALLBACK(export_account), view);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400633}
634
635static void
636account_view_class_init(AccountViewClass *klass)
637{
638 G_OBJECT_CLASS(klass)->dispose = account_view_dispose;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400639 G_OBJECT_CLASS(klass)->finalize = account_view_finalize;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400640
641 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
642 "/cx/ring/RingGnome/accountview.ui");
643
644 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, treeview_account_list);
645 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, stack_account);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400646 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_remove_account);
647 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_add_account);
648 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, combobox_account_type);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400649 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_import_account);
650 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_export_account);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400651}
652
653GtkWidget *
654account_view_new(void)
655{
656 return (GtkWidget *)g_object_new(ACCOUNT_VIEW_TYPE, NULL);
657}