blob: a60ee99550b3af6871ef843fa384d0fda5b5c567 [file] [log] [blame]
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001/*
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -04002 * Copyright (C) 2015 Savoir-faire Linux Inc.
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05003 * 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
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040024 * terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050025 * 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 Salenikovicha1b8cb32015-09-11 14:58:35 -040034#include <glib/gi18n.h>
Stepan Salenikovicha3557452015-02-20 14:14:12 -050035#include "models/gtkqtreemodel.h"
36#include <callmodel.h>
Stepan Salenikovich69771842015-02-24 18:11:45 -050037#include <call.h>
Stepan Salenikovichd2dbcee2015-02-27 16:52:28 -050038#include <QtCore/QItemSelectionModel>
Stepan Salenikovich69771842015-02-24 18:11:45 -050039#include "incomingcallview.h"
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050040#include "currentcallview.h"
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050041#include <string.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 Salenikovich0bd53492015-05-11 14:28:52 -040049#include "mediasettingsview.h"
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -040050#include <video/previewmanager.h>
Stepan Salenikovich6f687072015-03-26 10:43:37 -040051#include <personmodel.h>
52#include "utils/drawing.h"
53#include <memory>
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040054#include <globalinstances.h>
55#include "native/pixbufmanipulator.h"
Stepan Salenikovich6f687072015-03-26 10:43:37 -040056#include "models/activeitemproxymodel.h"
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -040057#include <numbercompletionmodel.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040058#include "utils/calling.h"
59#include "contactsview.h"
Stepan Salenikovich9d294492015-05-14 16:34:24 -040060#include <categorizedcontactmodel.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040061#include "historyview.h"
62#include "utils/models.h"
Stepan Salenikovichde896112015-05-11 16:46:33 -040063#include "generalsettingsview.h"
Stepan Salenikovich75a39172015-07-10 13:21:08 -040064#include "utils/accounts.h"
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040065#include "ringwelcomeview.h"
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040066#include "recentcontactsview.h"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040067
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040068static constexpr const char* CALL_VIEW_NAME = "calls";
69static constexpr const char* CREATE_ACCOUNT_1_VIEW_NAME = "create1";
70static constexpr const char* CREATE_ACCOUNT_2_VIEW_NAME = "create2";
71static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
72static constexpr const char* AUDIO_SETTINGS_VIEW_NAME = "audio";
73static constexpr const char* MEDIA_SETTINGS_VIEW_NAME = "media";
74static constexpr const char* ACCOUNT_SETTINGS_VIEW_NAME = "accounts";
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040075static constexpr const char* DEFAULT_VIEW_NAME = "welcome";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040076static constexpr const char* VIEW_CONTACTS = "contacts";
77static constexpr const char* VIEW_HISTORY = "history";
78static constexpr const char* VIEW_PRESENCE = "presence";
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050079
80struct _RingMainWindow
81{
82 GtkApplicationWindow parent;
83};
84
85struct _RingMainWindowClass
86{
87 GtkApplicationWindowClass parent_class;
88};
89
90typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
91
92struct _RingMainWindowPrivate
93{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040094 GtkWidget *ring_menu;
95 GtkWidget *image_ring;
96 GtkWidget *ring_settings;
97 GtkWidget *image_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040098 GtkWidget *hbox_settings;
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040099 GtkWidget *scrolled_window_smartview;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400100 GtkWidget *scrolled_window_contacts;
101 GtkWidget *scrolled_window_history;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400102 GtkWidget *vbox_left_pane;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500103 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500104 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400105 GtkWidget *vbox_call_view;
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400106 GtkWidget *frame_call;
107 GtkWidget *welcome_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500108 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400109 GtkWidget *account_settings_view;
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400110 GtkWidget *media_settings_view;
Stepan Salenikovichde896112015-05-11 16:46:33 -0400111 GtkWidget *general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400112 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400113 GtkWidget *radiobutton_general_settings;
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400114 GtkWidget *radiobutton_media_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400115 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400116
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400117 QMetaObject::Connection selection_updated;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400118
119 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400120
121 /* account creation */
122 GtkWidget *account_creation_1;
123 GtkWidget *image_ring_logo;
124 GtkWidget *label_enter_alias;
125 GtkWidget *entry_alias;
126 GtkWidget *label_generating_account;
127 GtkWidget *spinner_generating_account;
128 GtkWidget *button_account_creation_next;
129 GtkWidget *account_creation_2;
130 GtkWidget *entry_hash;
131 GtkWidget *button_account_creation_done;
132
133 QMetaObject::Connection hash_updated;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400134
135 /* allocd qmodels */
136 ActiveItemProxyModel *q_contact_model;
137 QSortFilterProxyModel *q_history_model;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400138 NumberCompletionModel *q_completion_model;
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500139
140 /* fullscreen */
141 gboolean is_fullscreen;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500142};
143
144G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
145
146#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
147
Stepan Salenikovich69771842015-02-24 18:11:45 -0500148static void
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500149enter_full_screen(RingMainWindow *self)
150{
151 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
152 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
153
154 if (!priv->is_fullscreen) {
155 gtk_widget_hide(priv->vbox_left_pane);
156 gtk_window_fullscreen(GTK_WINDOW(self));
157 priv->is_fullscreen = TRUE;
158 }
159}
160
161static void
162leave_full_screen(RingMainWindow *self)
163{
164 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
165 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
166
167 if (priv->is_fullscreen) {
168 gtk_widget_show(priv->vbox_left_pane);
169 gtk_window_unfullscreen(GTK_WINDOW(self));
170 priv->is_fullscreen = FALSE;
171 }
172}
173
174static void
Stepan Salenikovich5e431cf2015-12-24 14:09:01 -0500175video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500176{
177 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
178 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
179
180 if (priv->is_fullscreen) {
181 leave_full_screen(self);
182 } else {
183 enter_full_screen(self);
184 }
185}
186
187static void
188call_selection_changed(const QModelIndex& idx, RingMainWindow *win)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500189{
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400190 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500191 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
192
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500193 /* if we're showing the settings, then nothing needs to be done as the call
194 view is not shown */
195 if (priv->show_settings) return;
196
Stepan Salenikovich69771842015-02-24 18:11:45 -0500197 /* get the current visible stack child */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400198 GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500199
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500200 /* make sure we leave full screen, since the call selection is changing */
201 leave_full_screen(win);
202
Stepan Salenikovich69771842015-02-24 18:11:45 -0500203 if (idx.isValid()) {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400204 QVariant state = idx.data(static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500205 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500206 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500207
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400208 switch(state.value<Call::LifeCycleState>()) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400209 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500210 case Call::LifeCycleState::INITIALIZATION:
211 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500212 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500213 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500214 /* use the pointer of the call as a unique name */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400215 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance().getCall(idx));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500216 break;
217 case Call::LifeCycleState::PROGRESS:
218 new_call_view = current_call_view_new();
Stepan Salenikovich5e431cf2015-12-24 14:09:01 -0500219 g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500220 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
221 /* use the pointer of the call as a unique name */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400222 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance().getCall(idx));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500223 break;
224 case Call::LifeCycleState::COUNT__:
225 g_warning("LifeCycleState should never be COUNT");
226 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500227 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500228
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400229 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
230 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view);
231 gtk_widget_show(new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500232 g_free(new_call_view_name);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500233 } else {
234 /* nothing selected in the call model, so show the default screen */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400235 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
236 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500237 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500238}
239
240static void
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500241call_state_changed(Call *call, RingMainWindow *win)
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500242{
243 g_debug("call state changed");
244 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
245
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500246 /* if we're showing the settings, then nothing needs to be done as the call
247 view is not shown */
248 if (priv->show_settings) return;
249
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500250 /* check if the call that changed state is the same as the selected call */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400251 QModelIndex idx_selected = CallModel::instance().selectionModel()->currentIndex();
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500252
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400253 if( idx_selected.isValid() && call == CallModel::instance().getCall(idx_selected)) {
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500254 g_debug("selected call state changed");
255 /* check if we need to change the view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400256 GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500257 GtkWidget *new_call_view = NULL;
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400258 QVariant state = CallModel::instance().data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500259
260 /* check what the current state is vs what is displayed */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400261 switch(state.value<Call::LifeCycleState>()) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400262 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500263 case Call::LifeCycleState::INITIALIZATION:
264 /* LifeCycleState cannot go backwards, so it should not be possible
265 * that the call is displayed as current (meaning that its in progress)
266 * but have the state 'initialization' */
267 if (IS_CURRENT_CALL_VIEW(old_call_view))
268 g_warning("call displayed as current, but is in state of initialization");
269 break;
270 case Call::LifeCycleState::PROGRESS:
271 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
272 /* change from incoming to current */
273 new_call_view = current_call_view_new();
274 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
275 /* use the pointer of the call as a unique name */
276 char* new_call_view_name = NULL;
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400277 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance().getCall(idx_selected));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500278 g_free(new_call_view_name);
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400279 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
280 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view);
281 gtk_widget_show(new_call_view);
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500282 g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500283 }
284 break;
285 case Call::LifeCycleState::FINISHED:
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500286 /* leave fullscreen if call is over */
287 leave_full_screen(win);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500288 break;
289 case Call::LifeCycleState::COUNT__:
290 g_warning("LifeCycleState should never be COUNT");
291 break;
292 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500293 }
294}
295
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500296static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500297search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
298{
299 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
300
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400301 const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500302
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400303 if (number_entered && strlen(number_entered) > 0) {
304 /* detect Ring hash */
305 gboolean is_ring_hash = FALSE;
306 if (strlen(number_entered) == 40) {
307 is_ring_hash = TRUE;
308 /* must be 40 chars long and alphanumeric */
309 for (int i = 0; i < 40 && is_ring_hash; ++i) {
310 if (!g_ascii_isalnum(number_entered[i]))
311 is_ring_hash = FALSE;
312 }
313 }
314
315 QString number = QString{number_entered};
316
317 if (is_ring_hash)
318 number = "ring:" + number;
319
320 g_debug("dialing to number: %s", number.toUtf8().constData());
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400321
322 Call *call = priv->q_completion_model->call();
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500323 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500324 call->performAction(Call::Action::ACCEPT);
325
326 /* make this the currently selected call */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400327 QModelIndex idx = CallModel::instance().getIndex(call);
328 CallModel::instance().selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400329
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400330 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
331 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
332 /* clear the entry */
333 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
334 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500335}
336
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400337static gboolean
338grab_focus_on_widget(GtkWidget *widget)
Stepan Salenikovich15142182015-03-11 17:15:26 -0400339{
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400340 gtk_widget_grab_focus(widget);
341 return G_SOURCE_REMOVE;
Stepan Salenikovich15142182015-03-11 17:15:26 -0400342}
343
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400344static gboolean
345save_accounts(GtkWidget *working_dialog)
346{
347 /* save changes to accounts */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400348 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400349
350 if (working_dialog)
351 gtk_widget_destroy(working_dialog);
352
353 return G_SOURCE_REMOVE;
354}
355
Stepan Salenikovich15142182015-03-11 17:15:26 -0400356static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400357settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
358{
359 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
360 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
361
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400362 /* check which view to show */
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500363 if (!priv->show_settings) {
364 /* show the settings */
365
366 /* destroy the call view by passing an invalid call index, to make sure
367 we don't have more than one clutter stage at a time */
368 call_selection_changed(QModelIndex(), win);
369
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400370 /* show settings */
371 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
372
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400373 gtk_widget_show(priv->hbox_settings);
374
Stepan Salenikovich41118912015-05-01 11:25:46 -0400375 /* make sure to start preview if we're showing the video settings */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400376 if (priv->last_settings_view == priv->media_settings_view)
377 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich41118912015-05-01 11:25:46 -0400378
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400379 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400380 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500381
382 priv->show_settings = TRUE;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400383 } else {
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500384 /* hide the settings */
385 priv->show_settings = FALSE;
386
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400387 /* show working dialog in case save operation takes time */
388 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
389 gtk_window_present(GTK_WINDOW(working));
390
391 /* now save after the time it takes to transition back to the call view (400ms)
392 * the save doesn't happen before the "working" dialog is presented
393 * the timeout function should destroy the "working" dialog when done saving
394 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400395 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400396
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400397 /* show calls */
398 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
399
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400400 gtk_widget_hide(priv->hbox_settings);
401
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500402 /* make sure video preview is stopped, in case it was started */
403 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
404
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400405 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
406 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400407
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500408 /* show the call view, if there is a call in progress */
409 call_selection_changed(CallModel::instance().selectionModel()->currentIndex(), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400410 }
411}
412
413static void
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400414show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400415{
416 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
417 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
418
419 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400420 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400421 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400422 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400423 priv->last_settings_view = priv->media_settings_view;
Stepan Salenikovich41118912015-05-01 11:25:46 -0400424 } else {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400425 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400426 }
427}
428
429static void
430show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
431{
432 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
433 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
434
435 if (gtk_toggle_button_get_active(navbutton)) {
436 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
437 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
438 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400439 }
440}
441
Stepan Salenikovichde896112015-05-11 16:46:33 -0400442static void
443show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
444{
445 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
446 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
447
448 if (gtk_toggle_button_get_active(navbutton)) {
449 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
450 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
451 priv->last_settings_view = priv->general_settings_view;
452 }
453}
454
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400455static gboolean
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400456create_ring_account(RingMainWindow *win)
457{
458 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
459 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
460
461 /* create account and set UPnP enabled, as its not by default in the daemon */
462 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400463 Account *account = AccountModel::instance().add(alias, Account::Protocol::RING);
Stepan Salenikovich75a39172015-07-10 13:21:08 -0400464 account->setDisplayName(alias); // set the display name to the same as the alias
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400465 account->setUpnpEnabled(TRUE);
466
467 /* wait for hash to be generated to show the next view */
468 priv->hash_updated = QObject::connect(
469 account,
470 &Account::changed,
471 [=] (Account *a) {
472 QString hash = a->username();
473 if (!hash.isEmpty()) {
474 /* set the hash */
475 gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());
476
477 /* show the next accont creation view */
478 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
479 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);
480
481 /* select the hash text */
482 gtk_widget_grab_focus(priv->entry_hash);
483 gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
484 }
485 }
486 );
487
488 account->performAction(Account::EditAction::SAVE);
489
490 return G_SOURCE_REMOVE;
491}
492
493static void
494alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
495{
496 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
497
498 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
499 if (strlen(alias) > 0) {
500 /* enable "next" button */
501 gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
502 } else {
503 /* disable "next" button, as we require an alias */
504 gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
505 }
506}
507
508static void
509account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
510{
511 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
512
513 /* show/hide relevant widgets */
514 gtk_widget_hide(priv->label_enter_alias);
515 gtk_widget_hide(priv->entry_alias);
516 gtk_widget_hide(priv->button_account_creation_next);
517 gtk_widget_show(priv->label_generating_account);
518 gtk_widget_show(priv->spinner_generating_account);
519
520 /* now create account after a short timeout so that the the save doesn't
521 * happen freeze the client before the widget changes happen;
522 * the timeout function should then display the next step in account creation
523 */
524 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
525}
526
527static void
528account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
529{
530 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
531
532 QObject::disconnect(priv->hash_updated);
533
534 /* show the call view */
535 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
536 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
537
Stepan Salenikovich64505672015-09-24 10:46:07 -0400538 /* show the settings button*/
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400539 gtk_widget_show(priv->ring_settings);
540
541}
542
543static void
544entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
545{
546 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
547
548 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
549 if (strlen(alias) > 0)
550 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
551}
552
553static void
554show_account_creation(RingMainWindow *win)
555{
556 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
557
558 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
559 priv->account_creation_1,
560 CREATE_ACCOUNT_1_VIEW_NAME);
561
562 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
563 priv->account_creation_2,
564 CREATE_ACCOUNT_2_VIEW_NAME);
565
Stepan Salenikovich64505672015-09-24 10:46:07 -0400566 /* hide settings button until account creation is complete */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400567 gtk_widget_hide(priv->ring_settings);
568
569 /* set ring logo */
570 GError *error = NULL;
571 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
572 -1, 75, TRUE, &error);
573 if (logo_ring == NULL) {
574 g_debug("Could not load logo: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400575 g_clear_error(&error);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400576 } else
577 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
578
579 /* style of alias and hash entry; give them a larger font */
580 gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
581 gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));
582
583 /* connect signals */
584 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
585 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
586 g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
587 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
588 g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);
589
590 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
591}
592
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400593static void
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400594search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
595{
596 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
597
598 /* get the text from the entry */
599 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
600
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400601 if (text && strlen(text) > 0) {
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400602 /* edit the the dialing call (or create a new one) */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400603 if (auto call = CallModel::instance().dialingCall()) {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400604 call->setDialNumber(text);
605 priv->q_completion_model->setCall(call);
606 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400607 } else {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400608 if (auto call = priv->q_completion_model->call()) {
609 if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
610 call->performAction(Call::Action::REFUSE);
611 }
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400612 }
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400613}
614
615static gboolean
616completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
617 G_GNUC_UNUSED const gchar *key,
618 G_GNUC_UNUSED GtkTreeIter *iter,
619 G_GNUC_UNUSED RingMainWindow *win)
620{
621 /* the model is updated by lrc and should only every contain matching entries
622 * so always return TRUE */
623 return TRUE;
624}
625
626static QModelIndex
627get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
628 GtkTreeIter *filter_iter)
629{
630 GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
631 GtkTreeIter child_iter;
632 gtk_tree_model_filter_convert_iter_to_child_iter(
633 GTK_TREE_MODEL_FILTER(filter_model),
634 &child_iter,
635 filter_iter);
636
637 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
638}
639
640static void
641autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
642 GtkCellRenderer *cell,
643 GtkTreeModel *model,
644 GtkTreeIter *iter,
645 G_GNUC_UNUSED gpointer user_data)
646{
647 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
648 if (idx.isValid()) {
649 QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
650 if (photo_var.isValid()) {
651 std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
652 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
653 20, 20,
654 GDK_INTERP_BILINEAR);
655
656 g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
657 g_object_unref(scaled);
658 return;
659 }
660 }
661
662 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
663}
664
665static void
666autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
667 GtkCellRenderer *cell,
668 GtkTreeModel *model,
669 GtkTreeIter *iter,
670 G_GNUC_UNUSED gpointer user_data)
671{
672 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
673 if (idx.isValid()) {
674 QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
Stepan Salenikovich92189cc2015-09-24 10:59:22 -0400675 gchar *text = g_strdup_printf("<span weight=\"bold\">%s</span>",
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400676 name.value<QString>().toUtf8().constData());
677
678 g_object_set(G_OBJECT(cell), "markup", text, NULL);
679 g_free(text);
680 return;
681 }
682
683 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
684}
685
686static void
687autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
688 GtkCellRenderer *cell,
689 GtkTreeModel *model,
690 GtkTreeIter *iter,
691 G_GNUC_UNUSED gpointer user_data)
692{
693 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
694 if (idx.isValid()) {
695 QVariant uri = idx.data(Qt::DisplayRole);
Stepan Salenikovich92189cc2015-09-24 10:59:22 -0400696 gchar *text = g_strdup_printf("%s", uri.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400697
698 g_object_set(G_OBJECT(cell), "markup", text, NULL);
699 g_free(text);
700 return;
701 }
702
703 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
704}
705
706static void
707autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
708 GtkCellRenderer *cell,
709 GtkTreeModel *model,
710 GtkTreeIter *iter,
711 G_GNUC_UNUSED gpointer user_data)
712{
713 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
714 if (idx.isValid()) {
715 QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
Stepan Salenikovich92189cc2015-09-24 10:59:22 -0400716 gchar *text = g_strdup_printf("<span color=\"gray\">%s</span>",
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400717 alias.value<QString>().toUtf8().constData());
718
719 g_object_set(G_OBJECT(cell), "markup", text, NULL);
720 g_free(text);
721 return;
722 }
723
724 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
725}
726
727static gboolean
728select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
729 GtkTreeModel *model,
730 GtkTreeIter *iter,
731 RingMainWindow *win)
732{
733 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
734
735 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
736 if (idx.isValid()) {
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400737 priv->q_completion_model->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
738 Call *call = priv->q_completion_model->call();
739 priv->q_completion_model->callSelectedNumber();
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400740
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400741 /* make this the currently selected call */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400742 QModelIndex idx = CallModel::instance().getIndex(call);
743 CallModel::instance().selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400744
745 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
746 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400747
748 /* clear the entry */
749 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400750 } else {
751 g_warning("autocompletion selection is not a valid index!");
752 }
753 return TRUE;
754}
755
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400756static gboolean
757dtmf_pressed(RingMainWindow *win,
758 GdkEventKey *event,
759 G_GNUC_UNUSED gpointer user_data)
760{
761 g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
762
763 /* we want to react to digit key presses, as long as a GtkEntry is not the
764 * input focus
765 */
766 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
767 if (GTK_IS_ENTRY(focus))
768 return GDK_EVENT_PROPAGATE;
769
770 /* make sure that a call is selected*/
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400771 QItemSelectionModel *selection = CallModel::instance().selectionModel();
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400772 QModelIndex idx = selection->currentIndex();
773 if (!idx.isValid())
774 return GDK_EVENT_PROPAGATE;
775
776 /* make sure that the selected call is in progress */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400777 Call *call = CallModel::instance().getCall(idx);
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400778 Call::LifeCycleState state = call->lifeCycleState();
779 if (state != Call::LifeCycleState::PROGRESS)
780 return GDK_EVENT_PROPAGATE;
781
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400782 /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
783 * does not result in a 'c' being played.
784 * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
785 if ( event->state
786 & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
787 return GDK_EVENT_PROPAGATE;
788
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400789 /* pass the character that was entered to be played by the daemon;
790 * the daemon will filter out invalid DTMF characters */
791 guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
792 QString val = QString::fromUcs4(&unicode_val, 1);
793 call->playDTMF(val);
794 g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400795
796 /* always propogate the key, so we don't steal accelerators/shortcuts */
797 return GDK_EVENT_PROPAGATE;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400798}
799
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400800static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500801ring_main_window_init(RingMainWindow *win)
802{
803 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
804 gtk_widget_init_template(GTK_WIDGET(win));
805
806 /* set window icon */
807 GError *error = NULL;
808 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
809 if (icon == NULL) {
810 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400811 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500812 } else
813 gtk_window_set_icon(GTK_WINDOW(win), icon);
814
815 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400816 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
817 -1, 24, TRUE, &error);
818 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500819 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400820 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500821 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400822 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500823
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400824 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500825 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
826 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400827 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500828 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500829
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400830 /* settings icon */
831 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
832
833 /* connect settings button signal */
834 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
835
836 /* add the call view to the main stack */
837 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
838 priv->vbox_call_view,
839 CALL_VIEW_NAME);
840
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400841 if (has_ring_account()) {
842 /* user has ring account, so show the call view right away */
843 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
844 } else {
845 /* user has to create the ring account */
846 show_account_creation(win);
847 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400848
849 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400850 priv->account_settings_view = account_view_new();
851 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
852
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400853 priv->media_settings_view = media_settings_view_new();
854 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->media_settings_view, MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400855
Stepan Salenikovichde896112015-05-11 16:46:33 -0400856 priv->general_settings_view = general_settings_view_new();
857 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);
858
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400859 /* make the setting we will show first the active one */
Stepan Salenikovichde896112015-05-11 16:46:33 -0400860 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
861 priv->last_settings_view = priv->general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400862
863 /* connect the settings button signals to switch settings views */
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400864 g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400865 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400866 g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400867
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400868 /* populate the notebook */
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400869 auto smart_view = recent_contacts_view_new();
870 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), smart_view);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400871
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400872 auto contacts_view = contacts_view_new();
873 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), contacts_view);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400874
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400875 auto history_view = history_view_new();
876 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), history_view);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400877
Stepan Salenikovich2cde7612015-09-25 10:44:01 -0400878 /* welcome/default view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400879 priv->welcome_view = ring_welcome_view_new();
880 g_object_ref(priv->welcome_view);
881 // gtk_stack_add_named(GTK_STACK(priv->stack_call_view), welcome_view, DEFAULT_VIEW_NAME);
882 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
883 gtk_widget_show(priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500884
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400885 /* call selection */
886 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400887 CallModel::instance().selectionModel(),
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400888 &QItemSelectionModel::currentChanged,
889 [win](const QModelIndex current, G_GNUC_UNUSED const QModelIndex & previous) {
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400890 if (auto call = CallModel::instance().getCall(current)) {
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400891 /* if the call is on hold, we want to put it off hold automatically
892 * when switching to it */
893 if (call->state() == Call::State::HOLD)
894 call << Call::Action::HOLD;
895
896 /* this is a bit of a hack, as for some reason the call is not in the correct
897 * state in the UserActionModel when the selection model switches calls by itself */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400898 CallModel::instance().selectCall(call);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400899 }
900 call_selection_changed(current, win);
901 }
902 );
903
904 /* connect to call state changes to update relevant view(s) */
905 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400906 &CallModel::instance(),
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400907 &CallModel::callStateChanged,
908 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
909 call_state_changed(call, win);
910 }
911 );
912
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500913 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
914 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
915
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400916 /* autocompletion */
917 priv->q_completion_model = new NumberCompletionModel();
918
919 /* autocompletion renderers */
920 GtkCellArea *completion_area = gtk_cell_area_box_new();
921
922 /* photo renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400923 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400924 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
925 renderer,
926 TRUE, /* expand */
927 TRUE, /* align */
928 TRUE); /* fixed size */
929
930 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
931 renderer,
932 (GtkCellLayoutDataFunc)autocompletion_photo_render,
933 NULL, NULL);
934
935 /* name renderer */
936 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -0400937 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400938 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
939 renderer,
940 TRUE, /* expand */
941 TRUE, /* align */
942 TRUE); /* fixed size */
943
944 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
945 renderer,
946 (GtkCellLayoutDataFunc)autocompletion_name_render,
947 NULL, NULL);
948
949 /* number renderer */
950 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -0400951 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400952 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
953 renderer,
954 TRUE, /* expand */
955 TRUE, /* align */
956 TRUE); /* fixed size */
957
958 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
959 renderer,
960 (GtkCellLayoutDataFunc)autocompletion_number_render,
961 NULL, NULL);
962 /* account renderer */
963 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -0400964 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400965 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
966 renderer,
967 TRUE, /* expand */
968 TRUE, /* align */
969 TRUE); /* fixed size */
970
971 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
972 renderer,
973 (GtkCellLayoutDataFunc)autocompletion_account_render,
974 NULL, NULL);
975
976 GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);
977
978 GtkQTreeModel *completion_model = gtk_q_tree_model_new(
979 (QAbstractItemModel *)priv->q_completion_model,
980 1,
981 Qt::DisplayRole, G_TYPE_STRING);
982
983 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));
984
985 gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
986 gtk_entry_completion_set_match_func(
987 entry_completion,
988 (GtkEntryCompletionMatchFunc) completion_match_func,
989 NULL,
990 NULL);
991
992 /* connect signal to when text is entered in the entry */
993 g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
994 g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500995
996 /* connect to incoming call and focus */
997 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400998 &CallModel::instance(),
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500999 &CallModel::incomingCall,
1000 [=](Call* call) {
Guillaume Roguez5d1514b2015-10-22 15:55:31 -04001001 CallModel::instance().selectionModel()->setCurrentIndex(
1002 CallModel::instance().getIndex(call), QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001003 }
1004 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001005
Stepan Salenikovichb01d7362015-04-27 23:02:00 -04001006 /* react to digit key press events */
1007 g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
Stepan Salenikovich48b45c12015-09-25 15:49:51 -04001008
1009 /* set the search entry placeholder text */
1010 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
1011 C_("Please try to make the translation 50 chars or less so that it fits into the layout", "Search contacts or enter number"));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001012}
1013
1014static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001015ring_main_window_dispose(GObject *object)
1016{
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001017 RingMainWindow *self = RING_MAIN_WINDOW(object);
1018 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1019
1020 QObject::disconnect(priv->selection_updated);
1021
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001022 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1023}
1024
1025static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001026ring_main_window_finalize(GObject *object)
1027{
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001028 RingMainWindow *self = RING_MAIN_WINDOW(object);
1029 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1030
1031 delete priv->q_contact_model;
1032 delete priv->q_history_model;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001033 delete priv->q_completion_model;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001034
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001035 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001036}
1037
1038static void
1039ring_main_window_class_init(RingMainWindowClass *klass)
1040{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001041 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001042 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001043
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001044 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1045 "/cx/ring/RingGnome/ringmainwindow.ui");
1046
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001047 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001048 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001049 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
1050 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001051 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1052 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1053 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1054 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001055 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001056 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001057 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001058 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001059 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001060 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001061 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001062 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001063 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001064
1065 /* account creation */
1066 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
1067 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
1068 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
1069 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
1070 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1071 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1072 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
1073
1074 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
1075 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
1076 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001077}
1078
1079GtkWidget *
1080ring_main_window_new (GtkApplication *app)
1081{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001082 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1083
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001084 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001085}