blob: 6b803cbb359f64b0ef0948576dd01f19125d6a58 [file] [log] [blame]
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001/*
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 "ringmainwindow.h"
32
33#include <gtk/gtk.h>
Stepan Salenikovicha3557452015-02-20 14:14:12 -050034#include "models/gtkqtreemodel.h"
35#include <callmodel.h>
Stepan Salenikovich69771842015-02-24 18:11:45 -050036#include <call.h>
Stepan Salenikovichd2dbcee2015-02-27 16:52:28 -050037#include <QtCore/QItemSelectionModel>
Stepan Salenikovich69771842015-02-24 18:11:45 -050038#include "incomingcallview.h"
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050039#include "currentcallview.h"
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050040#include <string.h>
Stepan Salenikovichdd84cf92015-03-19 21:38:19 -040041#include <categorizedhistorymodel.h>
Stepan Salenikovich15142182015-03-11 17:15:26 -040042#include <contactmethod.h>
Stepan Salenikovich7ff47962015-03-16 15:10:14 -040043#include <QtCore/QSortFilterProxyModel>
44#include "models/gtkqsortfiltertreemodel.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040045#include "accountview.h"
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040046#include <accountmodel.h>
47#include <audio/codecmodel.h>
Stepan Salenikovichbd029582015-03-24 11:00:56 -040048#include "dialogs.h"
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -040049#include "videosettingsview.h"
50#include <video/previewmanager.h>
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040051
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040052#define CALL_VIEW_NAME "calls"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040053#define CREATE_ACCOUNT_1_VIEW_NAME "create1"
54#define CREATE_ACCOUNT_2_VIEW_NAME "create2"
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040055#define GENERAL_SETTINGS_VIEW_NAME "general"
56#define AUDIO_SETTINGS_VIEW_NAME "audio"
57#define VIDEO_SETTINGS_VIEW_NAME "video"
58#define ACCOUNT_SETTINGS_VIEW_NAME "accounts"
Stepan Salenikovich69771842015-02-24 18:11:45 -050059#define DEFAULT_VIEW_NAME "placeholder"
Stepan Salenikovich15142182015-03-11 17:15:26 -040060#define VIEW_CONTACTS "contacts"
61#define VIEW_HISTORY "history"
62#define VIEW_PRESENCE "presence"
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050063
64struct _RingMainWindow
65{
66 GtkApplicationWindow parent;
67};
68
69struct _RingMainWindowClass
70{
71 GtkApplicationWindowClass parent_class;
72};
73
74typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
75
76struct _RingMainWindowPrivate
77{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040078 GtkWidget *ring_menu;
79 GtkWidget *image_ring;
80 GtkWidget *ring_settings;
81 GtkWidget *image_settings;
82 GtkWidget *hbox_search;
83 GtkWidget *hbox_settings;
Stepan Salenikovich15142182015-03-11 17:15:26 -040084 GtkWidget *stack_contacts_history_presence;
85 GtkWidget *radiobutton_contacts;
86 GtkWidget *radiobutton_history;
87 GtkWidget *radiobutton_presence;
Stepan Salenikovicha3557452015-02-20 14:14:12 -050088 GtkWidget *treeview_call;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -050089 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -050090 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040091 GtkWidget *vbox_call_view;
92 GtkWidget *stack_call_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050093 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040094 GtkWidget *account_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -040095 GtkWidget *video_settings_view;
96 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040097 GtkWidget *radiobutton_audio_settings;
98 GtkWidget *radiobutton_general_settings;
99 GtkWidget *radiobutton_video_settings;
100 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400101 GtkWidget *entry_ring_id;
102
103 Account *active_ring_account;
104 QMetaObject::Connection active_ring_account_updates;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400105
106 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400107
108 /* account creation */
109 GtkWidget *account_creation_1;
110 GtkWidget *image_ring_logo;
111 GtkWidget *label_enter_alias;
112 GtkWidget *entry_alias;
113 GtkWidget *label_generating_account;
114 GtkWidget *spinner_generating_account;
115 GtkWidget *button_account_creation_next;
116 GtkWidget *account_creation_2;
117 GtkWidget *entry_hash;
118 GtkWidget *button_account_creation_done;
119
120 QMetaObject::Connection hash_updated;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500121};
122
123G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
124
125#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
126
Stepan Salenikovich69771842015-02-24 18:11:45 -0500127static QModelIndex
128get_index_from_selection(GtkTreeSelection *selection)
129{
130 GtkTreeIter iter;
131 GtkTreeModel *model = NULL;
132
133 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
134 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
135 } else {
136 return QModelIndex();
137 }
138}
139
140static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500141update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500142{
Stepan Salenikovich69771842015-02-24 18:11:45 -0500143 QModelIndex current = get_index_from_selection(selection);
144 if (current.isValid())
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500145 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500146 else
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500147 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500148}
149
150static void
151call_selection_changed(GtkTreeSelection *selection, gpointer win)
152{
153 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
154
155 /* get the current visible stack child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400156 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500157
158 QModelIndex idx = get_index_from_selection(selection);
159 if (idx.isValid()) {
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400160 QVariant state = CallModel::instance()->data(idx, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500161 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500162 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500163
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500164 /* FIXME: change when fixed
165 * switch(state.value<Call::LifeCycleState>()) { */
166 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
167 switch(lifecyclestate) {
168 case Call::LifeCycleState::INITIALIZATION:
169 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500170 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500171 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500172 /* use the pointer of the call as a unique name */
173 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance()->getCall(idx));
174 break;
175 case Call::LifeCycleState::PROGRESS:
176 new_call_view = current_call_view_new();
177 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
178 /* use the pointer of the call as a unique name */
179 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx));
180 break;
181 case Call::LifeCycleState::COUNT__:
182 g_warning("LifeCycleState should never be COUNT");
183 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500184 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500185
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400186 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
187 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500188 g_free(new_call_view_name);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500189
Stepan Salenikovich69771842015-02-24 18:11:45 -0500190 } else {
191 /* nothing selected in the call model, so show the default screen */
192
193 /* TODO: replace stack paceholder view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400194 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
195 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_call_view), DEFAULT_VIEW_NAME);
196 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500197
198 }
199
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500200 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400201 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500202 if (current_call_view != old_call_view && old_call_view != NULL) {
203 /* if the previous child was a call view, then remove it from
Stepan Salenikovich69771842015-02-24 18:11:45 -0500204 * the stack; removing it should destory it since there should not
205 * be any other references to it */
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500206 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400207 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500208 }
209 }
210}
211
212static void
213call_state_changed(Call *call, gpointer win)
214{
215 g_debug("call state changed");
216 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
217
218 /* check if the call that changed state is the same as the selected call */
219 QModelIndex idx_selected = CallModel::instance()->selectionModel()->currentIndex();
220
221 if( idx_selected.isValid() && call == CallModel::instance()->getCall(idx_selected)) {
222 g_debug("selected call state changed");
223 /* check if we need to change the view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400224 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500225 GtkWidget *new_call_view = NULL;
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400226 QVariant state = CallModel::instance()->data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500227
228 /* check what the current state is vs what is displayed */
229
230 /* FIXME: change when fixed
231 * switch(state.value<Call::LifeCycleState>()) { */
232 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
233 switch(lifecyclestate) {
234 case Call::LifeCycleState::INITIALIZATION:
235 /* LifeCycleState cannot go backwards, so it should not be possible
236 * that the call is displayed as current (meaning that its in progress)
237 * but have the state 'initialization' */
238 if (IS_CURRENT_CALL_VIEW(old_call_view))
239 g_warning("call displayed as current, but is in state of initialization");
240 break;
241 case Call::LifeCycleState::PROGRESS:
242 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
243 /* change from incoming to current */
244 new_call_view = current_call_view_new();
245 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
246 /* use the pointer of the call as a unique name */
247 char* new_call_view_name = NULL;
248 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx_selected));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400249 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500250 g_free(new_call_view_name);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400251 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
252 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
253 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500254 }
255 break;
256 case Call::LifeCycleState::FINISHED:
257 /* do nothing, either call view is valid for this state */
258 break;
259 case Call::LifeCycleState::COUNT__:
260 g_warning("LifeCycleState should never be COUNT");
261 break;
262 }
263
264 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400265 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500266 if (current_call_view != old_call_view && old_call_view != NULL) {
267 /* if the previous child was a call view, then remove it from
268 * the stack; removing it should destory it since there should not
269 * be any other references to it */
270 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400271 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500272 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500273 }
274 }
275}
276
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500277static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500278search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
279{
280 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
281
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400282 const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500283
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400284 if (number_entered && strlen(number_entered) > 0) {
285 /* detect Ring hash */
286 gboolean is_ring_hash = FALSE;
287 if (strlen(number_entered) == 40) {
288 is_ring_hash = TRUE;
289 /* must be 40 chars long and alphanumeric */
290 for (int i = 0; i < 40 && is_ring_hash; ++i) {
291 if (!g_ascii_isalnum(number_entered[i]))
292 is_ring_hash = FALSE;
293 }
294 }
295
296 QString number = QString{number_entered};
297
298 if (is_ring_hash)
299 number = "ring:" + number;
300
301 g_debug("dialing to number: %s", number.toUtf8().constData());
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500302 Call *call = CallModel::instance()->dialingCall();
303 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500304 call->performAction(Call::Action::ACCEPT);
305
306 /* make this the currently selected call */
307 QModelIndex idx = CallModel::instance()->getIndex(call);
308 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
309 }
310}
311
312static void
Stepan Salenikovich15142182015-03-11 17:15:26 -0400313call_history_item(GtkTreeView *tree_view,
314 GtkTreePath *path,
315 G_GNUC_UNUSED GtkTreeViewColumn *column,
316 G_GNUC_UNUSED gpointer user_data)
317{
318 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
319
320 /* get iter */
321 GtkTreeIter iter;
322 if (gtk_tree_model_get_iter(model, &iter, path)) {
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400323 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400324
325 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
326 /* create new call */
327 if (contact_method.value<ContactMethod*>()) {
328 Call *call = CallModel::instance()->dialingCall();
329 call->setDialNumber(contact_method.value<ContactMethod*>());
330 call->performAction(Call::Action::ACCEPT);
331
332 /* make this the currently selected call */
333 QModelIndex call_idx = CallModel::instance()->getIndex(call);
334 CallModel::instance()->selectionModel()->setCurrentIndex(call_idx, QItemSelectionModel::ClearAndSelect);
335 } else
336 g_warning("contact method is empty");
337 }
338}
339
340static void
341navbutton_contacts_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
342{
343 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
344 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
345
346 if (gtk_toggle_button_get_active(navbutton)) {
347
348 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
349
350 if (visible) {
351 /* contacts is left of both history and presence, so always slide right to show it */
352 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
353 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
354 } else {
355 g_warning("should always have a visible child in the nav stack");
356 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
357 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
358 }
359 }
360}
361
362static void
363navbutton_presence_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
364{
365 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
366 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
367
368 if (gtk_toggle_button_get_active(navbutton)) {
369
370 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
371 if (visible) {
372 /* presence is right of both history and contacts, so always slide left to show it */
373 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
374 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
375 } else {
376 g_warning("should always have a visible child in the nav stack");
377 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
378 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
379 }
380 }
381}
382
383static void
384navbutton_history_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
385{
386 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
387 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
388
389 if (gtk_toggle_button_get_active(navbutton)) {
390
391 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
392 if (visible) {
393 if (strcmp(visible, VIEW_CONTACTS) == 0) {
394 /* history is right of contacts, so slide left to show it */
395 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
396 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
397 } else if (strcmp(visible, VIEW_PRESENCE) == 0) {
398 /* history is left of presence, so slide right to show it */
399 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
400 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
401 }
402 } else {
403 g_warning("should always have a visible child in the nav stack");
404 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
405 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
406 }
407 }
408}
409
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400410static gboolean
411save_accounts(GtkWidget *working_dialog)
412{
413 /* save changes to accounts */
414 AccountModel::instance()->save();
415 /* save changes to codecs */
416 for (int i = 0; i < AccountModel::instance()->rowCount(); i++) {
417 QModelIndex idx = AccountModel::instance()->index(i, 0);
418 AccountModel::instance()->getAccountByModelIndex(idx)->codecModel()->save();
419 }
420
421 if (working_dialog)
422 gtk_widget_destroy(working_dialog);
423
424 return G_SOURCE_REMOVE;
425}
426
Stepan Salenikovich15142182015-03-11 17:15:26 -0400427static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400428settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
429{
430 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
431 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
432
433 /* toggle show settings */
434 priv->show_settings = !priv->show_settings;
435
436 /* check which view to show */
437 if (priv->show_settings) {
438 /* show settings */
439 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
440
441 gtk_widget_hide(priv->hbox_search);
442 gtk_widget_show(priv->hbox_settings);
443
444 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400445 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400446 } else {
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400447 /* show working dialog in case save operation takes time */
448 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
449 gtk_window_present(GTK_WINDOW(working));
450
451 /* now save after the time it takes to transition back to the call view (400ms)
452 * the save doesn't happen before the "working" dialog is presented
453 * the timeout function should destroy the "working" dialog when done saving
454 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400455 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400456
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400457 /* show calls */
458 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
459
460 gtk_widget_show(priv->hbox_search);
461 gtk_widget_hide(priv->hbox_settings);
462
463 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
464 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400465
466 /* make sure video preview is stopped, in case it was started */
467 Video::PreviewManager::instance()->stopPreview();
468 }
469}
470
471static void
472show_video_settings(GtkToggleButton *navbutton, RingMainWindow *win)
473{
474 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
475 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
476
477 if (gtk_toggle_button_get_active(navbutton)) {
478 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
479 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), VIDEO_SETTINGS_VIEW_NAME);
480 priv->last_settings_view = priv->video_settings_view;
481 }
482}
483
484static void
485show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
486{
487 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
488 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
489
490 if (gtk_toggle_button_get_active(navbutton)) {
491 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
492 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
493 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400494 }
495}
496
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400497static gboolean
498has_ring_account()
499{
500 /* check if a Ring account exists */
501 int a_count = AccountModel::instance()->rowCount();
502 for (int i = 0; i < a_count; ++i) {
503 QModelIndex idx = AccountModel::instance()->index(i, 0);
504 QVariant protocol = idx.data(static_cast<int>(Account::Role::Proto));
505 if ((Account::Protocol)protocol.toUInt() == Account::Protocol::RING)
506 return TRUE;
507 }
508
509 return FALSE;
510}
511
512static gboolean
513create_ring_account(RingMainWindow *win)
514{
515 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
516 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
517
518 /* create account and set UPnP enabled, as its not by default in the daemon */
519 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
520 Account *account = AccountModel::instance()->add(alias, Account::Protocol::RING);
521 account->setUpnpEnabled(TRUE);
522
523 /* wait for hash to be generated to show the next view */
524 priv->hash_updated = QObject::connect(
525 account,
526 &Account::changed,
527 [=] (Account *a) {
528 QString hash = a->username();
529 if (!hash.isEmpty()) {
530 /* set the hash */
531 gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());
532
533 /* show the next accont creation view */
534 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
535 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);
536
537 /* select the hash text */
538 gtk_widget_grab_focus(priv->entry_hash);
539 gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
540 }
541 }
542 );
543
544 account->performAction(Account::EditAction::SAVE);
545
546 return G_SOURCE_REMOVE;
547}
548
549static void
550alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
551{
552 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
553
554 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
555 if (strlen(alias) > 0) {
556 /* enable "next" button */
557 gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
558 } else {
559 /* disable "next" button, as we require an alias */
560 gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
561 }
562}
563
564static void
565account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
566{
567 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
568
569 /* show/hide relevant widgets */
570 gtk_widget_hide(priv->label_enter_alias);
571 gtk_widget_hide(priv->entry_alias);
572 gtk_widget_hide(priv->button_account_creation_next);
573 gtk_widget_show(priv->label_generating_account);
574 gtk_widget_show(priv->spinner_generating_account);
575
576 /* now create account after a short timeout so that the the save doesn't
577 * happen freeze the client before the widget changes happen;
578 * the timeout function should then display the next step in account creation
579 */
580 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
581}
582
583static void
584account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
585{
586 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
587
588 QObject::disconnect(priv->hash_updated);
589
590 /* show the call view */
591 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
592 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
593
594 /* show the search bar and settings */
595 gtk_widget_show(priv->hbox_search);
596 gtk_widget_show(priv->ring_settings);
597
598}
599
600static void
601entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
602{
603 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
604
605 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
606 if (strlen(alias) > 0)
607 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
608}
609
610static void
611show_account_creation(RingMainWindow *win)
612{
613 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
614
615 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
616 priv->account_creation_1,
617 CREATE_ACCOUNT_1_VIEW_NAME);
618
619 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
620 priv->account_creation_2,
621 CREATE_ACCOUNT_2_VIEW_NAME);
622
623 /* hide search bar and settings until account creation is complete */
624 gtk_widget_hide(priv->hbox_search);
625 gtk_widget_hide(priv->ring_settings);
626
627 /* set ring logo */
628 GError *error = NULL;
629 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
630 -1, 75, TRUE, &error);
631 if (logo_ring == NULL) {
632 g_debug("Could not load logo: %s", error->message);
633 g_error_free(error);
634 } else
635 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
636
637 /* style of alias and hash entry; give them a larger font */
638 gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
639 gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));
640
641 /* connect signals */
642 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
643 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
644 g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
645 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
646 g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);
647
648 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
649}
650
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400651static void
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400652show_ring_id(RingMainWindow *win, Account *account) {
653 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
654
655 /* display the ring id, if we found a ring account */
656 if (account) {
657 if (!account->username().isEmpty()) {
658 gtk_entry_set_text(GTK_ENTRY(priv->entry_ring_id), account->username().toUtf8().constData());
659 } else {
660 g_warning("got ring account, but Ring id is empty");
661 gtk_entry_set_text(GTK_ENTRY(priv->entry_ring_id), "");
662 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->entry_ring_id), "fetching Ring ID...");
663 }
664 } else {
665 gtk_entry_set_text(GTK_ENTRY(priv->entry_ring_id), "");
666 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->entry_ring_id), "no Ring account found");
667 }
668
669}
670
671static void
672get_active_ring_account(RingMainWindow *win)
673{
674 /* get the users Ring account
675 * if multiple accounts exist, get the first one which is registered,
676 * if none, then the first one which is enabled,
677 * if none, then the first one in the list of ring accounts
678 */
679 Account *registered_account = NULL;
680 Account *enabled_account = NULL;
681 Account *ring_account = NULL;
682 int a_count = AccountModel::instance()->rowCount();
683 for (int i = 0; i < a_count && !registered_account; ++i) {
684 QModelIndex idx = AccountModel::instance()->index(i, 0);
685 Account *account = AccountModel::instance()->getAccountByModelIndex(idx);
686 if (account->protocol() == Account::Protocol::RING) {
687 /* got RING account, check if active */
688 if (account->isEnabled()) {
689 /* got enabled account, check if connected */
690 if (account->registrationState() == Account::RegistrationState::READY) {
691 /* got registered account, use this one */
692 registered_account = enabled_account = ring_account = account;
693 // g_debug("got registered account: %s", ring_account->alias().toUtf8().constData());
694 } else {
695 /* not registered, but enabled, use if its the first one */
696 if (!enabled_account) {
697 enabled_account = ring_account = account;
698 // g_debug("got enabled ring accout: %s", ring_account->alias().toUtf8().constData());
699 }
700 }
701 } else {
702 /* not enabled, but a Ring account, use if its the first one */
703 if (!ring_account) {
704 ring_account = account;
705 // g_debug("got ring account: %s", ring_account->alias().toUtf8().constData());
706 }
707 }
708 }
709 }
710
711 show_ring_id(win, ring_account);
712}
713
714static void
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400715render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
716 GtkCellRenderer *cell,
717 GtkTreeModel *tree_model,
718 GtkTreeIter *iter,
719 G_GNUC_UNUSED gpointer data)
720{
721 /* check if this is a top level item (the fuzzy date item),
722 * in this case we don't want to show a call direction */
723 gchar *render_direction = NULL;
724 GtkTreeIter parent;
725 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
726 /* get direction and missed values */
727 GValue value = G_VALUE_INIT;
728 gtk_tree_model_get_value(tree_model, iter, 3, &value);
729 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
730 g_value_unset(&value);
731
732 gtk_tree_model_get_value(tree_model, iter, 4, &value);
733 gboolean missed = g_value_get_boolean(&value);
734 g_value_unset(&value);
735
736 switch (direction) {
737 case Call::Direction::INCOMING:
738 if (missed)
739 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
740 else
741 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
742 break;
743 case Call::Direction::OUTGOING:
744 if (missed)
745 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
746 else
747 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
748 break;
749 }
750 }
751 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
752 g_free(render_direction);
753}
754
755static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500756ring_main_window_init(RingMainWindow *win)
757{
758 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
759 gtk_widget_init_template(GTK_WIDGET(win));
760
761 /* set window icon */
762 GError *error = NULL;
763 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
764 if (icon == NULL) {
765 g_debug("Could not load icon: %s", error->message);
766 g_error_free(error);
767 } else
768 gtk_window_set_icon(GTK_WINDOW(win), icon);
769
770 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400771 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
772 -1, 24, TRUE, &error);
773 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500774 g_debug("Could not load icon: %s", error->message);
775 g_error_free(error);
776 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400777 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500778
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400779 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500780 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
781 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400782 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500783 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500784
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400785 /* settings icon */
786 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
787
788 /* connect settings button signal */
789 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
790
791 /* add the call view to the main stack */
792 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
793 priv->vbox_call_view,
794 CALL_VIEW_NAME);
795
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400796 if (has_ring_account()) {
797 /* user has ring account, so show the call view right away */
798 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
799 } else {
800 /* user has to create the ring account */
801 show_account_creation(win);
802 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400803
804 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400805 priv->account_settings_view = account_view_new();
806 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
807
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400808 priv->video_settings_view = video_settings_view_new();
809 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->video_settings_view, VIDEO_SETTINGS_VIEW_NAME);
810
811 /* make the setting we will show first the active one */
812 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_video_settings), TRUE);
813 priv->last_settings_view = priv->video_settings_view;
814
815 /* connect the settings button signals to switch settings views */
816 g_signal_connect(priv->radiobutton_video_settings, "toggled", G_CALLBACK(show_video_settings), win);
817 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
818
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500819 /* call model */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500820 GtkQTreeModel *call_model;
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500821 GtkCellRenderer *renderer;
822 GtkTreeViewColumn *column;
823
Stepan Salenikovich69771842015-02-24 18:11:45 -0500824 call_model = gtk_q_tree_model_new(CallModel::instance(), 4,
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500825 Call::Role::Name, G_TYPE_STRING,
826 Call::Role::Number, G_TYPE_STRING,
827 Call::Role::Length, G_TYPE_STRING,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400828 Call::Role::State, G_TYPE_STRING);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500829 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_call), GTK_TREE_MODEL(call_model));
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500830
831 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400832 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500833 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400834 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500835 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
836
837 renderer = gtk_cell_renderer_text_new();
838 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400839 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500840 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
841
Stepan Salenikovich69771842015-02-24 18:11:45 -0500842 /* connect signals to and from UserActionModel to sync selection betwee
843 * the QModel and the GtkTreeView */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500844 QObject::connect(
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500845 CallModel::instance()->selectionModel(),
Stepan Salenikovich69771842015-02-24 18:11:45 -0500846 &QItemSelectionModel::currentChanged,
847 [=](const QModelIndex & current, const QModelIndex & previous) {
848 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
849
850 /* first unselect the previous */
851 if (previous.isValid()) {
852 GtkTreeIter old_iter;
853 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
854 gtk_tree_selection_unselect_iter(selection, &old_iter);
855 } else {
856 g_warning("Trying to unselect invalid GtkTreeIter");
857 }
858 }
859
860 /* select the current */
861 if (current.isValid()) {
862 GtkTreeIter new_iter;
863 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
864 gtk_tree_selection_select_iter(selection, &new_iter);
865 } else {
866 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
867 }
868 }
869 }
870 );
871
872 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500873 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500874
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500875 /* connect to call state changes to update relevant view(s) */
876 QObject::connect(
877 CallModel::instance(),
878 &CallModel::callStateChanged,
879 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
880 call_state_changed(call, win);
881 }
882 );
883
Stepan Salenikovich15142182015-03-11 17:15:26 -0400884 /* contacts view/model */
885 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
886 GtkWidget *treeview_contacts = gtk_tree_view_new();
887 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_contacts), FALSE);
888 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_contacts);
889 gtk_widget_show_all(scrolled_window);
890 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
891 scrolled_window,
892 VIEW_CONTACTS);
893
894 /* history view/model */
895 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
896 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400897 /* make headers visible to allow column resizing */
898 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400899 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_history);
900 gtk_widget_show_all(scrolled_window);
901 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
902 scrolled_window,
903 VIEW_HISTORY);
904 /* TODO: make this linked to the client settings so that the last shown view is the same on startup */
905 gtk_stack_set_visible_child(GTK_STACK(priv->stack_contacts_history_presence),
906 scrolled_window);
907
Stepan Salenikovich15142182015-03-11 17:15:26 -0400908
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400909 /* sort the history in descending order by date */
Stepan Salenikovichdd84cf92015-03-19 21:38:19 -0400910 QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(CategorizedHistoryModel::instance());
911 proxyModel->setSourceModel(CategorizedHistoryModel::instance());
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400912 proxyModel->setSortRole(static_cast<int>(Call::Role::Date));
913 proxyModel->sort(0,Qt::DescendingOrder);
914
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400915 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
916 (QSortFilterProxyModel *)proxyModel,
917 5,
Stepan Salenikovich15142182015-03-11 17:15:26 -0400918 Qt::DisplayRole, G_TYPE_STRING,
919 Call::Role::Number, G_TYPE_STRING,
920 Call::Role::FormattedDate, G_TYPE_STRING,
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400921 Call::Role::Direction, G_TYPE_INT,
922 Call::Role::Missed, G_TYPE_BOOLEAN);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400923 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
924
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400925 /* name column, also used for call direction and fuzzy date for top level items */
926 column = gtk_tree_view_column_new();
927 gtk_tree_view_column_set_title(column, "Name");
928
929 /* call direction */
930 renderer = gtk_cell_renderer_text_new();
931 gtk_tree_view_column_pack_start(column, renderer, FALSE);
932
933 /* display the call direction with arrows */
934 gtk_tree_view_column_set_cell_data_func(
935 column,
936 renderer,
937 (GtkTreeCellDataFunc)render_call_direction,
938 NULL,
939 NULL);
940
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400941 /* name or time category column */
942 renderer = gtk_cell_renderer_text_new();
943 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400944 gtk_tree_view_column_pack_start(column, renderer, FALSE);
945 gtk_tree_view_column_set_attributes(column, renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400946 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400947 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400948
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400949 /* "number" column */
950 renderer = gtk_cell_renderer_text_new();
951 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
952 column = gtk_tree_view_column_new_with_attributes("Number", renderer, "text", 1, NULL);
953 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
954 gtk_tree_view_column_set_resizable(column, TRUE);
955
956 /* date column */
Stepan Salenikovich15142182015-03-11 17:15:26 -0400957 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400958 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400959 column = gtk_tree_view_column_new_with_attributes ("Date", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400960 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
961 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400962
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400963 /* expand the first row, which should be the most recent calls */
964 gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_history),
965 gtk_tree_path_new_from_string("0"),
966 FALSE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400967
968 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(call_history_item), NULL);
969
970 /* presence view/model */
971 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
972 GtkWidget *treeview_presence = gtk_tree_view_new();
973 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_presence), FALSE);
974 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_presence);
975 gtk_widget_show_all(scrolled_window);
976 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
977 scrolled_window,
978 VIEW_PRESENCE);
979
980 /* connect signals to change the contacts/history/presence stack view */
981 g_signal_connect(priv->radiobutton_contacts, "toggled", G_CALLBACK(navbutton_contacts_toggled), win);
982 g_signal_connect(priv->radiobutton_history, "toggled", G_CALLBACK(navbutton_history_toggled), win);
983 g_signal_connect(priv->radiobutton_presence, "toggled", G_CALLBACK(navbutton_presence_toggled), win);
984
Stepan Salenikovich69771842015-02-24 18:11:45 -0500985 /* TODO: replace stack paceholder view */
986 GtkWidget *placeholder_view = gtk_tree_view_new();
987 gtk_widget_show(placeholder_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400988 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), placeholder_view, DEFAULT_VIEW_NAME);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500989
990 /* connect signals */
991 g_signal_connect(call_selection, "changed", G_CALLBACK(call_selection_changed), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500992 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
993 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
994
995 /* style of search entry */
996 gtk_widget_override_font(priv->search_entry, pango_font_description_from_string("monospace 15"));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500997
998 /* connect to incoming call and focus */
999 QObject::connect(
1000 CallModel::instance(),
1001 &CallModel::incomingCall,
1002 [=](Call* call) {
1003 CallModel::instance()->selectionModel()->setCurrentIndex(
1004 CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect);
1005 }
1006 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001007
1008 /* display ring id by first getting the active ring account */
1009 gtk_widget_override_font(priv->entry_ring_id, pango_font_description_from_string("monospace"));
1010 gtk_widget_set_size_request(priv->entry_ring_id, 400, 35);
1011 get_active_ring_account(win);
1012 QObject::connect(
1013 AccountModel::instance(),
1014 &AccountModel::dataChanged,
1015 [=] () {
1016 /* check if the active ring account has changed,
1017 * eg: if it was deleted */
1018 get_active_ring_account(win);
1019 }
1020 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001021}
1022
1023static void
1024ring_main_window_finalize(GObject *object)
1025{
1026 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001027}
1028
1029static void
1030ring_main_window_class_init(RingMainWindowClass *klass)
1031{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001032 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
1033
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001034 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1035 "/cx/ring/RingGnome/ringmainwindow.ui");
1036
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001037 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, treeview_call);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001038 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_contacts_history_presence);
1039 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_contacts);
1040 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_history);
1041 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_presence);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001042 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1043 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1044 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1045 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
1046 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_search);
1047 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001048 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001049 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001050 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
1051 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_call_view);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001052 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001053 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_audio_settings);
1054 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
1055 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_video_settings);
1056 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001057 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_ring_id);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001058
1059 /* account creation */
1060 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
1061 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
1062 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
1063 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
1064 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1065 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1066 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
1067
1068 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
1069 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
1070 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001071}
1072
1073GtkWidget *
1074ring_main_window_new (GtkApplication *app)
1075{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001076 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1077
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001078 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001079}