blob: 89d00d9e63fd2305ca479d53cfd45c97143b882f [file] [log] [blame]
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001/*
Stepan Salenikovichbe87d2c2016-01-25 14:14:34 -05002 * Copyright (C) 2015-2016 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.
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050018 */
19
20#include "ringmainwindow.h"
21
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040022// system
23#include <string.h>
24#include <memory>
25
26// GTK+ related
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -040027#include <glib/gi18n.h>
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040028
29// Qt
30#include <QtCore/QItemSelectionModel>
31#include <QtCore/QSortFilterProxyModel>
Stepan Salenikoviche9933242016-06-21 18:08:48 -040032#include <QtCore/QDateTime>
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040033
34// LRC
Stepan Salenikovicha3557452015-02-20 14:14:12 -050035#include <callmodel.h>
Stepan Salenikovich69771842015-02-24 18:11:45 -050036#include <call.h>
Stepan Salenikovich15142182015-03-11 17:15:26 -040037#include <contactmethod.h>
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040038#include <accountmodel.h>
Edric Milaret58e71072016-03-21 12:16:37 -040039#include <codecmodel.h>
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -040040#include <video/previewmanager.h>
Stepan Salenikovich6f687072015-03-26 10:43:37 -040041#include <personmodel.h>
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040042#include <globalinstances.h>
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040043#include <numbercompletionmodel.h>
44#include <categorizedcontactmodel.h>
45#include <recentmodel.h>
46#include <profilemodel.h>
47#include <profile.h>
48
49// Ring client
50#include "models/gtkqtreemodel.h"
51#include "incomingcallview.h"
52#include "currentcallview.h"
Stepan Salenikovichf6078222016-10-03 17:31:16 -040053#include "models/gtkqtreemodel.h"
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040054#include "accountview.h"
55#include "dialogs.h"
56#include "mediasettingsview.h"
57#include "utils/drawing.h"
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040058#include "native/pixbufmanipulator.h"
Stepan Salenikovich6f687072015-03-26 10:43:37 -040059#include "models/activeitemproxymodel.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040060#include "utils/calling.h"
61#include "contactsview.h"
62#include "historyview.h"
63#include "utils/models.h"
Stepan Salenikovichde896112015-05-11 16:46:33 -040064#include "generalsettingsview.h"
Stepan Salenikovich75a39172015-07-10 13:21:08 -040065#include "utils/accounts.h"
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040066#include "ringwelcomeview.h"
aviau6aeb4852016-08-18 16:01:09 -040067#include "accountmigrationview.h"
68#include "accountcreationwizard.h"
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040069#include "recentcontactsview.h"
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050070#include "chatview.h"
Nicolas Jagerb413b302016-05-06 11:41:32 -040071#include "avatarmanipulation.h"
Stepan Salenikovichba87ae22016-09-13 14:27:20 -040072#include "utils/files.h"
Nicolas Jagerb413b302016-05-06 11:41:32 -040073
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040074static constexpr const char* CALL_VIEW_NAME = "calls";
aviau6aeb4852016-08-18 16:01:09 -040075static constexpr const char* ACCOUNT_CREATION_WIZARD_VIEW_NAME = "account-creation-wizard";
76static constexpr const char* ACCOUNT_MIGRATION_VIEW_NAME = "account-migration-view";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040077static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
78static constexpr const char* AUDIO_SETTINGS_VIEW_NAME = "audio";
79static constexpr const char* MEDIA_SETTINGS_VIEW_NAME = "media";
80static constexpr const char* ACCOUNT_SETTINGS_VIEW_NAME = "accounts";
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040081static constexpr const char* DEFAULT_VIEW_NAME = "welcome";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040082static constexpr const char* VIEW_CONTACTS = "contacts";
83static constexpr const char* VIEW_HISTORY = "history";
84static constexpr const char* VIEW_PRESENCE = "presence";
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050085
86struct _RingMainWindow
87{
88 GtkApplicationWindow parent;
89};
90
91struct _RingMainWindowClass
92{
93 GtkApplicationWindowClass parent_class;
94};
95
96typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
97
98struct _RingMainWindowPrivate
99{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400100 GtkWidget *ring_menu;
101 GtkWidget *image_ring;
102 GtkWidget *ring_settings;
103 GtkWidget *image_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400104 GtkWidget *hbox_settings;
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400105 GtkWidget *scrolled_window_smartview;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400106 GtkWidget *treeview_conversations;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400107 GtkWidget *scrolled_window_contacts;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400108 GtkWidget *treeview_contacts;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400109 GtkWidget *scrolled_window_history;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400110 GtkWidget *treeview_history;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400111 GtkWidget *vbox_left_pane;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500112 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500113 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400114 GtkWidget *vbox_call_view;
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400115 GtkWidget *frame_call;
116 GtkWidget *welcome_view;
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400117 GtkWidget *button_new_conversation ;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400118 GtkWidget *account_settings_view;
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400119 GtkWidget *media_settings_view;
Stepan Salenikovichde896112015-05-11 16:46:33 -0400120 GtkWidget *general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400121 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400122 GtkWidget *radiobutton_general_settings;
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400123 GtkWidget *radiobutton_media_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400124 GtkWidget *radiobutton_account_settings;
aviau69081842016-10-14 14:51:54 -0400125 GtkWidget *account_creation_wizard;
126 GtkWidget *account_migration_view;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400127
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400128 QMetaObject::Connection selected_item_changed;
129 QMetaObject::Connection selected_call_over;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400130
131 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400132
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400133 /* allocd qmodels */
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400134 NumberCompletionModel *q_completion_model;
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500135
136 /* fullscreen */
137 gboolean is_fullscreen;
Stepan Salenikovichba87ae22016-09-13 14:27:20 -0400138
139 GSettings *settings;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500140};
141
142G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
143
144#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
145
Stepan Salenikovich69771842015-02-24 18:11:45 -0500146static void
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500147enter_full_screen(RingMainWindow *self)
148{
149 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
150 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
151
152 if (!priv->is_fullscreen) {
153 gtk_widget_hide(priv->vbox_left_pane);
154 gtk_window_fullscreen(GTK_WINDOW(self));
155 priv->is_fullscreen = TRUE;
156 }
157}
158
159static void
160leave_full_screen(RingMainWindow *self)
161{
162 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
163 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
164
165 if (priv->is_fullscreen) {
166 gtk_widget_show(priv->vbox_left_pane);
167 gtk_window_unfullscreen(GTK_WINDOW(self));
168 priv->is_fullscreen = FALSE;
169 }
170}
171
172static void
Stepan Salenikovich5e431cf2015-12-24 14:09:01 -0500173video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500174{
175 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
176 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
177
178 if (priv->is_fullscreen) {
179 leave_full_screen(self);
180 } else {
181 enter_full_screen(self);
182 }
183}
184
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400185static void
186hide_view_clicked(G_GNUC_UNUSED GtkWidget *view, RingMainWindow *self)
187{
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400188 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400189
190 /* clear selection */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400191 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
192 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
193 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
194 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
195 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
196 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400197}
198
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500199/**
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400200 * This function determines which view to display in the right panel of the main window based on
201 * which item is selected in one of the 3 contact list views (Conversations, Contacts, History). The
202 * possible views ares:
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500203 * - incoming call view
204 * - current call view
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500205 * - chat view
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400206 * - welcome view (if no valid item is selected)
207 *
208 * There should never be a conflict of which item should be displayed (ie: which item is selected),
209 * as there should only ever be one selection at a time in the 3 views except in the case that the
210 * same item is selected in more than one view (see the compare_treeview_selection() function).
211 *
212 * This function could be called from a g_idle source, so it returns a boolean to remove itself from
213 * being called again. The boolean doesn't reflect success nor failure.
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500214 */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400215static gboolean
216selection_changed(RingMainWindow *win)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500217{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500218 // g_debug("selection changed");
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400219
220 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500221 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
222
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400223 auto old_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
224 GtkWidget *new_view = nullptr;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500225
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400226 /* if we're showing the settings, then we need to make sure to remove any possible ongoing call
227 * view so that we don't have more than one VideoWidget at a time */
228 if (priv->show_settings) {
229 if (old_view != priv->welcome_view) {
230 leave_full_screen(win);
231 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
232 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
233 gtk_widget_show(priv->welcome_view);
234 }
235 return G_SOURCE_REMOVE;
236 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500237
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400238 /* there should only be one item selected at a time, get which one is selected */
239 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
240 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
241 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
242
243 GtkTreeModel *model = nullptr;
244 GtkTreeIter iter;
245 QModelIndex idx;
246
247 if (gtk_tree_selection_get_selected(selection_conversations, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400248 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400249 } else if (gtk_tree_selection_get_selected(selection_contacts, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400250 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400251 } else if (gtk_tree_selection_get_selected(selection_history, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400252 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400253 }
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500254
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500255 /* check which object type is selected */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400256 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
257 auto object = idx.data(static_cast<int>(Ring::Role::Object));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500258
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400259 if (idx.isValid() && type.isValid() && object.isValid()) {
Stepan Salenikovich69771842015-02-24 18:11:45 -0500260
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400261 /* we prioritize the views in the following order:
262 * - call view (if there is an ongoing call associated with the item)
263 * - chat view built from Person
264 * - chat view built from ContactMethod
265 */
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500266
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400267 Person *person = nullptr;
268 ContactMethod *cm = nullptr;
269 Call *call = nullptr;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500270
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400271 /* use the RecentModel to see if there are any ongoing calls associated with the selected item */
272 switch(type.value<Ring::ObjectType>()) {
273 case Ring::ObjectType::Person:
274 {
275 person = object.value<Person *>();
276 auto recent_idx = RecentModel::instance().getIndex(person);
277 if (recent_idx.isValid()) {
278 call = RecentModel::instance().getActiveCall(recent_idx);
279 }
280 }
281 break;
282 case Ring::ObjectType::ContactMethod:
283 {
284 cm = object.value<ContactMethod *>();
285 auto recent_idx = RecentModel::instance().getIndex(cm);
286 if (recent_idx.isValid()) {
287 call = RecentModel::instance().getActiveCall(recent_idx);
288 }
289 }
290 break;
291 case Ring::ObjectType::Call:
292 {
293 /* if the call is over (eg: if it comes from the history model) then we first check
294 * if there is an ongoing call with the same ContactMethod, otherwise we need to get
295 * the associated Person or ContactMethod */
296 call = object.value<Call *>();
297 if (call->isHistory()) {
298 cm = call->peerContactMethod();
299 call = nullptr;
300 if (cm) {
301 auto recent_idx = RecentModel::instance().getIndex(cm);
302 if (recent_idx.isValid()) {
303 call = RecentModel::instance().getActiveCall(recent_idx);
304 }
305 person = cm->contact();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500306 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500307 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400308 }
309 break;
310 case Ring::ObjectType::Media:
311 case Ring::ObjectType::Certificate:
312 case Ring::ObjectType::COUNT__:
313 // nothing to do
314 break;
315 }
316
317 /* we prioritize showing the call view */
318 if (call) {
319 auto state = call->lifeCycleState();
320
321 Call *current_call = nullptr;
322
323 switch(state) {
324 case Call::LifeCycleState::CREATION:
325 case Call::LifeCycleState::INITIALIZATION:
326 case Call::LifeCycleState::FINISHED:
327 // check if we're already displaying this call
328 if (IS_INCOMING_CALL_VIEW(old_view))
329 current_call = incoming_call_view_get_call(INCOMING_CALL_VIEW(old_view));
330 if (current_call != call)
331 new_view = incoming_call_view_new(call);
332 break;
333 case Call::LifeCycleState::PROGRESS:
334 // check if we're already displaying this call
335 if (IS_CURRENT_CALL_VIEW(old_view))
336 current_call = current_call_view_get_call(CURRENT_CALL_VIEW(old_view));
337 if (current_call != call) {
338 new_view = current_call_view_new(call);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500339 g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500340 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400341 break;
342 case Call::LifeCycleState::COUNT__:
343 g_warning("LifeCycleState should never be COUNT");
344 break;
345 }
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500346
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400347 if (new_view) {
348 /* connect to the call's lifeCycleStateChanged signal to know when to change the view */
349 QObject::disconnect(priv->selected_item_changed);
350 priv->selected_item_changed = QObject::connect(
351 call,
352 &Call::lifeCycleStateChanged,
353 [win] (Call::LifeCycleState, Call::LifeCycleState)
354 {g_idle_add((GSourceFunc)selection_changed, win);}
355 );
356 /* we want to also change the view when the call is over */
357 QObject::disconnect(priv->selected_call_over);
358 priv->selected_call_over = QObject::connect(
359 call,
360 &Call::isOver,
361 [win] ()
362 {g_idle_add((GSourceFunc)selection_changed, win);}
363 );
364 }
365
366 } else if (person) {
367 /* show chat view constructed from Person object */
368
369 // check if we're already displaying the chat view for this person
370 Person *current_person = nullptr;
371 if (IS_CHAT_VIEW(old_view))
372 current_person = chat_view_get_person(CHAT_VIEW(old_view));
373
374 if (current_person != person) {
375 new_view = chat_view_new_person(person);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400376 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400377
378 /* connect to the Person's callAdded signal, because we want to switch to the call view
379 * in this case */
380 QObject::disconnect(priv->selected_item_changed);
381 QObject::disconnect(priv->selected_call_over);
382 priv->selected_item_changed = QObject::connect(
383 person,
384 &Person::callAdded,
385 [win] (Call*)
386 {g_idle_add((GSourceFunc)selection_changed, win);}
387 );
388 }
389 } else if (cm) {
390 /* show chat view constructed from CM */
391
392 // check if we're already displaying the chat view for this cm
393 ContactMethod *current_cm = nullptr;
394 if (IS_CHAT_VIEW(old_view))
395 current_cm = chat_view_get_cm(CHAT_VIEW(old_view));
396
397 if (current_cm != cm) {
398 new_view = chat_view_new_cm(cm);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400399 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400400
401 /* connect to the ContactMethod's callAdded signal, because we want to switch to the
402 *call view in this case */
403 QObject::disconnect(priv->selected_item_changed);
404 QObject::disconnect(priv->selected_call_over);
405 priv->selected_item_changed = QObject::connect(
406 cm,
407 &ContactMethod::callAdded,
408 [win] (Call*)
409 {g_idle_add((GSourceFunc)selection_changed, win);}
410 );
411 }
412 } else {
413 /* not a supported object type, display the welcome view */
414 if (old_view != priv->welcome_view) {
415 QObject::disconnect(priv->selected_item_changed);
416 QObject::disconnect(priv->selected_call_over);
417 new_view = priv->welcome_view;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500418 }
419 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400420 } else {
421 /* selection isn't valid, display the welcome view */
422 if (old_view != priv->welcome_view) {
423 QObject::disconnect(priv->selected_item_changed);
424 QObject::disconnect(priv->selected_call_over);
425 new_view = priv->welcome_view;
426 }
427 }
428
429 if (new_view) {
430 /* make sure we leave full screen, since the view is changing */
431 leave_full_screen(win);
432 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
433 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
434 gtk_widget_show(new_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500435 }
Stepan Salenikovich504f7722016-01-20 17:10:45 -0500436
437 return G_SOURCE_REMOVE;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500438}
439
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500440static void
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400441search_entry_activated(RingMainWindow *self)
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500442{
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400443 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500444
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400445 const auto *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500446
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400447 if (number_entered && strlen(number_entered) > 0) {
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -0400448 auto cm = PhoneDirectoryModel::instance().getNumber(number_entered);
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400449
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400450 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
451 place_new_call(cm);
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400452
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400453 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
454 gtk_widget_child_focus(GTK_WIDGET(self), GTK_DIR_TAB_FORWARD);
455 } else {
456 // if its a new CM, bring it to the top
457 if (cm->lastUsed() == 0)
458 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400459
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400460 // select cm
461 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
462 }
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400463 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400464 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500465}
466
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400467static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400468save_accounts(GtkWidget *working_dialog)
469{
470 /* save changes to accounts */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400471 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400472
473 if (working_dialog)
474 gtk_widget_destroy(working_dialog);
475
476 return G_SOURCE_REMOVE;
477}
478
Stepan Salenikovich15142182015-03-11 17:15:26 -0400479static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400480settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
481{
482 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
483 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
484
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400485 /* check which view to show */
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500486 if (!priv->show_settings) {
487 /* show the settings */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400488 priv->show_settings = TRUE;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500489
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500490 /* make sure we are not showing a call view so we don't have more than one clutter stage at a time */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400491 selection_changed(win);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500492
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400493 /* show settings */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400494 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400495
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400496 gtk_widget_show(priv->hbox_settings);
497
Stepan Salenikovich41118912015-05-01 11:25:46 -0400498 /* make sure to start preview if we're showing the video settings */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400499 if (priv->last_settings_view == priv->media_settings_view)
500 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich41118912015-05-01 11:25:46 -0400501
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400502 /* make sure to show the profile if we're showing the general settings */
503 if (priv->last_settings_view == priv->general_settings_view)
504 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
505
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400506 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400507 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400508 } else {
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500509 /* hide the settings */
510 priv->show_settings = FALSE;
511
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400512 /* show working dialog in case save operation takes time */
513 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
514 gtk_window_present(GTK_WINDOW(working));
515
516 /* now save after the time it takes to transition back to the call view (400ms)
517 * the save doesn't happen before the "working" dialog is presented
518 * the timeout function should destroy the "working" dialog when done saving
519 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400520 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400521
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400522 /* show calls */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400523 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400524
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400525 gtk_widget_hide(priv->hbox_settings);
526
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500527 /* make sure video preview is stopped, in case it was started */
528 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400529 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500530
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400531 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
532 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400533
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500534 /* show the view which was selected previously */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400535 selection_changed(win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400536 }
537}
538
539static void
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400540show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400541{
542 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
543 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
544
545 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400546 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400547 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400548 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400549 priv->last_settings_view = priv->media_settings_view;
Stepan Salenikovich41118912015-05-01 11:25:46 -0400550 } else {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400551 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400552 }
553}
554
555static void
556show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
557{
558 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
559 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
560
561 if (gtk_toggle_button_get_active(navbutton)) {
562 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
563 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
564 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400565 }
566}
567
Stepan Salenikovichde896112015-05-11 16:46:33 -0400568static void
569show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
570{
571 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
572 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
573
574 if (gtk_toggle_button_get_active(navbutton)) {
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400575 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400576 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
577 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
578 priv->last_settings_view = priv->general_settings_view;
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400579 } else {
580 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400581 }
582}
583
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400584static void
aviau6aeb4852016-08-18 16:01:09 -0400585on_account_creation_completed(RingMainWindow *win)
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400586{
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500587 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400588 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
589
aviau6aeb4852016-08-18 16:01:09 -0400590 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
591 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
592
593 /* destroy the wizard */
aviau69081842016-10-14 14:51:54 -0400594 if (priv->account_creation_wizard)
aviau6aeb4852016-08-18 16:01:09 -0400595 {
aviau69081842016-10-14 14:51:54 -0400596 gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), priv->account_creation_wizard);
597 gtk_widget_destroy(priv->account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400598 }
aviau6aeb4852016-08-18 16:01:09 -0400599
600 /* show the settings button*/
601 gtk_widget_show(priv->ring_settings);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400602}
603
604static void
aviau6aeb4852016-08-18 16:01:09 -0400605show_account_creation_wizard(RingMainWindow *win)
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400606{
607 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
608
aviau61d05b32016-10-18 16:47:53 -0400609 if (!priv->account_creation_wizard)
aviau6aeb4852016-08-18 16:01:09 -0400610 {
aviau69081842016-10-14 14:51:54 -0400611 priv->account_creation_wizard = account_creation_wizard_new(false);
612 g_object_add_weak_pointer(G_OBJECT(priv->account_creation_wizard), (gpointer *)&priv->account_creation_wizard);
613 g_signal_connect_swapped(priv->account_creation_wizard, "account-creation-completed", G_CALLBACK(on_account_creation_completed), win);
Stepan Salenikovich419414c2016-06-07 10:35:07 -0400614
aviau6aeb4852016-08-18 16:01:09 -0400615 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
aviau69081842016-10-14 14:51:54 -0400616 priv->account_creation_wizard,
aviau6aeb4852016-08-18 16:01:09 -0400617 ACCOUNT_CREATION_WIZARD_VIEW_NAME);
618 }
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400619
Stepan Salenikovich64505672015-09-24 10:46:07 -0400620 /* hide settings button until account creation is complete */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400621 gtk_widget_hide(priv->ring_settings);
622
aviau69081842016-10-14 14:51:54 -0400623 gtk_widget_show(priv->account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400624
aviau69081842016-10-14 14:51:54 -0400625 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400626}
627
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400628static void
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400629search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
630{
631 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
632
633 /* get the text from the entry */
634 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
635
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400636 if (text && strlen(text) > 0) {
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400637 /* edit the the dialing call (or create a new one) */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400638 if (auto call = CallModel::instance().dialingCall()) {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400639 call->setDialNumber(text);
640 priv->q_completion_model->setCall(call);
641 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400642 } else {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400643 if (auto call = priv->q_completion_model->call()) {
644 if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
645 call->performAction(Call::Action::REFUSE);
646 }
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400647 }
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400648}
649
650static gboolean
651completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
652 G_GNUC_UNUSED const gchar *key,
653 G_GNUC_UNUSED GtkTreeIter *iter,
654 G_GNUC_UNUSED RingMainWindow *win)
655{
656 /* the model is updated by lrc and should only every contain matching entries
657 * so always return TRUE */
658 return TRUE;
659}
660
661static QModelIndex
662get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
663 GtkTreeIter *filter_iter)
664{
665 GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
666 GtkTreeIter child_iter;
667 gtk_tree_model_filter_convert_iter_to_child_iter(
668 GTK_TREE_MODEL_FILTER(filter_model),
669 &child_iter,
670 filter_iter);
671
672 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
673}
674
675static void
676autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
677 GtkCellRenderer *cell,
678 GtkTreeModel *model,
679 GtkTreeIter *iter,
680 G_GNUC_UNUSED gpointer user_data)
681{
682 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
683 if (idx.isValid()) {
684 QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
685 if (photo_var.isValid()) {
686 std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
687 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
688 20, 20,
689 GDK_INTERP_BILINEAR);
690
691 g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
692 g_object_unref(scaled);
693 return;
694 }
695 }
696
697 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
698}
699
700static void
701autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
702 GtkCellRenderer *cell,
703 GtkTreeModel *model,
704 GtkTreeIter *iter,
705 G_GNUC_UNUSED gpointer user_data)
706{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400707 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400708 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
709 if (idx.isValid()) {
710 QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400711 text = g_markup_printf_escaped("<span weight=\"bold\">%s</span>",
712 name.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400713 }
714
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400715 g_object_set(G_OBJECT(cell), "markup", text, NULL);
716 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400717}
718
719static void
720autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
721 GtkCellRenderer *cell,
722 GtkTreeModel *model,
723 GtkTreeIter *iter,
724 G_GNUC_UNUSED gpointer user_data)
725{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400726 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400727 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
728 if (idx.isValid()) {
729 QVariant uri = idx.data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400730 text = g_markup_escape_text(uri.value<QString>().toUtf8().constData(), -1);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400731 }
732
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400733 g_object_set(G_OBJECT(cell), "markup", text, NULL);
734 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400735}
736
737static void
738autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
739 GtkCellRenderer *cell,
740 GtkTreeModel *model,
741 GtkTreeIter *iter,
742 G_GNUC_UNUSED gpointer user_data)
743{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400744 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400745 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
746 if (idx.isValid()) {
747 QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400748 text = g_markup_printf_escaped("<span color=\"gray\">%s</span>",
749 alias.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400750 }
751
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400752 g_object_set(G_OBJECT(cell), "markup", text, NULL);
753 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400754}
755
756static gboolean
757select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
758 GtkTreeModel *model,
759 GtkTreeIter *iter,
760 RingMainWindow *win)
761{
762 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
763
764 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
765 if (idx.isValid()) {
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -0400766 auto cm = priv->q_completion_model->number(idx);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400767
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400768 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
769 place_new_call(cm);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400770
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400771 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
772 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
773 } else {
774 // if its a new CM, bring it to the top
775 if (cm->lastUsed() == 0)
776 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
777
778 // select cm
779 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
780 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400781
782 /* clear the entry */
783 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400784 } else {
785 g_warning("autocompletion selection is not a valid index!");
786 }
787 return TRUE;
788}
789
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400790static gboolean
791dtmf_pressed(RingMainWindow *win,
792 GdkEventKey *event,
793 G_GNUC_UNUSED gpointer user_data)
794{
795 g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
796
797 /* we want to react to digit key presses, as long as a GtkEntry is not the
798 * input focus
799 */
800 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
801 if (GTK_IS_ENTRY(focus))
802 return GDK_EVENT_PROPAGATE;
803
804 /* make sure that a call is selected*/
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400805 QItemSelectionModel *selection = CallModel::instance().selectionModel();
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400806 QModelIndex idx = selection->currentIndex();
807 if (!idx.isValid())
808 return GDK_EVENT_PROPAGATE;
809
810 /* make sure that the selected call is in progress */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400811 Call *call = CallModel::instance().getCall(idx);
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400812 Call::LifeCycleState state = call->lifeCycleState();
813 if (state != Call::LifeCycleState::PROGRESS)
814 return GDK_EVENT_PROPAGATE;
815
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400816 /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
817 * does not result in a 'c' being played.
818 * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
819 if ( event->state
820 & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
821 return GDK_EVENT_PROPAGATE;
822
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400823 /* pass the character that was entered to be played by the daemon;
824 * the daemon will filter out invalid DTMF characters */
825 guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
826 QString val = QString::fromUcs4(&unicode_val, 1);
827 call->playDTMF(val);
828 g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400829
830 /* always propogate the key, so we don't steal accelerators/shortcuts */
831 return GDK_EVENT_PROPAGATE;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400832}
833
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400834/**
835 * This function determines if two different contact list views (Conversations, Contacts, History)
836 * have the same item selected. Note that the same item does not necessarily mean the same object.
837 * For example, if the history view has a Call selected and the Conversations view has a Person
838 * selected which is associated with the ContactMethod to which that Call was made, then this will
839 * evaluate to TRUE.
840 */
841static gboolean
842compare_treeview_selection(GtkTreeSelection *selection1, GtkTreeSelection *selection2)
843{
844 g_return_val_if_fail(selection1 && selection2, FALSE);
845
846 if (selection1 == selection2) return TRUE;
847
848 auto idx1 = get_index_from_selection(selection1);
849 auto type1 = idx1.data(static_cast<int>(Ring::Role::ObjectType));
850 auto object1 = idx1.data(static_cast<int>(Ring::Role::Object));
851
852 auto idx2 = get_index_from_selection(selection2);
853 auto type2 = idx2.data(static_cast<int>(Ring::Role::ObjectType));
854 auto object2 = idx2.data(static_cast<int>(Ring::Role::Object));
855
856 if (idx1.isValid() && type1.isValid() && object1.isValid() && idx2.isValid() && type2.isValid() && object2.isValid()) {
857 Call *call1 = nullptr;
858 ContactMethod *cm1 = nullptr;
859 Person *person1 = nullptr;
860
861 Call *call2 = nullptr;
862 ContactMethod *cm2 = nullptr;
863 Person *person2 = nullptr;
864
865 switch(type1.value<Ring::ObjectType>()) {
866 case Ring::ObjectType::Person:
867 person1 = object1.value<Person *>();
868 break;
869 case Ring::ObjectType::ContactMethod:
870 cm1 = object1.value<ContactMethod *>();
871 person1 = cm1->contact();
872 break;
873 case Ring::ObjectType::Call:
874 call1 = object1.value<Call *>();
875 cm1 = call1->peerContactMethod();
876 person1 = cm1->contact();
877 break;
878 case Ring::ObjectType::Media:
879 case Ring::ObjectType::Certificate:
880 case Ring::ObjectType::COUNT__:
881 // nothing to do
882 break;
883 }
884
885 switch(type2.value<Ring::ObjectType>()) {
886 case Ring::ObjectType::Person:
887 person2 = object2.value<Person *>();
888 break;
889 case Ring::ObjectType::ContactMethod:
890 cm2 = object2.value<ContactMethod *>();
891 person2 = cm2->contact();
892 break;
893 case Ring::ObjectType::Call:
894 call2 = object2.value<Call *>();
895 cm2 = call2->peerContactMethod();
896 person2 = cm2->contact();
897 break;
898 case Ring::ObjectType::Media:
899 case Ring::ObjectType::Certificate:
900 case Ring::ObjectType::COUNT__:
901 // nothing to do
902 break;
903 }
904
905 if (person1 != nullptr && person1 == person2)
906 return TRUE;
907 if (cm1 != nullptr && cm1 == cm2)
908 return TRUE;
909 if (call1 != nullptr && call1 == call2)
910 return TRUE;
911 }
912
913 return FALSE;
914}
915
916static void
917conversation_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
918{
919 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
920 GtkTreeIter iter;
921 GtkTreeModel *model = nullptr;
922 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
923 /* something is selected in the conversations view, clear the selection in the other views;
924 * unless the current selection is of the same item; we don't try to match the selection in
925 * all views since not all items exist in all 3 contact list views
926 */
927
928 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
929 if (!compare_treeview_selection(selection, selection_contacts))
930 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
931
932 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
933 if (!compare_treeview_selection(selection, selection_history))
934 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
935 }
936
937 selection_changed(self);
938}
939
940static void
941contact_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
942{
943 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
944 GtkTreeIter iter;
945 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
946 /* something is selected in the contacts view, clear the selection in the other views;
947 * unless the current selection is of the same item; we don't try to match the selection in
948 * all views since not all items exist in all 3 contact list views
949 */
950 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
951 if (!compare_treeview_selection(selection, selection_conversations))
952 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
953
954 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
955 if (!compare_treeview_selection(selection, selection_history))
956 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
957 }
958
959 selection_changed(self);
960}
961
962static void
963history_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
964{
965 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
966 GtkTreeIter iter;
967 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
968 /* something is selected in the history view, clear the selection in the other views;
969 * unless the current selection is of the same item; we don't try to match the selection in
970 * all views since not all items exist in all 3 contact list views
971 */
972
973 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
974 if (!compare_treeview_selection(selection, selection_conversations))
975 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
976
977 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
978 if (!compare_treeview_selection(selection, selection_contacts))
979 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
980 }
981
982 selection_changed(self);
983}
984
Stepan Salenikovichba87ae22016-09-13 14:27:20 -0400985static gboolean
986window_size_changed(GtkWidget *win, GdkEventConfigure *event, gpointer)
987{
988 auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
989
990 g_settings_set_int(priv->settings, "window-width", event->width);
991 g_settings_set_int(priv->settings, "window-height", event->height);
992
993 return GDK_EVENT_PROPAGATE;
994}
995
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400996static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400997search_entry_places_call_changed(GSettings *settings, const gchar *key, RingMainWindow *self)
998{
999 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1000
1001 if (g_settings_get_boolean(settings, key)) {
1002 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will place a new call", "place call"));
1003 } else {
1004 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will open chat", "open chat"));
1005 }
1006}
1007
1008static void
aviau6aeb4852016-08-18 16:01:09 -04001009handle_account_migrations(RingMainWindow *win)
1010{
1011 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1012
1013 /* If there is an existing migration view, remove it */
aviau69081842016-10-14 14:51:54 -04001014 if (priv->account_migration_view)
aviau6aeb4852016-08-18 16:01:09 -04001015 {
Stepan Salenikovich56530b12016-10-20 17:51:34 -04001016 gtk_widget_destroy(priv->account_migration_view);
1017 priv->account_migration_view = nullptr;
aviau6aeb4852016-08-18 16:01:09 -04001018 }
1019
1020 QList<Account*> accounts = AccountModel::instance().accountsToMigrate();
1021 if (!accounts.isEmpty())
1022 {
1023 Account* account = accounts.first();
aviau6aeb4852016-08-18 16:01:09 -04001024
aviau69081842016-10-14 14:51:54 -04001025 priv->account_migration_view = account_migration_view_new(account);
aviau69081842016-10-14 14:51:54 -04001026 g_signal_connect_swapped(priv->account_migration_view, "account-migration-completed", G_CALLBACK(handle_account_migrations), win);
1027 g_signal_connect_swapped(priv->account_migration_view, "account-migration-failed", G_CALLBACK(handle_account_migrations), win);
aviau6aeb4852016-08-18 16:01:09 -04001028
1029 gtk_widget_hide(priv->ring_settings);
aviau69081842016-10-14 14:51:54 -04001030 gtk_widget_show(priv->account_migration_view);
aviau6aeb4852016-08-18 16:01:09 -04001031 gtk_stack_add_named(
1032 GTK_STACK(priv->stack_main_view),
aviau69081842016-10-14 14:51:54 -04001033 priv->account_migration_view,
aviau6aeb4852016-08-18 16:01:09 -04001034 ACCOUNT_MIGRATION_VIEW_NAME
1035 );
1036 gtk_stack_set_visible_child_name(
1037 GTK_STACK(priv->stack_main_view),
1038 ACCOUNT_MIGRATION_VIEW_NAME
1039 );
1040 }
1041 else
1042 {
1043 gtk_widget_show(priv->ring_settings);
1044 }
1045}
1046
1047static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001048ring_main_window_init(RingMainWindow *win)
1049{
1050 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1051 gtk_widget_init_template(GTK_WIDGET(win));
1052
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001053 /* bind to window size settings */
1054 priv->settings = g_settings_new_full(get_ring_schema(), nullptr, nullptr);
1055 auto width = g_settings_get_int(priv->settings, "window-width");
1056 auto height = g_settings_get_int(priv->settings, "window-height");
1057 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
1058 g_signal_connect(win, "configure-event", G_CALLBACK(window_size_changed), nullptr);
1059
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001060 /* search-entry-places-call setting */
1061 search_entry_places_call_changed(priv->settings, "search-entry-places-call", win);
1062 g_signal_connect(priv->settings, "changed::search-entry-places-call", G_CALLBACK(search_entry_places_call_changed), win);
1063
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001064 /* set window icon */
1065 GError *error = NULL;
1066 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
1067 if (icon == NULL) {
1068 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001069 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001070 } else
1071 gtk_window_set_icon(GTK_WINDOW(win), icon);
1072
1073 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001074 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001075 -1, 16, TRUE, &error);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001076 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001077 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001078 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001079 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001080 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001081
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001082 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001083 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
1084 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001085 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001086 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001087
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001088 /* settings icon */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001089 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001090
1091 /* connect settings button signal */
1092 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
1093
1094 /* add the call view to the main stack */
1095 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
1096 priv->vbox_call_view,
1097 CALL_VIEW_NAME);
1098
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001099 if (has_ring_account()) {
1100 /* user has ring account, so show the call view right away */
1101 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
1102 } else {
1103 /* user has to create the ring account */
aviau6aeb4852016-08-18 16:01:09 -04001104 show_account_creation_wizard(win);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001105 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001106
1107 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001108 priv->account_settings_view = account_view_new();
1109 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
1110
Stepan Salenikovich0bd53492015-05-11 14:28:52 -04001111 priv->media_settings_view = media_settings_view_new();
1112 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 -04001113
Stepan Salenikovichde896112015-05-11 16:46:33 -04001114 priv->general_settings_view = general_settings_view_new();
1115 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);
1116
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001117 /* make the setting we will show first the active one */
Stepan Salenikovichde896112015-05-11 16:46:33 -04001118 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
1119 priv->last_settings_view = priv->general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001120
1121 /* connect the settings button signals to switch settings views */
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001122 g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001123 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
Stepan Salenikovichde896112015-05-11 16:46:33 -04001124 g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001125
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001126 /* populate the notebook */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001127 priv->treeview_conversations = recent_contacts_view_new();
1128 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), priv->treeview_conversations);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -04001129
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001130 priv->treeview_contacts = contacts_view_new();
1131 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), priv->treeview_contacts);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001132
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001133 priv->treeview_history = history_view_new();
1134 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), priv->treeview_history);
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001135
Stepan Salenikovich2cde7612015-09-25 10:44:01 -04001136 /* welcome/default view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001137 priv->welcome_view = ring_welcome_view_new();
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001138 g_object_ref(priv->welcome_view); // increase ref because don't want it to be destroyed when not displayed
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001139 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
1140 gtk_widget_show(priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001141
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001142 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1143 g_signal_connect(selection_conversations, "changed", G_CALLBACK(conversation_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001144
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001145 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1146 g_signal_connect(selection_contacts, "changed", G_CALLBACK(contact_selection_changed), win);
1147
1148 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
1149 g_signal_connect(selection_history, "changed", G_CALLBACK(history_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001150
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -04001151 g_signal_connect_swapped(priv->button_new_conversation, "clicked", G_CALLBACK(search_entry_activated), win);
1152 g_signal_connect_swapped(priv->search_entry, "activate", G_CALLBACK(search_entry_activated), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001153
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001154 /* autocompletion */
1155 priv->q_completion_model = new NumberCompletionModel();
1156
1157 /* autocompletion renderers */
1158 GtkCellArea *completion_area = gtk_cell_area_box_new();
1159
1160 /* photo renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001161 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001162 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1163 renderer,
1164 TRUE, /* expand */
1165 TRUE, /* align */
1166 TRUE); /* fixed size */
1167
1168 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1169 renderer,
1170 (GtkCellLayoutDataFunc)autocompletion_photo_render,
1171 NULL, NULL);
1172
1173 /* name renderer */
1174 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001175 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001176 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1177 renderer,
1178 TRUE, /* expand */
1179 TRUE, /* align */
1180 TRUE); /* fixed size */
1181
1182 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1183 renderer,
1184 (GtkCellLayoutDataFunc)autocompletion_name_render,
1185 NULL, NULL);
1186
1187 /* number renderer */
1188 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001189 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001190 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1191 renderer,
1192 TRUE, /* expand */
1193 TRUE, /* align */
1194 TRUE); /* fixed size */
1195
1196 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1197 renderer,
1198 (GtkCellLayoutDataFunc)autocompletion_number_render,
1199 NULL, NULL);
1200 /* account renderer */
1201 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001202 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001203 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1204 renderer,
1205 TRUE, /* expand */
1206 TRUE, /* align */
1207 TRUE); /* fixed size */
1208
1209 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1210 renderer,
1211 (GtkCellLayoutDataFunc)autocompletion_account_render,
1212 NULL, NULL);
1213
1214 GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);
1215
1216 GtkQTreeModel *completion_model = gtk_q_tree_model_new(
1217 (QAbstractItemModel *)priv->q_completion_model,
1218 1,
aviau9bbb19b2016-05-16 15:53:44 -04001219 0, Qt::DisplayRole, G_TYPE_STRING);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001220
1221 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));
1222
1223 gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
1224 gtk_entry_completion_set_match_func(
1225 entry_completion,
1226 (GtkEntryCompletionMatchFunc) completion_match_func,
1227 NULL,
1228 NULL);
1229
1230 /* connect signal to when text is entered in the entry */
1231 g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
1232 g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001233
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001234 /* make sure the incoming call is the selected call in the CallModel */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001235 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -04001236 &CallModel::instance(),
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001237 &CallModel::incomingCall,
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001238 [](Call* call) {
1239 CallModel::instance().selectCall(call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001240 }
1241 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001242
Stepan Salenikovichb01d7362015-04-27 23:02:00 -04001243 /* react to digit key press events */
1244 g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
Stepan Salenikovich48b45c12015-09-25 15:49:51 -04001245
1246 /* set the search entry placeholder text */
1247 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
1248 C_("Please try to make the translation 50 chars or less so that it fits into the layout", "Search contacts or enter number"));
aviau6aeb4852016-08-18 16:01:09 -04001249
1250 handle_account_migrations(win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001251}
1252
1253static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001254ring_main_window_dispose(GObject *object)
1255{
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001256 RingMainWindow *self = RING_MAIN_WINDOW(object);
1257 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1258
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001259 QObject::disconnect(priv->selected_item_changed);
1260 QObject::disconnect(priv->selected_call_over);
1261 g_object_unref(priv->welcome_view); // can now be destroyed
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001262
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001263 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1264}
1265
1266static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001267ring_main_window_finalize(GObject *object)
1268{
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001269 RingMainWindow *self = RING_MAIN_WINDOW(object);
1270 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1271
1272 g_clear_object(&priv->settings);
1273
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001274 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001275}
1276
1277static void
1278ring_main_window_class_init(RingMainWindowClass *klass)
1279{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001280 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001281 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001282
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001283 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1284 "/cx/ring/RingGnome/ringmainwindow.ui");
1285
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001286 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001287 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001288 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
1289 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001290 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1291 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1292 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1293 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001294 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001295 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001296 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001297 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001298 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001299 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_new_conversation );
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001300 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001301 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001302 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001303}
1304
1305GtkWidget *
1306ring_main_window_new (GtkApplication *app)
1307{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001308 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1309
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001310 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001311}