blob: 715b2e8d0c2e76ee117acaacfe0eec09d3c6d8af [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 Salenikovichd6a9ba92015-03-29 11:14:36 -040045#include <glib/gprintf.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040046#include "utils/models.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040047
48struct _AccountView
49{
50 GtkBox parent;
51};
52
53struct _AccountViewClass
54{
55 GtkBoxClass parent_class;
56};
57
58typedef struct _AccountViewPrivate AccountViewPrivate;
59
60struct _AccountViewPrivate
61{
62 GtkWidget *treeview_account_list;
63 GtkWidget *stack_account;
64 GtkWidget *current_account_notebook;
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040065 GtkWidget *button_remove_account;
66 GtkWidget *button_add_account;
67 GtkWidget *combobox_account_type;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040068
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040069 gint current_page; /* keeps track of current notebook page displayed */
Stepan Salenikovichc3fede22015-03-20 17:01:47 -040070
71 ActiveItemProxyModel *active_protocols;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040072 QMetaObject::Connection protocol_selection_changed;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040073};
74
75G_DEFINE_TYPE_WITH_PRIVATE(AccountView, account_view, GTK_TYPE_BOX);
76
77#define ACCOUNT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ACCOUNT_VIEW_TYPE, AccountViewPrivate))
78
79static void
80account_view_dispose(GObject *object)
81{
82 AccountView *view = ACCOUNT_VIEW(object);
83 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
84
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040085 QObject::disconnect(priv->protocol_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
91account_view_finalize(GObject *object)
92{
93 AccountView *view = ACCOUNT_VIEW(object);
94 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
95
96 delete priv->active_protocols;
97
98 G_OBJECT_CLASS(account_view_parent_class)->finalize(object);
99}
100
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400101static void
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400102update_account_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
103{
104 QModelIndex current = get_index_from_selection(selection);
105 if (current.isValid())
106 AccountModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
107 else
108 AccountModel::instance()->selectionModel()->clearCurrentIndex();
109}
110
111static void
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400112account_selection_changed(GtkTreeSelection *selection, AccountView *view)
113{
114 g_return_if_fail(IS_ACCOUNT_VIEW(view));
115 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
116
117 GtkWidget *old_account_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_account));
118
119 /* keep track of the last tab displayed */
120 if (priv->current_account_notebook)
121 priv->current_page = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->current_account_notebook));
122
123 if (priv->current_page < 0)
124 priv->current_page = 0;
125
126 QModelIndex account_idx = get_index_from_selection(selection);
127 if (!account_idx.isValid()) {
128 /* it nothing is slected, simply display something empty */
129 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
130 gtk_widget_show(empty_box);
131 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
132 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
133 priv->current_account_notebook = NULL;
134 } else {
135 Account *account = AccountModel::instance()->getAccountByModelIndex(account_idx);
136
137 /* build new account view */
138 GtkWidget *hbox_account = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
139
140 /* create account notebook */
141 priv->current_account_notebook = gtk_notebook_new();
142 gtk_box_pack_start(GTK_BOX(hbox_account), priv->current_account_notebook, TRUE, TRUE, 0);
143
144 /* customize account view based on account */
145 GtkWidget *general_tab = account_general_tab_new(account);
146 gtk_notebook_append_page(GTK_NOTEBOOK(priv->current_account_notebook),
147 general_tab,
148 gtk_label_new("General"));
149 GtkWidget *audio_tab = account_audio_tab_new(account);
150 gtk_notebook_append_page(GTK_NOTEBOOK(priv->current_account_notebook),
151 audio_tab,
152 gtk_label_new("Audio"));
153 GtkWidget *video_tab = account_video_tab_new(account);
154 gtk_notebook_append_page(GTK_NOTEBOOK(priv->current_account_notebook),
155 video_tab,
156 gtk_label_new("Video"));
157
158 /* set the tab displayed to the same as the prev account selected */
159 gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->current_account_notebook), priv->current_page);
160
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400161 gtk_widget_show_all(hbox_account);
162
163 /* set the new account view as visible */
164 char *account_view_name = g_strdup_printf("%p_account", account);
165 gtk_stack_add_named(GTK_STACK(priv->stack_account), hbox_account, account_view_name);
166 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), hbox_account);
167 g_free(account_view_name);
168 }
169
170 /* remove the old account view */
171 if (old_account_view)
172 gtk_container_remove(GTK_CONTAINER(priv->stack_account), old_account_view);
173}
174
175static void
176account_active_toggled(GtkCellRendererToggle *renderer, gchar *path, AccountView *view)
177{
178 g_return_if_fail(IS_ACCOUNT_VIEW(view));
179 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
180
181 /* we want to set it to the opposite of the current value */
182 gboolean toggle = !gtk_cell_renderer_toggle_get_active(renderer);
183
184 /* get iter which was clicked */
185 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
186 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(priv->treeview_account_list));
187 GtkTreeIter iter;
188 gtk_tree_model_get_iter(model, &iter, tree_path);
189
190 /* get qmodelindex from iter and set the model data */
191 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
192 if (idx.isValid()) {
193 /* check if it is the IP2IP account, as we don't want to be able to disable it */
194 QVariant alias = idx.data(static_cast<int>(Account::Role::Alias));
Stepan Salenikovich101f3102015-05-01 12:39:59 -0400195 if (strcmp(alias.value<QString>().toLocal8Bit().constData(), "IP2IP") != 0) {
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400196 AccountModel::instance()->setData(idx, QVariant(toggle), Qt::CheckStateRole);
Stepan Salenikovich101f3102015-05-01 12:39:59 -0400197 /* save the account to apply the changed state right away */
198 AccountModel::instance()->getAccountByModelIndex(idx)->performAction(Account::EditAction::SAVE);
199 }
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400200 }
201}
202
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400203static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400204remove_account_dialog(AccountView *view, Account *account)
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400205{
206 gboolean response = FALSE;
207 GtkWidget *dialog = gtk_message_dialog_new(NULL,
208 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
209 GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
210 "Are you sure you want to delete account \"%s\"?",
211 account->alias().toLocal8Bit().constData());
212
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400213 gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
214
215 /* get parent window so we can center on it */
216 GtkWidget *parent = gtk_widget_get_toplevel(GTK_WIDGET(view));
217 if (gtk_widget_is_toplevel(parent)) {
218 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent));
219 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ON_PARENT);
220 }
221
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400222 switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
223 case GTK_RESPONSE_OK:
224 response = TRUE;
225 break;
226 default:
227 response = FALSE;
228 break;
229 }
230
231 gtk_widget_destroy(dialog);
232
233 return response;
234}
235
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400236static gboolean
237save_account(GtkWidget *working_dialog)
238{
239 AccountModel::instance()->save();
240 if (working_dialog)
241 gtk_widget_destroy(working_dialog);
242
243 return G_SOURCE_REMOVE;
244}
245
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400246static void
247remove_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
248{
249 g_return_if_fail(IS_ACCOUNT_VIEW(view));
250 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
251
252 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
253 QModelIndex idx = get_index_from_selection(selection);
254
255 if (idx.isValid()) {
256 /* this is a destructive operation, ask the user if they are sure */
257 Account *account = AccountModel::instance()->getAccountByModelIndex(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400258 if (remove_account_dialog(view, account)) {
259 /* show working dialog in case save operation takes time */
260 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
261 gtk_window_present(GTK_WINDOW(working));
262
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400263 AccountModel::instance()->remove(idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400264
265 /* now save the time it takes to transition the account view to the new account (300ms)
266 * the save doesn't happen before the "working" dialog is presented
267 * the timeout function should destroy the "working" dialog when done saving
268 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400269 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400270 }
271 }
272}
273
274static void
275add_account(G_GNUC_UNUSED GtkWidget *entry, AccountView *view)
276{
277 g_return_if_fail(IS_ACCOUNT_VIEW(view));
278 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
279
280 GtkTreeIter protocol_iter;
281 if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(priv->combobox_account_type), &protocol_iter)) {
282 /* get the qmodelindex of the protocol */
283 GtkTreeModel *protocol_model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->combobox_account_type));
284 QModelIndex protocol_idx = gtk_q_sort_filter_tree_model_get_source_idx(
285 GTK_Q_SORT_FILTER_TREE_MODEL(protocol_model),
286 &protocol_iter);
287 if (protocol_idx.isValid()) {
288 protocol_idx = priv->active_protocols->mapToSource(protocol_idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400289
290 /* show working dialog in case save operation takes time */
291 GtkWidget *working = ring_dialog_working(GTK_WIDGET(view), NULL);
292 gtk_window_present(GTK_WINDOW(working));
293
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400294 AccountModel::instance()->add(QString("New Account"), protocol_idx);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400295
296 /* now save after a short timeout to make sure that
297 * the save doesn't happen before the "working" dialog is presented
298 * the timeout function should destroy the "working" dialog when done saving
299 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400300 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)save_account, working, NULL);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400301 }
302 }
303}
304
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400305static void
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400306state_to_string(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
307 GtkCellRenderer *cell,
308 GtkTreeModel *tree_model,
309 GtkTreeIter *iter,
310 G_GNUC_UNUSED gpointer data)
311{
312 GValue value = G_VALUE_INIT;
313 gtk_tree_model_get_value(tree_model, iter, 3, &value);
314 Account::RegistrationState state = (Account::RegistrationState)g_value_get_uint(&value);
315 g_value_unset(&value);
316
317 gchar *display_state = NULL;
318 switch (state) {
319 case Account::RegistrationState::READY:
320 display_state = g_strdup_printf("<span fgcolor=\"green\">ready</span>");
321 break;
322 case Account::RegistrationState::UNREGISTERED:
323 display_state = g_strdup_printf("<span fgcolor=\"gray\">unregistered</span>");
324 break;
325 case Account::RegistrationState::TRYING:
326 display_state = g_strdup_printf("<span fgcolor=\"orange\">trying</span>");
327 break;
328 case Account::RegistrationState::ERROR:
329 display_state = g_strdup_printf("<span fgcolor=\"red\">error</span>");
330 break;
331 case Account::RegistrationState::COUNT__:
332 g_warning("registration state should never be \"count\"");
333 break;
334 }
335 g_object_set(G_OBJECT(cell), "markup", display_state, NULL);
336 g_free(display_state);
337}
338
339static void
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400340account_view_init(AccountView *view)
341{
342 gtk_widget_init_template(GTK_WIDGET(view));
343
344 AccountViewPrivate *priv = ACCOUNT_VIEW_GET_PRIVATE(view);
345
346 /* account model */
347 GtkQTreeModel *account_model;
348 GtkCellRenderer *renderer;
349 GtkTreeViewColumn *column;
350
351 account_model = gtk_q_tree_model_new(AccountModel::instance(), 4,
352 Account::Role::Enabled, G_TYPE_BOOLEAN,
353 Account::Role::Alias, G_TYPE_STRING,
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400354 Account::Role::Proto, G_TYPE_STRING,
355 Account::Role::RegistrationState, G_TYPE_UINT);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400356 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_account_list), GTK_TREE_MODEL(account_model));
357
358 renderer = gtk_cell_renderer_toggle_new();
359 column = gtk_tree_view_column_new_with_attributes("Enabled", renderer, "active", 0, NULL);
360 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
361
362 g_signal_connect(renderer, "toggled", G_CALLBACK(account_active_toggled), view);
363
364 renderer = gtk_cell_renderer_text_new();
365 column = gtk_tree_view_column_new_with_attributes("Alias", renderer, "text", 1, NULL);
366 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
367
368 renderer = gtk_cell_renderer_text_new();
369 column = gtk_tree_view_column_new_with_attributes("Status", renderer, "text", 3, NULL);
370 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_account_list), column);
371
Stepan Salenikovichd6a9ba92015-03-29 11:14:36 -0400372 /* the registration state is an enum, we want to display it as a string */
373 gtk_tree_view_column_set_cell_data_func(
374 column,
375 renderer,
376 (GtkTreeCellDataFunc)state_to_string,
377 NULL,
378 NULL);
379
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400380 /* add an empty box to the account stack initially, otherwise there will
381 * be no cool animation when the first account is selected */
382 GtkWidget *empty_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
383 gtk_widget_show(empty_box);
384 gtk_stack_add_named(GTK_STACK(priv->stack_account), empty_box, "placeholder");
385 gtk_stack_set_visible_child(GTK_STACK(priv->stack_account), empty_box);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400386
387 /* populate account type combo box */
388 /* TODO: when to delete this model? */
389 priv->active_protocols = new ActiveItemProxyModel((QAbstractItemModel *)AccountModel::instance()->protocolModel());
390
391 GtkQSortFilterTreeModel *protocol_model = gtk_q_sort_filter_tree_model_new(
392 (QSortFilterProxyModel *)priv->active_protocols,
393 1,
394 Qt::DisplayRole, G_TYPE_STRING);
395
396 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_account_type), GTK_TREE_MODEL(protocol_model));
397
398 renderer = gtk_cell_renderer_text_new();
399 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_account_type), renderer, FALSE);
400 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(priv->combobox_account_type), renderer,
401 "text", 0, NULL);
402
403 /* connect signals to and from the selection model of the account model */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400404 priv->protocol_selection_changed = QObject::connect(
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400405 AccountModel::instance()->selectionModel(),
406 &QItemSelectionModel::currentChanged,
407 [=](const QModelIndex & current, const QModelIndex & previous) {
408 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
409
410 /* first unselect the previous */
411 if (previous.isValid()) {
412 GtkTreeIter old_iter;
413 if (gtk_q_tree_model_source_index_to_iter(account_model, previous, &old_iter)) {
414 gtk_tree_selection_unselect_iter(selection, &old_iter);
415 } else {
416 g_warning("Trying to unselect invalid GtkTreeIter");
417 }
418 }
419
420 /* select the current */
421 if (current.isValid()) {
422 GtkTreeIter new_iter;
423 if (gtk_q_tree_model_source_index_to_iter(account_model, current, &new_iter)) {
424 gtk_tree_selection_select_iter(selection, &new_iter);
425 } else {
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400426 g_warning("SelectionModel of AccountModel changed to invalid QModelIndex?");
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400427 }
428 }
429 }
430 );
431
432 GtkTreeSelection *account_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_account_list));
433 g_signal_connect(account_selection, "changed", G_CALLBACK(update_account_model_selection), NULL);
434 g_signal_connect(account_selection, "changed", G_CALLBACK(account_selection_changed), view);
435
436 /* select the default protocol */
437 QModelIndex protocol_idx = AccountModel::instance()->protocolModel()->selectionModel()->currentIndex();
438 if (protocol_idx.isValid()) {
439 protocol_idx = priv->active_protocols->mapFromSource(protocol_idx);
440 GtkTreeIter protocol_iter;
441 if (gtk_q_sort_filter_tree_model_source_index_to_iter(
442 (GtkQSortFilterTreeModel *)protocol_model,
443 protocol_idx,
444 &protocol_iter)) {
445 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(priv->combobox_account_type), &protocol_iter);
446 }
447 }
448
449 /* connect signals to add/remove accounts */
450 g_signal_connect(priv->button_remove_account, "clicked", G_CALLBACK(remove_account), view);
451 g_signal_connect(priv->button_add_account, "clicked", G_CALLBACK(add_account), view);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400452}
453
454static void
455account_view_class_init(AccountViewClass *klass)
456{
457 G_OBJECT_CLASS(klass)->dispose = account_view_dispose;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400458 G_OBJECT_CLASS(klass)->finalize = account_view_finalize;
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400459
460 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
461 "/cx/ring/RingGnome/accountview.ui");
462
463 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, treeview_account_list);
464 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, stack_account);
Stepan Salenikovichc3fede22015-03-20 17:01:47 -0400465 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_remove_account);
466 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, button_add_account);
467 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), AccountView, combobox_account_type);
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400468}
469
470GtkWidget *
471account_view_new(void)
472{
473 return (GtkWidget *)g_object_new(ACCOUNT_VIEW_TYPE, NULL);
474}