blob: a8bc243e9b1eef9fbb7d32b24d0bfee9d8d4b898 [file] [log] [blame]
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001/*
Guillaume Roguez77c579d2018-01-30 15:54:02 -05002 * Copyright (C) 2015-2018 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>
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040025#include <protocolmodel.h>
26#include <QtCore/QItemSelectionModel>
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040027#include "models/gtkqtreemodel.h"
Stepan Salenikovichf6078222016-10-03 17:31:16 -040028#include "models/gtkqtreemodel.h"
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040029#include "models/activeitemproxymodel.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040030#include "accountgeneraltab.h"
aviau6aeb4852016-08-18 16:01:09 -040031#include "accountcreationwizard.h"
Stepan Salenikovichb7e6a732015-05-04 17:13:22 -040032#include "accountadvancedtab.h"
Stepan Salenikovich7a1e71c2015-05-07 11:14:48 -040033#include "accountsecuritytab.h"
aviau6aeb4852016-08-18 16:01:09 -040034#include "accountdevicestab.h"
Stepan Salenikovichbd029582015-03-24 11:00:56 -040035#include "dialogs.h"
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -040036#include <glib/gprintf.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040037#include "utils/models.h"
Nicolas Jager005fc552017-04-26 07:50:26 -040038#include "accountbanstab.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040039
Sébastien Blin276202f2018-05-11 09:37:34 -040040#include <api/newaccountmodel.h>
41
aviau6aeb4852016-08-18 16:01:09 -040042static constexpr const char* ACCOUNT_CREATION_WIZARD_VIEW_NAME = "account-creation-wizard";
43
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040044struct _AccountView
45{
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -050046 GtkPaned parent;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040047};
48
49struct _AccountViewClass
50{
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -050051 GtkPanedClass parent_class;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040052};
53
54typedef struct _AccountViewPrivate AccountViewPrivate;
55
56struct _AccountViewPrivate
57{
Sébastien Blin276202f2018-05-11 09:37:34 -040058 AccountInfoPointer const *accountInfo_ = nullptr;
59 AccountInfoPointer selectedInfo_ = nullptr;
60
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040061 GtkWidget *treeview_account_list;
62 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;
aviau69081842016-10-14 14:51:54 -040066 GtkWidget *account_creation_wizard;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040067
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040068 gint current_page; /* keeps track of current notebook page displayed */
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040069
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040070 QMetaObject::Connection protocol_selection_changed;
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -040071 QMetaObject::Connection account_selection_changed;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040072};
73
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -050074G_DEFINE_TYPE_WITH_PRIVATE(AccountView, account_view, GTK_TYPE_PANED);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040075
76#define ACCOUNT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ACCOUNT_VIEW_TYPE, AccountViewPrivate))
77
78static void
79account_view_dispose(GObject *object)
80{
81 AccountView *view = ACCOUNT_VIEW(object);
82 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
83
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040084 QObject::disconnect(priv->protocol_selection_changed);
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -040085 QObject::disconnect(priv->account_selection_changed);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040086
87 G_OBJECT_CLASS(account_view_parent_class)->dispose(object);
88}
89
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040090static void
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040091update_account_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
92{
93 QModelIndex current = get_index_from_selection(selection);
94 if (current.isValid())
Guillaume Roguez5d1514b2015-10-22 15:55:31 -040095 AccountModel::instance().selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040096 else
Guillaume Roguez5d1514b2015-10-22 15:55:31 -040097 AccountModel::instance().selectionModel()->clearCurrentIndex();
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040098}
99
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400100static GtkWidget *
101create_scrolled_account_view(GtkWidget *account_view)
102{
103 auto scrolled = gtk_scrolled_window_new(NULL, NULL);
104 gtk_container_add(GTK_CONTAINER(scrolled), account_view);
105 return scrolled;
106}
107
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400108static void
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400109tab_selection_changed(G_GNUC_UNUSED GtkNotebook *notebook,
110 G_GNUC_UNUSED GtkWidget *page,
111 guint page_num,
112 AccountView *self)
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400113{
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400114 g_return_if_fail(IS_ACCOUNT_VIEW(self));
115 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400116
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400117 priv->current_page = page_num;
118}
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400119
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400120static void
121account_selection_changed(GtkTreeSelection *selection, AccountView *self)
122{
123 g_return_if_fail(IS_ACCOUNT_VIEW(self));
124 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(self);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400125
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400126 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
aviau6aeb4852016-08-18 16:01:09 -0400127 if(IS_ACCOUNT_CREATION_WIZARD(old_view))
128 {
Hugo Lefeuvre24356d42018-05-11 10:41:41 -0400129 // cancel account creation, this will call hide_account_creation_wizard via callback
130 account_creation_wizard_cancel(ACCOUNT_CREATION_WIZARD(old_view));
aviau6aeb4852016-08-18 16:01:09 -0400131 return;
132 }
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400133
134 QModelIndex account_idx = get_index_from_selection(selection);
135 if (!account_idx.isValid()) {
Hugo Lefeuvre24356d42018-05-11 10:41:41 -0400136 g_debug("accountview: invalid account selection");
137
138 /* If no account is selected, check for account list size. If not 0, select
139 the first account. */
140 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
141 if (gtk_tree_model_iter_n_children(model, NULL)) {
142 g_debug("accountview: other accounts are available, selecting first one");
143 GtkTreePath *path = gtk_tree_path_new_from_indices(0, -1);
144 gtk_tree_selection_select_path(gtk_tree_view_get_selection (GTK_TREE_VIEW(priv->treeview_account_list)), path);
145 gtk_tree_path_free(path);
146 return;
147 }
148
149 /* there is no account to select, display empty */
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400150 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
151 gtk_widget_show(empty_box);
152 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
153 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400154
Sébastien Blin1f92b4a2018-02-28 11:39:11 -0500155 /* cannot delete accounts */
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400156 gtk_widget_set_sensitive(priv->button_remove_account, FALSE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400157 } else {
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400158 Account *account = AccountModel::instance().getAccountByModelIndex(account_idx);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400159
160 /* build new account view */
161 GtkWidget *hbox_account = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
aviaub135a692016-12-12 14:41:01 -0500162 gtk_widget_show(hbox_account);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400163
164 /* create account notebook */
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400165 auto account_notebook = gtk_notebook_new();
aviaub135a692016-12-12 14:41:01 -0500166 gtk_widget_show(account_notebook);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400167 gtk_notebook_set_scrollable(GTK_NOTEBOOK(account_notebook), TRUE);
168 gtk_notebook_set_show_border(GTK_NOTEBOOK(account_notebook), FALSE);
169 gtk_box_pack_start(GTK_BOX(hbox_account), account_notebook, TRUE, TRUE, 0);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400170
Sébastien Blin276202f2018-05-11 09:37:34 -0400171 // Build a new AccountInfoPointer pointing on selected account in this view, not in the app
172 // TODO in the future, get rid of the Account class (pass the selected id)
173 auto selectedId = account->id().toStdString();
Sébastien Bline05cde12018-05-14 16:21:14 -0400174 try {
175 priv->selectedInfo_ = &(*priv->accountInfo_)->accountModel->getAccountInfo(selectedId);
176 } catch (std::out_of_range& e) {
177 g_debug("Can't load account %s", selectedId.c_str());
178 return;
179 }
Sébastien Blin276202f2018-05-11 09:37:34 -0400180
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400181 /* customize account view based on account */
Sébastien Blin276202f2018-05-11 09:37:34 -0400182 auto general_tab = create_scrolled_account_view(account_general_tab_new(account, priv->selectedInfo_));
aviaub135a692016-12-12 14:41:01 -0500183 gtk_widget_show(general_tab);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400184 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400185 general_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400186 gtk_label_new(C_("Account settings", "General")));
aviau2a231832016-12-12 12:27:38 -0500187
aviau9e4e84d2016-12-12 12:41:54 -0500188 if (account->protocol() == Account::Protocol::RING)
189 {
190 auto devices_tab = create_scrolled_account_view(account_devices_tab_new(account));
aviaub135a692016-12-12 14:41:01 -0500191 gtk_widget_show(devices_tab);
aviau9e4e84d2016-12-12 12:41:54 -0500192 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
193 devices_tab,
194 gtk_label_new(C_("Account settings", "Devices")));
195 }
Stepan Salenikovichd45e6392015-08-11 10:16:35 -0400196 auto security_tab = create_scrolled_account_view(account_security_tab_new(account));
aviaub135a692016-12-12 14:41:01 -0500197 gtk_widget_show(security_tab);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400198 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
Stepan Salenikovich7a1e71c2015-05-07 11:14:48 -0400199 security_tab,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400200 gtk_label_new(C_("Account settings", "Security")));
aviau2a231832016-12-12 12:27:38 -0500201 auto advanced_tab = create_scrolled_account_view(account_advanced_tab_new(account));
aviaub135a692016-12-12 14:41:01 -0500202 gtk_widget_show(advanced_tab);
aviau6aeb4852016-08-18 16:01:09 -0400203 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
aviau2a231832016-12-12 12:27:38 -0500204 advanced_tab,
205 gtk_label_new(C_("Account settings", "Advanced")));
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400206
Nicolas Jager005fc552017-04-26 07:50:26 -0400207 if (account->protocol() == Account::Protocol::RING) {
208 auto banned_tab = create_scrolled_account_view(account_bans_tab_new(account));
209 gtk_widget_show(banned_tab);
210 gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook),
211 banned_tab,
212 gtk_label_new(C_("List of banned peers", "Bans")));
213 }
214
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400215 /* set the tab displayed to the same as the prev account selected */
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400216 gtk_notebook_set_current_page(GTK_NOTEBOOK(account_notebook), priv->current_page);
217 /* now connect to the tab changed signal */
218 g_signal_connect(account_notebook, "switch-page", G_CALLBACK(tab_selection_changed), self);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400219
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400220 /* set the new account view as visible */
221 char *account_view_name = g_strdup_printf("%p_account", account);
222 gtk_stack_add_named(GTK_STACK(priv->stack_account), hbox_account, account_view_name);
223 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), hbox_account);
224 g_free(account_view_name);
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400225
Sébastien Blin1f92b4a2018-02-28 11:39:11 -0500226 /* can delete accounts */
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400227 gtk_widget_set_sensitive(priv->button_remove_account, TRUE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400228 }
229
Stepan Salenikovichbdb0ecd2016-05-18 13:30:55 -0400230 /* remove the old view */
231 if (old_view)
232 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400233}
234
235static void
236account_active_toggled(GtkCellRendererToggle *renderer, gchar *path, AccountView *view)
237{
238 g_return_if_fail(IS_ACCOUNT_VIEW(view));
239 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
240
241 /* we want to set it to the opposite of the current value */
242 gboolean toggle = !gtk_cell_renderer_toggle_get_active(renderer);
243
244 /* get iter which was clicked */
245 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
246 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
247 GtkTreeIter iter;
248 gtk_tree_model_get_iter(model, &iter, tree_path);
249
250 /* get qmodelindex from iter and set the model data */
251 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
252 if (idx.isValid()) {
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400253 QVariant alias = idx.data(static_cast<int>(Account::Role::Alias));
Stepan Salenikovichfea84052016-05-13 16:32:51 -0400254 AccountModel::instance().setData(idx, QVariant(toggle), Qt::CheckStateRole);
255 /* save the account to apply the changed state right away */
256 AccountModel::instance().getAccountByModelIndex(idx)->performAction(Account::EditAction::SAVE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400257 }
258}
259
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400260static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400261remove_account_dialog(AccountView *view, Account *account)
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400262{
263 gboolean response = FALSE;
264 GtkWidget *dialog = gtk_message_dialog_new(NULL,
265 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
266 GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400267 _("Are you sure you want to delete account \"%s\"?"),
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400268 account->alias().toLocal8Bit().constData());
269
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400270 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
271
272 /* get parent window so we can center on it */
273 GtkWidget *parent = gtk_widget_get_toplevel(GTK_WIDGET(view));
274 if (gtk_widget_is_toplevel(parent)) {
275 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
276 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
277 }
278
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400279 switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
280 case GTK_RESPONSE_OK:
281 response = TRUE;
282 break;
283 default:
284 response = FALSE;
285 break;
286 }
287
288 gtk_widget_destroy(dialog);
289
290 return response;
291}
292
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400293static gboolean
294save_account(GtkWidget *working_dialog)
295{
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400296 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400297 if (working_dialog)
298 gtk_widget_destroy(working_dialog);
299
300 return G_SOURCE_REMOVE;
301}
302
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400303static void
304remove_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
305{
306 g_return_if_fail(IS_ACCOUNT_VIEW(view));
307 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
308
309 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
310 QModelIndex idx = get_index_from_selection(selection);
311
312 if (idx.isValid()) {
313 /* this is a destructive operation, ask the user if they are sure */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400314 Account *account = AccountModel::instance().getAccountByModelIndex(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400315 if (remove_account_dialog(view, account)) {
316 /* show working dialog in case save operation takes time */
317 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
318 gtk_window_present(GTK_WINDOW(working));
319
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400320 AccountModel::instance().remove(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400321
322 /* now save the time it takes to transition the account view to the new account (300ms)
323 * the save doesn't happen before the "working" dialog is presented
324 * the timeout function should destroy the "working" dialog when done saving
325 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400326 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400327 }
328 }
329}
330
331static void
aviau6aeb4852016-08-18 16:01:09 -0400332hide_account_creation_wizard(AccountView *view)
333{
334 g_return_if_fail(IS_ACCOUNT_VIEW(view));
335 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
336
337 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
338 if (IS_ACCOUNT_CREATION_WIZARD(old_view))
339 {
Hugo Lefeuvre24356d42018-05-11 10:41:41 -0400340 account_creation_wizard_show_preview(ACCOUNT_CREATION_WIZARD(old_view), FALSE);
aviau6aeb4852016-08-18 16:01:09 -0400341 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
342 }
343
344 auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
345 account_selection_changed(selection, view);
346}
347
348static void
349show_account_creation_wizard(AccountView *view)
350{
351 g_return_if_fail(IS_ACCOUNT_VIEW(view));
352 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
353
354 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
355
aviau69081842016-10-14 14:51:54 -0400356 if (!priv->account_creation_wizard)
aviau6aeb4852016-08-18 16:01:09 -0400357 {
aviau69081842016-10-14 14:51:54 -0400358 priv->account_creation_wizard = account_creation_wizard_new(true);
359 g_object_add_weak_pointer(G_OBJECT(priv->account_creation_wizard), (gpointer *)&priv->account_creation_wizard);
aviau6aeb4852016-08-18 16:01:09 -0400360 gtk_stack_add_named(GTK_STACK(priv->stack_account),
aviau69081842016-10-14 14:51:54 -0400361 priv->account_creation_wizard,
aviau6aeb4852016-08-18 16:01:09 -0400362 ACCOUNT_CREATION_WIZARD_VIEW_NAME);
aviau69081842016-10-14 14:51:54 -0400363 g_signal_connect_swapped(priv->account_creation_wizard, "account-creation-completed", G_CALLBACK(hide_account_creation_wizard), view);
364 g_signal_connect_swapped(priv->account_creation_wizard, "account-creation-canceled", G_CALLBACK(hide_account_creation_wizard), view);
aviau6aeb4852016-08-18 16:01:09 -0400365 }
366
aviau69081842016-10-14 14:51:54 -0400367 gtk_widget_show(priv->account_creation_wizard);
aviau6aeb4852016-08-18 16:01:09 -0400368
369 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_account), ACCOUNT_CREATION_WIZARD_VIEW_NAME);
370
371 /* remove the old view */
372 if (old_view)
373 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_view);
374}
375
376static void
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400377add_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
378{
379 g_return_if_fail(IS_ACCOUNT_VIEW(view));
380 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
381
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -0400382 auto idx = gtk_combo_box_get_index(GTK_COMBO_BOX(priv->combobox_account_type));
383 if (idx.isValid()) {
384 Account::Protocol protocol = qvariant_cast<Account::Protocol>(idx.data((int)ProtocolModel::Role::Protocol));
385 if (protocol == Account::Protocol::RING)
386 {
Hugo Lefeuvre24356d42018-05-11 10:41:41 -0400387 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
388 if (IS_ACCOUNT_CREATION_WIZARD(old_view))
389 {
390 account_creation_wizard_cancel(ACCOUNT_CREATION_WIZARD(old_view));
391 } else {
392 show_account_creation_wizard(view);
393 }
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -0400394 }
395 else
396 {
397 /* show working dialog in case save operation takes time */
398 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
399 gtk_window_present(GTK_WINDOW(working));
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400400
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -0400401 AccountModel::instance().add(QString(_("New Account")), idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400402
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -0400403 /* now save after a short timeout to make sure that
404 * the save doesn't happen before the "working" dialog is presented
405 * the timeout function should destroy the "working" dialog when done saving
406 */
407 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400408 }
409 }
410}
411
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400412static void
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400413state_to_string(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
414 GtkCellRenderer *cell,
415 GtkTreeModel *tree_model,
416 GtkTreeIter *iter,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400417 GtkTreeView *treeview)
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400418{
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400419 // check if this iter is selected
420 gboolean is_selected = FALSE;
421 if (GTK_IS_TREE_VIEW(treeview)) {
422 auto selection = gtk_tree_view_get_selection(treeview);
423 is_selected = gtk_tree_selection_iter_is_selected(selection, iter);
424 }
425
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400426 gchar *display_state = NULL;
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400427
428 /* get account */
429 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
430 if (idx.isValid()) {
431
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400432 auto account = AccountModel::instance().getAccountByModelIndex(idx);
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400433 auto humanState = account->toHumanStateName();
434
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400435 auto escaped_state = g_markup_escape_text(humanState.toUtf8().constData(), -1);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400436 /* we want the color of the status text to be the default color if this iter is
437 * selected so that the treeview is able to invert it against the selection color */
438 if (is_selected) {
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400439 display_state = escaped_state;
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400440 } else {
441 switch (account->registrationState()) {
442 case Account::RegistrationState::READY:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400443 display_state = g_strdup_printf("<span fgcolor=\"green\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400444 break;
445 case Account::RegistrationState::UNREGISTERED:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400446 display_state = g_strdup_printf("<span fgcolor=\"gray\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400447 break;
448 case Account::RegistrationState::TRYING:
aviau6aeb4852016-08-18 16:01:09 -0400449 case Account::RegistrationState::INITIALIZING:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400450 display_state = g_strdup_printf("<span fgcolor=\"orange\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400451 break;
452 case Account::RegistrationState::ERROR:
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400453 display_state = g_strdup_printf("<span fgcolor=\"red\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400454 break;
455 case Account::RegistrationState::COUNT__:
456 g_warning("registration state should never be \"count\"");
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400457 display_state = g_strdup_printf("<span fgcolor=\"red\">%s</span>", escaped_state);
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400458 break;
459 }
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400460 g_free(escaped_state);
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400461 }
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400462 }
Stepan Salenikoviche7a73142015-08-11 11:55:59 -0400463
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400464 g_object_set(G_OBJECT(cell), "markup", display_state, NULL);
465 g_free(display_state);
466}
467
468static void
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400469account_view_init(AccountView *view)
470{
471 gtk_widget_init_template(GTK_WIDGET(view));
472
473 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
474
475 /* account model */
476 GtkQTreeModel *account_model;
477 GtkCellRenderer *renderer;
478 GtkTreeViewColumn *column;
479
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400480 account_model = gtk_q_tree_model_new(&AccountModel::instance(), 4,
aviau9bbb19b2016-05-16 15:53:44 -0400481 0, Account::Role::Enabled, G_TYPE_BOOLEAN,
482 0, Account::Role::Alias, G_TYPE_STRING,
483 0, Account::Role::Proto, G_TYPE_STRING,
484 0, Account::Role::RegistrationState, G_TYPE_UINT);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400485 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_account_list), GTK_TREE_MODEL(account_model));
486
487 renderer = gtk_cell_renderer_toggle_new();
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400488 column = gtk_tree_view_column_new_with_attributes(C_("Account state column", "Enabled"), renderer, "active", 0, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400489 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
490
491 g_signal_connect(renderer, "toggled", G_CALLBACK(account_active_toggled), view);
492
493 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400494 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 -0400495 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -0500496 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
497 gtk_tree_view_column_set_expand(column, TRUE);
498 // set a min width so most of the account name is visible
499 g_object_set(G_OBJECT(renderer), "width", 75, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400500
501 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400502 column = gtk_tree_view_column_new_with_attributes(C_("Account status column", "Status"), renderer, "text", 3, NULL);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400503 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
Stepan Salenikovichab0f5be2016-02-05 15:16:35 -0500504 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
505 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400506
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400507 /* the registration state is an enum, we want to display it as a string */
508 gtk_tree_view_column_set_cell_data_func(
509 column,
510 renderer,
511 (GtkTreeCellDataFunc)state_to_string,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400512 priv->treeview_account_list,
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400513 NULL);
514
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400515 /* add an empty box to the account stack initially, otherwise there will
516 * be no cool animation when the first account is selected */
517 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
518 gtk_widget_show(empty_box);
519 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
520 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400521
522 /* populate account type combo box */
Stepan Salenikovich08c4ab22017-04-26 14:27:24 -0400523 priv->protocol_selection_changed = gtk_combo_box_set_qmodel_text(
524 GTK_COMBO_BOX(priv->combobox_account_type),
525 AccountModel::instance().protocolModel(),
526 AccountModel::instance().protocolModel()->selectionModel()
527 );
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400528
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400529 GtkTreeSelection *account_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
530 g_signal_connect(account_selection, "changed", G_CALLBACK(update_account_model_selection), NULL);
531 g_signal_connect(account_selection, "changed", G_CALLBACK(account_selection_changed), view);
532
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400533 /* connect signals to add/remove accounts */
534 g_signal_connect(priv->button_remove_account, "clicked", G_CALLBACK(remove_account), view);
535 g_signal_connect(priv->button_add_account, "clicked", G_CALLBACK(add_account), view);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400536}
537
538static void
539account_view_class_init(AccountViewClass *klass)
540{
541 G_OBJECT_CLASS(klass)->dispose = account_view_dispose;
542
543 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
544 "/cx/ring/RingGnome/accountview.ui");
545
546 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, treeview_account_list);
547 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, stack_account);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400548 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_remove_account);
549 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_add_account);
550 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, combobox_account_type);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400551}
552
Sébastien Bline05cde12018-05-14 16:21:14 -0400553static void
554build_account_view(AccountView *view) {
555 g_return_if_fail(IS_ACCOUNT_VIEW(view));
556 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
557
558 // NOTE: this is temporary. We still use AccountModel here, so if a new account is added,
559 // we should select it!
560 priv->account_selection_changed = QObject::connect(
561 &*(*priv->accountInfo_)->accountModel,
562 &lrc::api::NewAccountModel::accountAdded,
563 [=] (const std::string& id) {
564 auto idx = 0;
565 auto iterIsCorrect = true;
566 GtkTreeIter iter;
567 auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
568 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
569
570 while (iterIsCorrect) {
571 iterIsCorrect = gtk_tree_model_iter_nth_child(model, &iter, nullptr, idx);
572 if (!iterIsCorrect)
573 break;
574 QModelIndex accountIdx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
575 if (accountIdx.isValid()) {
576 auto account = AccountModel::instance().getAccountByModelIndex(accountIdx);
577 if (account->id().toStdString() == id)
578 gtk_tree_selection_select_iter(selection, &iter);
579 }
580 idx++;
581 }
582 }
583 );
584}
585
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400586GtkWidget *
Sébastien Blin276202f2018-05-11 09:37:34 -0400587account_view_new(AccountInfoPointer const & accountInfo)
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400588{
Sébastien Blin276202f2018-05-11 09:37:34 -0400589 auto* view = g_object_new(ACCOUNT_VIEW_TYPE, NULL);
590 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
591 priv->accountInfo_ = &accountInfo;
Sébastien Bline05cde12018-05-14 16:21:14 -0400592 if (*priv->accountInfo_)
593 build_account_view(ACCOUNT_VIEW(view));
Sébastien Blin276202f2018-05-11 09:37:34 -0400594 return (GtkWidget *)view;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400595}
Hugo Lefeuvre24356d42018-05-11 10:41:41 -0400596
597void
598account_settings_view_show(AccountView *view, gboolean show) {
599 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
600
601 /* If displayed stack element is still the placeholder element, thats is it hasn't
602 been initialized yet, change account selection. */
603 if (show && strcmp(gtk_stack_get_visible_child_name (GTK_STACK(priv->stack_account)), "placeholder") == 0) {
604 auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
605 account_selection_changed(selection, view);
606 return;
607 }
608
609 /* If account creation wizard is currently open and settings get closed, cancel so
610 the preview is stopped properly. */
611 auto old_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
612 if (!show && IS_ACCOUNT_CREATION_WIZARD(old_view)) {
613 account_creation_wizard_cancel(ACCOUNT_CREATION_WIZARD(old_view));
614 }
615}