blob: 25da465caf53f4d2d7b89e657c3ef56b5a814478 [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"
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040059#include "frequentcontactsview.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040060#include "contactsview.h"
Stepan Salenikovich9d294492015-05-14 16:34:24 -040061#include <categorizedcontactmodel.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040062#include "historyview.h"
63#include "utils/models.h"
Stepan Salenikovichde896112015-05-11 16:46:33 -040064#include "generalsettingsview.h"
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040065#include "callsview.h"
Stepan Salenikovich75a39172015-07-10 13:21:08 -040066#include "utils/accounts.h"
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040067#include "ringwelcomeview.h"
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040068#include "recentcontactsview.h"
Stepan Salenikovichb8e41272015-03-27 14:31:54 -040069
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040070static constexpr const char* CALL_VIEW_NAME = "calls";
71static constexpr const char* CREATE_ACCOUNT_1_VIEW_NAME = "create1";
72static constexpr const char* CREATE_ACCOUNT_2_VIEW_NAME = "create2";
73static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
74static constexpr const char* AUDIO_SETTINGS_VIEW_NAME = "audio";
75static constexpr const char* MEDIA_SETTINGS_VIEW_NAME = "media";
76static constexpr const char* ACCOUNT_SETTINGS_VIEW_NAME = "accounts";
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040077static constexpr const char* DEFAULT_VIEW_NAME = "welcome";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040078static constexpr const char* VIEW_CONTACTS = "contacts";
79static constexpr const char* VIEW_HISTORY = "history";
80static constexpr const char* VIEW_PRESENCE = "presence";
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050081
Stepan Salenikovich48b45c12015-09-25 15:49:51 -040082static constexpr int SEARCH_ENTRY_WIDTH_CHARS_MAX = 50;
83
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050084struct _RingMainWindow
85{
86 GtkApplicationWindow parent;
87};
88
89struct _RingMainWindowClass
90{
91 GtkApplicationWindowClass parent_class;
92};
93
94typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
95
96struct _RingMainWindowPrivate
97{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040098 GtkWidget *ring_menu;
99 GtkWidget *image_ring;
100 GtkWidget *ring_settings;
101 GtkWidget *image_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400102 GtkWidget *hbox_settings;
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400103 GtkWidget *scrolled_window_smartview;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400104 GtkWidget *scrolled_window_contacts;
105 GtkWidget *scrolled_window_history;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400106 GtkWidget *vbox_left_pane;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500107 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500108 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400109 GtkWidget *vbox_call_view;
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400110 GtkWidget *frame_call;
111 GtkWidget *welcome_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500112 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400113 GtkWidget *account_settings_view;
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400114 GtkWidget *media_settings_view;
Stepan Salenikovichde896112015-05-11 16:46:33 -0400115 GtkWidget *general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400116 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400117 GtkWidget *radiobutton_general_settings;
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400118 GtkWidget *radiobutton_media_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400119 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich86648732015-09-25 11:01:25 -0400120 GtkWidget *hbox_ring_hash;
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400121 GtkWidget *label_ring_id;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400122
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400123 QMetaObject::Connection selection_updated;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400124
125 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400126
127 /* account creation */
128 GtkWidget *account_creation_1;
129 GtkWidget *image_ring_logo;
130 GtkWidget *label_enter_alias;
131 GtkWidget *entry_alias;
132 GtkWidget *label_generating_account;
133 GtkWidget *spinner_generating_account;
134 GtkWidget *button_account_creation_next;
135 GtkWidget *account_creation_2;
136 GtkWidget *entry_hash;
137 GtkWidget *button_account_creation_done;
138
139 QMetaObject::Connection hash_updated;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400140
141 /* allocd qmodels */
142 ActiveItemProxyModel *q_contact_model;
143 QSortFilterProxyModel *q_history_model;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400144 NumberCompletionModel *q_completion_model;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500145};
146
147G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
148
149#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
150
Stepan Salenikovich69771842015-02-24 18:11:45 -0500151static void
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400152call_selection_changed(const QModelIndex& idx, gpointer win)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500153{
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400154 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500155 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
156
157 /* get the current visible stack child */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400158 GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500159
Stepan Salenikovich69771842015-02-24 18:11:45 -0500160 if (idx.isValid()) {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400161 QVariant state = idx.data(static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500162 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500163 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500164
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400165 switch(state.value<Call::LifeCycleState>()) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400166 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500167 case Call::LifeCycleState::INITIALIZATION:
168 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500169 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500170 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500171 /* use the pointer of the call as a unique name */
172 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance()->getCall(idx));
173 break;
174 case Call::LifeCycleState::PROGRESS:
175 new_call_view = current_call_view_new();
176 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
177 /* use the pointer of the call as a unique name */
178 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx));
179 break;
180 case Call::LifeCycleState::COUNT__:
181 g_warning("LifeCycleState should never be COUNT");
182 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500183 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500184
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400185 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
186 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view);
187 gtk_widget_show(new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500188 g_free(new_call_view_name);
Stepan Salenikovich86648732015-09-25 11:01:25 -0400189 /* show ringID at the bottom */
190 gtk_widget_show(priv->hbox_ring_hash);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500191 } else {
192 /* nothing selected in the call model, so show the default screen */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400193 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
194 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
Stepan Salenikovich86648732015-09-25 11:01:25 -0400195 /* hide ringID at the bottom */
196 gtk_widget_hide(priv->hbox_ring_hash);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500197 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500198}
199
200static void
201call_state_changed(Call *call, gpointer win)
202{
203 g_debug("call state changed");
204 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
205
206 /* check if the call that changed state is the same as the selected call */
207 QModelIndex idx_selected = CallModel::instance()->selectionModel()->currentIndex();
208
209 if( idx_selected.isValid() && call == CallModel::instance()->getCall(idx_selected)) {
210 g_debug("selected call state changed");
211 /* check if we need to change the view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400212 GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500213 GtkWidget *new_call_view = NULL;
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400214 QVariant state = CallModel::instance()->data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500215
216 /* check what the current state is vs what is displayed */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400217 switch(state.value<Call::LifeCycleState>()) {
Stepan Salenikovich46e3e992015-04-08 11:42:13 -0400218 case Call::LifeCycleState::CREATION:
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500219 case Call::LifeCycleState::INITIALIZATION:
220 /* LifeCycleState cannot go backwards, so it should not be possible
221 * that the call is displayed as current (meaning that its in progress)
222 * but have the state 'initialization' */
223 if (IS_CURRENT_CALL_VIEW(old_call_view))
224 g_warning("call displayed as current, but is in state of initialization");
225 break;
226 case Call::LifeCycleState::PROGRESS:
227 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
228 /* change from incoming to current */
229 new_call_view = current_call_view_new();
230 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
231 /* use the pointer of the call as a unique name */
232 char* new_call_view_name = NULL;
233 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx_selected));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500234 g_free(new_call_view_name);
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), new_call_view);
237 gtk_widget_show(new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500238 }
239 break;
240 case Call::LifeCycleState::FINISHED:
241 /* do nothing, either call view is valid for this state */
242 break;
243 case Call::LifeCycleState::COUNT__:
244 g_warning("LifeCycleState should never be COUNT");
245 break;
246 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500247 }
248}
249
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500250static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500251search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
252{
253 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
254
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400255 const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500256
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400257 if (number_entered && strlen(number_entered) > 0) {
258 /* detect Ring hash */
259 gboolean is_ring_hash = FALSE;
260 if (strlen(number_entered) == 40) {
261 is_ring_hash = TRUE;
262 /* must be 40 chars long and alphanumeric */
263 for (int i = 0; i < 40 && is_ring_hash; ++i) {
264 if (!g_ascii_isalnum(number_entered[i]))
265 is_ring_hash = FALSE;
266 }
267 }
268
269 QString number = QString{number_entered};
270
271 if (is_ring_hash)
272 number = "ring:" + number;
273
274 g_debug("dialing to number: %s", number.toUtf8().constData());
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400275
276 Call *call = priv->q_completion_model->call();
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500277 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500278 call->performAction(Call::Action::ACCEPT);
279
280 /* make this the currently selected call */
281 QModelIndex idx = CallModel::instance()->getIndex(call);
282 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400283
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400284 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
285 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
286 /* clear the entry */
287 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
288 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500289}
290
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400291static gboolean
292grab_focus_on_widget(GtkWidget *widget)
Stepan Salenikovich15142182015-03-11 17:15:26 -0400293{
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400294 gtk_widget_grab_focus(widget);
295 return G_SOURCE_REMOVE;
Stepan Salenikovich15142182015-03-11 17:15:26 -0400296}
297
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400298static gboolean
299save_accounts(GtkWidget *working_dialog)
300{
301 /* save changes to accounts */
302 AccountModel::instance()->save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400303
304 if (working_dialog)
305 gtk_widget_destroy(working_dialog);
306
307 return G_SOURCE_REMOVE;
308}
309
Stepan Salenikovich15142182015-03-11 17:15:26 -0400310static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400311settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
312{
313 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
314 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
315
316 /* toggle show settings */
317 priv->show_settings = !priv->show_settings;
318
319 /* check which view to show */
320 if (priv->show_settings) {
321 /* show settings */
322 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
323
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400324 gtk_widget_show(priv->hbox_settings);
325
Stepan Salenikovich41118912015-05-01 11:25:46 -0400326 /* make sure to start preview if we're showing the video settings */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400327 if (priv->last_settings_view == priv->media_settings_view)
328 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich41118912015-05-01 11:25:46 -0400329
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400330 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400331 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400332 } else {
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400333 /* show working dialog in case save operation takes time */
334 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
335 gtk_window_present(GTK_WINDOW(working));
336
337 /* now save after the time it takes to transition back to the call view (400ms)
338 * the save doesn't happen before the "working" dialog is presented
339 * the timeout function should destroy the "working" dialog when done saving
340 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400341 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400342
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400343 /* show calls */
344 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
345
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400346 gtk_widget_hide(priv->hbox_settings);
347
348 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
349 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400350
351 /* make sure video preview is stopped, in case it was started */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400352 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400353 }
354}
355
356static void
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400357show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400358{
359 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
360 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
361
362 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400363 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400364 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400365 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400366 priv->last_settings_view = priv->media_settings_view;
Stepan Salenikovich41118912015-05-01 11:25:46 -0400367 } else {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400368 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400369 }
370}
371
372static void
373show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
374{
375 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
376 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
377
378 if (gtk_toggle_button_get_active(navbutton)) {
379 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
380 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
381 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400382 }
383}
384
Stepan Salenikovichde896112015-05-11 16:46:33 -0400385static void
386show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
387{
388 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
389 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
390
391 if (gtk_toggle_button_get_active(navbutton)) {
392 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
393 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
394 priv->last_settings_view = priv->general_settings_view;
395 }
396}
397
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400398static gboolean
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400399create_ring_account(RingMainWindow *win)
400{
401 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
402 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
403
404 /* create account and set UPnP enabled, as its not by default in the daemon */
405 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
406 Account *account = AccountModel::instance()->add(alias, Account::Protocol::RING);
Stepan Salenikovich75a39172015-07-10 13:21:08 -0400407 account->setDisplayName(alias); // set the display name to the same as the alias
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400408 account->setUpnpEnabled(TRUE);
409
410 /* wait for hash to be generated to show the next view */
411 priv->hash_updated = QObject::connect(
412 account,
413 &Account::changed,
414 [=] (Account *a) {
415 QString hash = a->username();
416 if (!hash.isEmpty()) {
417 /* set the hash */
418 gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());
419
420 /* show the next accont creation view */
421 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
422 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);
423
424 /* select the hash text */
425 gtk_widget_grab_focus(priv->entry_hash);
426 gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
427 }
428 }
429 );
430
431 account->performAction(Account::EditAction::SAVE);
432
433 return G_SOURCE_REMOVE;
434}
435
436static void
437alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
438{
439 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
440
441 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
442 if (strlen(alias) > 0) {
443 /* enable "next" button */
444 gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
445 } else {
446 /* disable "next" button, as we require an alias */
447 gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
448 }
449}
450
451static void
452account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
453{
454 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
455
456 /* show/hide relevant widgets */
457 gtk_widget_hide(priv->label_enter_alias);
458 gtk_widget_hide(priv->entry_alias);
459 gtk_widget_hide(priv->button_account_creation_next);
460 gtk_widget_show(priv->label_generating_account);
461 gtk_widget_show(priv->spinner_generating_account);
462
463 /* now create account after a short timeout so that the the save doesn't
464 * happen freeze the client before the widget changes happen;
465 * the timeout function should then display the next step in account creation
466 */
467 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
468}
469
470static void
471account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
472{
473 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
474
475 QObject::disconnect(priv->hash_updated);
476
477 /* show the call 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), CALL_VIEW_NAME);
480
Stepan Salenikovich64505672015-09-24 10:46:07 -0400481 /* show the settings button*/
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400482 gtk_widget_show(priv->ring_settings);
483
484}
485
486static void
487entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
488{
489 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
490
491 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
492 if (strlen(alias) > 0)
493 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
494}
495
496static void
497show_account_creation(RingMainWindow *win)
498{
499 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
500
501 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
502 priv->account_creation_1,
503 CREATE_ACCOUNT_1_VIEW_NAME);
504
505 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
506 priv->account_creation_2,
507 CREATE_ACCOUNT_2_VIEW_NAME);
508
Stepan Salenikovich64505672015-09-24 10:46:07 -0400509 /* hide settings button until account creation is complete */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400510 gtk_widget_hide(priv->ring_settings);
511
512 /* set ring logo */
513 GError *error = NULL;
514 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
515 -1, 75, TRUE, &error);
516 if (logo_ring == NULL) {
517 g_debug("Could not load logo: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400518 g_clear_error(&error);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400519 } else
520 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
521
522 /* style of alias and hash entry; give them a larger font */
523 gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
524 gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));
525
526 /* connect signals */
527 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
528 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
529 g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
530 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
531 g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);
532
533 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
534}
535
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400536static void
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400537show_ring_id(RingMainWindow *win, Account *account) {
538 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
539
540 /* display the ring id, if we found a ring account */
541 if (account) {
542 if (!account->username().isEmpty()) {
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400543 QString hash = "<span fgcolor=\"black\">" + account->username() + "</span>";
544 gtk_label_set_label(GTK_LABEL(priv->label_ring_id), hash.toUtf8().constData());
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400545 } else {
546 g_warning("got ring account, but Ring id is empty");
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400547 gtk_label_set_label(GTK_LABEL(priv->label_ring_id),
548 g_strdup_printf("<span fgcolor=\"gray\">%s</span>",
549 _("fetching RingID...")));
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400550 }
551 } else {
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400552 gtk_label_set_label(GTK_LABEL(priv->label_ring_id),
553 g_strdup_printf("<span fgcolor=\"gray\">%s</span>",
554 _("no Ring account")));
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400555 }
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400556}
557
558static void
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400559search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
560{
561 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
562
563 /* get the text from the entry */
564 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
565
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400566 if (text && strlen(text) > 0) {
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400567 /* edit the the dialing call (or create a new one) */
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400568 if (auto call = CallModel::instance()->dialingCall()) {
569 call->setDialNumber(text);
570 priv->q_completion_model->setCall(call);
571 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400572 } else {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400573 if (auto call = priv->q_completion_model->call()) {
574 if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
575 call->performAction(Call::Action::REFUSE);
576 }
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400577 }
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400578}
579
580static gboolean
581completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
582 G_GNUC_UNUSED const gchar *key,
583 G_GNUC_UNUSED GtkTreeIter *iter,
584 G_GNUC_UNUSED RingMainWindow *win)
585{
586 /* the model is updated by lrc and should only every contain matching entries
587 * so always return TRUE */
588 return TRUE;
589}
590
591static QModelIndex
592get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
593 GtkTreeIter *filter_iter)
594{
595 GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
596 GtkTreeIter child_iter;
597 gtk_tree_model_filter_convert_iter_to_child_iter(
598 GTK_TREE_MODEL_FILTER(filter_model),
599 &child_iter,
600 filter_iter);
601
602 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
603}
604
605static void
606autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
607 GtkCellRenderer *cell,
608 GtkTreeModel *model,
609 GtkTreeIter *iter,
610 G_GNUC_UNUSED gpointer user_data)
611{
612 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
613 if (idx.isValid()) {
614 QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
615 if (photo_var.isValid()) {
616 std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
617 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
618 20, 20,
619 GDK_INTERP_BILINEAR);
620
621 g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
622 g_object_unref(scaled);
623 return;
624 }
625 }
626
627 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
628}
629
630static void
631autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
632 GtkCellRenderer *cell,
633 GtkTreeModel *model,
634 GtkTreeIter *iter,
635 G_GNUC_UNUSED gpointer user_data)
636{
637 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
638 if (idx.isValid()) {
639 QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
Stepan Salenikovich92189cc2015-09-24 10:59:22 -0400640 gchar *text = g_strdup_printf("<span weight=\"bold\">%s</span>",
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400641 name.value<QString>().toUtf8().constData());
642
643 g_object_set(G_OBJECT(cell), "markup", text, NULL);
644 g_free(text);
645 return;
646 }
647
648 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
649}
650
651static void
652autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
653 GtkCellRenderer *cell,
654 GtkTreeModel *model,
655 GtkTreeIter *iter,
656 G_GNUC_UNUSED gpointer user_data)
657{
658 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
659 if (idx.isValid()) {
660 QVariant uri = idx.data(Qt::DisplayRole);
Stepan Salenikovich92189cc2015-09-24 10:59:22 -0400661 gchar *text = g_strdup_printf("%s", uri.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400662
663 g_object_set(G_OBJECT(cell), "markup", text, NULL);
664 g_free(text);
665 return;
666 }
667
668 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
669}
670
671static void
672autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
673 GtkCellRenderer *cell,
674 GtkTreeModel *model,
675 GtkTreeIter *iter,
676 G_GNUC_UNUSED gpointer user_data)
677{
678 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
679 if (idx.isValid()) {
680 QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
Stepan Salenikovich92189cc2015-09-24 10:59:22 -0400681 gchar *text = g_strdup_printf("<span color=\"gray\">%s</span>",
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400682 alias.value<QString>().toUtf8().constData());
683
684 g_object_set(G_OBJECT(cell), "markup", text, NULL);
685 g_free(text);
686 return;
687 }
688
689 g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
690}
691
692static gboolean
693select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
694 GtkTreeModel *model,
695 GtkTreeIter *iter,
696 RingMainWindow *win)
697{
698 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
699
700 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
701 if (idx.isValid()) {
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400702 priv->q_completion_model->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
703 Call *call = priv->q_completion_model->call();
704 priv->q_completion_model->callSelectedNumber();
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400705
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400706 /* make this the currently selected call */
707 QModelIndex idx = CallModel::instance()->getIndex(call);
708 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400709
710 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
711 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400712
713 /* clear the entry */
714 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400715 } else {
716 g_warning("autocompletion selection is not a valid index!");
717 }
718 return TRUE;
719}
720
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400721static gboolean
722dtmf_pressed(RingMainWindow *win,
723 GdkEventKey *event,
724 G_GNUC_UNUSED gpointer user_data)
725{
726 g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
727
728 /* we want to react to digit key presses, as long as a GtkEntry is not the
729 * input focus
730 */
731 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
732 if (GTK_IS_ENTRY(focus))
733 return GDK_EVENT_PROPAGATE;
734
735 /* make sure that a call is selected*/
736 QItemSelectionModel *selection = CallModel::instance()->selectionModel();
737 QModelIndex idx = selection->currentIndex();
738 if (!idx.isValid())
739 return GDK_EVENT_PROPAGATE;
740
741 /* make sure that the selected call is in progress */
742 Call *call = CallModel::instance()->getCall(idx);
743 Call::LifeCycleState state = call->lifeCycleState();
744 if (state != Call::LifeCycleState::PROGRESS)
745 return GDK_EVENT_PROPAGATE;
746
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400747 /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
748 * does not result in a 'c' being played.
749 * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
750 if ( event->state
751 & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
752 return GDK_EVENT_PROPAGATE;
753
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400754 /* pass the character that was entered to be played by the daemon;
755 * the daemon will filter out invalid DTMF characters */
756 guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
757 QString val = QString::fromUcs4(&unicode_val, 1);
758 call->playDTMF(val);
759 g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400760
761 /* always propogate the key, so we don't steal accelerators/shortcuts */
762 return GDK_EVENT_PROPAGATE;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400763}
764
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400765static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500766ring_main_window_init(RingMainWindow *win)
767{
768 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
769 gtk_widget_init_template(GTK_WIDGET(win));
770
771 /* set window icon */
772 GError *error = NULL;
773 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
774 if (icon == NULL) {
775 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400776 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500777 } else
778 gtk_window_set_icon(GTK_WINDOW(win), icon);
779
780 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400781 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
782 -1, 24, TRUE, &error);
783 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500784 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400785 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500786 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400787 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500788
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400789 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500790 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
791 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400792 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500793 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500794
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400795 /* settings icon */
796 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
797
798 /* connect settings button signal */
799 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
800
801 /* add the call view to the main stack */
802 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
803 priv->vbox_call_view,
804 CALL_VIEW_NAME);
805
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400806 if (has_ring_account()) {
807 /* user has ring account, so show the call view right away */
808 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
809 } else {
810 /* user has to create the ring account */
811 show_account_creation(win);
812 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400813
814 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400815 priv->account_settings_view = account_view_new();
816 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
817
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400818 priv->media_settings_view = media_settings_view_new();
819 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 -0400820
Stepan Salenikovichde896112015-05-11 16:46:33 -0400821 priv->general_settings_view = general_settings_view_new();
822 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);
823
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400824 /* make the setting we will show first the active one */
Stepan Salenikovichde896112015-05-11 16:46:33 -0400825 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
826 priv->last_settings_view = priv->general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400827
828 /* connect the settings button signals to switch settings views */
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400829 g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400830 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400831 g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400832
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400833 /* populate the notebook */
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400834 auto smart_view = recent_contacts_view_new();
835 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), smart_view);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400836
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400837 auto contacts_view = contacts_view_new();
838 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), contacts_view);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400839
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400840 auto history_view = history_view_new();
841 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), history_view);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400842
Stepan Salenikovich2cde7612015-09-25 10:44:01 -0400843 /* welcome/default view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400844 priv->welcome_view = ring_welcome_view_new();
845 g_object_ref(priv->welcome_view);
846 // gtk_stack_add_named(GTK_STACK(priv->stack_call_view), welcome_view, DEFAULT_VIEW_NAME);
847 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
848 gtk_widget_show(priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500849
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400850 /* call selection */
851 QObject::connect(
852 CallModel::instance()->selectionModel(),
853 &QItemSelectionModel::currentChanged,
854 [win](const QModelIndex current, G_GNUC_UNUSED const QModelIndex & previous) {
855 if (auto call = CallModel::instance()->getCall(current)) {
856 /* if the call is on hold, we want to put it off hold automatically
857 * when switching to it */
858 if (call->state() == Call::State::HOLD)
859 call << Call::Action::HOLD;
860
861 /* this is a bit of a hack, as for some reason the call is not in the correct
862 * state in the UserActionModel when the selection model switches calls by itself */
863 CallModel::instance()->selectCall(call);
864 }
865 call_selection_changed(current, win);
866 }
867 );
868
869 /* connect to call state changes to update relevant view(s) */
870 QObject::connect(
871 CallModel::instance(),
872 &CallModel::callStateChanged,
873 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
874 call_state_changed(call, win);
875 }
876 );
877
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500878 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
879 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
880
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400881 /* autocompletion */
882 priv->q_completion_model = new NumberCompletionModel();
883
884 /* autocompletion renderers */
885 GtkCellArea *completion_area = gtk_cell_area_box_new();
886
887 /* photo renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400888 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400889 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
890 renderer,
891 TRUE, /* expand */
892 TRUE, /* align */
893 TRUE); /* fixed size */
894
895 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
896 renderer,
897 (GtkCellLayoutDataFunc)autocompletion_photo_render,
898 NULL, NULL);
899
900 /* name renderer */
901 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -0400902 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400903 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
904 renderer,
905 TRUE, /* expand */
906 TRUE, /* align */
907 TRUE); /* fixed size */
908
909 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
910 renderer,
911 (GtkCellLayoutDataFunc)autocompletion_name_render,
912 NULL, NULL);
913
914 /* number renderer */
915 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -0400916 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400917 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
918 renderer,
919 TRUE, /* expand */
920 TRUE, /* align */
921 TRUE); /* fixed size */
922
923 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
924 renderer,
925 (GtkCellLayoutDataFunc)autocompletion_number_render,
926 NULL, NULL);
927 /* account renderer */
928 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -0400929 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400930 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
931 renderer,
932 TRUE, /* expand */
933 TRUE, /* align */
934 TRUE); /* fixed size */
935
936 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
937 renderer,
938 (GtkCellLayoutDataFunc)autocompletion_account_render,
939 NULL, NULL);
940
941 GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);
942
943 GtkQTreeModel *completion_model = gtk_q_tree_model_new(
944 (QAbstractItemModel *)priv->q_completion_model,
945 1,
946 Qt::DisplayRole, G_TYPE_STRING);
947
948 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));
949
950 gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
951 gtk_entry_completion_set_match_func(
952 entry_completion,
953 (GtkEntryCompletionMatchFunc) completion_match_func,
954 NULL,
955 NULL);
956
957 /* connect signal to when text is entered in the entry */
958 g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
959 g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500960
961 /* connect to incoming call and focus */
962 QObject::connect(
963 CallModel::instance(),
964 &CallModel::incomingCall,
965 [=](Call* call) {
966 CallModel::instance()->selectionModel()->setCurrentIndex(
967 CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect);
968 }
969 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400970
971 /* display ring id by first getting the active ring account */
Stepan Salenikovich2ea27302015-03-31 10:28:46 -0400972 gtk_widget_override_font(priv->label_ring_id, pango_font_description_from_string("monospace"));
Stepan Salenikovich30e134e2015-09-24 21:20:25 -0400973 show_ring_id(win, get_active_ring_account());
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400974 QObject::connect(
975 AccountModel::instance(),
976 &AccountModel::dataChanged,
Stepan Salenikovich30e134e2015-09-24 21:20:25 -0400977 [win] () {
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400978 /* check if the active ring account has changed,
979 * eg: if it was deleted */
Stepan Salenikovich30e134e2015-09-24 21:20:25 -0400980 show_ring_id(win, get_active_ring_account());
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400981 }
982 );
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400983
984 /* react to digit key press events */
985 g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
Stepan Salenikovich48b45c12015-09-25 15:49:51 -0400986
987 /* set the search entry placeholder text */
988 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
989 C_("Please try to make the translation 50 chars or less so that it fits into the layout", "Search contacts or enter number"));
990 if (const auto placeholder = gtk_entry_get_placeholder_text(GTK_ENTRY(priv->search_entry))) {
991 auto len = strlen(placeholder);
992 if (len > SEARCH_ENTRY_WIDTH_CHARS_MAX)
993 len = SEARCH_ENTRY_WIDTH_CHARS_MAX;
994 gtk_entry_set_width_chars(GTK_ENTRY(priv->search_entry), len);
995 }
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500996}
997
998static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400999ring_main_window_dispose(GObject *object)
1000{
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001001 RingMainWindow *self = RING_MAIN_WINDOW(object);
1002 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1003
1004 QObject::disconnect(priv->selection_updated);
1005
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001006 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1007}
1008
1009static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001010ring_main_window_finalize(GObject *object)
1011{
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001012 RingMainWindow *self = RING_MAIN_WINDOW(object);
1013 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1014
1015 delete priv->q_contact_model;
1016 delete priv->q_history_model;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001017 delete priv->q_completion_model;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001018
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001019 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001020}
1021
1022static void
1023ring_main_window_class_init(RingMainWindowClass *klass)
1024{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001025 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001026 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001027
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001028 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1029 "/cx/ring/RingGnome/ringmainwindow.ui");
1030
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001031 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001032 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001033 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
1034 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001035 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1036 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1037 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1038 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001039 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001040 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001041 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001042 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001043 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001044 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001045 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001046 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001047 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich86648732015-09-25 11:01:25 -04001048 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_ring_hash);
Stepan Salenikovich2ea27302015-03-31 10:28:46 -04001049 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_ring_id);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001050
1051 /* account creation */
1052 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
1053 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
1054 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
1055 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
1056 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1057 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1058 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
1059
1060 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
1061 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
1062 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001063}
1064
1065GtkWidget *
1066ring_main_window_new (GtkApplication *app)
1067{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001068 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1069
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001070 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001071}