blob: 5a969fab41415e1e0dc0c2e697078327f650e53f [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;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400125
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400126 QMetaObject::Connection selected_item_changed;
127 QMetaObject::Connection selected_call_over;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400128
129 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400130
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400131 /* allocd qmodels */
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400132 NumberCompletionModel *q_completion_model;
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500133
134 /* fullscreen */
135 gboolean is_fullscreen;
Stepan Salenikovichba87ae22016-09-13 14:27:20 -0400136
137 GSettings *settings;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500138};
139
140G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
141
142#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
143
Stepan Salenikovich69771842015-02-24 18:11:45 -0500144static void
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500145enter_full_screen(RingMainWindow *self)
146{
147 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
148 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
149
150 if (!priv->is_fullscreen) {
151 gtk_widget_hide(priv->vbox_left_pane);
152 gtk_window_fullscreen(GTK_WINDOW(self));
153 priv->is_fullscreen = TRUE;
154 }
155}
156
157static void
158leave_full_screen(RingMainWindow *self)
159{
160 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
161 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
162
163 if (priv->is_fullscreen) {
164 gtk_widget_show(priv->vbox_left_pane);
165 gtk_window_unfullscreen(GTK_WINDOW(self));
166 priv->is_fullscreen = FALSE;
167 }
168}
169
170static void
Stepan Salenikovich5e431cf2015-12-24 14:09:01 -0500171video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500172{
173 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
174 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
175
176 if (priv->is_fullscreen) {
177 leave_full_screen(self);
178 } else {
179 enter_full_screen(self);
180 }
181}
182
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400183static void
184hide_view_clicked(G_GNUC_UNUSED GtkWidget *view, RingMainWindow *self)
185{
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400186 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400187
188 /* clear selection */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400189 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
190 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
191 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
192 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
193 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
194 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400195}
196
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500197/**
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400198 * This function determines which view to display in the right panel of the main window based on
199 * which item is selected in one of the 3 contact list views (Conversations, Contacts, History). The
200 * possible views ares:
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500201 * - incoming call view
202 * - current call view
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500203 * - chat view
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400204 * - welcome view (if no valid item is selected)
205 *
206 * There should never be a conflict of which item should be displayed (ie: which item is selected),
207 * as there should only ever be one selection at a time in the 3 views except in the case that the
208 * same item is selected in more than one view (see the compare_treeview_selection() function).
209 *
210 * This function could be called from a g_idle source, so it returns a boolean to remove itself from
211 * being called again. The boolean doesn't reflect success nor failure.
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500212 */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400213static gboolean
214selection_changed(RingMainWindow *win)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500215{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500216 // g_debug("selection changed");
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400217
218 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500219 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
220
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400221 auto old_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
222 GtkWidget *new_view = nullptr;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500223
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400224 /* if we're showing the settings, then we need to make sure to remove any possible ongoing call
225 * view so that we don't have more than one VideoWidget at a time */
226 if (priv->show_settings) {
227 if (old_view != priv->welcome_view) {
228 leave_full_screen(win);
229 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
230 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
231 gtk_widget_show(priv->welcome_view);
232 }
233 return G_SOURCE_REMOVE;
234 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500235
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400236 /* there should only be one item selected at a time, get which one is selected */
237 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
238 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
239 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
240
241 GtkTreeModel *model = nullptr;
242 GtkTreeIter iter;
243 QModelIndex idx;
244
245 if (gtk_tree_selection_get_selected(selection_conversations, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400246 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400247 } else if (gtk_tree_selection_get_selected(selection_contacts, &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_history, &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 }
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500252
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500253 /* check which object type is selected */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400254 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
255 auto object = idx.data(static_cast<int>(Ring::Role::Object));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500256
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400257 if (idx.isValid() && type.isValid() && object.isValid()) {
Stepan Salenikovich69771842015-02-24 18:11:45 -0500258
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400259 /* we prioritize the views in the following order:
260 * - call view (if there is an ongoing call associated with the item)
261 * - chat view built from Person
262 * - chat view built from ContactMethod
263 */
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500264
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400265 Person *person = nullptr;
266 ContactMethod *cm = nullptr;
267 Call *call = nullptr;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500268
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400269 /* use the RecentModel to see if there are any ongoing calls associated with the selected item */
270 switch(type.value<Ring::ObjectType>()) {
271 case Ring::ObjectType::Person:
272 {
273 person = object.value<Person *>();
274 auto recent_idx = RecentModel::instance().getIndex(person);
275 if (recent_idx.isValid()) {
276 call = RecentModel::instance().getActiveCall(recent_idx);
277 }
278 }
279 break;
280 case Ring::ObjectType::ContactMethod:
281 {
282 cm = object.value<ContactMethod *>();
283 auto recent_idx = RecentModel::instance().getIndex(cm);
284 if (recent_idx.isValid()) {
285 call = RecentModel::instance().getActiveCall(recent_idx);
286 }
287 }
288 break;
289 case Ring::ObjectType::Call:
290 {
291 /* if the call is over (eg: if it comes from the history model) then we first check
292 * if there is an ongoing call with the same ContactMethod, otherwise we need to get
293 * the associated Person or ContactMethod */
294 call = object.value<Call *>();
295 if (call->isHistory()) {
296 cm = call->peerContactMethod();
297 call = nullptr;
298 if (cm) {
299 auto recent_idx = RecentModel::instance().getIndex(cm);
300 if (recent_idx.isValid()) {
301 call = RecentModel::instance().getActiveCall(recent_idx);
302 }
303 person = cm->contact();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500304 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500305 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400306 }
307 break;
308 case Ring::ObjectType::Media:
309 case Ring::ObjectType::Certificate:
310 case Ring::ObjectType::COUNT__:
311 // nothing to do
312 break;
313 }
314
315 /* we prioritize showing the call view */
316 if (call) {
317 auto state = call->lifeCycleState();
318
319 Call *current_call = nullptr;
320
321 switch(state) {
322 case Call::LifeCycleState::CREATION:
323 case Call::LifeCycleState::INITIALIZATION:
324 case Call::LifeCycleState::FINISHED:
325 // check if we're already displaying this call
326 if (IS_INCOMING_CALL_VIEW(old_view))
327 current_call = incoming_call_view_get_call(INCOMING_CALL_VIEW(old_view));
328 if (current_call != call)
329 new_view = incoming_call_view_new(call);
330 break;
331 case Call::LifeCycleState::PROGRESS:
332 // check if we're already displaying this call
333 if (IS_CURRENT_CALL_VIEW(old_view))
334 current_call = current_call_view_get_call(CURRENT_CALL_VIEW(old_view));
335 if (current_call != call) {
336 new_view = current_call_view_new(call);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500337 g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500338 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400339 break;
340 case Call::LifeCycleState::COUNT__:
341 g_warning("LifeCycleState should never be COUNT");
342 break;
343 }
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500344
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400345 if (new_view) {
346 /* connect to the call's lifeCycleStateChanged signal to know when to change the view */
347 QObject::disconnect(priv->selected_item_changed);
348 priv->selected_item_changed = QObject::connect(
349 call,
350 &Call::lifeCycleStateChanged,
351 [win] (Call::LifeCycleState, Call::LifeCycleState)
352 {g_idle_add((GSourceFunc)selection_changed, win);}
353 );
354 /* we want to also change the view when the call is over */
355 QObject::disconnect(priv->selected_call_over);
356 priv->selected_call_over = QObject::connect(
357 call,
358 &Call::isOver,
359 [win] ()
360 {g_idle_add((GSourceFunc)selection_changed, win);}
361 );
362 }
363
364 } else if (person) {
365 /* show chat view constructed from Person object */
366
367 // check if we're already displaying the chat view for this person
368 Person *current_person = nullptr;
369 if (IS_CHAT_VIEW(old_view))
370 current_person = chat_view_get_person(CHAT_VIEW(old_view));
371
372 if (current_person != person) {
373 new_view = chat_view_new_person(person);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400374 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400375
376 /* connect to the Person's callAdded signal, because we want to switch to the call view
377 * in this case */
378 QObject::disconnect(priv->selected_item_changed);
379 QObject::disconnect(priv->selected_call_over);
380 priv->selected_item_changed = QObject::connect(
381 person,
382 &Person::callAdded,
383 [win] (Call*)
384 {g_idle_add((GSourceFunc)selection_changed, win);}
385 );
386 }
387 } else if (cm) {
388 /* show chat view constructed from CM */
389
390 // check if we're already displaying the chat view for this cm
391 ContactMethod *current_cm = nullptr;
392 if (IS_CHAT_VIEW(old_view))
393 current_cm = chat_view_get_cm(CHAT_VIEW(old_view));
394
395 if (current_cm != cm) {
396 new_view = chat_view_new_cm(cm);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400397 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400398
399 /* connect to the ContactMethod's callAdded signal, because we want to switch to the
400 *call view in this case */
401 QObject::disconnect(priv->selected_item_changed);
402 QObject::disconnect(priv->selected_call_over);
403 priv->selected_item_changed = QObject::connect(
404 cm,
405 &ContactMethod::callAdded,
406 [win] (Call*)
407 {g_idle_add((GSourceFunc)selection_changed, win);}
408 );
409 }
410 } else {
411 /* not a supported object type, display the welcome view */
412 if (old_view != priv->welcome_view) {
413 QObject::disconnect(priv->selected_item_changed);
414 QObject::disconnect(priv->selected_call_over);
415 new_view = priv->welcome_view;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500416 }
417 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400418 } else {
419 /* selection isn't valid, display the welcome view */
420 if (old_view != priv->welcome_view) {
421 QObject::disconnect(priv->selected_item_changed);
422 QObject::disconnect(priv->selected_call_over);
423 new_view = priv->welcome_view;
424 }
425 }
426
427 if (new_view) {
428 /* make sure we leave full screen, since the view is changing */
429 leave_full_screen(win);
430 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
431 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
432 gtk_widget_show(new_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500433 }
Stepan Salenikovich504f7722016-01-20 17:10:45 -0500434
435 return G_SOURCE_REMOVE;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500436}
437
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500438static void
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400439search_entry_activated(RingMainWindow *self)
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500440{
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400441 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500442
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400443 const auto *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500444
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400445 if (number_entered && strlen(number_entered) > 0) {
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -0400446 auto cm = PhoneDirectoryModel::instance().getNumber(number_entered);
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400447
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400448 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
449 place_new_call(cm);
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400450
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400451 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
452 gtk_widget_child_focus(GTK_WIDGET(self), GTK_DIR_TAB_FORWARD);
453 } else {
454 // if its a new CM, bring it to the top
455 if (cm->lastUsed() == 0)
456 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400457
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400458 // select cm
459 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
460 }
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400461 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400462 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500463}
464
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400465static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400466save_accounts(GtkWidget *working_dialog)
467{
468 /* save changes to accounts */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400469 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400470
471 if (working_dialog)
472 gtk_widget_destroy(working_dialog);
473
474 return G_SOURCE_REMOVE;
475}
476
Stepan Salenikovich15142182015-03-11 17:15:26 -0400477static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400478settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
479{
480 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
481 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
482
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400483 /* check which view to show */
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500484 if (!priv->show_settings) {
485 /* show the settings */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400486 priv->show_settings = TRUE;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500487
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500488 /* 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 -0400489 selection_changed(win);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500490
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400491 /* show settings */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400492 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 -0400493
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400494 gtk_widget_show(priv->hbox_settings);
495
Stepan Salenikovich41118912015-05-01 11:25:46 -0400496 /* make sure to start preview if we're showing the video settings */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400497 if (priv->last_settings_view == priv->media_settings_view)
498 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich41118912015-05-01 11:25:46 -0400499
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400500 /* make sure to show the profile if we're showing the general settings */
501 if (priv->last_settings_view == priv->general_settings_view)
502 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
503
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400504 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400505 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400506 } else {
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500507 /* hide the settings */
508 priv->show_settings = FALSE;
509
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400510 /* show working dialog in case save operation takes time */
511 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
512 gtk_window_present(GTK_WINDOW(working));
513
514 /* now save after the time it takes to transition back to the call view (400ms)
515 * the save doesn't happen before the "working" dialog is presented
516 * the timeout function should destroy the "working" dialog when done saving
517 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400518 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400519
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400520 /* show calls */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400521 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 -0400522
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400523 gtk_widget_hide(priv->hbox_settings);
524
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500525 /* make sure video preview is stopped, in case it was started */
526 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400527 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500528
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400529 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
530 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400531
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500532 /* show the view which was selected previously */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400533 selection_changed(win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400534 }
535}
536
537static void
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400538show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400539{
540 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
541 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
542
543 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400544 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400545 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400546 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400547 priv->last_settings_view = priv->media_settings_view;
Stepan Salenikovich41118912015-05-01 11:25:46 -0400548 } else {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400549 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400550 }
551}
552
553static void
554show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
555{
556 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
557 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
558
559 if (gtk_toggle_button_get_active(navbutton)) {
560 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
561 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
562 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400563 }
564}
565
Stepan Salenikovichde896112015-05-11 16:46:33 -0400566static void
567show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
568{
569 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
570 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
571
572 if (gtk_toggle_button_get_active(navbutton)) {
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400573 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400574 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
575 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
576 priv->last_settings_view = priv->general_settings_view;
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400577 } else {
578 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400579 }
580}
581
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400582static void
aviau6aeb4852016-08-18 16:01:09 -0400583on_account_creation_completed(RingMainWindow *win)
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400584{
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500585 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400586 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
587
aviau6aeb4852016-08-18 16:01:09 -0400588 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
589 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
590
591 /* destroy the wizard */
592 GtkWidget* account_creation_wizard = gtk_stack_get_child_by_name(
593 GTK_STACK(priv->stack_main_view),
594 ACCOUNT_CREATION_WIZARD_VIEW_NAME
595 );
596 if (account_creation_wizard)
597 {
598 gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), account_creation_wizard);
599 gtk_widget_destroy(account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400600 }
aviau6aeb4852016-08-18 16:01:09 -0400601
602 /* show the settings button*/
603 gtk_widget_show(priv->ring_settings);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400604}
605
606static void
aviau6aeb4852016-08-18 16:01:09 -0400607show_account_creation_wizard(RingMainWindow *win)
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400608{
609 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
610
aviau6aeb4852016-08-18 16:01:09 -0400611 auto account_creation_wizard = gtk_stack_get_child_by_name(
612 GTK_STACK(priv->stack_main_view),
613 ACCOUNT_CREATION_WIZARD_VIEW_NAME
614 );
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400615
aviau6aeb4852016-08-18 16:01:09 -0400616 if (!account_creation_wizard)
617 {
618 account_creation_wizard = account_creation_wizard_new(false);
619 g_signal_connect_swapped(account_creation_wizard, "account-creation-completed", G_CALLBACK(on_account_creation_completed), win);
Stepan Salenikovich419414c2016-06-07 10:35:07 -0400620
aviau6aeb4852016-08-18 16:01:09 -0400621 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
622 account_creation_wizard,
623 ACCOUNT_CREATION_WIZARD_VIEW_NAME);
624 }
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400625
Stepan Salenikovich64505672015-09-24 10:46:07 -0400626 /* hide settings button until account creation is complete */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400627 gtk_widget_hide(priv->ring_settings);
628
aviau6aeb4852016-08-18 16:01:09 -0400629 gtk_widget_show(account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400630
aviau6aeb4852016-08-18 16:01:09 -0400631 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_CREATION_WIZARD_VIEW_NAME);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400632}
633
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400634static void
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400635search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
636{
637 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
638
639 /* get the text from the entry */
640 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
641
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400642 if (text && strlen(text) > 0) {
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400643 /* edit the the dialing call (or create a new one) */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400644 if (auto call = CallModel::instance().dialingCall()) {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400645 call->setDialNumber(text);
646 priv->q_completion_model->setCall(call);
647 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400648 } else {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400649 if (auto call = priv->q_completion_model->call()) {
650 if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
651 call->performAction(Call::Action::REFUSE);
652 }
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400653 }
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400654}
655
656static gboolean
657completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
658 G_GNUC_UNUSED const gchar *key,
659 G_GNUC_UNUSED GtkTreeIter *iter,
660 G_GNUC_UNUSED RingMainWindow *win)
661{
662 /* the model is updated by lrc and should only every contain matching entries
663 * so always return TRUE */
664 return TRUE;
665}
666
667static QModelIndex
668get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
669 GtkTreeIter *filter_iter)
670{
671 GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
672 GtkTreeIter child_iter;
673 gtk_tree_model_filter_convert_iter_to_child_iter(
674 GTK_TREE_MODEL_FILTER(filter_model),
675 &child_iter,
676 filter_iter);
677
678 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
679}
680
681static void
682autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
683 GtkCellRenderer *cell,
684 GtkTreeModel *model,
685 GtkTreeIter *iter,
686 G_GNUC_UNUSED gpointer user_data)
687{
688 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
689 if (idx.isValid()) {
690 QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
691 if (photo_var.isValid()) {
692 std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
693 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
694 20, 20,
695 GDK_INTERP_BILINEAR);
696
697 g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
698 g_object_unref(scaled);
699 return;
700 }
701 }
702
703 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
704}
705
706static void
707autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
708 GtkCellRenderer *cell,
709 GtkTreeModel *model,
710 GtkTreeIter *iter,
711 G_GNUC_UNUSED gpointer user_data)
712{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400713 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400714 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
715 if (idx.isValid()) {
716 QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400717 text = g_markup_printf_escaped("<span weight=\"bold\">%s</span>",
718 name.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400719 }
720
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400721 g_object_set(G_OBJECT(cell), "markup", text, NULL);
722 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400723}
724
725static void
726autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
727 GtkCellRenderer *cell,
728 GtkTreeModel *model,
729 GtkTreeIter *iter,
730 G_GNUC_UNUSED gpointer user_data)
731{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400732 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400733 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
734 if (idx.isValid()) {
735 QVariant uri = idx.data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400736 text = g_markup_escape_text(uri.value<QString>().toUtf8().constData(), -1);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400737 }
738
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400739 g_object_set(G_OBJECT(cell), "markup", text, NULL);
740 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400741}
742
743static void
744autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
745 GtkCellRenderer *cell,
746 GtkTreeModel *model,
747 GtkTreeIter *iter,
748 G_GNUC_UNUSED gpointer user_data)
749{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400750 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400751 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
752 if (idx.isValid()) {
753 QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400754 text = g_markup_printf_escaped("<span color=\"gray\">%s</span>",
755 alias.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400756 }
757
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400758 g_object_set(G_OBJECT(cell), "markup", text, NULL);
759 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400760}
761
762static gboolean
763select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
764 GtkTreeModel *model,
765 GtkTreeIter *iter,
766 RingMainWindow *win)
767{
768 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
769
770 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
771 if (idx.isValid()) {
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -0400772 auto cm = priv->q_completion_model->number(idx);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400773
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400774 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
775 place_new_call(cm);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400776
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400777 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
778 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
779 } else {
780 // if its a new CM, bring it to the top
781 if (cm->lastUsed() == 0)
782 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
783
784 // select cm
785 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
786 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400787
788 /* clear the entry */
789 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400790 } else {
791 g_warning("autocompletion selection is not a valid index!");
792 }
793 return TRUE;
794}
795
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400796static gboolean
797dtmf_pressed(RingMainWindow *win,
798 GdkEventKey *event,
799 G_GNUC_UNUSED gpointer user_data)
800{
801 g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
802
803 /* we want to react to digit key presses, as long as a GtkEntry is not the
804 * input focus
805 */
806 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
807 if (GTK_IS_ENTRY(focus))
808 return GDK_EVENT_PROPAGATE;
809
810 /* make sure that a call is selected*/
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400811 QItemSelectionModel *selection = CallModel::instance().selectionModel();
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400812 QModelIndex idx = selection->currentIndex();
813 if (!idx.isValid())
814 return GDK_EVENT_PROPAGATE;
815
816 /* make sure that the selected call is in progress */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400817 Call *call = CallModel::instance().getCall(idx);
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400818 Call::LifeCycleState state = call->lifeCycleState();
819 if (state != Call::LifeCycleState::PROGRESS)
820 return GDK_EVENT_PROPAGATE;
821
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400822 /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
823 * does not result in a 'c' being played.
824 * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
825 if ( event->state
826 & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
827 return GDK_EVENT_PROPAGATE;
828
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400829 /* pass the character that was entered to be played by the daemon;
830 * the daemon will filter out invalid DTMF characters */
831 guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
832 QString val = QString::fromUcs4(&unicode_val, 1);
833 call->playDTMF(val);
834 g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400835
836 /* always propogate the key, so we don't steal accelerators/shortcuts */
837 return GDK_EVENT_PROPAGATE;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400838}
839
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400840/**
841 * This function determines if two different contact list views (Conversations, Contacts, History)
842 * have the same item selected. Note that the same item does not necessarily mean the same object.
843 * For example, if the history view has a Call selected and the Conversations view has a Person
844 * selected which is associated with the ContactMethod to which that Call was made, then this will
845 * evaluate to TRUE.
846 */
847static gboolean
848compare_treeview_selection(GtkTreeSelection *selection1, GtkTreeSelection *selection2)
849{
850 g_return_val_if_fail(selection1 && selection2, FALSE);
851
852 if (selection1 == selection2) return TRUE;
853
854 auto idx1 = get_index_from_selection(selection1);
855 auto type1 = idx1.data(static_cast<int>(Ring::Role::ObjectType));
856 auto object1 = idx1.data(static_cast<int>(Ring::Role::Object));
857
858 auto idx2 = get_index_from_selection(selection2);
859 auto type2 = idx2.data(static_cast<int>(Ring::Role::ObjectType));
860 auto object2 = idx2.data(static_cast<int>(Ring::Role::Object));
861
862 if (idx1.isValid() && type1.isValid() && object1.isValid() && idx2.isValid() && type2.isValid() && object2.isValid()) {
863 Call *call1 = nullptr;
864 ContactMethod *cm1 = nullptr;
865 Person *person1 = nullptr;
866
867 Call *call2 = nullptr;
868 ContactMethod *cm2 = nullptr;
869 Person *person2 = nullptr;
870
871 switch(type1.value<Ring::ObjectType>()) {
872 case Ring::ObjectType::Person:
873 person1 = object1.value<Person *>();
874 break;
875 case Ring::ObjectType::ContactMethod:
876 cm1 = object1.value<ContactMethod *>();
877 person1 = cm1->contact();
878 break;
879 case Ring::ObjectType::Call:
880 call1 = object1.value<Call *>();
881 cm1 = call1->peerContactMethod();
882 person1 = cm1->contact();
883 break;
884 case Ring::ObjectType::Media:
885 case Ring::ObjectType::Certificate:
886 case Ring::ObjectType::COUNT__:
887 // nothing to do
888 break;
889 }
890
891 switch(type2.value<Ring::ObjectType>()) {
892 case Ring::ObjectType::Person:
893 person2 = object2.value<Person *>();
894 break;
895 case Ring::ObjectType::ContactMethod:
896 cm2 = object2.value<ContactMethod *>();
897 person2 = cm2->contact();
898 break;
899 case Ring::ObjectType::Call:
900 call2 = object2.value<Call *>();
901 cm2 = call2->peerContactMethod();
902 person2 = cm2->contact();
903 break;
904 case Ring::ObjectType::Media:
905 case Ring::ObjectType::Certificate:
906 case Ring::ObjectType::COUNT__:
907 // nothing to do
908 break;
909 }
910
911 if (person1 != nullptr && person1 == person2)
912 return TRUE;
913 if (cm1 != nullptr && cm1 == cm2)
914 return TRUE;
915 if (call1 != nullptr && call1 == call2)
916 return TRUE;
917 }
918
919 return FALSE;
920}
921
922static void
923conversation_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
924{
925 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
926 GtkTreeIter iter;
927 GtkTreeModel *model = nullptr;
928 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
929 /* something is selected in the conversations view, clear the selection in the other views;
930 * unless the current selection is of the same item; we don't try to match the selection in
931 * all views since not all items exist in all 3 contact list views
932 */
933
934 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
935 if (!compare_treeview_selection(selection, selection_contacts))
936 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
937
938 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
939 if (!compare_treeview_selection(selection, selection_history))
940 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
941 }
942
943 selection_changed(self);
944}
945
946static void
947contact_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
948{
949 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
950 GtkTreeIter iter;
951 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
952 /* something is selected in the contacts view, clear the selection in the other views;
953 * unless the current selection is of the same item; we don't try to match the selection in
954 * all views since not all items exist in all 3 contact list views
955 */
956 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
957 if (!compare_treeview_selection(selection, selection_conversations))
958 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
959
960 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
961 if (!compare_treeview_selection(selection, selection_history))
962 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
963 }
964
965 selection_changed(self);
966}
967
968static void
969history_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
970{
971 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
972 GtkTreeIter iter;
973 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
974 /* something is selected in the history view, clear the selection in the other views;
975 * unless the current selection is of the same item; we don't try to match the selection in
976 * all views since not all items exist in all 3 contact list views
977 */
978
979 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
980 if (!compare_treeview_selection(selection, selection_conversations))
981 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
982
983 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
984 if (!compare_treeview_selection(selection, selection_contacts))
985 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
986 }
987
988 selection_changed(self);
989}
990
Stepan Salenikovichba87ae22016-09-13 14:27:20 -0400991static gboolean
992window_size_changed(GtkWidget *win, GdkEventConfigure *event, gpointer)
993{
994 auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
995
996 g_settings_set_int(priv->settings, "window-width", event->width);
997 g_settings_set_int(priv->settings, "window-height", event->height);
998
999 return GDK_EVENT_PROPAGATE;
1000}
1001
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001002static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001003search_entry_places_call_changed(GSettings *settings, const gchar *key, RingMainWindow *self)
1004{
1005 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1006
1007 if (g_settings_get_boolean(settings, key)) {
1008 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will place a new call", "place call"));
1009 } else {
1010 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will open chat", "open chat"));
1011 }
1012}
1013
1014static void
aviau6aeb4852016-08-18 16:01:09 -04001015handle_account_migrations(RingMainWindow *win)
1016{
1017 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1018
1019 /* If there is an existing migration view, remove it */
1020 GtkWidget* old_view = gtk_stack_get_child_by_name(GTK_STACK(priv->stack_main_view), ACCOUNT_MIGRATION_VIEW_NAME);
1021 if (old_view)
1022 {
1023 gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), old_view);
1024 }
1025
1026 QList<Account*> accounts = AccountModel::instance().accountsToMigrate();
1027 if (!accounts.isEmpty())
1028 {
1029 Account* account = accounts.first();
1030 g_debug("Migrating account: %s", account->id().toStdString().c_str());
1031
1032 GtkWidget* account_migration_view = account_migration_view_new(account);
1033 g_signal_connect_swapped(account_migration_view, "account-migration-completed", G_CALLBACK(handle_account_migrations), win);
1034 g_signal_connect_swapped(account_migration_view, "account-migration-failed", G_CALLBACK(handle_account_migrations), win);
1035
1036 gtk_widget_hide(priv->ring_settings);
1037 gtk_widget_show(account_migration_view);
1038 gtk_stack_add_named(
1039 GTK_STACK(priv->stack_main_view),
1040 account_migration_view,
1041 ACCOUNT_MIGRATION_VIEW_NAME
1042 );
1043 gtk_stack_set_visible_child_name(
1044 GTK_STACK(priv->stack_main_view),
1045 ACCOUNT_MIGRATION_VIEW_NAME
1046 );
1047 }
1048 else
1049 {
1050 gtk_widget_show(priv->ring_settings);
1051 }
1052}
1053
1054static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001055ring_main_window_init(RingMainWindow *win)
1056{
1057 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1058 gtk_widget_init_template(GTK_WIDGET(win));
1059
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001060 /* bind to window size settings */
1061 priv->settings = g_settings_new_full(get_ring_schema(), nullptr, nullptr);
1062 auto width = g_settings_get_int(priv->settings, "window-width");
1063 auto height = g_settings_get_int(priv->settings, "window-height");
1064 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
1065 g_signal_connect(win, "configure-event", G_CALLBACK(window_size_changed), nullptr);
1066
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001067 /* search-entry-places-call setting */
1068 search_entry_places_call_changed(priv->settings, "search-entry-places-call", win);
1069 g_signal_connect(priv->settings, "changed::search-entry-places-call", G_CALLBACK(search_entry_places_call_changed), win);
1070
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001071 /* set window icon */
1072 GError *error = NULL;
1073 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
1074 if (icon == NULL) {
1075 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001076 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001077 } else
1078 gtk_window_set_icon(GTK_WINDOW(win), icon);
1079
1080 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001081 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001082 -1, 16, TRUE, &error);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001083 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001084 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001085 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001086 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001087 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001088
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001089 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001090 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
1091 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001092 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001093 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001094
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001095 /* settings icon */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001096 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 -04001097
1098 /* connect settings button signal */
1099 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
1100
1101 /* add the call view to the main stack */
1102 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
1103 priv->vbox_call_view,
1104 CALL_VIEW_NAME);
1105
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001106 if (has_ring_account()) {
1107 /* user has ring account, so show the call view right away */
1108 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
1109 } else {
1110 /* user has to create the ring account */
aviau6aeb4852016-08-18 16:01:09 -04001111 show_account_creation_wizard(win);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001112 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001113
1114 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001115 priv->account_settings_view = account_view_new();
1116 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
1117
Stepan Salenikovich0bd53492015-05-11 14:28:52 -04001118 priv->media_settings_view = media_settings_view_new();
1119 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 -04001120
Stepan Salenikovichde896112015-05-11 16:46:33 -04001121 priv->general_settings_view = general_settings_view_new();
1122 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);
1123
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001124 /* make the setting we will show first the active one */
Stepan Salenikovichde896112015-05-11 16:46:33 -04001125 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
1126 priv->last_settings_view = priv->general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001127
1128 /* connect the settings button signals to switch settings views */
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001129 g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001130 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
Stepan Salenikovichde896112015-05-11 16:46:33 -04001131 g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001132
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001133 /* populate the notebook */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001134 priv->treeview_conversations = recent_contacts_view_new();
1135 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), priv->treeview_conversations);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -04001136
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001137 priv->treeview_contacts = contacts_view_new();
1138 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), priv->treeview_contacts);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001139
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001140 priv->treeview_history = history_view_new();
1141 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), priv->treeview_history);
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001142
Stepan Salenikovich2cde7612015-09-25 10:44:01 -04001143 /* welcome/default view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001144 priv->welcome_view = ring_welcome_view_new();
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001145 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 -04001146 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
1147 gtk_widget_show(priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001148
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001149 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1150 g_signal_connect(selection_conversations, "changed", G_CALLBACK(conversation_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001151
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001152 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1153 g_signal_connect(selection_contacts, "changed", G_CALLBACK(contact_selection_changed), win);
1154
1155 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
1156 g_signal_connect(selection_history, "changed", G_CALLBACK(history_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001157
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -04001158 g_signal_connect_swapped(priv->button_new_conversation, "clicked", G_CALLBACK(search_entry_activated), win);
1159 g_signal_connect_swapped(priv->search_entry, "activate", G_CALLBACK(search_entry_activated), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001160
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001161 /* autocompletion */
1162 priv->q_completion_model = new NumberCompletionModel();
1163
1164 /* autocompletion renderers */
1165 GtkCellArea *completion_area = gtk_cell_area_box_new();
1166
1167 /* photo renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001168 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001169 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1170 renderer,
1171 TRUE, /* expand */
1172 TRUE, /* align */
1173 TRUE); /* fixed size */
1174
1175 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1176 renderer,
1177 (GtkCellLayoutDataFunc)autocompletion_photo_render,
1178 NULL, NULL);
1179
1180 /* name renderer */
1181 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001182 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001183 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1184 renderer,
1185 TRUE, /* expand */
1186 TRUE, /* align */
1187 TRUE); /* fixed size */
1188
1189 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1190 renderer,
1191 (GtkCellLayoutDataFunc)autocompletion_name_render,
1192 NULL, NULL);
1193
1194 /* number renderer */
1195 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001196 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001197 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1198 renderer,
1199 TRUE, /* expand */
1200 TRUE, /* align */
1201 TRUE); /* fixed size */
1202
1203 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1204 renderer,
1205 (GtkCellLayoutDataFunc)autocompletion_number_render,
1206 NULL, NULL);
1207 /* account renderer */
1208 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001209 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001210 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1211 renderer,
1212 TRUE, /* expand */
1213 TRUE, /* align */
1214 TRUE); /* fixed size */
1215
1216 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1217 renderer,
1218 (GtkCellLayoutDataFunc)autocompletion_account_render,
1219 NULL, NULL);
1220
1221 GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);
1222
1223 GtkQTreeModel *completion_model = gtk_q_tree_model_new(
1224 (QAbstractItemModel *)priv->q_completion_model,
1225 1,
aviau9bbb19b2016-05-16 15:53:44 -04001226 0, Qt::DisplayRole, G_TYPE_STRING);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001227
1228 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));
1229
1230 gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
1231 gtk_entry_completion_set_match_func(
1232 entry_completion,
1233 (GtkEntryCompletionMatchFunc) completion_match_func,
1234 NULL,
1235 NULL);
1236
1237 /* connect signal to when text is entered in the entry */
1238 g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
1239 g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001240
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001241 /* make sure the incoming call is the selected call in the CallModel */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001242 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -04001243 &CallModel::instance(),
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001244 &CallModel::incomingCall,
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001245 [](Call* call) {
1246 CallModel::instance().selectCall(call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001247 }
1248 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001249
Stepan Salenikovichb01d7362015-04-27 23:02:00 -04001250 /* react to digit key press events */
1251 g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
Stepan Salenikovich48b45c12015-09-25 15:49:51 -04001252
1253 /* set the search entry placeholder text */
1254 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
1255 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 -04001256
1257 handle_account_migrations(win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001258}
1259
1260static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001261ring_main_window_dispose(GObject *object)
1262{
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001263 RingMainWindow *self = RING_MAIN_WINDOW(object);
1264 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1265
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001266 QObject::disconnect(priv->selected_item_changed);
1267 QObject::disconnect(priv->selected_call_over);
1268 g_object_unref(priv->welcome_view); // can now be destroyed
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001269
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001270 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1271}
1272
1273static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001274ring_main_window_finalize(GObject *object)
1275{
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001276 RingMainWindow *self = RING_MAIN_WINDOW(object);
1277 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1278
1279 g_clear_object(&priv->settings);
1280
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001281 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001282}
1283
1284static void
1285ring_main_window_class_init(RingMainWindowClass *klass)
1286{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001287 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001288 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001289
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001290 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1291 "/cx/ring/RingGnome/ringmainwindow.ui");
1292
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001293 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001294 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001295 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
1296 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001297 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1298 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1299 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1300 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001301 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001302 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001303 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001304 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001305 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001306 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_new_conversation );
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001307 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001308 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001309 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001310}
1311
1312GtkWidget *
1313ring_main_window_new (GtkApplication *app)
1314{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001315 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1316
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001317 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001318}