blob: e89f6bb6d66a766dba5e5e10b02538ad1f27a14e [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 Salenikovich6f687072015-03-26 10:43:37 -040052#include <categorizedcontactmodel.h>
53#include <personmodel.h>
54#include "utils/drawing.h"
55#include <memory>
56#include "delegates/pixbufdelegate.h"
57#include "models/activeitemproxymodel.h"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040058
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040059#define CALL_VIEW_NAME "calls"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040060#define CREATE_ACCOUNT_1_VIEW_NAME "create1"
61#define CREATE_ACCOUNT_2_VIEW_NAME "create2"
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040062#define GENERAL_SETTINGS_VIEW_NAME "general"
63#define AUDIO_SETTINGS_VIEW_NAME "audio"
64#define VIDEO_SETTINGS_VIEW_NAME "video"
65#define ACCOUNT_SETTINGS_VIEW_NAME "accounts"
Stepan Salenikovich69771842015-02-24 18:11:45 -050066#define DEFAULT_VIEW_NAME "placeholder"
Stepan Salenikovich15142182015-03-11 17:15:26 -040067#define VIEW_CONTACTS "contacts"
68#define VIEW_HISTORY "history"
69#define VIEW_PRESENCE "presence"
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050070
71struct _RingMainWindow
72{
73 GtkApplicationWindow parent;
74};
75
76struct _RingMainWindowClass
77{
78 GtkApplicationWindowClass parent_class;
79};
80
81typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
82
83struct _RingMainWindowPrivate
84{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040085 GtkWidget *ring_menu;
86 GtkWidget *image_ring;
87 GtkWidget *ring_settings;
88 GtkWidget *image_settings;
89 GtkWidget *hbox_search;
90 GtkWidget *hbox_settings;
Stepan Salenikovich15142182015-03-11 17:15:26 -040091 GtkWidget *stack_contacts_history_presence;
92 GtkWidget *radiobutton_contacts;
93 GtkWidget *radiobutton_history;
94 GtkWidget *radiobutton_presence;
Stepan Salenikovicha3557452015-02-20 14:14:12 -050095 GtkWidget *treeview_call;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -050096 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -050097 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040098 GtkWidget *vbox_call_view;
99 GtkWidget *stack_call_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500100 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400101 GtkWidget *account_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400102 GtkWidget *video_settings_view;
103 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400104 GtkWidget *radiobutton_audio_settings;
105 GtkWidget *radiobutton_general_settings;
106 GtkWidget *radiobutton_video_settings;
107 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400108 GtkWidget *label_ring_id;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400109
110 Account *active_ring_account;
111 QMetaObject::Connection active_ring_account_updates;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400112
113 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400114
115 /* account creation */
116 GtkWidget *account_creation_1;
117 GtkWidget *image_ring_logo;
118 GtkWidget *label_enter_alias;
119 GtkWidget *entry_alias;
120 GtkWidget *label_generating_account;
121 GtkWidget *spinner_generating_account;
122 GtkWidget *button_account_creation_next;
123 GtkWidget *account_creation_2;
124 GtkWidget *entry_hash;
125 GtkWidget *button_account_creation_done;
126
127 QMetaObject::Connection hash_updated;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400128
129 /* allocd qmodels */
130 ActiveItemProxyModel *q_contact_model;
131 QSortFilterProxyModel *q_history_model;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500132};
133
134G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
135
136#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
137
Stepan Salenikovich69771842015-02-24 18:11:45 -0500138static QModelIndex
139get_index_from_selection(GtkTreeSelection *selection)
140{
141 GtkTreeIter iter;
142 GtkTreeModel *model = NULL;
143
144 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400145 if (GTK_IS_Q_TREE_MODEL(model))
146 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
147 else if (GTK_IS_Q_SORT_FILTER_TREE_MODEL(model))
148 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 -0500149 }
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400150 return QModelIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500151}
152
153static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500154update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500155{
Stepan Salenikovich69771842015-02-24 18:11:45 -0500156 QModelIndex current = get_index_from_selection(selection);
157 if (current.isValid())
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500158 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500159 else
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500160 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500161}
162
163static void
164call_selection_changed(GtkTreeSelection *selection, gpointer win)
165{
166 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
167
168 /* get the current visible stack child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400169 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500170
171 QModelIndex idx = get_index_from_selection(selection);
172 if (idx.isValid()) {
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400173 QVariant state = CallModel::instance()->data(idx, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500174 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500175 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500176
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500177 /* FIXME: change when fixed
178 * switch(state.value<Call::LifeCycleState>()) { */
179 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
180 switch(lifecyclestate) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400181 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500182 case Call::LifeCycleState::INITIALIZATION:
183 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500184 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500185 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500186 /* use the pointer of the call as a unique name */
187 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance()->getCall(idx));
188 break;
189 case Call::LifeCycleState::PROGRESS:
190 new_call_view = current_call_view_new();
191 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
192 /* use the pointer of the call as a unique name */
193 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx));
194 break;
195 case Call::LifeCycleState::COUNT__:
196 g_warning("LifeCycleState should never be COUNT");
197 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500198 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500199
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400200 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
201 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500202 g_free(new_call_view_name);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500203
Stepan Salenikovich69771842015-02-24 18:11:45 -0500204 } else {
205 /* nothing selected in the call model, so show the default screen */
206
207 /* TODO: replace stack paceholder view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400208 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
209 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_call_view), DEFAULT_VIEW_NAME);
210 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500211
212 }
213
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500214 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400215 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500216 if (current_call_view != old_call_view && old_call_view != NULL) {
217 /* if the previous child was a call view, then remove it from
Stepan Salenikovich69771842015-02-24 18:11:45 -0500218 * the stack; removing it should destory it since there should not
219 * be any other references to it */
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500220 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400221 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500222 }
223 }
224}
225
226static void
227call_state_changed(Call *call, gpointer win)
228{
229 g_debug("call state changed");
230 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
231
232 /* check if the call that changed state is the same as the selected call */
233 QModelIndex idx_selected = CallModel::instance()->selectionModel()->currentIndex();
234
235 if( idx_selected.isValid() && call == CallModel::instance()->getCall(idx_selected)) {
236 g_debug("selected call state changed");
237 /* check if we need to change the view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400238 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500239 GtkWidget *new_call_view = NULL;
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400240 QVariant state = CallModel::instance()->data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500241
242 /* check what the current state is vs what is displayed */
243
244 /* FIXME: change when fixed
245 * switch(state.value<Call::LifeCycleState>()) { */
246 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
247 switch(lifecyclestate) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400248 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500249 case Call::LifeCycleState::INITIALIZATION:
250 /* LifeCycleState cannot go backwards, so it should not be possible
251 * that the call is displayed as current (meaning that its in progress)
252 * but have the state 'initialization' */
253 if (IS_CURRENT_CALL_VIEW(old_call_view))
254 g_warning("call displayed as current, but is in state of initialization");
255 break;
256 case Call::LifeCycleState::PROGRESS:
257 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
258 /* change from incoming to current */
259 new_call_view = current_call_view_new();
260 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
261 /* use the pointer of the call as a unique name */
262 char* new_call_view_name = NULL;
263 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx_selected));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400264 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500265 g_free(new_call_view_name);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400266 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
267 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
268 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500269 }
270 break;
271 case Call::LifeCycleState::FINISHED:
272 /* do nothing, either call view is valid for this state */
273 break;
274 case Call::LifeCycleState::COUNT__:
275 g_warning("LifeCycleState should never be COUNT");
276 break;
277 }
278
279 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400280 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500281 if (current_call_view != old_call_view && old_call_view != NULL) {
282 /* if the previous child was a call view, then remove it from
283 * the stack; removing it should destory it since there should not
284 * be any other references to it */
285 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400286 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500287 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500288 }
289 }
290}
291
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500292static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400293place_new_call(const ContactMethod *n)
294{
295 Call *call = CallModel::instance()->dialingCall();
296 call->setDialNumber(n);
297 call->performAction(Call::Action::ACCEPT);
298
299 /* make this the currently selected call */
300 QModelIndex call_idx = CallModel::instance()->getIndex(call);
301 CallModel::instance()->selectionModel()->setCurrentIndex(call_idx, QItemSelectionModel::ClearAndSelect);
302}
303
304static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500305search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
306{
307 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
308
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400309 const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500310
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400311 if (number_entered && strlen(number_entered) > 0) {
312 /* detect Ring hash */
313 gboolean is_ring_hash = FALSE;
314 if (strlen(number_entered) == 40) {
315 is_ring_hash = TRUE;
316 /* must be 40 chars long and alphanumeric */
317 for (int i = 0; i < 40 && is_ring_hash; ++i) {
318 if (!g_ascii_isalnum(number_entered[i]))
319 is_ring_hash = FALSE;
320 }
321 }
322
323 QString number = QString{number_entered};
324
325 if (is_ring_hash)
326 number = "ring:" + number;
327
328 g_debug("dialing to number: %s", number.toUtf8().constData());
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500329 Call *call = CallModel::instance()->dialingCall();
330 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500331 call->performAction(Call::Action::ACCEPT);
332
333 /* make this the currently selected call */
334 QModelIndex idx = CallModel::instance()->getIndex(call);
335 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
336 }
337}
338
339static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400340activate_history_item(GtkTreeView *tree_view,
341 GtkTreePath *path,
342 G_GNUC_UNUSED GtkTreeViewColumn *column,
343 G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich15142182015-03-11 17:15:26 -0400344{
345 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
346
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400347 /* expand / collapse row */
348 if (gtk_tree_view_row_expanded(tree_view, path))
349 gtk_tree_view_collapse_row(tree_view, path);
350 else
351 gtk_tree_view_expand_row(tree_view, path, FALSE);
352
Stepan Salenikovich15142182015-03-11 17:15:26 -0400353 /* get iter */
354 GtkTreeIter iter;
355 if (gtk_tree_model_get_iter(model, &iter, path)) {
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400356 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 -0400357
358 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
359 /* create new call */
360 if (contact_method.value<ContactMethod*>()) {
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400361 place_new_call(contact_method.value<ContactMethod*>());
362 }
Stepan Salenikovich15142182015-03-11 17:15:26 -0400363 }
364}
365
366static void
367navbutton_contacts_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
368{
369 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
370 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
371
372 if (gtk_toggle_button_get_active(navbutton)) {
373
374 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
375
376 if (visible) {
377 /* contacts is left of both history and presence, so always slide right to show it */
378 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
379 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
380 } else {
381 g_warning("should always have a visible child in the nav stack");
382 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
383 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
384 }
385 }
386}
387
388static void
389navbutton_presence_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
390{
391 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
392 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
393
394 if (gtk_toggle_button_get_active(navbutton)) {
395
396 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
397 if (visible) {
398 /* presence is right of both history and contacts, so always slide left to show it */
399 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
400 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
401 } else {
402 g_warning("should always have a visible child in the nav stack");
403 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
404 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
405 }
406 }
407}
408
409static void
410navbutton_history_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
411{
412 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
413 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
414
415 if (gtk_toggle_button_get_active(navbutton)) {
416
417 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
418 if (visible) {
419 if (strcmp(visible, VIEW_CONTACTS) == 0) {
420 /* history is right of contacts, so slide left to show it */
421 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
422 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
423 } else if (strcmp(visible, VIEW_PRESENCE) == 0) {
424 /* history is left of presence, so slide right to show it */
425 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
426 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
427 }
428 } else {
429 g_warning("should always have a visible child in the nav stack");
430 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
431 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
432 }
433 }
434}
435
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400436static gboolean
437save_accounts(GtkWidget *working_dialog)
438{
439 /* save changes to accounts */
440 AccountModel::instance()->save();
441 /* save changes to codecs */
442 for (int i = 0; i < AccountModel::instance()->rowCount(); i++) {
443 QModelIndex idx = AccountModel::instance()->index(i, 0);
444 AccountModel::instance()->getAccountByModelIndex(idx)->codecModel()->save();
445 }
446
447 if (working_dialog)
448 gtk_widget_destroy(working_dialog);
449
450 return G_SOURCE_REMOVE;
451}
452
Stepan Salenikovich15142182015-03-11 17:15:26 -0400453static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400454settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
455{
456 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
457 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
458
459 /* toggle show settings */
460 priv->show_settings = !priv->show_settings;
461
462 /* check which view to show */
463 if (priv->show_settings) {
464 /* show settings */
465 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
466
467 gtk_widget_hide(priv->hbox_search);
468 gtk_widget_show(priv->hbox_settings);
469
470 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400471 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400472 } else {
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400473 /* show working dialog in case save operation takes time */
474 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
475 gtk_window_present(GTK_WINDOW(working));
476
477 /* now save after the time it takes to transition back to the call view (400ms)
478 * the save doesn't happen before the "working" dialog is presented
479 * the timeout function should destroy the "working" dialog when done saving
480 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400481 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400482
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400483 /* show calls */
484 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
485
486 gtk_widget_show(priv->hbox_search);
487 gtk_widget_hide(priv->hbox_settings);
488
489 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
490 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400491
492 /* make sure video preview is stopped, in case it was started */
493 Video::PreviewManager::instance()->stopPreview();
494 }
495}
496
497static void
498show_video_settings(GtkToggleButton *navbutton, RingMainWindow *win)
499{
500 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
501 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
502
503 if (gtk_toggle_button_get_active(navbutton)) {
504 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
505 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), VIDEO_SETTINGS_VIEW_NAME);
506 priv->last_settings_view = priv->video_settings_view;
507 }
508}
509
510static void
511show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
512{
513 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
514 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
515
516 if (gtk_toggle_button_get_active(navbutton)) {
517 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
518 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
519 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400520 }
521}
522
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400523static gboolean
524has_ring_account()
525{
526 /* check if a Ring account exists */
527 int a_count = AccountModel::instance()->rowCount();
528 for (int i = 0; i < a_count; ++i) {
529 QModelIndex idx = AccountModel::instance()->index(i, 0);
530 QVariant protocol = idx.data(static_cast<int>(Account::Role::Proto));
531 if ((Account::Protocol)protocol.toUInt() == Account::Protocol::RING)
532 return TRUE;
533 }
534
535 return FALSE;
536}
537
538static gboolean
539create_ring_account(RingMainWindow *win)
540{
541 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
542 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
543
544 /* create account and set UPnP enabled, as its not by default in the daemon */
545 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
546 Account *account = AccountModel::instance()->add(alias, Account::Protocol::RING);
547 account->setUpnpEnabled(TRUE);
548
549 /* wait for hash to be generated to show the next view */
550 priv->hash_updated = QObject::connect(
551 account,
552 &Account::changed,
553 [=] (Account *a) {
554 QString hash = a->username();
555 if (!hash.isEmpty()) {
556 /* set the hash */
557 gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());
558
559 /* show the next accont creation view */
560 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
561 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);
562
563 /* select the hash text */
564 gtk_widget_grab_focus(priv->entry_hash);
565 gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
566 }
567 }
568 );
569
570 account->performAction(Account::EditAction::SAVE);
571
572 return G_SOURCE_REMOVE;
573}
574
575static void
576alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
577{
578 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
579
580 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
581 if (strlen(alias) > 0) {
582 /* enable "next" button */
583 gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
584 } else {
585 /* disable "next" button, as we require an alias */
586 gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
587 }
588}
589
590static void
591account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
592{
593 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
594
595 /* show/hide relevant widgets */
596 gtk_widget_hide(priv->label_enter_alias);
597 gtk_widget_hide(priv->entry_alias);
598 gtk_widget_hide(priv->button_account_creation_next);
599 gtk_widget_show(priv->label_generating_account);
600 gtk_widget_show(priv->spinner_generating_account);
601
602 /* now create account after a short timeout so that the the save doesn't
603 * happen freeze the client before the widget changes happen;
604 * the timeout function should then display the next step in account creation
605 */
606 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
607}
608
609static void
610account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
611{
612 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
613
614 QObject::disconnect(priv->hash_updated);
615
616 /* show the call view */
617 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
618 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
619
620 /* show the search bar and settings */
621 gtk_widget_show(priv->hbox_search);
622 gtk_widget_show(priv->ring_settings);
623
624}
625
626static void
627entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
628{
629 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
630
631 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
632 if (strlen(alias) > 0)
633 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
634}
635
636static void
637show_account_creation(RingMainWindow *win)
638{
639 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
640
641 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
642 priv->account_creation_1,
643 CREATE_ACCOUNT_1_VIEW_NAME);
644
645 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
646 priv->account_creation_2,
647 CREATE_ACCOUNT_2_VIEW_NAME);
648
649 /* hide search bar and settings until account creation is complete */
650 gtk_widget_hide(priv->hbox_search);
651 gtk_widget_hide(priv->ring_settings);
652
653 /* set ring logo */
654 GError *error = NULL;
655 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
656 -1, 75, TRUE, &error);
657 if (logo_ring == NULL) {
658 g_debug("Could not load logo: %s", error->message);
659 g_error_free(error);
660 } else
661 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
662
663 /* style of alias and hash entry; give them a larger font */
664 gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
665 gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));
666
667 /* connect signals */
668 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
669 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
670 g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
671 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
672 g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);
673
674 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
675}
676
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400677static void
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400678show_ring_id(RingMainWindow *win, Account *account) {
679 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
680
681 /* display the ring id, if we found a ring account */
682 if (account) {
683 if (!account->username().isEmpty()) {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400684 QString hash = "<span fgcolor=\"black\">" + account->username() + "</span>";
685 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), hash.toUtf8().constData());
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400686 } else {
687 g_warning("got ring account, but Ring id is empty");
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400688 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), "<span fgcolor=\"gray\">fetching Ring ID...</span>");
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400689 }
690 } else {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400691 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), "<span fgcolor=\"gray\">no Ring account</span>");
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400692 }
693
694}
695
696static void
697get_active_ring_account(RingMainWindow *win)
698{
699 /* get the users Ring account
700 * if multiple accounts exist, get the first one which is registered,
701 * if none, then the first one which is enabled,
702 * if none, then the first one in the list of ring accounts
703 */
704 Account *registered_account = NULL;
705 Account *enabled_account = NULL;
706 Account *ring_account = NULL;
707 int a_count = AccountModel::instance()->rowCount();
708 for (int i = 0; i < a_count && !registered_account; ++i) {
709 QModelIndex idx = AccountModel::instance()->index(i, 0);
710 Account *account = AccountModel::instance()->getAccountByModelIndex(idx);
711 if (account->protocol() == Account::Protocol::RING) {
712 /* got RING account, check if active */
713 if (account->isEnabled()) {
714 /* got enabled account, check if connected */
715 if (account->registrationState() == Account::RegistrationState::READY) {
716 /* got registered account, use this one */
717 registered_account = enabled_account = ring_account = account;
718 // g_debug("got registered account: %s", ring_account->alias().toUtf8().constData());
719 } else {
720 /* not registered, but enabled, use if its the first one */
721 if (!enabled_account) {
722 enabled_account = ring_account = account;
723 // g_debug("got enabled ring accout: %s", ring_account->alias().toUtf8().constData());
724 }
725 }
726 } else {
727 /* not enabled, but a Ring account, use if its the first one */
728 if (!ring_account) {
729 ring_account = account;
730 // g_debug("got ring account: %s", ring_account->alias().toUtf8().constData());
731 }
732 }
733 }
734 }
735
736 show_ring_id(win, ring_account);
737}
738
739static void
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400740render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
741 GtkCellRenderer *cell,
742 GtkTreeModel *tree_model,
743 GtkTreeIter *iter,
744 G_GNUC_UNUSED gpointer data)
745{
746 /* check if this is a top level item (the fuzzy date item),
747 * in this case we don't want to show a call direction */
748 gchar *render_direction = NULL;
749 GtkTreeIter parent;
750 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
751 /* get direction and missed values */
752 GValue value = G_VALUE_INIT;
753 gtk_tree_model_get_value(tree_model, iter, 3, &value);
754 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
755 g_value_unset(&value);
756
757 gtk_tree_model_get_value(tree_model, iter, 4, &value);
758 gboolean missed = g_value_get_boolean(&value);
759 g_value_unset(&value);
760
761 switch (direction) {
762 case Call::Direction::INCOMING:
763 if (missed)
764 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
765 else
766 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
767 break;
768 case Call::Direction::OUTGOING:
769 if (missed)
770 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
771 else
772 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
773 break;
774 }
775 }
776 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
777 g_free(render_direction);
778}
779
780static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400781copy_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
782{
783 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
784 QModelIndex idx = get_index_from_selection(selection);
785
786 if (idx.isValid()) {
787 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
788
789 const gchar* number = idx.data(static_cast<int>(Call::Role::Number)).toString().toUtf8().constData();
790 gtk_clipboard_set_text(clip, number, -1);
791 }
792}
793
794/* TODO: can't seem to delete just one item for now, add when supported in backend
795 * static void
796 * delete_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
797 * {
798 * GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
799 * QModelIndex idx = get_index_from_selection(selection);
800 *
801 * if (idx.isValid()) {
802 * g_debug("deleting history item");
803 * CategorizedHistoryModel::instance()->removeRow(idx.row(), idx.parent());
804 * }
805 * }
806 */
807
808static gboolean
809history_popup_menu(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, GtkTreeView *treeview)
810{
811 /* build popup menu when right clicking on history item
812 * user should be able to copy the "number",
813 * delete history item or all of the history,
814 * and eventualy add the number to a contact
815 */
816
817 /* check for right click */
818 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
819 return FALSE;
820
821 GtkWidget *menu = gtk_menu_new();
822
823 /* copy */
824 GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Copy");
825 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
826 g_signal_connect(item, "activate", G_CALLBACK(copy_history_item), treeview);
827
828 /* TODO: delete history entry
829 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
830 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
831 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
832 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
833 */
834
835 /* show menu */
836 gtk_widget_show_all(menu);
837 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
838
839 return FALSE; /* continue to default handler */
840}
841
842static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400843render_contact_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
844 GtkCellRenderer *cell,
845 GtkTreeModel *tree_model,
846 GtkTreeIter *iter,
847 G_GNUC_UNUSED gpointer data)
848{
849 /* check if this is a top level item (category),
850 * or a bottom level item (contact method)
851 * in this case we don't want to show a photo */
852 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
853 int depth = gtk_tree_path_get_depth(path);
854 gtk_tree_path_free(path);
855 if (depth == 2) {
856 /* get person */
857 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
858 if (idx.isValid()) {
859 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
860 Person *c = var_c.value<Person *>();
861 /* get photo */
862 QVariant var_p = PixbufDelegate::instance()->contactPhoto(c, QSize(50, 50), false);
863 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
864 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
865 return;
866 }
867 }
868
869 /* otherwise, make sure its an empty pixbuf */
870 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
871}
872
873static void
874expand_if_child(G_GNUC_UNUSED GtkTreeModel *tree_model,
875 GtkTreePath *path,
876 G_GNUC_UNUSED GtkTreeIter *iter,
877 GtkTreeView *treeview)
878{
879 if (gtk_tree_path_get_depth(path) == 2)
880 gtk_tree_view_expand_to_path(treeview, path);
881}
882
883static void
884activate_contact_item(GtkTreeView *tree_view,
885 GtkTreePath *path,
886 G_GNUC_UNUSED GtkTreeViewColumn *column,
887 G_GNUC_UNUSED gpointer user_data)
888{
889 /* expand / contract row */
890 if (gtk_tree_view_row_expanded(tree_view, path))
891 gtk_tree_view_collapse_row(tree_view, path);
892 else
893 gtk_tree_view_expand_row(tree_view, path, FALSE);
894
895 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
896
897 /* get iter */
898 GtkTreeIter iter;
899 if (gtk_tree_model_get_iter(model, &iter, path)) {
900 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
901 if (idx.isValid()) {
902 int depth = gtk_tree_path_get_depth(path);
903 switch (depth) {
904 case 0:
905 case 1:
906 /* category, nothing to do */
907 break;
908 case 2:
909 {
910 /* contact (person), use contact method if there is only one */
911 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
912 if (var_c.isValid()) {
913 Person *c = var_c.value<Person *>();
914 if (c->phoneNumbers().size() == 1) {
915 /* call with contact method */
916 place_new_call(c->phoneNumbers().first());
917 }
918 }
919 break;
920 }
921 default:
922 {
923 /* contact method (or deeper) */
924 QVariant var_n = idx.data(static_cast<int>(ContactMethod::Role::Object));
925 if (var_n.isValid()) {
926 /* call with contat method */
927 place_new_call(var_n.value<ContactMethod *>());
928 }
929 break;
930 }
931 }
932 }
933 }
934}
935
936static void
937render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
938 GtkCellRenderer *cell,
939 GtkTreeModel *tree_model,
940 GtkTreeIter *iter,
941 G_GNUC_UNUSED gpointer data)
942{
943 /**
944 * If contact (person), show the name and the contact method (number)
945 * underneath; if multiple contact methods, then indicate as such
946 *
947 * Otherwise just display the category or contact method
948 */
949 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
950 int depth = gtk_tree_path_get_depth(path);
951 gtk_tree_path_free(path);
952
953 gchar *text = NULL;
954
955 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
956 if (idx.isValid()) {
957 QVariant var = idx.data(Qt::DisplayRole);
958 if (depth == 1) {
959 /* category */
960 text = g_strdup_printf("<b>%s</b>", var.value<QString>().toUtf8().constData());
961 } else if (depth == 2) {
962 /* contact, check for contact methods */
963 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
964 if (var_c.isValid()) {
965 Person *c = var_c.value<Person *>();
966 switch (c->phoneNumbers().size()) {
967 case 0:
968 text = g_strdup_printf("%s\n", c->formattedName().toUtf8().constData());
969 break;
970 case 1:
971 {
972 QString number;
973 QVariant var_n = c->phoneNumbers().first()->roleData(Qt::DisplayRole);
974 if (var_n.isValid())
975 number = var_n.value<QString>();
976
977 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
978 c->formattedName().toUtf8().constData(),
979 number.toUtf8().constData());
980 break;
981 }
982 default:
983 /* more than one, for now don't show any of the contact methods */
984 text = g_strdup_printf("%s\n", c->formattedName().toUtf8().constData());
985 break;
986 }
987 } else {
988 /* should never happen since depth 2 should always be a contact (person) */
989 text = g_strdup_printf("%s", var.value<QString>().toUtf8().constData());
990 }
991 } else {
992 /* contact method (or deeper??) */
993 text = g_strdup_printf("%s", var.value<QString>().toUtf8().constData());
994 }
995 }
996
997 g_object_set(G_OBJECT(cell), "markup", text, NULL);
998 g_free(text);
999
1000 /* set the colour */
1001 if ( depth == 1) {
1002 /* nice blue taken from the ring logo */
1003 GdkRGBA rgba = {0.2, 0.75294117647, 0.82745098039, 0.1};
1004 g_object_set(G_OBJECT(cell), "cell-background-rgba", &rgba, NULL);
1005 } else {
1006 g_object_set(G_OBJECT(cell), "cell-background", NULL, NULL);
1007 }
1008}
1009
1010static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001011ring_main_window_init(RingMainWindow *win)
1012{
1013 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1014 gtk_widget_init_template(GTK_WIDGET(win));
1015
1016 /* set window icon */
1017 GError *error = NULL;
1018 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
1019 if (icon == NULL) {
1020 g_debug("Could not load icon: %s", error->message);
1021 g_error_free(error);
1022 } else
1023 gtk_window_set_icon(GTK_WINDOW(win), icon);
1024
1025 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001026 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
1027 -1, 24, TRUE, &error);
1028 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001029 g_debug("Could not load icon: %s", error->message);
1030 g_error_free(error);
1031 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001032 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001033
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001034 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001035 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
1036 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001037 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001038 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001039
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001040 /* settings icon */
1041 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
1042
1043 /* connect settings button signal */
1044 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
1045
1046 /* add the call view to the main stack */
1047 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
1048 priv->vbox_call_view,
1049 CALL_VIEW_NAME);
1050
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001051 if (has_ring_account()) {
1052 /* user has ring account, so show the call view right away */
1053 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
1054 } else {
1055 /* user has to create the ring account */
1056 show_account_creation(win);
1057 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001058
1059 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001060 priv->account_settings_view = account_view_new();
1061 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
1062
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001063 priv->video_settings_view = video_settings_view_new();
1064 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->video_settings_view, VIDEO_SETTINGS_VIEW_NAME);
1065
1066 /* make the setting we will show first the active one */
1067 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_video_settings), TRUE);
1068 priv->last_settings_view = priv->video_settings_view;
1069
1070 /* connect the settings button signals to switch settings views */
1071 g_signal_connect(priv->radiobutton_video_settings, "toggled", G_CALLBACK(show_video_settings), win);
1072 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
1073
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001074 /* call model */
Stepan Salenikovich69771842015-02-24 18:11:45 -05001075 GtkQTreeModel *call_model;
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001076 GtkCellRenderer *renderer;
1077 GtkTreeViewColumn *column;
1078
Stepan Salenikovich69771842015-02-24 18:11:45 -05001079 call_model = gtk_q_tree_model_new(CallModel::instance(), 4,
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001080 Call::Role::Name, G_TYPE_STRING,
1081 Call::Role::Number, G_TYPE_STRING,
1082 Call::Role::Length, G_TYPE_STRING,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -04001083 Call::Role::State, G_TYPE_STRING);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001084 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_call), GTK_TREE_MODEL(call_model));
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001085
1086 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001087 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001088 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001089 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001090 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
1091
1092 renderer = gtk_cell_renderer_text_new();
1093 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001094 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001095 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
1096
Stepan Salenikovich69771842015-02-24 18:11:45 -05001097 /* connect signals to and from UserActionModel to sync selection betwee
1098 * the QModel and the GtkTreeView */
Stepan Salenikovich69771842015-02-24 18:11:45 -05001099 QObject::connect(
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001100 CallModel::instance()->selectionModel(),
Stepan Salenikovich69771842015-02-24 18:11:45 -05001101 &QItemSelectionModel::currentChanged,
1102 [=](const QModelIndex & current, const QModelIndex & previous) {
1103 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
1104
1105 /* first unselect the previous */
1106 if (previous.isValid()) {
1107 GtkTreeIter old_iter;
1108 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
1109 gtk_tree_selection_unselect_iter(selection, &old_iter);
1110 } else {
1111 g_warning("Trying to unselect invalid GtkTreeIter");
1112 }
1113 }
1114
1115 /* select the current */
1116 if (current.isValid()) {
1117 GtkTreeIter new_iter;
1118 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
1119 gtk_tree_selection_select_iter(selection, &new_iter);
1120 } else {
1121 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
1122 }
1123 }
1124 }
1125 );
1126
1127 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001128 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001129
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001130 /* connect to call state changes to update relevant view(s) */
1131 QObject::connect(
1132 CallModel::instance(),
1133 &CallModel::callStateChanged,
1134 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
1135 call_state_changed(call, win);
1136 }
1137 );
1138
Stepan Salenikovich15142182015-03-11 17:15:26 -04001139 /* contacts view/model */
1140 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1141 GtkWidget *treeview_contacts = gtk_tree_view_new();
1142 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_contacts), FALSE);
1143 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_contacts);
1144 gtk_widget_show_all(scrolled_window);
1145 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1146 scrolled_window,
1147 VIEW_CONTACTS);
1148
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001149 CategorizedContactModel::instance()->setUnreachableHidden(true);
1150 priv->q_contact_model = new ActiveItemProxyModel(CategorizedContactModel::instance());
1151 priv->q_contact_model->setSortRole(Qt::DisplayRole);
1152 priv->q_contact_model->setSortLocaleAware(true);
1153 priv->q_contact_model->setSortCaseSensitivity(Qt::CaseInsensitive);
1154 priv->q_contact_model->sort(0);
1155
1156 GtkQSortFilterTreeModel *contact_model = gtk_q_sort_filter_tree_model_new(
1157 (QSortFilterProxyModel *)priv->q_contact_model,
1158 1,
1159 Qt::DisplayRole, G_TYPE_STRING);
1160 gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_contacts), GTK_TREE_MODEL(contact_model));
1161
1162 /* photo and name/contact method column */
1163 GtkCellArea *area = gtk_cell_area_box_new();
1164 column = gtk_tree_view_column_new_with_area(area);
1165 gtk_tree_view_column_set_title(column, "Name");
1166
1167 /* photo renderer */
1168 renderer = gtk_cell_renderer_pixbuf_new();
1169 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
1170
1171 /* get the photo */
1172 gtk_tree_view_column_set_cell_data_func(
1173 column,
1174 renderer,
1175 (GtkTreeCellDataFunc)render_contact_photo,
1176 NULL,
1177 NULL);
1178
1179 /* name and contact method renderer */
1180 renderer = gtk_cell_renderer_text_new();
1181 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
1182
1183 gtk_tree_view_column_set_cell_data_func(
1184 column,
1185 renderer,
1186 (GtkTreeCellDataFunc)render_name_and_contact_method,
1187 NULL,
1188 NULL);
1189
1190 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_contacts), column);
1191 gtk_tree_view_column_set_resizable(column, TRUE);
1192
1193 gtk_tree_view_expand_all(GTK_TREE_VIEW(treeview_contacts));
1194 g_signal_connect(contact_model, "row-inserted", G_CALLBACK(expand_if_child), treeview_contacts);
1195 g_signal_connect(treeview_contacts, "row-activated", G_CALLBACK(activate_contact_item), NULL);
1196
Stepan Salenikovich15142182015-03-11 17:15:26 -04001197 /* history view/model */
1198 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1199 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001200 /* make headers visible to allow column resizing */
1201 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001202 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_history);
1203 gtk_widget_show_all(scrolled_window);
1204 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1205 scrolled_window,
1206 VIEW_HISTORY);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001207
Stepan Salenikovich7ff47962015-03-16 15:10:14 -04001208 /* sort the history in descending order by date */
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001209 priv->q_history_model = new QSortFilterProxyModel();
1210 priv->q_history_model->setSourceModel(CategorizedHistoryModel::instance());
1211 priv->q_history_model->setSortRole(static_cast<int>(Call::Role::Date));
1212 priv->q_history_model->sort(0,Qt::DescendingOrder);
Stepan Salenikovich7ff47962015-03-16 15:10:14 -04001213
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001214 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001215 priv->q_history_model,
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001216 5,
Stepan Salenikovich15142182015-03-11 17:15:26 -04001217 Qt::DisplayRole, G_TYPE_STRING,
1218 Call::Role::Number, G_TYPE_STRING,
1219 Call::Role::FormattedDate, G_TYPE_STRING,
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001220 Call::Role::Direction, G_TYPE_INT,
1221 Call::Role::Missed, G_TYPE_BOOLEAN);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001222 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
1223
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001224 /* name column, also used for call direction and fuzzy date for top level items */
1225 column = gtk_tree_view_column_new();
1226 gtk_tree_view_column_set_title(column, "Name");
1227
1228 /* call direction */
1229 renderer = gtk_cell_renderer_text_new();
1230 gtk_tree_view_column_pack_start(column, renderer, FALSE);
1231
1232 /* display the call direction with arrows */
1233 gtk_tree_view_column_set_cell_data_func(
1234 column,
1235 renderer,
1236 (GtkTreeCellDataFunc)render_call_direction,
1237 NULL,
1238 NULL);
1239
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001240 /* name or time category column */
1241 renderer = gtk_cell_renderer_text_new();
1242 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001243 gtk_tree_view_column_pack_start(column, renderer, FALSE);
1244 gtk_tree_view_column_set_attributes(column, renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001245 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001246 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001247
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001248 /* "number" column */
1249 renderer = gtk_cell_renderer_text_new();
1250 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1251 column = gtk_tree_view_column_new_with_attributes("Number", renderer, "text", 1, NULL);
1252 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
1253 gtk_tree_view_column_set_resizable(column, TRUE);
1254
1255 /* date column */
Stepan Salenikovich15142182015-03-11 17:15:26 -04001256 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001257 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001258 column = gtk_tree_view_column_new_with_attributes ("Date", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001259 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
1260 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001261
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001262 /* expand the first row, which should be the most recent calls */
1263 gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_history),
1264 gtk_tree_path_new_from_string("0"),
1265 FALSE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001266
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001267 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(activate_history_item), NULL);
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -04001268 g_signal_connect(treeview_history, "button-press-event", G_CALLBACK(history_popup_menu), treeview_history);
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001269 g_signal_connect(history_model, "row-inserted", G_CALLBACK(expand_if_child), treeview_history);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001270
1271 /* presence view/model */
1272 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1273 GtkWidget *treeview_presence = gtk_tree_view_new();
1274 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_presence), FALSE);
1275 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_presence);
1276 gtk_widget_show_all(scrolled_window);
1277 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1278 scrolled_window,
1279 VIEW_PRESENCE);
1280
1281 /* connect signals to change the contacts/history/presence stack view */
1282 g_signal_connect(priv->radiobutton_contacts, "toggled", G_CALLBACK(navbutton_contacts_toggled), win);
1283 g_signal_connect(priv->radiobutton_history, "toggled", G_CALLBACK(navbutton_history_toggled), win);
1284 g_signal_connect(priv->radiobutton_presence, "toggled", G_CALLBACK(navbutton_presence_toggled), win);
1285
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001286 /* TODO: make this linked to the client settings so that the last shown view is the same on startup */
1287 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_contacts), TRUE);
1288
Stepan Salenikovich69771842015-02-24 18:11:45 -05001289 /* TODO: replace stack paceholder view */
1290 GtkWidget *placeholder_view = gtk_tree_view_new();
1291 gtk_widget_show(placeholder_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001292 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), placeholder_view, DEFAULT_VIEW_NAME);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001293
1294 /* connect signals */
1295 g_signal_connect(call_selection, "changed", G_CALLBACK(call_selection_changed), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001296 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
1297 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
1298
1299 /* style of search entry */
1300 gtk_widget_override_font(priv->search_entry, pango_font_description_from_string("monospace 15"));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001301
1302 /* connect to incoming call and focus */
1303 QObject::connect(
1304 CallModel::instance(),
1305 &CallModel::incomingCall,
1306 [=](Call* call) {
1307 CallModel::instance()->selectionModel()->setCurrentIndex(
1308 CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect);
1309 }
1310 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001311
1312 /* display ring id by first getting the active ring account */
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001313 gtk_widget_override_font(priv->label_ring_id, pango_font_description_from_string("monospace"));
1314 gtk_widget_set_size_request(priv->label_ring_id, 400, 35);
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001315 get_active_ring_account(win);
1316 QObject::connect(
1317 AccountModel::instance(),
1318 &AccountModel::dataChanged,
1319 [=] () {
1320 /* check if the active ring account has changed,
1321 * eg: if it was deleted */
1322 get_active_ring_account(win);
1323 }
1324 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001325}
1326
1327static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001328ring_main_window_dispose(GObject *object)
1329{
1330 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1331}
1332
1333static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001334ring_main_window_finalize(GObject *object)
1335{
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001336 RingMainWindow *self = RING_MAIN_WINDOW(object);
1337 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1338
1339 delete priv->q_contact_model;
1340 delete priv->q_history_model;
1341
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001342 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001343}
1344
1345static void
1346ring_main_window_class_init(RingMainWindowClass *klass)
1347{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001348 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001349 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001350
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001351 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1352 "/cx/ring/RingGnome/ringmainwindow.ui");
1353
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001354 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, treeview_call);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001355 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_contacts_history_presence);
1356 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_contacts);
1357 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_history);
1358 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_presence);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001359 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1360 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1361 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1362 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
1363 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_search);
1364 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001365 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001366 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001367 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
1368 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_call_view);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001369 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001370 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_audio_settings);
1371 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
1372 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_video_settings);
1373 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001374 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_ring_id);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001375
1376 /* account creation */
1377 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
1378 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
1379 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
1380 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
1381 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1382 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1383 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
1384
1385 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
1386 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
1387 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001388}
1389
1390GtkWidget *
1391ring_main_window_new (GtkApplication *app)
1392{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001393 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1394
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001395 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001396}