blob: 62bebbcafc68e57d31110ee3d45a53702cdc945c [file] [log] [blame]
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001/*
2 * Copyright (C) 2015 Savoir-Faire Linux Inc.
3 * 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.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "accountview.h"
32
33#include <gtk/gtk.h>
34#include <accountmodel.h>
35#include <audio/codecmodel.h>
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040036#include <protocolmodel.h>
37#include <QtCore/QItemSelectionModel>
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040038#include "models/gtkqtreemodel.h"
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040039#include "models/gtkqsortfiltertreemodel.h"
40#include "models/activeitemproxymodel.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040041#include "accountgeneraltab.h"
42#include "accountaudiotab.h"
43#include "accountvideotab.h"
Stepan Salenikovichbd029582015-03-24 11:00:56 -040044#include "dialogs.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040045
46struct _AccountView
47{
48 GtkBox parent;
49};
50
51struct _AccountViewClass
52{
53 GtkBoxClass parent_class;
54};
55
56typedef struct _AccountViewPrivate AccountViewPrivate;
57
58struct _AccountViewPrivate
59{
60 GtkWidget *treeview_account_list;
61 GtkWidget *stack_account;
62 GtkWidget *current_account_notebook;
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040063 GtkWidget *button_remove_account;
64 GtkWidget *button_add_account;
65 GtkWidget *combobox_account_type;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040066
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040067 gint current_page; /* keeps track of current notebook page displayed */
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040068
69 ActiveItemProxyModel *active_protocols;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040070 QMetaObject::Connection protocol_selection_changed;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040071};
72
73G_DEFINE_TYPE_WITH_PRIVATE(AccountView, account_view, GTK_TYPE_BOX);
74
75#define ACCOUNT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ACCOUNT_VIEW_TYPE, AccountViewPrivate))
76
77static void
78account_view_dispose(GObject *object)
79{
80 AccountView *view = ACCOUNT_VIEW(object);
81 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
82
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040083 QObject::disconnect(priv->protocol_selection_changed);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040084
85 G_OBJECT_CLASS(account_view_parent_class)->dispose(object);
86}
87
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040088static void
89account_view_finalize(GObject *object)
90{
91 AccountView *view = ACCOUNT_VIEW(object);
92 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
93
94 delete priv->active_protocols;
95
96 G_OBJECT_CLASS(account_view_parent_class)->finalize(object);
97}
98
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040099static QModelIndex
100get_index_from_selection(GtkTreeSelection *selection)
101{
102 GtkTreeIter iter;
103 GtkTreeModel *model = NULL;
104
105 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
106 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
107 } else {
108 return QModelIndex();
109 }
110}
111
112static void
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400113update_account_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
114{
115 QModelIndex current = get_index_from_selection(selection);
116 if (current.isValid())
117 AccountModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
118 else
119 AccountModel::instance()->selectionModel()->clearCurrentIndex();
120}
121
122static void
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400123account_selection_changed(GtkTreeSelection *selection, AccountView *view)
124{
125 g_return_if_fail(IS_ACCOUNT_VIEW(view));
126 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
127
128 GtkWidget *old_account_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
129
130 /* keep track of the last tab displayed */
131 if (priv->current_account_notebook)
132 priv->current_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->current_account_notebook));
133
134 if (priv->current_page < 0)
135 priv->current_page = 0;
136
137 QModelIndex account_idx = get_index_from_selection(selection);
138 if (!account_idx.isValid()) {
139 /* it nothing is slected, simply display something empty */
140 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
141 gtk_widget_show(empty_box);
142 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
143 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
144 priv->current_account_notebook = NULL;
145 } else {
146 Account *account = AccountModel::instance()->getAccountByModelIndex(account_idx);
147
148 /* build new account view */
149 GtkWidget *hbox_account = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
150
151 /* create account notebook */
152 priv->current_account_notebook = gtk_notebook_new();
153 gtk_box_pack_start(GTK_BOX(hbox_account), priv->current_account_notebook, TRUE, TRUE, 0);
154
155 /* customize account view based on account */
156 GtkWidget *general_tab = account_general_tab_new(account);
157 gtk_notebook_append_page(GTK_NOTEBOOK(priv->current_account_notebook),
158 general_tab,
159 gtk_label_new("General"));
160 GtkWidget *audio_tab = account_audio_tab_new(account);
161 gtk_notebook_append_page(GTK_NOTEBOOK(priv->current_account_notebook),
162 audio_tab,
163 gtk_label_new("Audio"));
164 GtkWidget *video_tab = account_video_tab_new(account);
165 gtk_notebook_append_page(GTK_NOTEBOOK(priv->current_account_notebook),
166 video_tab,
167 gtk_label_new("Video"));
168
169 /* set the tab displayed to the same as the prev account selected */
170 gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->current_account_notebook), priv->current_page);
171
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400172 gtk_widget_show_all(hbox_account);
173
174 /* set the new account view as visible */
175 char *account_view_name = g_strdup_printf("%p_account", account);
176 gtk_stack_add_named(GTK_STACK(priv->stack_account), hbox_account, account_view_name);
177 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), hbox_account);
178 g_free(account_view_name);
179 }
180
181 /* remove the old account view */
182 if (old_account_view)
183 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_account_view);
184}
185
186static void
187account_active_toggled(GtkCellRendererToggle *renderer, gchar *path, AccountView *view)
188{
189 g_return_if_fail(IS_ACCOUNT_VIEW(view));
190 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
191
192 /* we want to set it to the opposite of the current value */
193 gboolean toggle = !gtk_cell_renderer_toggle_get_active(renderer);
194
195 /* get iter which was clicked */
196 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
197 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
198 GtkTreeIter iter;
199 gtk_tree_model_get_iter(model, &iter, tree_path);
200
201 /* get qmodelindex from iter and set the model data */
202 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
203 if (idx.isValid()) {
204 /* check if it is the IP2IP account, as we don't want to be able to disable it */
205 QVariant alias = idx.data(static_cast<int>(Account::Role::Alias));
206 if (strcmp(alias.value<QString>().toLocal8Bit().constData(), "IP2IP") != 0)
207 AccountModel::instance()->setData(idx, QVariant(toggle), Qt::CheckStateRole);
208 }
209}
210
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400211static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400212remove_account_dialog(AccountView *view, Account *account)
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400213{
214 gboolean response = FALSE;
215 GtkWidget *dialog = gtk_message_dialog_new(NULL,
216 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
217 GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
218 "Are you sure you want to delete account \"%s\"?",
219 account->alias().toLocal8Bit().constData());
220
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400221 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
222
223 /* get parent window so we can center on it */
224 GtkWidget *parent = gtk_widget_get_toplevel(GTK_WIDGET(view));
225 if (gtk_widget_is_toplevel(parent)) {
226 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
227 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
228 }
229
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400230 switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
231 case GTK_RESPONSE_OK:
232 response = TRUE;
233 break;
234 default:
235 response = FALSE;
236 break;
237 }
238
239 gtk_widget_destroy(dialog);
240
241 return response;
242}
243
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400244static gboolean
245save_account(GtkWidget *working_dialog)
246{
247 AccountModel::instance()->save();
248 if (working_dialog)
249 gtk_widget_destroy(working_dialog);
250
251 return G_SOURCE_REMOVE;
252}
253
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400254static void
255remove_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
256{
257 g_return_if_fail(IS_ACCOUNT_VIEW(view));
258 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
259
260 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
261 QModelIndex idx = get_index_from_selection(selection);
262
263 if (idx.isValid()) {
264 /* this is a destructive operation, ask the user if they are sure */
265 Account *account = AccountModel::instance()->getAccountByModelIndex(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400266 if (remove_account_dialog(view, account)) {
267 /* show working dialog in case save operation takes time */
268 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
269 gtk_window_present(GTK_WINDOW(working));
270
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400271 AccountModel::instance()->remove(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400272
273 /* now save the time it takes to transition the account view to the new account (300ms)
274 * the save doesn't happen before the "working" dialog is presented
275 * the timeout function should destroy the "working" dialog when done saving
276 */
277 g_timeout_add_full(G_PRIORITY_LOW, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400278 }
279 }
280}
281
282static void
283add_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
284{
285 g_return_if_fail(IS_ACCOUNT_VIEW(view));
286 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
287
288 GtkTreeIter protocol_iter;
289 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->combobox_account_type), &protocol_iter)) {
290 /* get the qmodelindex of the protocol */
291 GtkTreeModel *protocol_model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->combobox_account_type));
292 QModelIndex protocol_idx = gtk_q_sort_filter_tree_model_get_source_idx(
293 GTK_Q_SORT_FILTER_TREE_MODEL(protocol_model),
294 &protocol_iter);
295 if (protocol_idx.isValid()) {
296 protocol_idx = priv->active_protocols->mapToSource(protocol_idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400297
298 /* show working dialog in case save operation takes time */
299 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
300 gtk_window_present(GTK_WINDOW(working));
301
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400302 AccountModel::instance()->add(QString("New Account"), protocol_idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400303
304 /* now save after a short timeout to make sure that
305 * the save doesn't happen before the "working" dialog is presented
306 * the timeout function should destroy the "working" dialog when done saving
307 */
308 g_timeout_add_full(G_PRIORITY_LOW, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400309 }
310 }
311}
312
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400313static void
314account_view_init(AccountView *view)
315{
316 gtk_widget_init_template(GTK_WIDGET(view));
317
318 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
319
320 /* account model */
321 GtkQTreeModel *account_model;
322 GtkCellRenderer *renderer;
323 GtkTreeViewColumn *column;
324
325 account_model = gtk_q_tree_model_new(AccountModel::instance(), 4,
326 Account::Role::Enabled, G_TYPE_BOOLEAN,
327 Account::Role::Alias, G_TYPE_STRING,
328 Account::Role::Proto, G_TYPE_INT,
329 Account::Role::RegistrationState, G_TYPE_STRING);
330 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_account_list), GTK_TREE_MODEL(account_model));
331
332 renderer = gtk_cell_renderer_toggle_new();
333 column = gtk_tree_view_column_new_with_attributes("Enabled", renderer, "active", 0, NULL);
334 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
335
336 g_signal_connect(renderer, "toggled", G_CALLBACK(account_active_toggled), view);
337
338 renderer = gtk_cell_renderer_text_new();
339 column = gtk_tree_view_column_new_with_attributes("Alias", renderer, "text", 1, NULL);
340 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
341
342 renderer = gtk_cell_renderer_text_new();
343 column = gtk_tree_view_column_new_with_attributes("Status", renderer, "text", 3, NULL);
344 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
345
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400346 /* add an empty box to the account stack initially, otherwise there will
347 * be no cool animation when the first account is selected */
348 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
349 gtk_widget_show(empty_box);
350 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
351 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400352
353 /* populate account type combo box */
354 /* TODO: when to delete this model? */
355 priv->active_protocols = new ActiveItemProxyModel((QAbstractItemModel *)AccountModel::instance()->protocolModel());
356
357 GtkQSortFilterTreeModel *protocol_model = gtk_q_sort_filter_tree_model_new(
358 (QSortFilterProxyModel *)priv->active_protocols,
359 1,
360 Qt::DisplayRole, G_TYPE_STRING);
361
362 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_account_type), GTK_TREE_MODEL(protocol_model));
363
364 renderer = gtk_cell_renderer_text_new();
365 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_account_type), renderer, FALSE);
366 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(priv->combobox_account_type), renderer,
367 "text", 0, NULL);
368
369 /* connect signals to and from the selection model of the account model */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400370 priv->protocol_selection_changed = QObject::connect(
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400371 AccountModel::instance()->selectionModel(),
372 &QItemSelectionModel::currentChanged,
373 [=](const QModelIndex & current, const QModelIndex & previous) {
374 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
375
376 /* first unselect the previous */
377 if (previous.isValid()) {
378 GtkTreeIter old_iter;
379 if (gtk_q_tree_model_source_index_to_iter(account_model, previous, &old_iter)) {
380 gtk_tree_selection_unselect_iter(selection, &old_iter);
381 } else {
382 g_warning("Trying to unselect invalid GtkTreeIter");
383 }
384 }
385
386 /* select the current */
387 if (current.isValid()) {
388 GtkTreeIter new_iter;
389 if (gtk_q_tree_model_source_index_to_iter(account_model, current, &new_iter)) {
390 gtk_tree_selection_select_iter(selection, &new_iter);
391 } else {
392 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
393 }
394 }
395 }
396 );
397
398 GtkTreeSelection *account_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
399 g_signal_connect(account_selection, "changed", G_CALLBACK(update_account_model_selection), NULL);
400 g_signal_connect(account_selection, "changed", G_CALLBACK(account_selection_changed), view);
401
402 /* select the default protocol */
403 QModelIndex protocol_idx = AccountModel::instance()->protocolModel()->selectionModel()->currentIndex();
404 if (protocol_idx.isValid()) {
405 protocol_idx = priv->active_protocols->mapFromSource(protocol_idx);
406 GtkTreeIter protocol_iter;
407 if (gtk_q_sort_filter_tree_model_source_index_to_iter(
408 (GtkQSortFilterTreeModel *)protocol_model,
409 protocol_idx,
410 &protocol_iter)) {
411 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(priv->combobox_account_type), &protocol_iter);
412 }
413 }
414
415 /* connect signals to add/remove accounts */
416 g_signal_connect(priv->button_remove_account, "clicked", G_CALLBACK(remove_account), view);
417 g_signal_connect(priv->button_add_account, "clicked", G_CALLBACK(add_account), view);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400418}
419
420static void
421account_view_class_init(AccountViewClass *klass)
422{
423 G_OBJECT_CLASS(klass)->dispose = account_view_dispose;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400424 G_OBJECT_CLASS(klass)->finalize = account_view_finalize;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400425
426 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
427 "/cx/ring/RingGnome/accountview.ui");
428
429 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, treeview_account_list);
430 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, stack_account);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400431 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_remove_account);
432 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_add_account);
433 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, combobox_account_type);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400434}
435
436GtkWidget *
437account_view_new(void)
438{
439 return (GtkWidget *)g_object_new(ACCOUNT_VIEW_TYPE, NULL);
440}