blob: 99111835c1467f6f881fb68da7ffb8cf3a2fc9ea [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 Salenikovichfb5ff0a2015-03-29 22:47:47 -040051#include "defines.h"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040052
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040053#define CALL_VIEW_NAME "calls"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040054#define CREATE_ACCOUNT_1_VIEW_NAME "create1"
55#define CREATE_ACCOUNT_2_VIEW_NAME "create2"
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040056#define GENERAL_SETTINGS_VIEW_NAME "general"
57#define AUDIO_SETTINGS_VIEW_NAME "audio"
58#define VIDEO_SETTINGS_VIEW_NAME "video"
59#define ACCOUNT_SETTINGS_VIEW_NAME "accounts"
Stepan Salenikovich69771842015-02-24 18:11:45 -050060#define DEFAULT_VIEW_NAME "placeholder"
Stepan Salenikovich15142182015-03-11 17:15:26 -040061#define VIEW_CONTACTS "contacts"
62#define VIEW_HISTORY "history"
63#define VIEW_PRESENCE "presence"
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050064
65struct _RingMainWindow
66{
67 GtkApplicationWindow parent;
68};
69
70struct _RingMainWindowClass
71{
72 GtkApplicationWindowClass parent_class;
73};
74
75typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
76
77struct _RingMainWindowPrivate
78{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040079 GtkWidget *ring_menu;
80 GtkWidget *image_ring;
81 GtkWidget *ring_settings;
82 GtkWidget *image_settings;
83 GtkWidget *hbox_search;
84 GtkWidget *hbox_settings;
Stepan Salenikovich15142182015-03-11 17:15:26 -040085 GtkWidget *stack_contacts_history_presence;
86 GtkWidget *radiobutton_contacts;
87 GtkWidget *radiobutton_history;
88 GtkWidget *radiobutton_presence;
Stepan Salenikovicha3557452015-02-20 14:14:12 -050089 GtkWidget *treeview_call;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -050090 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -050091 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040092 GtkWidget *vbox_call_view;
93 GtkWidget *stack_call_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050094 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040095 GtkWidget *account_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -040096 GtkWidget *video_settings_view;
97 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040098 GtkWidget *radiobutton_audio_settings;
99 GtkWidget *radiobutton_general_settings;
100 GtkWidget *radiobutton_video_settings;
101 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400102 GtkWidget *label_ring_id;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400103
104 Account *active_ring_account;
105 QMetaObject::Connection active_ring_account_updates;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400106
107 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400108
109 /* account creation */
110 GtkWidget *account_creation_1;
111 GtkWidget *image_ring_logo;
112 GtkWidget *label_enter_alias;
113 GtkWidget *entry_alias;
114 GtkWidget *label_generating_account;
115 GtkWidget *spinner_generating_account;
116 GtkWidget *button_account_creation_next;
117 GtkWidget *account_creation_2;
118 GtkWidget *entry_hash;
119 GtkWidget *button_account_creation_done;
120
121 QMetaObject::Connection hash_updated;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500122};
123
124G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
125
126#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
127
Stepan Salenikovich69771842015-02-24 18:11:45 -0500128static QModelIndex
129get_index_from_selection(GtkTreeSelection *selection)
130{
131 GtkTreeIter iter;
132 GtkTreeModel *model = NULL;
133
134 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400135 if (GTK_IS_Q_TREE_MODEL(model))
136 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
137 else if (GTK_IS_Q_SORT_FILTER_TREE_MODEL(model))
138 return gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500139 }
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400140 return QModelIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500141}
142
143static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500144update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500145{
Stepan Salenikovich69771842015-02-24 18:11:45 -0500146 QModelIndex current = get_index_from_selection(selection);
147 if (current.isValid())
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500148 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500149 else
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500150 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500151}
152
153static void
154call_selection_changed(GtkTreeSelection *selection, gpointer win)
155{
156 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
157
158 /* get the current visible stack child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400159 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500160
161 QModelIndex idx = get_index_from_selection(selection);
162 if (idx.isValid()) {
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400163 QVariant state = CallModel::instance()->data(idx, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500164 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500165 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500166
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500167 /* FIXME: change when fixed
168 * switch(state.value<Call::LifeCycleState>()) { */
169 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
170 switch(lifecyclestate) {
171 case Call::LifeCycleState::INITIALIZATION:
172 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500173 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500174 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500175 /* use the pointer of the call as a unique name */
176 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance()->getCall(idx));
177 break;
178 case Call::LifeCycleState::PROGRESS:
179 new_call_view = current_call_view_new();
180 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
181 /* use the pointer of the call as a unique name */
182 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx));
183 break;
184 case Call::LifeCycleState::COUNT__:
185 g_warning("LifeCycleState should never be COUNT");
186 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500187 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500188
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400189 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
190 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500191 g_free(new_call_view_name);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500192
Stepan Salenikovich69771842015-02-24 18:11:45 -0500193 } else {
194 /* nothing selected in the call model, so show the default screen */
195
196 /* TODO: replace stack paceholder view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400197 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
198 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_call_view), DEFAULT_VIEW_NAME);
199 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500200
201 }
202
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500203 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400204 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500205 if (current_call_view != old_call_view && old_call_view != NULL) {
206 /* if the previous child was a call view, then remove it from
Stepan Salenikovich69771842015-02-24 18:11:45 -0500207 * the stack; removing it should destory it since there should not
208 * be any other references to it */
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500209 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400210 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500211 }
212 }
213}
214
215static void
216call_state_changed(Call *call, gpointer win)
217{
218 g_debug("call state changed");
219 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
220
221 /* check if the call that changed state is the same as the selected call */
222 QModelIndex idx_selected = CallModel::instance()->selectionModel()->currentIndex();
223
224 if( idx_selected.isValid() && call == CallModel::instance()->getCall(idx_selected)) {
225 g_debug("selected call state changed");
226 /* check if we need to change the view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400227 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500228 GtkWidget *new_call_view = NULL;
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400229 QVariant state = CallModel::instance()->data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500230
231 /* check what the current state is vs what is displayed */
232
233 /* FIXME: change when fixed
234 * switch(state.value<Call::LifeCycleState>()) { */
235 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
236 switch(lifecyclestate) {
237 case Call::LifeCycleState::INITIALIZATION:
238 /* LifeCycleState cannot go backwards, so it should not be possible
239 * that the call is displayed as current (meaning that its in progress)
240 * but have the state 'initialization' */
241 if (IS_CURRENT_CALL_VIEW(old_call_view))
242 g_warning("call displayed as current, but is in state of initialization");
243 break;
244 case Call::LifeCycleState::PROGRESS:
245 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
246 /* change from incoming to current */
247 new_call_view = current_call_view_new();
248 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
249 /* use the pointer of the call as a unique name */
250 char* new_call_view_name = NULL;
251 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx_selected));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400252 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500253 g_free(new_call_view_name);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400254 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
255 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
256 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500257 }
258 break;
259 case Call::LifeCycleState::FINISHED:
260 /* do nothing, either call view is valid for this state */
261 break;
262 case Call::LifeCycleState::COUNT__:
263 g_warning("LifeCycleState should never be COUNT");
264 break;
265 }
266
267 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400268 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500269 if (current_call_view != old_call_view && old_call_view != NULL) {
270 /* if the previous child was a call view, then remove it from
271 * the stack; removing it should destory it since there should not
272 * be any other references to it */
273 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400274 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500275 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500276 }
277 }
278}
279
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500280static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500281search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
282{
283 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
284
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400285 const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500286
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400287 if (number_entered && strlen(number_entered) > 0) {
288 /* detect Ring hash */
289 gboolean is_ring_hash = FALSE;
290 if (strlen(number_entered) == 40) {
291 is_ring_hash = TRUE;
292 /* must be 40 chars long and alphanumeric */
293 for (int i = 0; i < 40 && is_ring_hash; ++i) {
294 if (!g_ascii_isalnum(number_entered[i]))
295 is_ring_hash = FALSE;
296 }
297 }
298
299 QString number = QString{number_entered};
300
301 if (is_ring_hash)
302 number = "ring:" + number;
303
304 g_debug("dialing to number: %s", number.toUtf8().constData());
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500305 Call *call = CallModel::instance()->dialingCall();
306 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500307 call->performAction(Call::Action::ACCEPT);
308
309 /* make this the currently selected call */
310 QModelIndex idx = CallModel::instance()->getIndex(call);
311 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
312 }
313}
314
315static void
Stepan Salenikovich15142182015-03-11 17:15:26 -0400316call_history_item(GtkTreeView *tree_view,
317 GtkTreePath *path,
318 G_GNUC_UNUSED GtkTreeViewColumn *column,
319 G_GNUC_UNUSED gpointer user_data)
320{
321 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
322
323 /* get iter */
324 GtkTreeIter iter;
325 if (gtk_tree_model_get_iter(model, &iter, path)) {
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400326 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 -0400327
328 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
329 /* create new call */
330 if (contact_method.value<ContactMethod*>()) {
331 Call *call = CallModel::instance()->dialingCall();
332 call->setDialNumber(contact_method.value<ContactMethod*>());
333 call->performAction(Call::Action::ACCEPT);
334
335 /* make this the currently selected call */
336 QModelIndex call_idx = CallModel::instance()->getIndex(call);
337 CallModel::instance()->selectionModel()->setCurrentIndex(call_idx, QItemSelectionModel::ClearAndSelect);
338 } else
339 g_warning("contact method is empty");
340 }
341}
342
343static void
344navbutton_contacts_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
345{
346 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
347 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
348
349 if (gtk_toggle_button_get_active(navbutton)) {
350
351 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
352
353 if (visible) {
354 /* contacts is left of both history and presence, so always slide right to show it */
355 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
356 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
357 } else {
358 g_warning("should always have a visible child in the nav stack");
359 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
360 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
361 }
362 }
363}
364
365static void
366navbutton_presence_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
367{
368 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
369 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
370
371 if (gtk_toggle_button_get_active(navbutton)) {
372
373 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
374 if (visible) {
375 /* presence is right of both history and contacts, so always slide left to show it */
376 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
377 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
378 } else {
379 g_warning("should always have a visible child in the nav stack");
380 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
381 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
382 }
383 }
384}
385
386static void
387navbutton_history_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
388{
389 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
390 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
391
392 if (gtk_toggle_button_get_active(navbutton)) {
393
394 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
395 if (visible) {
396 if (strcmp(visible, VIEW_CONTACTS) == 0) {
397 /* history is right of contacts, so slide left to show it */
398 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
399 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
400 } else if (strcmp(visible, VIEW_PRESENCE) == 0) {
401 /* history is left of presence, so slide right to show it */
402 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
403 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
404 }
405 } else {
406 g_warning("should always have a visible child in the nav stack");
407 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
408 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
409 }
410 }
411}
412
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400413static gboolean
414save_accounts(GtkWidget *working_dialog)
415{
416 /* save changes to accounts */
417 AccountModel::instance()->save();
418 /* save changes to codecs */
419 for (int i = 0; i < AccountModel::instance()->rowCount(); i++) {
420 QModelIndex idx = AccountModel::instance()->index(i, 0);
421 AccountModel::instance()->getAccountByModelIndex(idx)->codecModel()->save();
422 }
423
424 if (working_dialog)
425 gtk_widget_destroy(working_dialog);
426
427 return G_SOURCE_REMOVE;
428}
429
Stepan Salenikovich15142182015-03-11 17:15:26 -0400430static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400431settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
432{
433 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
434 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
435
436 /* toggle show settings */
437 priv->show_settings = !priv->show_settings;
438
439 /* check which view to show */
440 if (priv->show_settings) {
441 /* show settings */
442 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
443
444 gtk_widget_hide(priv->hbox_search);
445 gtk_widget_show(priv->hbox_settings);
446
447 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400448 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400449 } else {
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400450 /* show working dialog in case save operation takes time */
451 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
452 gtk_window_present(GTK_WINDOW(working));
453
454 /* now save after the time it takes to transition back to the call view (400ms)
455 * the save doesn't happen before the "working" dialog is presented
456 * the timeout function should destroy the "working" dialog when done saving
457 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400458 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400459
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400460 /* show calls */
461 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
462
463 gtk_widget_show(priv->hbox_search);
464 gtk_widget_hide(priv->hbox_settings);
465
466 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
467 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400468
469 /* make sure video preview is stopped, in case it was started */
470 Video::PreviewManager::instance()->stopPreview();
471 }
472}
473
474static void
475show_video_settings(GtkToggleButton *navbutton, RingMainWindow *win)
476{
477 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
478 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
479
480 if (gtk_toggle_button_get_active(navbutton)) {
481 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
482 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), VIDEO_SETTINGS_VIEW_NAME);
483 priv->last_settings_view = priv->video_settings_view;
484 }
485}
486
487static void
488show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
489{
490 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
491 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
492
493 if (gtk_toggle_button_get_active(navbutton)) {
494 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
495 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
496 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400497 }
498}
499
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400500static gboolean
501has_ring_account()
502{
503 /* check if a Ring account exists */
504 int a_count = AccountModel::instance()->rowCount();
505 for (int i = 0; i < a_count; ++i) {
506 QModelIndex idx = AccountModel::instance()->index(i, 0);
507 QVariant protocol = idx.data(static_cast<int>(Account::Role::Proto));
508 if ((Account::Protocol)protocol.toUInt() == Account::Protocol::RING)
509 return TRUE;
510 }
511
512 return FALSE;
513}
514
515static gboolean
516create_ring_account(RingMainWindow *win)
517{
518 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
519 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
520
521 /* create account and set UPnP enabled, as its not by default in the daemon */
522 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
523 Account *account = AccountModel::instance()->add(alias, Account::Protocol::RING);
524 account->setUpnpEnabled(TRUE);
525
526 /* wait for hash to be generated to show the next view */
527 priv->hash_updated = QObject::connect(
528 account,
529 &Account::changed,
530 [=] (Account *a) {
531 QString hash = a->username();
532 if (!hash.isEmpty()) {
533 /* set the hash */
534 gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());
535
536 /* show the next accont creation view */
537 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
538 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);
539
540 /* select the hash text */
541 gtk_widget_grab_focus(priv->entry_hash);
542 gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
543 }
544 }
545 );
546
547 account->performAction(Account::EditAction::SAVE);
548
549 return G_SOURCE_REMOVE;
550}
551
552static void
553alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
554{
555 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
556
557 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
558 if (strlen(alias) > 0) {
559 /* enable "next" button */
560 gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
561 } else {
562 /* disable "next" button, as we require an alias */
563 gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
564 }
565}
566
567static void
568account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
569{
570 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
571
572 /* show/hide relevant widgets */
573 gtk_widget_hide(priv->label_enter_alias);
574 gtk_widget_hide(priv->entry_alias);
575 gtk_widget_hide(priv->button_account_creation_next);
576 gtk_widget_show(priv->label_generating_account);
577 gtk_widget_show(priv->spinner_generating_account);
578
579 /* now create account after a short timeout so that the the save doesn't
580 * happen freeze the client before the widget changes happen;
581 * the timeout function should then display the next step in account creation
582 */
583 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
584}
585
586static void
587account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
588{
589 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
590
591 QObject::disconnect(priv->hash_updated);
592
593 /* show the call view */
594 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
595 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
596
597 /* show the search bar and settings */
598 gtk_widget_show(priv->hbox_search);
599 gtk_widget_show(priv->ring_settings);
600
601}
602
603static void
604entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
605{
606 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
607
608 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
609 if (strlen(alias) > 0)
610 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
611}
612
613static void
614show_account_creation(RingMainWindow *win)
615{
616 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
617
618 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
619 priv->account_creation_1,
620 CREATE_ACCOUNT_1_VIEW_NAME);
621
622 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
623 priv->account_creation_2,
624 CREATE_ACCOUNT_2_VIEW_NAME);
625
626 /* hide search bar and settings until account creation is complete */
627 gtk_widget_hide(priv->hbox_search);
628 gtk_widget_hide(priv->ring_settings);
629
630 /* set ring logo */
631 GError *error = NULL;
632 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
633 -1, 75, TRUE, &error);
634 if (logo_ring == NULL) {
635 g_debug("Could not load logo: %s", error->message);
636 g_error_free(error);
637 } else
638 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
639
640 /* style of alias and hash entry; give them a larger font */
641 gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
642 gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));
643
644 /* connect signals */
645 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
646 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
647 g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
648 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
649 g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);
650
651 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
652}
653
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400654static void
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400655show_ring_id(RingMainWindow *win, Account *account) {
656 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
657
658 /* display the ring id, if we found a ring account */
659 if (account) {
660 if (!account->username().isEmpty()) {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400661 QString hash = "<span fgcolor=\"black\">" + account->username() + "</span>";
662 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), hash.toUtf8().constData());
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400663 } else {
664 g_warning("got ring account, but Ring id is empty");
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400665 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), "<span fgcolor=\"gray\">fetching Ring ID...</span>");
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400666 }
667 } else {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400668 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), "<span fgcolor=\"gray\">no Ring account</span>");
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400669 }
670
671}
672
673static void
674get_active_ring_account(RingMainWindow *win)
675{
676 /* get the users Ring account
677 * if multiple accounts exist, get the first one which is registered,
678 * if none, then the first one which is enabled,
679 * if none, then the first one in the list of ring accounts
680 */
681 Account *registered_account = NULL;
682 Account *enabled_account = NULL;
683 Account *ring_account = NULL;
684 int a_count = AccountModel::instance()->rowCount();
685 for (int i = 0; i < a_count && !registered_account; ++i) {
686 QModelIndex idx = AccountModel::instance()->index(i, 0);
687 Account *account = AccountModel::instance()->getAccountByModelIndex(idx);
688 if (account->protocol() == Account::Protocol::RING) {
689 /* got RING account, check if active */
690 if (account->isEnabled()) {
691 /* got enabled account, check if connected */
692 if (account->registrationState() == Account::RegistrationState::READY) {
693 /* got registered account, use this one */
694 registered_account = enabled_account = ring_account = account;
695 // g_debug("got registered account: %s", ring_account->alias().toUtf8().constData());
696 } else {
697 /* not registered, but enabled, use if its the first one */
698 if (!enabled_account) {
699 enabled_account = ring_account = account;
700 // g_debug("got enabled ring accout: %s", ring_account->alias().toUtf8().constData());
701 }
702 }
703 } else {
704 /* not enabled, but a Ring account, use if its the first one */
705 if (!ring_account) {
706 ring_account = account;
707 // g_debug("got ring account: %s", ring_account->alias().toUtf8().constData());
708 }
709 }
710 }
711 }
712
713 show_ring_id(win, ring_account);
714}
715
716static void
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400717render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
718 GtkCellRenderer *cell,
719 GtkTreeModel *tree_model,
720 GtkTreeIter *iter,
721 G_GNUC_UNUSED gpointer data)
722{
723 /* check if this is a top level item (the fuzzy date item),
724 * in this case we don't want to show a call direction */
725 gchar *render_direction = NULL;
726 GtkTreeIter parent;
727 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
728 /* get direction and missed values */
729 GValue value = G_VALUE_INIT;
730 gtk_tree_model_get_value(tree_model, iter, 3, &value);
731 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
732 g_value_unset(&value);
733
734 gtk_tree_model_get_value(tree_model, iter, 4, &value);
735 gboolean missed = g_value_get_boolean(&value);
736 g_value_unset(&value);
737
738 switch (direction) {
739 case Call::Direction::INCOMING:
740 if (missed)
741 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
742 else
743 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
744 break;
745 case Call::Direction::OUTGOING:
746 if (missed)
747 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
748 else
749 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
750 break;
751 }
752 }
753 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
754 g_free(render_direction);
755}
756
757static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400758copy_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
759{
760 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
761 QModelIndex idx = get_index_from_selection(selection);
762
763 if (idx.isValid()) {
764 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
765
766 const gchar* number = idx.data(static_cast<int>(Call::Role::Number)).toString().toUtf8().constData();
767 gtk_clipboard_set_text(clip, number, -1);
768 }
769}
770
771/* TODO: can't seem to delete just one item for now, add when supported in backend
772 * static void
773 * delete_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
774 * {
775 * GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
776 * QModelIndex idx = get_index_from_selection(selection);
777 *
778 * if (idx.isValid()) {
779 * g_debug("deleting history item");
780 * CategorizedHistoryModel::instance()->removeRow(idx.row(), idx.parent());
781 * }
782 * }
783 */
784
785static gboolean
786history_popup_menu(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, GtkTreeView *treeview)
787{
788 /* build popup menu when right clicking on history item
789 * user should be able to copy the "number",
790 * delete history item or all of the history,
791 * and eventualy add the number to a contact
792 */
793
794 /* check for right click */
795 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
796 return FALSE;
797
798 GtkWidget *menu = gtk_menu_new();
799
800 /* copy */
801 GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Copy");
802 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
803 g_signal_connect(item, "activate", G_CALLBACK(copy_history_item), treeview);
804
805 /* TODO: delete history entry
806 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
807 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
808 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
809 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
810 */
811
812 /* show menu */
813 gtk_widget_show_all(menu);
814 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
815
816 return FALSE; /* continue to default handler */
817}
818
819static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500820ring_main_window_init(RingMainWindow *win)
821{
822 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
823 gtk_widget_init_template(GTK_WIDGET(win));
824
825 /* set window icon */
826 GError *error = NULL;
827 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
828 if (icon == NULL) {
829 g_debug("Could not load icon: %s", error->message);
830 g_error_free(error);
831 } else
832 gtk_window_set_icon(GTK_WINDOW(win), icon);
833
834 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400835 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
836 -1, 24, TRUE, &error);
837 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500838 g_debug("Could not load icon: %s", error->message);
839 g_error_free(error);
840 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400841 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500842
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400843 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500844 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
845 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400846 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500847 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500848
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400849 /* settings icon */
850 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
851
852 /* connect settings button signal */
853 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
854
855 /* add the call view to the main stack */
856 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
857 priv->vbox_call_view,
858 CALL_VIEW_NAME);
859
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400860 if (has_ring_account()) {
861 /* user has ring account, so show the call view right away */
862 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
863 } else {
864 /* user has to create the ring account */
865 show_account_creation(win);
866 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400867
868 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400869 priv->account_settings_view = account_view_new();
870 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
871
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400872 priv->video_settings_view = video_settings_view_new();
873 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->video_settings_view, VIDEO_SETTINGS_VIEW_NAME);
874
875 /* make the setting we will show first the active one */
876 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_video_settings), TRUE);
877 priv->last_settings_view = priv->video_settings_view;
878
879 /* connect the settings button signals to switch settings views */
880 g_signal_connect(priv->radiobutton_video_settings, "toggled", G_CALLBACK(show_video_settings), win);
881 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
882
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500883 /* call model */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500884 GtkQTreeModel *call_model;
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500885 GtkCellRenderer *renderer;
886 GtkTreeViewColumn *column;
887
Stepan Salenikovich69771842015-02-24 18:11:45 -0500888 call_model = gtk_q_tree_model_new(CallModel::instance(), 4,
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500889 Call::Role::Name, G_TYPE_STRING,
890 Call::Role::Number, G_TYPE_STRING,
891 Call::Role::Length, G_TYPE_STRING,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400892 Call::Role::State, G_TYPE_STRING);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500893 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_call), GTK_TREE_MODEL(call_model));
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500894
895 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400896 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500897 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400898 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500899 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
900
901 renderer = gtk_cell_renderer_text_new();
902 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400903 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500904 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
905
Stepan Salenikovich69771842015-02-24 18:11:45 -0500906 /* connect signals to and from UserActionModel to sync selection betwee
907 * the QModel and the GtkTreeView */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500908 QObject::connect(
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500909 CallModel::instance()->selectionModel(),
Stepan Salenikovich69771842015-02-24 18:11:45 -0500910 &QItemSelectionModel::currentChanged,
911 [=](const QModelIndex & current, const QModelIndex & previous) {
912 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
913
914 /* first unselect the previous */
915 if (previous.isValid()) {
916 GtkTreeIter old_iter;
917 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
918 gtk_tree_selection_unselect_iter(selection, &old_iter);
919 } else {
920 g_warning("Trying to unselect invalid GtkTreeIter");
921 }
922 }
923
924 /* select the current */
925 if (current.isValid()) {
926 GtkTreeIter new_iter;
927 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
928 gtk_tree_selection_select_iter(selection, &new_iter);
929 } else {
930 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
931 }
932 }
933 }
934 );
935
936 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500937 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500938
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500939 /* connect to call state changes to update relevant view(s) */
940 QObject::connect(
941 CallModel::instance(),
942 &CallModel::callStateChanged,
943 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
944 call_state_changed(call, win);
945 }
946 );
947
Stepan Salenikovich15142182015-03-11 17:15:26 -0400948 /* contacts view/model */
949 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
950 GtkWidget *treeview_contacts = gtk_tree_view_new();
951 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_contacts), FALSE);
952 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_contacts);
953 gtk_widget_show_all(scrolled_window);
954 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
955 scrolled_window,
956 VIEW_CONTACTS);
957
958 /* history view/model */
959 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
960 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400961 /* make headers visible to allow column resizing */
962 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400963 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_history);
964 gtk_widget_show_all(scrolled_window);
965 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
966 scrolled_window,
967 VIEW_HISTORY);
968 /* TODO: make this linked to the client settings so that the last shown view is the same on startup */
969 gtk_stack_set_visible_child(GTK_STACK(priv->stack_contacts_history_presence),
970 scrolled_window);
971
Stepan Salenikovich15142182015-03-11 17:15:26 -0400972
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400973 /* sort the history in descending order by date */
Stepan Salenikovichdd84cf92015-03-19 21:38:19 -0400974 QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(CategorizedHistoryModel::instance());
975 proxyModel->setSourceModel(CategorizedHistoryModel::instance());
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400976 proxyModel->setSortRole(static_cast<int>(Call::Role::Date));
977 proxyModel->sort(0,Qt::DescendingOrder);
978
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400979 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
980 (QSortFilterProxyModel *)proxyModel,
981 5,
Stepan Salenikovich15142182015-03-11 17:15:26 -0400982 Qt::DisplayRole, G_TYPE_STRING,
983 Call::Role::Number, G_TYPE_STRING,
984 Call::Role::FormattedDate, G_TYPE_STRING,
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400985 Call::Role::Direction, G_TYPE_INT,
986 Call::Role::Missed, G_TYPE_BOOLEAN);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400987 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
988
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400989 /* name column, also used for call direction and fuzzy date for top level items */
990 column = gtk_tree_view_column_new();
991 gtk_tree_view_column_set_title(column, "Name");
992
993 /* call direction */
994 renderer = gtk_cell_renderer_text_new();
995 gtk_tree_view_column_pack_start(column, renderer, FALSE);
996
997 /* display the call direction with arrows */
998 gtk_tree_view_column_set_cell_data_func(
999 column,
1000 renderer,
1001 (GtkTreeCellDataFunc)render_call_direction,
1002 NULL,
1003 NULL);
1004
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001005 /* name or time category column */
1006 renderer = gtk_cell_renderer_text_new();
1007 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001008 gtk_tree_view_column_pack_start(column, renderer, FALSE);
1009 gtk_tree_view_column_set_attributes(column, renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001010 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001011 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001012
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001013 /* "number" column */
1014 renderer = gtk_cell_renderer_text_new();
1015 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1016 column = gtk_tree_view_column_new_with_attributes("Number", renderer, "text", 1, NULL);
1017 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
1018 gtk_tree_view_column_set_resizable(column, TRUE);
1019
1020 /* date column */
Stepan Salenikovich15142182015-03-11 17:15:26 -04001021 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001022 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001023 column = gtk_tree_view_column_new_with_attributes ("Date", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001024 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
1025 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001026
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001027 /* expand the first row, which should be the most recent calls */
1028 gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_history),
1029 gtk_tree_path_new_from_string("0"),
1030 FALSE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001031
1032 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(call_history_item), NULL);
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -04001033 g_signal_connect(treeview_history, "button-press-event", G_CALLBACK(history_popup_menu), treeview_history);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001034
1035 /* presence view/model */
1036 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1037 GtkWidget *treeview_presence = gtk_tree_view_new();
1038 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_presence), FALSE);
1039 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_presence);
1040 gtk_widget_show_all(scrolled_window);
1041 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1042 scrolled_window,
1043 VIEW_PRESENCE);
1044
1045 /* connect signals to change the contacts/history/presence stack view */
1046 g_signal_connect(priv->radiobutton_contacts, "toggled", G_CALLBACK(navbutton_contacts_toggled), win);
1047 g_signal_connect(priv->radiobutton_history, "toggled", G_CALLBACK(navbutton_history_toggled), win);
1048 g_signal_connect(priv->radiobutton_presence, "toggled", G_CALLBACK(navbutton_presence_toggled), win);
1049
Stepan Salenikovich69771842015-02-24 18:11:45 -05001050 /* TODO: replace stack paceholder view */
1051 GtkWidget *placeholder_view = gtk_tree_view_new();
1052 gtk_widget_show(placeholder_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001053 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), placeholder_view, DEFAULT_VIEW_NAME);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001054
1055 /* connect signals */
1056 g_signal_connect(call_selection, "changed", G_CALLBACK(call_selection_changed), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001057 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
1058 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
1059
1060 /* style of search entry */
1061 gtk_widget_override_font(priv->search_entry, pango_font_description_from_string("monospace 15"));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001062
1063 /* connect to incoming call and focus */
1064 QObject::connect(
1065 CallModel::instance(),
1066 &CallModel::incomingCall,
1067 [=](Call* call) {
1068 CallModel::instance()->selectionModel()->setCurrentIndex(
1069 CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect);
1070 }
1071 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001072
1073 /* display ring id by first getting the active ring account */
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001074 gtk_widget_override_font(priv->label_ring_id, pango_font_description_from_string("monospace"));
1075 gtk_widget_set_size_request(priv->label_ring_id, 400, 35);
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001076 get_active_ring_account(win);
1077 QObject::connect(
1078 AccountModel::instance(),
1079 &AccountModel::dataChanged,
1080 [=] () {
1081 /* check if the active ring account has changed,
1082 * eg: if it was deleted */
1083 get_active_ring_account(win);
1084 }
1085 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001086}
1087
1088static void
1089ring_main_window_finalize(GObject *object)
1090{
1091 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001092}
1093
1094static void
1095ring_main_window_class_init(RingMainWindowClass *klass)
1096{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001097 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
1098
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001099 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1100 "/cx/ring/RingGnome/ringmainwindow.ui");
1101
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001102 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, treeview_call);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001103 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_contacts_history_presence);
1104 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_contacts);
1105 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_history);
1106 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_presence);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001107 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1108 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1109 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1110 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
1111 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_search);
1112 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001113 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001114 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001115 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
1116 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_call_view);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001117 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001118 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_audio_settings);
1119 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
1120 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_video_settings);
1121 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001122 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_ring_id);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001123
1124 /* account creation */
1125 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
1126 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
1127 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
1128 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
1129 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1130 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1131 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
1132
1133 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
1134 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
1135 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001136}
1137
1138GtkWidget *
1139ring_main_window_new (GtkApplication *app)
1140{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001141 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1142
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001143 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001144}