blob: 668cae788408e4d5ec388f21c5f94a00e7710da8 [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 Salenikovicheafc3dd2015-04-16 18:38:01 -040058#include <numbercompletionmodel.h>
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040059
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040060#define CALL_VIEW_NAME "calls"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040061#define CREATE_ACCOUNT_1_VIEW_NAME "create1"
62#define CREATE_ACCOUNT_2_VIEW_NAME "create2"
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040063#define GENERAL_SETTINGS_VIEW_NAME "general"
64#define AUDIO_SETTINGS_VIEW_NAME "audio"
65#define VIDEO_SETTINGS_VIEW_NAME "video"
66#define ACCOUNT_SETTINGS_VIEW_NAME "accounts"
Stepan Salenikovich69771842015-02-24 18:11:45 -050067#define DEFAULT_VIEW_NAME "placeholder"
Stepan Salenikovich15142182015-03-11 17:15:26 -040068#define VIEW_CONTACTS "contacts"
69#define VIEW_HISTORY "history"
70#define VIEW_PRESENCE "presence"
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050071
72struct _RingMainWindow
73{
74 GtkApplicationWindow parent;
75};
76
77struct _RingMainWindowClass
78{
79 GtkApplicationWindowClass parent_class;
80};
81
82typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
83
84struct _RingMainWindowPrivate
85{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040086 GtkWidget *ring_menu;
87 GtkWidget *image_ring;
88 GtkWidget *ring_settings;
89 GtkWidget *image_settings;
90 GtkWidget *hbox_search;
91 GtkWidget *hbox_settings;
Stepan Salenikovich15142182015-03-11 17:15:26 -040092 GtkWidget *stack_contacts_history_presence;
93 GtkWidget *radiobutton_contacts;
94 GtkWidget *radiobutton_history;
95 GtkWidget *radiobutton_presence;
Stepan Salenikovicha3557452015-02-20 14:14:12 -050096 GtkWidget *treeview_call;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -050097 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -050098 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040099 GtkWidget *vbox_call_view;
100 GtkWidget *stack_call_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500101 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400102 GtkWidget *account_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400103 GtkWidget *video_settings_view;
104 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400105 GtkWidget *radiobutton_audio_settings;
106 GtkWidget *radiobutton_general_settings;
107 GtkWidget *radiobutton_video_settings;
108 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400109 GtkWidget *label_ring_id;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400110
111 Account *active_ring_account;
112 QMetaObject::Connection active_ring_account_updates;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400113
114 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400115
116 /* account creation */
117 GtkWidget *account_creation_1;
118 GtkWidget *image_ring_logo;
119 GtkWidget *label_enter_alias;
120 GtkWidget *entry_alias;
121 GtkWidget *label_generating_account;
122 GtkWidget *spinner_generating_account;
123 GtkWidget *button_account_creation_next;
124 GtkWidget *account_creation_2;
125 GtkWidget *entry_hash;
126 GtkWidget *button_account_creation_done;
127
128 QMetaObject::Connection hash_updated;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400129
130 /* allocd qmodels */
131 ActiveItemProxyModel *q_contact_model;
132 QSortFilterProxyModel *q_history_model;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400133 NumberCompletionModel *q_completion_model;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500134};
135
136G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
137
138#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
139
Stepan Salenikovich69771842015-02-24 18:11:45 -0500140static QModelIndex
141get_index_from_selection(GtkTreeSelection *selection)
142{
143 GtkTreeIter iter;
144 GtkTreeModel *model = NULL;
145
146 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400147 if (GTK_IS_Q_TREE_MODEL(model))
148 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
149 else if (GTK_IS_Q_SORT_FILTER_TREE_MODEL(model))
150 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 -0500151 }
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400152 return QModelIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500153}
154
155static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500156update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500157{
Stepan Salenikovich69771842015-02-24 18:11:45 -0500158 QModelIndex current = get_index_from_selection(selection);
159 if (current.isValid())
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500160 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500161 else
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500162 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500163}
164
165static void
166call_selection_changed(GtkTreeSelection *selection, gpointer win)
167{
168 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
169
170 /* get the current visible stack child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400171 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500172
173 QModelIndex idx = get_index_from_selection(selection);
174 if (idx.isValid()) {
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400175 QVariant state = CallModel::instance()->data(idx, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500176 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500177 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500178
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500179 /* FIXME: change when fixed
180 * switch(state.value<Call::LifeCycleState>()) { */
181 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
182 switch(lifecyclestate) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400183 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500184 case Call::LifeCycleState::INITIALIZATION:
185 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500186 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500187 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500188 /* use the pointer of the call as a unique name */
189 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance()->getCall(idx));
190 break;
191 case Call::LifeCycleState::PROGRESS:
192 new_call_view = current_call_view_new();
193 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
194 /* use the pointer of the call as a unique name */
195 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx));
196 break;
197 case Call::LifeCycleState::COUNT__:
198 g_warning("LifeCycleState should never be COUNT");
199 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500200 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500201
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400202 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
203 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500204 g_free(new_call_view_name);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500205
Stepan Salenikovich69771842015-02-24 18:11:45 -0500206 } else {
207 /* nothing selected in the call model, so show the default screen */
208
209 /* TODO: replace stack paceholder view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400210 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
211 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_call_view), DEFAULT_VIEW_NAME);
212 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500213
214 }
215
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500216 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400217 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500218 if (current_call_view != old_call_view && old_call_view != NULL) {
219 /* if the previous child was a call view, then remove it from
Stepan Salenikovich69771842015-02-24 18:11:45 -0500220 * the stack; removing it should destory it since there should not
221 * be any other references to it */
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500222 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400223 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500224 }
225 }
226}
227
228static void
229call_state_changed(Call *call, gpointer win)
230{
231 g_debug("call state changed");
232 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
233
234 /* check if the call that changed state is the same as the selected call */
235 QModelIndex idx_selected = CallModel::instance()->selectionModel()->currentIndex();
236
237 if( idx_selected.isValid() && call == CallModel::instance()->getCall(idx_selected)) {
238 g_debug("selected call state changed");
239 /* check if we need to change the view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400240 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500241 GtkWidget *new_call_view = NULL;
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400242 QVariant state = CallModel::instance()->data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500243
244 /* check what the current state is vs what is displayed */
245
246 /* FIXME: change when fixed
247 * switch(state.value<Call::LifeCycleState>()) { */
248 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
249 switch(lifecyclestate) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400250 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500251 case Call::LifeCycleState::INITIALIZATION:
252 /* LifeCycleState cannot go backwards, so it should not be possible
253 * that the call is displayed as current (meaning that its in progress)
254 * but have the state 'initialization' */
255 if (IS_CURRENT_CALL_VIEW(old_call_view))
256 g_warning("call displayed as current, but is in state of initialization");
257 break;
258 case Call::LifeCycleState::PROGRESS:
259 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
260 /* change from incoming to current */
261 new_call_view = current_call_view_new();
262 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
263 /* use the pointer of the call as a unique name */
264 char* new_call_view_name = NULL;
265 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx_selected));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400266 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500267 g_free(new_call_view_name);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400268 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
269 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
270 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500271 }
272 break;
273 case Call::LifeCycleState::FINISHED:
274 /* do nothing, either call view is valid for this state */
275 break;
276 case Call::LifeCycleState::COUNT__:
277 g_warning("LifeCycleState should never be COUNT");
278 break;
279 }
280
281 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400282 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500283 if (current_call_view != old_call_view && old_call_view != NULL) {
284 /* if the previous child was a call view, then remove it from
285 * the stack; removing it should destory it since there should not
286 * be any other references to it */
287 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400288 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500289 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500290 }
291 }
292}
293
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500294static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400295place_new_call(const ContactMethod *n)
296{
297 Call *call = CallModel::instance()->dialingCall();
298 call->setDialNumber(n);
299 call->performAction(Call::Action::ACCEPT);
300
301 /* make this the currently selected call */
302 QModelIndex call_idx = CallModel::instance()->getIndex(call);
303 CallModel::instance()->selectionModel()->setCurrentIndex(call_idx, QItemSelectionModel::ClearAndSelect);
304}
305
306static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500307search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
308{
309 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
310
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400311 const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500312
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400313 if (number_entered && strlen(number_entered) > 0) {
314 /* detect Ring hash */
315 gboolean is_ring_hash = FALSE;
316 if (strlen(number_entered) == 40) {
317 is_ring_hash = TRUE;
318 /* must be 40 chars long and alphanumeric */
319 for (int i = 0; i < 40 && is_ring_hash; ++i) {
320 if (!g_ascii_isalnum(number_entered[i]))
321 is_ring_hash = FALSE;
322 }
323 }
324
325 QString number = QString{number_entered};
326
327 if (is_ring_hash)
328 number = "ring:" + number;
329
330 g_debug("dialing to number: %s", number.toUtf8().constData());
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500331 Call *call = CallModel::instance()->dialingCall();
332 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500333 call->performAction(Call::Action::ACCEPT);
334
335 /* make this the currently selected call */
336 QModelIndex idx = CallModel::instance()->getIndex(call);
337 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
338 }
339}
340
341static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400342activate_history_item(GtkTreeView *tree_view,
343 GtkTreePath *path,
344 G_GNUC_UNUSED GtkTreeViewColumn *column,
345 G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich15142182015-03-11 17:15:26 -0400346{
347 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
348
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400349 /* expand / collapse row */
350 if (gtk_tree_view_row_expanded(tree_view, path))
351 gtk_tree_view_collapse_row(tree_view, path);
352 else
353 gtk_tree_view_expand_row(tree_view, path, FALSE);
354
Stepan Salenikovich15142182015-03-11 17:15:26 -0400355 /* get iter */
356 GtkTreeIter iter;
357 if (gtk_tree_model_get_iter(model, &iter, path)) {
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400358 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 -0400359
360 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
361 /* create new call */
362 if (contact_method.value<ContactMethod*>()) {
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400363 place_new_call(contact_method.value<ContactMethod*>());
364 }
Stepan Salenikovich15142182015-03-11 17:15:26 -0400365 }
366}
367
368static void
369navbutton_contacts_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
370{
371 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
372 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
373
374 if (gtk_toggle_button_get_active(navbutton)) {
375
376 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
377
378 if (visible) {
379 /* contacts is left of both history and presence, so always slide right to show it */
380 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
381 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
382 } else {
383 g_warning("should always have a visible child in the nav stack");
384 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
385 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
386 }
387 }
388}
389
390static void
391navbutton_presence_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
392{
393 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
394 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
395
396 if (gtk_toggle_button_get_active(navbutton)) {
397
398 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
399 if (visible) {
400 /* presence is right of both history and contacts, so always slide left to show it */
401 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
402 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
403 } else {
404 g_warning("should always have a visible child in the nav stack");
405 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
406 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
407 }
408 }
409}
410
411static void
412navbutton_history_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
413{
414 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
415 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
416
417 if (gtk_toggle_button_get_active(navbutton)) {
418
419 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
420 if (visible) {
421 if (strcmp(visible, VIEW_CONTACTS) == 0) {
422 /* history is right of contacts, so slide left to show it */
423 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
424 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
425 } else if (strcmp(visible, VIEW_PRESENCE) == 0) {
426 /* history is left of presence, so slide right to show it */
427 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
428 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
429 }
430 } else {
431 g_warning("should always have a visible child in the nav stack");
432 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
433 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
434 }
435 }
436}
437
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400438static gboolean
439save_accounts(GtkWidget *working_dialog)
440{
441 /* save changes to accounts */
442 AccountModel::instance()->save();
443 /* save changes to codecs */
444 for (int i = 0; i < AccountModel::instance()->rowCount(); i++) {
445 QModelIndex idx = AccountModel::instance()->index(i, 0);
446 AccountModel::instance()->getAccountByModelIndex(idx)->codecModel()->save();
447 }
448
449 if (working_dialog)
450 gtk_widget_destroy(working_dialog);
451
452 return G_SOURCE_REMOVE;
453}
454
Stepan Salenikovich15142182015-03-11 17:15:26 -0400455static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400456settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
457{
458 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
459 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
460
461 /* toggle show settings */
462 priv->show_settings = !priv->show_settings;
463
464 /* check which view to show */
465 if (priv->show_settings) {
466 /* show settings */
467 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
468
469 gtk_widget_hide(priv->hbox_search);
470 gtk_widget_show(priv->hbox_settings);
471
472 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400473 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400474 } else {
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400475 /* show working dialog in case save operation takes time */
476 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
477 gtk_window_present(GTK_WINDOW(working));
478
479 /* now save after the time it takes to transition back to the call view (400ms)
480 * the save doesn't happen before the "working" dialog is presented
481 * the timeout function should destroy the "working" dialog when done saving
482 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400483 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400484
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400485 /* show calls */
486 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
487
488 gtk_widget_show(priv->hbox_search);
489 gtk_widget_hide(priv->hbox_settings);
490
491 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
492 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400493
494 /* make sure video preview is stopped, in case it was started */
495 Video::PreviewManager::instance()->stopPreview();
496 }
497}
498
499static void
500show_video_settings(GtkToggleButton *navbutton, RingMainWindow *win)
501{
502 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
503 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
504
505 if (gtk_toggle_button_get_active(navbutton)) {
506 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
507 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), VIDEO_SETTINGS_VIEW_NAME);
508 priv->last_settings_view = priv->video_settings_view;
509 }
510}
511
512static void
513show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
514{
515 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
516 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
517
518 if (gtk_toggle_button_get_active(navbutton)) {
519 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
520 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
521 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400522 }
523}
524
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400525static gboolean
526has_ring_account()
527{
528 /* check if a Ring account exists */
529 int a_count = AccountModel::instance()->rowCount();
530 for (int i = 0; i < a_count; ++i) {
531 QModelIndex idx = AccountModel::instance()->index(i, 0);
532 QVariant protocol = idx.data(static_cast<int>(Account::Role::Proto));
533 if ((Account::Protocol)protocol.toUInt() == Account::Protocol::RING)
534 return TRUE;
535 }
536
537 return FALSE;
538}
539
540static gboolean
541create_ring_account(RingMainWindow *win)
542{
543 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
544 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
545
546 /* create account and set UPnP enabled, as its not by default in the daemon */
547 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
548 Account *account = AccountModel::instance()->add(alias, Account::Protocol::RING);
549 account->setUpnpEnabled(TRUE);
550
551 /* wait for hash to be generated to show the next view */
552 priv->hash_updated = QObject::connect(
553 account,
554 &Account::changed,
555 [=] (Account *a) {
556 QString hash = a->username();
557 if (!hash.isEmpty()) {
558 /* set the hash */
559 gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());
560
561 /* show the next accont creation view */
562 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
563 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);
564
565 /* select the hash text */
566 gtk_widget_grab_focus(priv->entry_hash);
567 gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
568 }
569 }
570 );
571
572 account->performAction(Account::EditAction::SAVE);
573
574 return G_SOURCE_REMOVE;
575}
576
577static void
578alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
579{
580 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
581
582 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
583 if (strlen(alias) > 0) {
584 /* enable "next" button */
585 gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
586 } else {
587 /* disable "next" button, as we require an alias */
588 gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
589 }
590}
591
592static void
593account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
594{
595 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
596
597 /* show/hide relevant widgets */
598 gtk_widget_hide(priv->label_enter_alias);
599 gtk_widget_hide(priv->entry_alias);
600 gtk_widget_hide(priv->button_account_creation_next);
601 gtk_widget_show(priv->label_generating_account);
602 gtk_widget_show(priv->spinner_generating_account);
603
604 /* now create account after a short timeout so that the the save doesn't
605 * happen freeze the client before the widget changes happen;
606 * the timeout function should then display the next step in account creation
607 */
608 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
609}
610
611static void
612account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
613{
614 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
615
616 QObject::disconnect(priv->hash_updated);
617
618 /* show the call view */
619 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
620 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
621
622 /* show the search bar and settings */
623 gtk_widget_show(priv->hbox_search);
624 gtk_widget_show(priv->ring_settings);
625
626}
627
628static void
629entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
630{
631 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
632
633 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
634 if (strlen(alias) > 0)
635 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
636}
637
638static void
639show_account_creation(RingMainWindow *win)
640{
641 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
642
643 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
644 priv->account_creation_1,
645 CREATE_ACCOUNT_1_VIEW_NAME);
646
647 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
648 priv->account_creation_2,
649 CREATE_ACCOUNT_2_VIEW_NAME);
650
651 /* hide search bar and settings until account creation is complete */
652 gtk_widget_hide(priv->hbox_search);
653 gtk_widget_hide(priv->ring_settings);
654
655 /* set ring logo */
656 GError *error = NULL;
657 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
658 -1, 75, TRUE, &error);
659 if (logo_ring == NULL) {
660 g_debug("Could not load logo: %s", error->message);
661 g_error_free(error);
662 } else
663 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
664
665 /* style of alias and hash entry; give them a larger font */
666 gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
667 gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));
668
669 /* connect signals */
670 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
671 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
672 g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
673 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
674 g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);
675
676 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
677}
678
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400679static void
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400680show_ring_id(RingMainWindow *win, Account *account) {
681 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
682
683 /* display the ring id, if we found a ring account */
684 if (account) {
685 if (!account->username().isEmpty()) {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400686 QString hash = "<span fgcolor=\"black\">" + account->username() + "</span>";
687 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), hash.toUtf8().constData());
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400688 } else {
689 g_warning("got ring account, but Ring id is empty");
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400690 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), "<span fgcolor=\"gray\">fetching Ring ID...</span>");
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400691 }
692 } else {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400693 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), "<span fgcolor=\"gray\">no Ring account</span>");
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400694 }
695
696}
697
698static void
699get_active_ring_account(RingMainWindow *win)
700{
701 /* get the users Ring account
702 * if multiple accounts exist, get the first one which is registered,
703 * if none, then the first one which is enabled,
704 * if none, then the first one in the list of ring accounts
705 */
706 Account *registered_account = NULL;
707 Account *enabled_account = NULL;
708 Account *ring_account = NULL;
709 int a_count = AccountModel::instance()->rowCount();
710 for (int i = 0; i < a_count && !registered_account; ++i) {
711 QModelIndex idx = AccountModel::instance()->index(i, 0);
712 Account *account = AccountModel::instance()->getAccountByModelIndex(idx);
713 if (account->protocol() == Account::Protocol::RING) {
714 /* got RING account, check if active */
715 if (account->isEnabled()) {
716 /* got enabled account, check if connected */
717 if (account->registrationState() == Account::RegistrationState::READY) {
718 /* got registered account, use this one */
719 registered_account = enabled_account = ring_account = account;
720 // g_debug("got registered account: %s", ring_account->alias().toUtf8().constData());
721 } else {
722 /* not registered, but enabled, use if its the first one */
723 if (!enabled_account) {
724 enabled_account = ring_account = account;
725 // g_debug("got enabled ring accout: %s", ring_account->alias().toUtf8().constData());
726 }
727 }
728 } else {
729 /* not enabled, but a Ring account, use if its the first one */
730 if (!ring_account) {
731 ring_account = account;
732 // g_debug("got ring account: %s", ring_account->alias().toUtf8().constData());
733 }
734 }
735 }
736 }
737
738 show_ring_id(win, ring_account);
739}
740
741static void
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -0400742render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
743 GtkCellRenderer *cell,
744 GtkTreeModel *tree_model,
745 GtkTreeIter *iter,
746 G_GNUC_UNUSED gpointer data)
747{
748 /* check if this is a top level item (the fuzzy date item),
749 * in this case we don't want to show a call direction */
750 gchar *render_direction = NULL;
751 GtkTreeIter parent;
752 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
753 /* get direction and missed values */
754 GValue value = G_VALUE_INIT;
755 gtk_tree_model_get_value(tree_model, iter, 3, &value);
756 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
757 g_value_unset(&value);
758
759 gtk_tree_model_get_value(tree_model, iter, 4, &value);
760 gboolean missed = g_value_get_boolean(&value);
761 g_value_unset(&value);
762
763 switch (direction) {
764 case Call::Direction::INCOMING:
765 if (missed)
766 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
767 else
768 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
769 break;
770 case Call::Direction::OUTGOING:
771 if (missed)
772 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
773 else
774 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
775 break;
776 }
777 }
778 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
779 g_free(render_direction);
780}
781
782static void
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -0400783copy_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
784{
785 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
786 QModelIndex idx = get_index_from_selection(selection);
787
788 if (idx.isValid()) {
789 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
790
791 const gchar* number = idx.data(static_cast<int>(Call::Role::Number)).toString().toUtf8().constData();
792 gtk_clipboard_set_text(clip, number, -1);
793 }
794}
795
796/* TODO: can't seem to delete just one item for now, add when supported in backend
797 * static void
798 * delete_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
799 * {
800 * GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
801 * QModelIndex idx = get_index_from_selection(selection);
802 *
803 * if (idx.isValid()) {
804 * g_debug("deleting history item");
805 * CategorizedHistoryModel::instance()->removeRow(idx.row(), idx.parent());
806 * }
807 * }
808 */
809
810static gboolean
811history_popup_menu(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, GtkTreeView *treeview)
812{
813 /* build popup menu when right clicking on history item
814 * user should be able to copy the "number",
815 * delete history item or all of the history,
816 * and eventualy add the number to a contact
817 */
818
819 /* check for right click */
820 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
821 return FALSE;
822
823 GtkWidget *menu = gtk_menu_new();
824
825 /* copy */
826 GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Copy");
827 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
828 g_signal_connect(item, "activate", G_CALLBACK(copy_history_item), treeview);
829
830 /* TODO: delete history entry
831 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
832 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
833 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
834 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
835 */
836
837 /* show menu */
838 gtk_widget_show_all(menu);
839 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
840
841 return FALSE; /* continue to default handler */
842}
843
844static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400845render_contact_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
846 GtkCellRenderer *cell,
847 GtkTreeModel *tree_model,
848 GtkTreeIter *iter,
849 G_GNUC_UNUSED gpointer data)
850{
851 /* check if this is a top level item (category),
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400852 * or a bottom level item (contact method)gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400853 * in this case we don't want to show a photo */
854 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
855 int depth = gtk_tree_path_get_depth(path);
856 gtk_tree_path_free(path);
857 if (depth == 2) {
858 /* get person */
859 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
860 if (idx.isValid()) {
861 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
862 Person *c = var_c.value<Person *>();
863 /* get photo */
864 QVariant var_p = PixbufDelegate::instance()->contactPhoto(c, QSize(50, 50), false);
865 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
866 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
867 return;
868 }
869 }
870
871 /* otherwise, make sure its an empty pixbuf */
872 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
873}
874
875static void
876expand_if_child(G_GNUC_UNUSED GtkTreeModel *tree_model,
877 GtkTreePath *path,
878 G_GNUC_UNUSED GtkTreeIter *iter,
879 GtkTreeView *treeview)
880{
881 if (gtk_tree_path_get_depth(path) == 2)
882 gtk_tree_view_expand_to_path(treeview, path);
883}
884
885static void
886activate_contact_item(GtkTreeView *tree_view,
887 GtkTreePath *path,
888 G_GNUC_UNUSED GtkTreeViewColumn *column,
889 G_GNUC_UNUSED gpointer user_data)
890{
891 /* expand / contract row */
892 if (gtk_tree_view_row_expanded(tree_view, path))
893 gtk_tree_view_collapse_row(tree_view, path);
894 else
895 gtk_tree_view_expand_row(tree_view, path, FALSE);
896
897 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
898
899 /* get iter */
900 GtkTreeIter iter;
901 if (gtk_tree_model_get_iter(model, &iter, path)) {
902 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
903 if (idx.isValid()) {
904 int depth = gtk_tree_path_get_depth(path);
905 switch (depth) {
906 case 0:
907 case 1:
908 /* category, nothing to do */
909 break;
910 case 2:
911 {
912 /* contact (person), use contact method if there is only one */
913 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
914 if (var_c.isValid()) {
915 Person *c = var_c.value<Person *>();
916 if (c->phoneNumbers().size() == 1) {
917 /* call with contact method */
918 place_new_call(c->phoneNumbers().first());
919 }
920 }
921 break;
922 }
923 default:
924 {
925 /* contact method (or deeper) */
926 QVariant var_n = idx.data(static_cast<int>(ContactMethod::Role::Object));
927 if (var_n.isValid()) {
928 /* call with contat method */
929 place_new_call(var_n.value<ContactMethod *>());
930 }
931 break;
932 }
933 }
934 }
935 }
936}
937
938static void
939render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
940 GtkCellRenderer *cell,
941 GtkTreeModel *tree_model,
942 GtkTreeIter *iter,
943 G_GNUC_UNUSED gpointer data)
944{
945 /**
946 * If contact (person), show the name and the contact method (number)
947 * underneath; if multiple contact methods, then indicate as such
948 *
949 * Otherwise just display the category or contact method
950 */
951 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
952 int depth = gtk_tree_path_get_depth(path);
953 gtk_tree_path_free(path);
954
955 gchar *text = NULL;
956
957 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
958 if (idx.isValid()) {
959 QVariant var = idx.data(Qt::DisplayRole);
960 if (depth == 1) {
961 /* category */
962 text = g_strdup_printf("<b>%s</b>", var.value<QString>().toUtf8().constData());
963 } else if (depth == 2) {
964 /* contact, check for contact methods */
965 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
966 if (var_c.isValid()) {
967 Person *c = var_c.value<Person *>();
968 switch (c->phoneNumbers().size()) {
969 case 0:
970 text = g_strdup_printf("%s\n", c->formattedName().toUtf8().constData());
971 break;
972 case 1:
973 {
974 QString number;
975 QVariant var_n = c->phoneNumbers().first()->roleData(Qt::DisplayRole);
976 if (var_n.isValid())
977 number = var_n.value<QString>();
978
979 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
980 c->formattedName().toUtf8().constData(),
981 number.toUtf8().constData());
982 break;
983 }
984 default:
985 /* more than one, for now don't show any of the contact methods */
986 text = g_strdup_printf("%s\n", c->formattedName().toUtf8().constData());
987 break;
988 }
989 } else {
990 /* should never happen since depth 2 should always be a contact (person) */
991 text = g_strdup_printf("%s", var.value<QString>().toUtf8().constData());
992 }
993 } else {
994 /* contact method (or deeper??) */
995 text = g_strdup_printf("%s", var.value<QString>().toUtf8().constData());
996 }
997 }
998
999 g_object_set(G_OBJECT(cell), "markup", text, NULL);
1000 g_free(text);
1001
1002 /* set the colour */
1003 if ( depth == 1) {
1004 /* nice blue taken from the ring logo */
1005 GdkRGBA rgba = {0.2, 0.75294117647, 0.82745098039, 0.1};
1006 g_object_set(G_OBJECT(cell), "cell-background-rgba", &rgba, NULL);
1007 } else {
1008 g_object_set(G_OBJECT(cell), "cell-background", NULL, NULL);
1009 }
1010}
1011
1012static void
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001013search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
1014{
1015 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1016
1017 /* get the text from the entry */
1018 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
1019
1020 if (text)
1021 priv->q_completion_model->setPrefix(text);
1022 else
1023 priv->q_completion_model->setPrefix(QString());
1024}
1025
1026static gboolean
1027completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
1028 G_GNUC_UNUSED const gchar *key,
1029 G_GNUC_UNUSED GtkTreeIter *iter,
1030 G_GNUC_UNUSED RingMainWindow *win)
1031{
1032 /* the model is updated by lrc and should only every contain matching entries
1033 * so always return TRUE */
1034 return TRUE;
1035}
1036
1037static QModelIndex
1038get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
1039 GtkTreeIter *filter_iter)
1040{
1041 GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
1042 GtkTreeIter child_iter;
1043 gtk_tree_model_filter_convert_iter_to_child_iter(
1044 GTK_TREE_MODEL_FILTER(filter_model),
1045 &child_iter,
1046 filter_iter);
1047
1048 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
1049}
1050
1051static void
1052autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
1053 GtkCellRenderer *cell,
1054 GtkTreeModel *model,
1055 GtkTreeIter *iter,
1056 G_GNUC_UNUSED gpointer user_data)
1057{
1058 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
1059 if (idx.isValid()) {
1060 QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
1061 if (photo_var.isValid()) {
1062 std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
1063 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
1064 20, 20,
1065 GDK_INTERP_BILINEAR);
1066
1067 g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
1068 g_object_unref(scaled);
1069 return;
1070 }
1071 }
1072
1073 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
1074}
1075
1076static void
1077autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
1078 GtkCellRenderer *cell,
1079 GtkTreeModel *model,
1080 GtkTreeIter *iter,
1081 G_GNUC_UNUSED gpointer user_data)
1082{
1083 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
1084 if (idx.isValid()) {
1085 QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
1086 gchar *text = g_strdup_printf("<span font=\"12\" weight=\"bold\">%s</span>",
1087 name.value<QString>().toUtf8().constData());
1088
1089 g_object_set(G_OBJECT(cell), "markup", text, NULL);
1090 g_free(text);
1091 return;
1092 }
1093
1094 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
1095}
1096
1097static void
1098autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
1099 GtkCellRenderer *cell,
1100 GtkTreeModel *model,
1101 GtkTreeIter *iter,
1102 G_GNUC_UNUSED gpointer user_data)
1103{
1104 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
1105 if (idx.isValid()) {
1106 QVariant uri = idx.data(Qt::DisplayRole);
1107 gchar *text = g_strdup_printf("<span font=\"12\">%s</span>",
1108 uri.value<QString>().toUtf8().constData());
1109
1110 g_object_set(G_OBJECT(cell), "markup", text, NULL);
1111 g_free(text);
1112 return;
1113 }
1114
1115 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
1116}
1117
1118static void
1119autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
1120 GtkCellRenderer *cell,
1121 GtkTreeModel *model,
1122 GtkTreeIter *iter,
1123 G_GNUC_UNUSED gpointer user_data)
1124{
1125 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
1126 if (idx.isValid()) {
1127 QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
1128 gchar *text = g_strdup_printf("<span font=\"12\" color=\"gray\">%s</span>",
1129 alias.value<QString>().toUtf8().constData());
1130
1131 g_object_set(G_OBJECT(cell), "markup", text, NULL);
1132 g_free(text);
1133 return;
1134 }
1135
1136 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
1137}
1138
1139static gboolean
1140select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
1141 GtkTreeModel *model,
1142 GtkTreeIter *iter,
1143 RingMainWindow *win)
1144{
1145 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1146
1147 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
1148 if (idx.isValid()) {
1149 ContactMethod *n = priv->q_completion_model->number(idx);
1150 place_new_call(n);
1151 } else {
1152 g_warning("autocompletion selection is not a valid index!");
1153 }
1154 return TRUE;
1155}
1156
1157static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001158ring_main_window_init(RingMainWindow *win)
1159{
1160 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1161 gtk_widget_init_template(GTK_WIDGET(win));
1162
1163 /* set window icon */
1164 GError *error = NULL;
1165 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
1166 if (icon == NULL) {
1167 g_debug("Could not load icon: %s", error->message);
1168 g_error_free(error);
1169 } else
1170 gtk_window_set_icon(GTK_WINDOW(win), icon);
1171
1172 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001173 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
1174 -1, 24, TRUE, &error);
1175 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001176 g_debug("Could not load icon: %s", error->message);
1177 g_error_free(error);
1178 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001179 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001180
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001181 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001182 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
1183 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001184 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001185 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001186
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001187 /* settings icon */
1188 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
1189
1190 /* connect settings button signal */
1191 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
1192
1193 /* add the call view to the main stack */
1194 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
1195 priv->vbox_call_view,
1196 CALL_VIEW_NAME);
1197
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001198 if (has_ring_account()) {
1199 /* user has ring account, so show the call view right away */
1200 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
1201 } else {
1202 /* user has to create the ring account */
1203 show_account_creation(win);
1204 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001205
1206 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001207 priv->account_settings_view = account_view_new();
1208 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
1209
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001210 priv->video_settings_view = video_settings_view_new();
1211 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->video_settings_view, VIDEO_SETTINGS_VIEW_NAME);
1212
1213 /* make the setting we will show first the active one */
1214 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_video_settings), TRUE);
1215 priv->last_settings_view = priv->video_settings_view;
1216
1217 /* connect the settings button signals to switch settings views */
1218 g_signal_connect(priv->radiobutton_video_settings, "toggled", G_CALLBACK(show_video_settings), win);
1219 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
1220
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001221 /* call model */
Stepan Salenikovich69771842015-02-24 18:11:45 -05001222 GtkQTreeModel *call_model;
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001223 GtkCellRenderer *renderer;
1224 GtkTreeViewColumn *column;
1225
Stepan Salenikovich69771842015-02-24 18:11:45 -05001226 call_model = gtk_q_tree_model_new(CallModel::instance(), 4,
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001227 Call::Role::Name, G_TYPE_STRING,
1228 Call::Role::Number, G_TYPE_STRING,
1229 Call::Role::Length, G_TYPE_STRING,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -04001230 Call::Role::State, G_TYPE_STRING);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001231 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_call), GTK_TREE_MODEL(call_model));
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001232
1233 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001234 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001235 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001236 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001237 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
1238
1239 renderer = gtk_cell_renderer_text_new();
1240 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001241 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001242 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
1243
Stepan Salenikovich69771842015-02-24 18:11:45 -05001244 /* connect signals to and from UserActionModel to sync selection betwee
1245 * the QModel and the GtkTreeView */
Stepan Salenikovich69771842015-02-24 18:11:45 -05001246 QObject::connect(
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001247 CallModel::instance()->selectionModel(),
Stepan Salenikovich69771842015-02-24 18:11:45 -05001248 &QItemSelectionModel::currentChanged,
1249 [=](const QModelIndex & current, const QModelIndex & previous) {
1250 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
1251
1252 /* first unselect the previous */
1253 if (previous.isValid()) {
1254 GtkTreeIter old_iter;
1255 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
1256 gtk_tree_selection_unselect_iter(selection, &old_iter);
1257 } else {
1258 g_warning("Trying to unselect invalid GtkTreeIter");
1259 }
1260 }
1261
1262 /* select the current */
1263 if (current.isValid()) {
1264 GtkTreeIter new_iter;
1265 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
1266 gtk_tree_selection_select_iter(selection, &new_iter);
1267 } else {
1268 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
1269 }
1270 }
1271 }
1272 );
1273
1274 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001275 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001276
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001277 /* connect to call state changes to update relevant view(s) */
1278 QObject::connect(
1279 CallModel::instance(),
1280 &CallModel::callStateChanged,
1281 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
1282 call_state_changed(call, win);
1283 }
1284 );
1285
Stepan Salenikovich15142182015-03-11 17:15:26 -04001286 /* contacts view/model */
1287 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1288 GtkWidget *treeview_contacts = gtk_tree_view_new();
1289 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_contacts), FALSE);
1290 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_contacts);
1291 gtk_widget_show_all(scrolled_window);
1292 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1293 scrolled_window,
1294 VIEW_CONTACTS);
1295
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001296 CategorizedContactModel::instance()->setUnreachableHidden(true);
1297 priv->q_contact_model = new ActiveItemProxyModel(CategorizedContactModel::instance());
1298 priv->q_contact_model->setSortRole(Qt::DisplayRole);
1299 priv->q_contact_model->setSortLocaleAware(true);
1300 priv->q_contact_model->setSortCaseSensitivity(Qt::CaseInsensitive);
1301 priv->q_contact_model->sort(0);
1302
1303 GtkQSortFilterTreeModel *contact_model = gtk_q_sort_filter_tree_model_new(
1304 (QSortFilterProxyModel *)priv->q_contact_model,
1305 1,
1306 Qt::DisplayRole, G_TYPE_STRING);
1307 gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_contacts), GTK_TREE_MODEL(contact_model));
1308
1309 /* photo and name/contact method column */
1310 GtkCellArea *area = gtk_cell_area_box_new();
1311 column = gtk_tree_view_column_new_with_area(area);
1312 gtk_tree_view_column_set_title(column, "Name");
1313
1314 /* photo renderer */
1315 renderer = gtk_cell_renderer_pixbuf_new();
1316 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
1317
1318 /* get the photo */
1319 gtk_tree_view_column_set_cell_data_func(
1320 column,
1321 renderer,
1322 (GtkTreeCellDataFunc)render_contact_photo,
1323 NULL,
1324 NULL);
1325
1326 /* name and contact method renderer */
1327 renderer = gtk_cell_renderer_text_new();
1328 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
1329
1330 gtk_tree_view_column_set_cell_data_func(
1331 column,
1332 renderer,
1333 (GtkTreeCellDataFunc)render_name_and_contact_method,
1334 NULL,
1335 NULL);
1336
1337 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_contacts), column);
1338 gtk_tree_view_column_set_resizable(column, TRUE);
1339
1340 gtk_tree_view_expand_all(GTK_TREE_VIEW(treeview_contacts));
1341 g_signal_connect(contact_model, "row-inserted", G_CALLBACK(expand_if_child), treeview_contacts);
1342 g_signal_connect(treeview_contacts, "row-activated", G_CALLBACK(activate_contact_item), NULL);
1343
Stepan Salenikovich15142182015-03-11 17:15:26 -04001344 /* history view/model */
1345 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1346 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001347 /* make headers visible to allow column resizing */
1348 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001349 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_history);
1350 gtk_widget_show_all(scrolled_window);
1351 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1352 scrolled_window,
1353 VIEW_HISTORY);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001354
Stepan Salenikovich7ff47962015-03-16 15:10:14 -04001355 /* sort the history in descending order by date */
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001356 priv->q_history_model = new QSortFilterProxyModel();
1357 priv->q_history_model->setSourceModel(CategorizedHistoryModel::instance());
1358 priv->q_history_model->setSortRole(static_cast<int>(Call::Role::Date));
1359 priv->q_history_model->sort(0,Qt::DescendingOrder);
Stepan Salenikovich7ff47962015-03-16 15:10:14 -04001360
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001361 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001362 priv->q_history_model,
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001363 5,
Stepan Salenikovich15142182015-03-11 17:15:26 -04001364 Qt::DisplayRole, G_TYPE_STRING,
1365 Call::Role::Number, G_TYPE_STRING,
1366 Call::Role::FormattedDate, G_TYPE_STRING,
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001367 Call::Role::Direction, G_TYPE_INT,
1368 Call::Role::Missed, G_TYPE_BOOLEAN);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001369 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
1370
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001371 /* name column, also used for call direction and fuzzy date for top level items */
1372 column = gtk_tree_view_column_new();
1373 gtk_tree_view_column_set_title(column, "Name");
1374
1375 /* call direction */
1376 renderer = gtk_cell_renderer_text_new();
1377 gtk_tree_view_column_pack_start(column, renderer, FALSE);
1378
1379 /* display the call direction with arrows */
1380 gtk_tree_view_column_set_cell_data_func(
1381 column,
1382 renderer,
1383 (GtkTreeCellDataFunc)render_call_direction,
1384 NULL,
1385 NULL);
1386
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001387 /* name or time category column */
1388 renderer = gtk_cell_renderer_text_new();
1389 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich164f8ec2015-03-29 12:28:19 -04001390 gtk_tree_view_column_pack_start(column, renderer, FALSE);
1391 gtk_tree_view_column_set_attributes(column, renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001392 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001393 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001394
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001395 /* "number" column */
1396 renderer = gtk_cell_renderer_text_new();
1397 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1398 column = gtk_tree_view_column_new_with_attributes("Number", renderer, "text", 1, NULL);
1399 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
1400 gtk_tree_view_column_set_resizable(column, TRUE);
1401
1402 /* date column */
Stepan Salenikovich15142182015-03-11 17:15:26 -04001403 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001404 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001405 column = gtk_tree_view_column_new_with_attributes ("Date", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001406 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
1407 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001408
Stepan Salenikovich2efd1982015-03-22 12:10:23 -04001409 /* expand the first row, which should be the most recent calls */
1410 gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_history),
1411 gtk_tree_path_new_from_string("0"),
1412 FALSE);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001413
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001414 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(activate_history_item), NULL);
Stepan Salenikovichfb5ff0a2015-03-29 22:47:47 -04001415 g_signal_connect(treeview_history, "button-press-event", G_CALLBACK(history_popup_menu), treeview_history);
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001416 g_signal_connect(history_model, "row-inserted", G_CALLBACK(expand_if_child), treeview_history);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001417
1418 /* presence view/model */
1419 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1420 GtkWidget *treeview_presence = gtk_tree_view_new();
1421 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_presence), FALSE);
1422 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_presence);
1423 gtk_widget_show_all(scrolled_window);
1424 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
1425 scrolled_window,
1426 VIEW_PRESENCE);
1427
1428 /* connect signals to change the contacts/history/presence stack view */
1429 g_signal_connect(priv->radiobutton_contacts, "toggled", G_CALLBACK(navbutton_contacts_toggled), win);
1430 g_signal_connect(priv->radiobutton_history, "toggled", G_CALLBACK(navbutton_history_toggled), win);
1431 g_signal_connect(priv->radiobutton_presence, "toggled", G_CALLBACK(navbutton_presence_toggled), win);
1432
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001433 /* TODO: make this linked to the client settings so that the last shown view is the same on startup */
1434 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_contacts), TRUE);
1435
Stepan Salenikovich69771842015-02-24 18:11:45 -05001436 /* TODO: replace stack paceholder view */
1437 GtkWidget *placeholder_view = gtk_tree_view_new();
1438 gtk_widget_show(placeholder_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001439 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), placeholder_view, DEFAULT_VIEW_NAME);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001440
1441 /* connect signals */
1442 g_signal_connect(call_selection, "changed", G_CALLBACK(call_selection_changed), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001443 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
1444 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
1445
1446 /* style of search entry */
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001447 gtk_widget_override_font(priv->search_entry, pango_font_description_from_string("15")); //"monospace 15"));
1448
1449 /* autocompletion */
1450 priv->q_completion_model = new NumberCompletionModel();
1451
1452 /* autocompletion renderers */
1453 GtkCellArea *completion_area = gtk_cell_area_box_new();
1454
1455 /* photo renderer */
1456 renderer = gtk_cell_renderer_pixbuf_new();
1457 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1458 renderer,
1459 TRUE, /* expand */
1460 TRUE, /* align */
1461 TRUE); /* fixed size */
1462
1463 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1464 renderer,
1465 (GtkCellLayoutDataFunc)autocompletion_photo_render,
1466 NULL, NULL);
1467
1468 /* name renderer */
1469 renderer = gtk_cell_renderer_text_new();
1470 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1471 renderer,
1472 TRUE, /* expand */
1473 TRUE, /* align */
1474 TRUE); /* fixed size */
1475
1476 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1477 renderer,
1478 (GtkCellLayoutDataFunc)autocompletion_name_render,
1479 NULL, NULL);
1480
1481 /* number renderer */
1482 renderer = gtk_cell_renderer_text_new();
1483 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1484 renderer,
1485 TRUE, /* expand */
1486 TRUE, /* align */
1487 TRUE); /* fixed size */
1488
1489 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1490 renderer,
1491 (GtkCellLayoutDataFunc)autocompletion_number_render,
1492 NULL, NULL);
1493 /* account renderer */
1494 renderer = gtk_cell_renderer_text_new();
1495 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1496 renderer,
1497 TRUE, /* expand */
1498 TRUE, /* align */
1499 TRUE); /* fixed size */
1500
1501 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1502 renderer,
1503 (GtkCellLayoutDataFunc)autocompletion_account_render,
1504 NULL, NULL);
1505
1506 GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);
1507
1508 GtkQTreeModel *completion_model = gtk_q_tree_model_new(
1509 (QAbstractItemModel *)priv->q_completion_model,
1510 1,
1511 Qt::DisplayRole, G_TYPE_STRING);
1512
1513 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));
1514
1515 gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
1516 gtk_entry_completion_set_match_func(
1517 entry_completion,
1518 (GtkEntryCompletionMatchFunc) completion_match_func,
1519 NULL,
1520 NULL);
1521
1522 /* connect signal to when text is entered in the entry */
1523 g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
1524 g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001525
1526 /* connect to incoming call and focus */
1527 QObject::connect(
1528 CallModel::instance(),
1529 &CallModel::incomingCall,
1530 [=](Call* call) {
1531 CallModel::instance()->selectionModel()->setCurrentIndex(
1532 CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect);
1533 }
1534 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001535
1536 /* display ring id by first getting the active ring account */
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001537 gtk_widget_override_font(priv->label_ring_id, pango_font_description_from_string("monospace"));
1538 gtk_widget_set_size_request(priv->label_ring_id, 400, 35);
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001539 get_active_ring_account(win);
1540 QObject::connect(
1541 AccountModel::instance(),
1542 &AccountModel::dataChanged,
1543 [=] () {
1544 /* check if the active ring account has changed,
1545 * eg: if it was deleted */
1546 get_active_ring_account(win);
1547 }
1548 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001549}
1550
1551static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001552ring_main_window_dispose(GObject *object)
1553{
1554 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1555}
1556
1557static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001558ring_main_window_finalize(GObject *object)
1559{
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001560 RingMainWindow *self = RING_MAIN_WINDOW(object);
1561 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1562
1563 delete priv->q_contact_model;
1564 delete priv->q_history_model;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001565 delete priv->q_completion_model;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001566
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001567 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001568}
1569
1570static void
1571ring_main_window_class_init(RingMainWindowClass *klass)
1572{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001573 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001574 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001575
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001576 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1577 "/cx/ring/RingGnome/ringmainwindow.ui");
1578
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001579 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, treeview_call);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001580 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_contacts_history_presence);
1581 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_contacts);
1582 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_history);
1583 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_presence);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001584 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1585 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1586 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1587 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
1588 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_search);
1589 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001590 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001591 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001592 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
1593 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_call_view);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001594 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001595 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_audio_settings);
1596 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
1597 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_video_settings);
1598 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001599 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_ring_id);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001600
1601 /* account creation */
1602 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
1603 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
1604 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
1605 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
1606 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1607 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1608 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
1609
1610 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
1611 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
1612 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001613}
1614
1615GtkWidget *
1616ring_main_window_new (GtkApplication *app)
1617{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001618 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1619
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001620 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001621}