blob: 95d86d3a377d85de7732e1878836d4350efc8253 [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"
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040067#include "recentcontactsview.h"
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050068#include "chatview.h"
Nicolas Jagerb413b302016-05-06 11:41:32 -040069#include "avatarmanipulation.h"
Stepan Salenikovichba87ae22016-09-13 14:27:20 -040070#include "utils/files.h"
Nicolas Jagerb413b302016-05-06 11:41:32 -040071
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040072static constexpr const char* CALL_VIEW_NAME = "calls";
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -050073static constexpr const char* CREATE_ACCOUNT_VIEW_NAME = "wizard";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040074static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
75static constexpr const char* AUDIO_SETTINGS_VIEW_NAME = "audio";
76static constexpr const char* MEDIA_SETTINGS_VIEW_NAME = "media";
77static constexpr const char* ACCOUNT_SETTINGS_VIEW_NAME = "accounts";
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040078static constexpr const char* DEFAULT_VIEW_NAME = "welcome";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040079static constexpr const char* VIEW_CONTACTS = "contacts";
80static constexpr const char* VIEW_HISTORY = "history";
81static constexpr const char* VIEW_PRESENCE = "presence";
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050082
83struct _RingMainWindow
84{
85 GtkApplicationWindow parent;
86};
87
88struct _RingMainWindowClass
89{
90 GtkApplicationWindowClass parent_class;
91};
92
93typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
94
95struct _RingMainWindowPrivate
96{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040097 GtkWidget *ring_menu;
98 GtkWidget *image_ring;
99 GtkWidget *ring_settings;
100 GtkWidget *image_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400101 GtkWidget *hbox_settings;
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400102 GtkWidget *scrolled_window_smartview;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400103 GtkWidget *treeview_conversations;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400104 GtkWidget *scrolled_window_contacts;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400105 GtkWidget *treeview_contacts;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400106 GtkWidget *scrolled_window_history;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400107 GtkWidget *treeview_history;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400108 GtkWidget *vbox_left_pane;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500109 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500110 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400111 GtkWidget *vbox_call_view;
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400112 GtkWidget *frame_call;
113 GtkWidget *welcome_view;
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400114 GtkWidget *button_new_conversation ;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400115 GtkWidget *account_settings_view;
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400116 GtkWidget *media_settings_view;
Stepan Salenikovichde896112015-05-11 16:46:33 -0400117 GtkWidget *general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400118 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400119 GtkWidget *radiobutton_general_settings;
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400120 GtkWidget *radiobutton_media_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400121 GtkWidget *radiobutton_account_settings;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400122
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400123 QMetaObject::Connection selected_item_changed;
124 QMetaObject::Connection selected_call_over;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400125
126 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400127
128 /* account creation */
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500129 GtkWidget *account_creation;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400130 GtkWidget *image_ring_logo;
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500131 GtkWidget *vbox_account_creation_entry;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400132 GtkWidget *entry_alias;
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500133 GtkWidget *label_default_name;
134 GtkWidget *label_paceholder;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400135 GtkWidget *label_generating_account;
136 GtkWidget *spinner_generating_account;
137 GtkWidget *button_account_creation_next;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400138 GtkWidget *box_avatarselection;
Stepan Salenikovich419414c2016-06-07 10:35:07 -0400139 GtkWidget* avatar_manipulation;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400140
141 QMetaObject::Connection hash_updated;
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400142
143 /* allocd qmodels */
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400144 NumberCompletionModel *q_completion_model;
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500145
146 /* fullscreen */
147 gboolean is_fullscreen;
Stepan Salenikovichba87ae22016-09-13 14:27:20 -0400148
149 GSettings *settings;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500150};
151
152G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
153
154#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
155
Stepan Salenikovich69771842015-02-24 18:11:45 -0500156static void
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500157enter_full_screen(RingMainWindow *self)
158{
159 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
160 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
161
162 if (!priv->is_fullscreen) {
163 gtk_widget_hide(priv->vbox_left_pane);
164 gtk_window_fullscreen(GTK_WINDOW(self));
165 priv->is_fullscreen = TRUE;
166 }
167}
168
169static void
170leave_full_screen(RingMainWindow *self)
171{
172 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
173 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
174
175 if (priv->is_fullscreen) {
176 gtk_widget_show(priv->vbox_left_pane);
177 gtk_window_unfullscreen(GTK_WINDOW(self));
178 priv->is_fullscreen = FALSE;
179 }
180}
181
182static void
Stepan Salenikovich5e431cf2015-12-24 14:09:01 -0500183video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500184{
185 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
186 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
187
188 if (priv->is_fullscreen) {
189 leave_full_screen(self);
190 } else {
191 enter_full_screen(self);
192 }
193}
194
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400195static void
196hide_view_clicked(G_GNUC_UNUSED GtkWidget *view, RingMainWindow *self)
197{
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400198 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400199
200 /* clear selection */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400201 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
202 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
203 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
204 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
205 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
206 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400207}
208
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500209/**
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400210 * This function determines which view to display in the right panel of the main window based on
211 * which item is selected in one of the 3 contact list views (Conversations, Contacts, History). The
212 * possible views ares:
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500213 * - incoming call view
214 * - current call view
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500215 * - chat view
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400216 * - welcome view (if no valid item is selected)
217 *
218 * There should never be a conflict of which item should be displayed (ie: which item is selected),
219 * as there should only ever be one selection at a time in the 3 views except in the case that the
220 * same item is selected in more than one view (see the compare_treeview_selection() function).
221 *
222 * This function could be called from a g_idle source, so it returns a boolean to remove itself from
223 * being called again. The boolean doesn't reflect success nor failure.
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500224 */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400225static gboolean
226selection_changed(RingMainWindow *win)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500227{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500228 // g_debug("selection changed");
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400229
230 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500231 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
232
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400233 auto old_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
234 GtkWidget *new_view = nullptr;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500235
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400236 /* if we're showing the settings, then we need to make sure to remove any possible ongoing call
237 * view so that we don't have more than one VideoWidget at a time */
238 if (priv->show_settings) {
239 if (old_view != priv->welcome_view) {
240 leave_full_screen(win);
241 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
242 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
243 gtk_widget_show(priv->welcome_view);
244 }
245 return G_SOURCE_REMOVE;
246 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500247
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400248 /* there should only be one item selected at a time, get which one is selected */
249 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
250 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
251 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
252
253 GtkTreeModel *model = nullptr;
254 GtkTreeIter iter;
255 QModelIndex idx;
256
257 if (gtk_tree_selection_get_selected(selection_conversations, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400258 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400259 } else if (gtk_tree_selection_get_selected(selection_contacts, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400260 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400261 } else if (gtk_tree_selection_get_selected(selection_history, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400262 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400263 }
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500264
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500265 /* check which object type is selected */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400266 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
267 auto object = idx.data(static_cast<int>(Ring::Role::Object));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500268
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400269 if (idx.isValid() && type.isValid() && object.isValid()) {
Stepan Salenikovich69771842015-02-24 18:11:45 -0500270
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400271 /* we prioritize the views in the following order:
272 * - call view (if there is an ongoing call associated with the item)
273 * - chat view built from Person
274 * - chat view built from ContactMethod
275 */
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500276
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400277 Person *person = nullptr;
278 ContactMethod *cm = nullptr;
279 Call *call = nullptr;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500280
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400281 /* use the RecentModel to see if there are any ongoing calls associated with the selected item */
282 switch(type.value<Ring::ObjectType>()) {
283 case Ring::ObjectType::Person:
284 {
285 person = object.value<Person *>();
286 auto recent_idx = RecentModel::instance().getIndex(person);
287 if (recent_idx.isValid()) {
288 call = RecentModel::instance().getActiveCall(recent_idx);
289 }
290 }
291 break;
292 case Ring::ObjectType::ContactMethod:
293 {
294 cm = object.value<ContactMethod *>();
295 auto recent_idx = RecentModel::instance().getIndex(cm);
296 if (recent_idx.isValid()) {
297 call = RecentModel::instance().getActiveCall(recent_idx);
298 }
299 }
300 break;
301 case Ring::ObjectType::Call:
302 {
303 /* if the call is over (eg: if it comes from the history model) then we first check
304 * if there is an ongoing call with the same ContactMethod, otherwise we need to get
305 * the associated Person or ContactMethod */
306 call = object.value<Call *>();
307 if (call->isHistory()) {
308 cm = call->peerContactMethod();
309 call = nullptr;
310 if (cm) {
311 auto recent_idx = RecentModel::instance().getIndex(cm);
312 if (recent_idx.isValid()) {
313 call = RecentModel::instance().getActiveCall(recent_idx);
314 }
315 person = cm->contact();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500316 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500317 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400318 }
319 break;
320 case Ring::ObjectType::Media:
321 case Ring::ObjectType::Certificate:
322 case Ring::ObjectType::COUNT__:
323 // nothing to do
324 break;
325 }
326
327 /* we prioritize showing the call view */
328 if (call) {
329 auto state = call->lifeCycleState();
330
331 Call *current_call = nullptr;
332
333 switch(state) {
334 case Call::LifeCycleState::CREATION:
335 case Call::LifeCycleState::INITIALIZATION:
336 case Call::LifeCycleState::FINISHED:
337 // check if we're already displaying this call
338 if (IS_INCOMING_CALL_VIEW(old_view))
339 current_call = incoming_call_view_get_call(INCOMING_CALL_VIEW(old_view));
340 if (current_call != call)
341 new_view = incoming_call_view_new(call);
342 break;
343 case Call::LifeCycleState::PROGRESS:
344 // check if we're already displaying this call
345 if (IS_CURRENT_CALL_VIEW(old_view))
346 current_call = current_call_view_get_call(CURRENT_CALL_VIEW(old_view));
347 if (current_call != call) {
348 new_view = current_call_view_new(call);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500349 g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500350 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400351 break;
352 case Call::LifeCycleState::COUNT__:
353 g_warning("LifeCycleState should never be COUNT");
354 break;
355 }
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500356
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400357 if (new_view) {
358 /* connect to the call's lifeCycleStateChanged signal to know when to change the view */
359 QObject::disconnect(priv->selected_item_changed);
360 priv->selected_item_changed = QObject::connect(
361 call,
362 &Call::lifeCycleStateChanged,
363 [win] (Call::LifeCycleState, Call::LifeCycleState)
364 {g_idle_add((GSourceFunc)selection_changed, win);}
365 );
366 /* we want to also change the view when the call is over */
367 QObject::disconnect(priv->selected_call_over);
368 priv->selected_call_over = QObject::connect(
369 call,
370 &Call::isOver,
371 [win] ()
372 {g_idle_add((GSourceFunc)selection_changed, win);}
373 );
374 }
375
376 } else if (person) {
377 /* show chat view constructed from Person object */
378
379 // check if we're already displaying the chat view for this person
380 Person *current_person = nullptr;
381 if (IS_CHAT_VIEW(old_view))
382 current_person = chat_view_get_person(CHAT_VIEW(old_view));
383
384 if (current_person != person) {
385 new_view = chat_view_new_person(person);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400386 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400387
388 /* connect to the Person's callAdded signal, because we want to switch to the call view
389 * in this case */
390 QObject::disconnect(priv->selected_item_changed);
391 QObject::disconnect(priv->selected_call_over);
392 priv->selected_item_changed = QObject::connect(
393 person,
394 &Person::callAdded,
395 [win] (Call*)
396 {g_idle_add((GSourceFunc)selection_changed, win);}
397 );
398 }
399 } else if (cm) {
400 /* show chat view constructed from CM */
401
402 // check if we're already displaying the chat view for this cm
403 ContactMethod *current_cm = nullptr;
404 if (IS_CHAT_VIEW(old_view))
405 current_cm = chat_view_get_cm(CHAT_VIEW(old_view));
406
407 if (current_cm != cm) {
408 new_view = chat_view_new_cm(cm);
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400409 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400410
411 /* connect to the ContactMethod's callAdded signal, because we want to switch to the
412 *call view in this case */
413 QObject::disconnect(priv->selected_item_changed);
414 QObject::disconnect(priv->selected_call_over);
415 priv->selected_item_changed = QObject::connect(
416 cm,
417 &ContactMethod::callAdded,
418 [win] (Call*)
419 {g_idle_add((GSourceFunc)selection_changed, win);}
420 );
421 }
422 } else {
423 /* not a supported object type, display the welcome view */
424 if (old_view != priv->welcome_view) {
425 QObject::disconnect(priv->selected_item_changed);
426 QObject::disconnect(priv->selected_call_over);
427 new_view = priv->welcome_view;
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500428 }
429 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400430 } else {
431 /* selection isn't valid, display the welcome view */
432 if (old_view != priv->welcome_view) {
433 QObject::disconnect(priv->selected_item_changed);
434 QObject::disconnect(priv->selected_call_over);
435 new_view = priv->welcome_view;
436 }
437 }
438
439 if (new_view) {
440 /* make sure we leave full screen, since the view is changing */
441 leave_full_screen(win);
442 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
443 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
444 gtk_widget_show(new_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500445 }
Stepan Salenikovich504f7722016-01-20 17:10:45 -0500446
447 return G_SOURCE_REMOVE;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500448}
449
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500450static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400451search_entry_activated(GtkWidget *entry, RingMainWindow *self)
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500452{
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400453 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500454
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400455 const auto *number_entered = gtk_entry_get_text(GTK_ENTRY(entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500456
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400457 if (number_entered && strlen(number_entered) > 0) {
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -0400458 auto cm = PhoneDirectoryModel::instance().getNumber(number_entered);
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400459
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400460 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
461 place_new_call(cm);
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400462
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400463 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
464 gtk_widget_child_focus(GTK_WIDGET(self), GTK_DIR_TAB_FORWARD);
465 } else {
466 // if its a new CM, bring it to the top
467 if (cm->lastUsed() == 0)
468 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400469
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400470 // select cm
471 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
472 }
473 gtk_entry_set_text(GTK_ENTRY(entry), "");
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400474 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500475}
476
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400477static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400478save_accounts(GtkWidget *working_dialog)
479{
480 /* save changes to accounts */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400481 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400482
483 if (working_dialog)
484 gtk_widget_destroy(working_dialog);
485
486 return G_SOURCE_REMOVE;
487}
488
Stepan Salenikovich15142182015-03-11 17:15:26 -0400489static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400490settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
491{
492 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
493 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
494
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400495 /* check which view to show */
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500496 if (!priv->show_settings) {
497 /* show the settings */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400498 priv->show_settings = TRUE;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500499
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500500 /* 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 -0400501 selection_changed(win);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500502
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400503 /* show settings */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400504 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 -0400505
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400506 gtk_widget_show(priv->hbox_settings);
507
Stepan Salenikovich41118912015-05-01 11:25:46 -0400508 /* make sure to start preview if we're showing the video settings */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400509 if (priv->last_settings_view == priv->media_settings_view)
510 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich41118912015-05-01 11:25:46 -0400511
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400512 /* make sure to show the profile if we're showing the general settings */
513 if (priv->last_settings_view == priv->general_settings_view)
514 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
515
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400516 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400517 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400518 } else {
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500519 /* hide the settings */
520 priv->show_settings = FALSE;
521
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400522 /* show working dialog in case save operation takes time */
523 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
524 gtk_window_present(GTK_WINDOW(working));
525
526 /* now save after the time it takes to transition back to the call view (400ms)
527 * the save doesn't happen before the "working" dialog is presented
528 * the timeout function should destroy the "working" dialog when done saving
529 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400530 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400531
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400532 /* show calls */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400533 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 -0400534
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400535 gtk_widget_hide(priv->hbox_settings);
536
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500537 /* make sure video preview is stopped, in case it was started */
538 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400539 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500540
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400541 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
542 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400543
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500544 /* show the view which was selected previously */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400545 selection_changed(win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400546 }
547}
548
549static void
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400550show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400551{
552 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
553 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
554
555 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400556 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400557 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400558 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400559 priv->last_settings_view = priv->media_settings_view;
Stepan Salenikovich41118912015-05-01 11:25:46 -0400560 } else {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400561 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400562 }
563}
564
565static void
566show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
567{
568 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
569 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
570
571 if (gtk_toggle_button_get_active(navbutton)) {
572 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
573 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
574 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400575 }
576}
577
Stepan Salenikovichde896112015-05-11 16:46:33 -0400578static void
579show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
580{
581 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
582 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
583
584 if (gtk_toggle_button_get_active(navbutton)) {
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400585 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400586 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
587 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
588 priv->last_settings_view = priv->general_settings_view;
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400589 } else {
590 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400591 }
592}
593
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400594static gboolean
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400595create_ring_account(RingMainWindow *win)
596{
597 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
598 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
599
600 /* create account and set UPnP enabled, as its not by default in the daemon */
601 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500602 Account *account = nullptr;
Nicolas Jagerb413b302016-05-06 11:41:32 -0400603
604 /* get profile (if so) */
605 auto profile = ProfileModel::instance().selectedProfile();
606
607 if (profile) {
608 if (alias && strlen(alias) > 0) {
609 account = AccountModel::instance().add(alias, Account::Protocol::RING);
610 profile->person()->setFormattedName(alias);
611 } else {
612 auto unknown_alias = C_("The default username / account alias, if none is set by the user", "Unknown");
613 account = AccountModel::instance().add(unknown_alias, Account::Protocol::RING);
614 profile->person()->setFormattedName(unknown_alias);
615 }
616 }
617
Stepan Salenikovich75a39172015-07-10 13:21:08 -0400618 account->setDisplayName(alias); // set the display name to the same as the alias
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400619 account->setUpnpEnabled(TRUE);
620
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500621 /* wait for hash to be generated to show the main view */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400622 priv->hash_updated = QObject::connect(
623 account,
624 &Account::changed,
625 [=] (Account *a) {
626 QString hash = a->username();
627 if (!hash.isEmpty()) {
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500628 /* show the call view */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400629 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500630 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400631
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500632 /* show the settings button*/
633 gtk_widget_show(priv->ring_settings);
Stepan Salenikovich4ce8a2b2016-08-25 11:23:29 -0400634
635 //once the account is created we don't want this lambda to be called again
636 QObject::disconnect(priv->hash_updated);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400637 }
638 }
639 );
640
641 account->performAction(Account::EditAction::SAVE);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400642 profile->save();
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400643
644 return G_SOURCE_REMOVE;
645}
646
647static void
648alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
649{
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500650 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400651 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
652
653 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500654 if (!alias || strlen(alias) == 0) {
655 gtk_widget_show(priv->label_default_name);
656 gtk_widget_hide(priv->label_paceholder);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400657 } else {
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500658 gtk_widget_hide(priv->label_default_name);
659 gtk_widget_show(priv->label_paceholder);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400660 }
661}
662
663static void
664account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
665{
666 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
667
668 /* show/hide relevant widgets */
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500669 gtk_widget_hide(priv->vbox_account_creation_entry);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400670 gtk_widget_hide(priv->button_account_creation_next);
671 gtk_widget_show(priv->label_generating_account);
672 gtk_widget_show(priv->spinner_generating_account);
673
Stepan Salenikovich419414c2016-06-07 10:35:07 -0400674 /* make sure the AvatarManipulation widget is destroyed so the VideoWidget inside of it is too;
675 * NOTE: destorying its parent (box_avatarselection) first will cause a mystery 'BadDrawable'
676 * crash due to X error */
677 gtk_container_remove(GTK_CONTAINER(priv->box_avatarselection), priv->avatar_manipulation);
678 priv->avatar_manipulation = nullptr;
679
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400680 /* now create account after a short timeout so that the the save doesn't
681 * happen freeze the client before the widget changes happen;
682 * the timeout function should then display the next step in account creation
683 */
684 g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
685}
686
687static void
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400688entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
689{
690 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
691
692 const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
693 if (strlen(alias) > 0)
694 gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
695}
696
697static void
698show_account_creation(RingMainWindow *win)
699{
700 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
701
702 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500703 priv->account_creation,
704 CREATE_ACCOUNT_VIEW_NAME);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400705
Stepan Salenikovich64505672015-09-24 10:46:07 -0400706 /* hide settings button until account creation is complete */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400707 gtk_widget_hide(priv->ring_settings);
708
709 /* set ring logo */
710 GError *error = NULL;
711 GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500712 -1, 50, TRUE, &error);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400713 if (logo_ring == NULL) {
714 g_debug("Could not load logo: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -0400715 g_clear_error(&error);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400716 } else
717 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);
718
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500719 /* use the real name / username of the logged in user as the default */
Nicolas Jager5e604d52016-05-13 11:09:12 -0400720 const char* real_name = g_get_real_name();
721 const char* user_name = g_get_user_name();
722 g_debug("real_name = %s",real_name);
723 g_debug("user_name = %s",user_name);
724
725 /* check first if the real name was determined */
726 if (g_strcmp0 (real_name,"Unknown") != 0)
727 gtk_entry_set_text(GTK_ENTRY(priv->entry_alias), real_name);
728 else
729 gtk_entry_set_text(GTK_ENTRY(priv->entry_alias), user_name);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400730
Nicolas Jagerb413b302016-05-06 11:41:32 -0400731 /* avatar manipulation widget */
Stepan Salenikovich419414c2016-06-07 10:35:07 -0400732 priv->avatar_manipulation = avatar_manipulation_new_from_wizard();
733 gtk_box_pack_start(GTK_BOX(priv->box_avatarselection), priv->avatar_manipulation, true, true, 0);
Nicolas Jagerb413b302016-05-06 11:41:32 -0400734
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400735 /* connect signals */
736 g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
737 g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400738 g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400739
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500740 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_VIEW_NAME);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400741}
742
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400743static void
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400744search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
745{
746 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
747
748 /* get the text from the entry */
749 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
750
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400751 if (text && strlen(text) > 0) {
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400752 /* edit the the dialing call (or create a new one) */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400753 if (auto call = CallModel::instance().dialingCall()) {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400754 call->setDialNumber(text);
755 priv->q_completion_model->setCall(call);
756 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400757 } else {
Guillaume Roguez6a478f22015-05-20 09:54:08 -0400758 if (auto call = priv->q_completion_model->call()) {
759 if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
760 call->performAction(Call::Action::REFUSE);
761 }
Stepan Salenikovich4e409932015-04-24 12:12:39 -0400762 }
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400763}
764
765static gboolean
766completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
767 G_GNUC_UNUSED const gchar *key,
768 G_GNUC_UNUSED GtkTreeIter *iter,
769 G_GNUC_UNUSED RingMainWindow *win)
770{
771 /* the model is updated by lrc and should only every contain matching entries
772 * so always return TRUE */
773 return TRUE;
774}
775
776static QModelIndex
777get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
778 GtkTreeIter *filter_iter)
779{
780 GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
781 GtkTreeIter child_iter;
782 gtk_tree_model_filter_convert_iter_to_child_iter(
783 GTK_TREE_MODEL_FILTER(filter_model),
784 &child_iter,
785 filter_iter);
786
787 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
788}
789
790static void
791autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
792 GtkCellRenderer *cell,
793 GtkTreeModel *model,
794 GtkTreeIter *iter,
795 G_GNUC_UNUSED gpointer user_data)
796{
797 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
798 if (idx.isValid()) {
799 QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
800 if (photo_var.isValid()) {
801 std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
802 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
803 20, 20,
804 GDK_INTERP_BILINEAR);
805
806 g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
807 g_object_unref(scaled);
808 return;
809 }
810 }
811
812 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
813}
814
815static void
816autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
817 GtkCellRenderer *cell,
818 GtkTreeModel *model,
819 GtkTreeIter *iter,
820 G_GNUC_UNUSED gpointer user_data)
821{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400822 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400823 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
824 if (idx.isValid()) {
825 QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400826 text = g_markup_printf_escaped("<span weight=\"bold\">%s</span>",
827 name.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400828 }
829
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400830 g_object_set(G_OBJECT(cell), "markup", text, NULL);
831 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400832}
833
834static void
835autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
836 GtkCellRenderer *cell,
837 GtkTreeModel *model,
838 GtkTreeIter *iter,
839 G_GNUC_UNUSED gpointer user_data)
840{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400841 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400842 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
843 if (idx.isValid()) {
844 QVariant uri = idx.data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400845 text = g_markup_escape_text(uri.value<QString>().toUtf8().constData(), -1);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400846 }
847
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400848 g_object_set(G_OBJECT(cell), "markup", text, NULL);
849 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400850}
851
852static void
853autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
854 GtkCellRenderer *cell,
855 GtkTreeModel *model,
856 GtkTreeIter *iter,
857 G_GNUC_UNUSED gpointer user_data)
858{
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400859 gchar *text = nullptr;
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400860 QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
861 if (idx.isValid()) {
862 QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400863 text = g_markup_printf_escaped("<span color=\"gray\">%s</span>",
864 alias.value<QString>().toUtf8().constData());
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400865 }
866
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400867 g_object_set(G_OBJECT(cell), "markup", text, NULL);
868 g_free(text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400869}
870
871static gboolean
872select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
873 GtkTreeModel *model,
874 GtkTreeIter *iter,
875 RingMainWindow *win)
876{
877 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
878
879 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
880 if (idx.isValid()) {
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -0400881 auto cm = priv->q_completion_model->number(idx);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400882
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400883 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
884 place_new_call(cm);
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400885
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400886 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
887 gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
888 } else {
889 // if its a new CM, bring it to the top
890 if (cm->lastUsed() == 0)
891 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
892
893 // select cm
894 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
895 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400896
897 /* clear the entry */
898 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400899 } else {
900 g_warning("autocompletion selection is not a valid index!");
901 }
902 return TRUE;
903}
904
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400905static gboolean
906dtmf_pressed(RingMainWindow *win,
907 GdkEventKey *event,
908 G_GNUC_UNUSED gpointer user_data)
909{
910 g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
911
912 /* we want to react to digit key presses, as long as a GtkEntry is not the
913 * input focus
914 */
915 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
916 if (GTK_IS_ENTRY(focus))
917 return GDK_EVENT_PROPAGATE;
918
919 /* make sure that a call is selected*/
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400920 QItemSelectionModel *selection = CallModel::instance().selectionModel();
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400921 QModelIndex idx = selection->currentIndex();
922 if (!idx.isValid())
923 return GDK_EVENT_PROPAGATE;
924
925 /* make sure that the selected call is in progress */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400926 Call *call = CallModel::instance().getCall(idx);
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400927 Call::LifeCycleState state = call->lifeCycleState();
928 if (state != Call::LifeCycleState::PROGRESS)
929 return GDK_EVENT_PROPAGATE;
930
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400931 /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
932 * does not result in a 'c' being played.
933 * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
934 if ( event->state
935 & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
936 return GDK_EVENT_PROPAGATE;
937
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400938 /* pass the character that was entered to be played by the daemon;
939 * the daemon will filter out invalid DTMF characters */
940 guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
941 QString val = QString::fromUcs4(&unicode_val, 1);
942 call->playDTMF(val);
943 g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400944
945 /* always propogate the key, so we don't steal accelerators/shortcuts */
946 return GDK_EVENT_PROPAGATE;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400947}
948
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400949/**
950 * This function determines if two different contact list views (Conversations, Contacts, History)
951 * have the same item selected. Note that the same item does not necessarily mean the same object.
952 * For example, if the history view has a Call selected and the Conversations view has a Person
953 * selected which is associated with the ContactMethod to which that Call was made, then this will
954 * evaluate to TRUE.
955 */
956static gboolean
957compare_treeview_selection(GtkTreeSelection *selection1, GtkTreeSelection *selection2)
958{
959 g_return_val_if_fail(selection1 && selection2, FALSE);
960
961 if (selection1 == selection2) return TRUE;
962
963 auto idx1 = get_index_from_selection(selection1);
964 auto type1 = idx1.data(static_cast<int>(Ring::Role::ObjectType));
965 auto object1 = idx1.data(static_cast<int>(Ring::Role::Object));
966
967 auto idx2 = get_index_from_selection(selection2);
968 auto type2 = idx2.data(static_cast<int>(Ring::Role::ObjectType));
969 auto object2 = idx2.data(static_cast<int>(Ring::Role::Object));
970
971 if (idx1.isValid() && type1.isValid() && object1.isValid() && idx2.isValid() && type2.isValid() && object2.isValid()) {
972 Call *call1 = nullptr;
973 ContactMethod *cm1 = nullptr;
974 Person *person1 = nullptr;
975
976 Call *call2 = nullptr;
977 ContactMethod *cm2 = nullptr;
978 Person *person2 = nullptr;
979
980 switch(type1.value<Ring::ObjectType>()) {
981 case Ring::ObjectType::Person:
982 person1 = object1.value<Person *>();
983 break;
984 case Ring::ObjectType::ContactMethod:
985 cm1 = object1.value<ContactMethod *>();
986 person1 = cm1->contact();
987 break;
988 case Ring::ObjectType::Call:
989 call1 = object1.value<Call *>();
990 cm1 = call1->peerContactMethod();
991 person1 = cm1->contact();
992 break;
993 case Ring::ObjectType::Media:
994 case Ring::ObjectType::Certificate:
995 case Ring::ObjectType::COUNT__:
996 // nothing to do
997 break;
998 }
999
1000 switch(type2.value<Ring::ObjectType>()) {
1001 case Ring::ObjectType::Person:
1002 person2 = object2.value<Person *>();
1003 break;
1004 case Ring::ObjectType::ContactMethod:
1005 cm2 = object2.value<ContactMethod *>();
1006 person2 = cm2->contact();
1007 break;
1008 case Ring::ObjectType::Call:
1009 call2 = object2.value<Call *>();
1010 cm2 = call2->peerContactMethod();
1011 person2 = cm2->contact();
1012 break;
1013 case Ring::ObjectType::Media:
1014 case Ring::ObjectType::Certificate:
1015 case Ring::ObjectType::COUNT__:
1016 // nothing to do
1017 break;
1018 }
1019
1020 if (person1 != nullptr && person1 == person2)
1021 return TRUE;
1022 if (cm1 != nullptr && cm1 == cm2)
1023 return TRUE;
1024 if (call1 != nullptr && call1 == call2)
1025 return TRUE;
1026 }
1027
1028 return FALSE;
1029}
1030
1031static void
1032conversation_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
1033{
1034 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1035 GtkTreeIter iter;
1036 GtkTreeModel *model = nullptr;
1037 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
1038 /* something is selected in the conversations view, clear the selection in the other views;
1039 * unless the current selection is of the same item; we don't try to match the selection in
1040 * all views since not all items exist in all 3 contact list views
1041 */
1042
1043 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1044 if (!compare_treeview_selection(selection, selection_contacts))
1045 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
1046
1047 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
1048 if (!compare_treeview_selection(selection, selection_history))
1049 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
1050 }
1051
1052 selection_changed(self);
1053}
1054
1055static void
1056contact_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
1057{
1058 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1059 GtkTreeIter iter;
1060 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
1061 /* something is selected in the contacts view, clear the selection in the other views;
1062 * unless the current selection is of the same item; we don't try to match the selection in
1063 * all views since not all items exist in all 3 contact list views
1064 */
1065 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1066 if (!compare_treeview_selection(selection, selection_conversations))
1067 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
1068
1069 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
1070 if (!compare_treeview_selection(selection, selection_history))
1071 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
1072 }
1073
1074 selection_changed(self);
1075}
1076
1077static void
1078history_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
1079{
1080 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1081 GtkTreeIter iter;
1082 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
1083 /* something is selected in the history view, clear the selection in the other views;
1084 * unless the current selection is of the same item; we don't try to match the selection in
1085 * all views since not all items exist in all 3 contact list views
1086 */
1087
1088 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1089 if (!compare_treeview_selection(selection, selection_conversations))
1090 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
1091
1092 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1093 if (!compare_treeview_selection(selection, selection_contacts))
1094 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
1095 }
1096
1097 selection_changed(self);
1098}
1099
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001100static gboolean
1101window_size_changed(GtkWidget *win, GdkEventConfigure *event, gpointer)
1102{
1103 auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1104
1105 g_settings_set_int(priv->settings, "window-width", event->width);
1106 g_settings_set_int(priv->settings, "window-height", event->height);
1107
1108 return GDK_EVENT_PROPAGATE;
1109}
1110
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001111static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001112search_entry_places_call_changed(GSettings *settings, const gchar *key, RingMainWindow *self)
1113{
1114 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1115
1116 if (g_settings_get_boolean(settings, key)) {
1117 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will place a new call", "place call"));
1118 } else {
1119 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will open chat", "open chat"));
1120 }
1121}
1122
1123static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001124ring_main_window_init(RingMainWindow *win)
1125{
1126 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1127 gtk_widget_init_template(GTK_WIDGET(win));
1128
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001129 /* bind to window size settings */
1130 priv->settings = g_settings_new_full(get_ring_schema(), nullptr, nullptr);
1131 auto width = g_settings_get_int(priv->settings, "window-width");
1132 auto height = g_settings_get_int(priv->settings, "window-height");
1133 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
1134 g_signal_connect(win, "configure-event", G_CALLBACK(window_size_changed), nullptr);
1135
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001136 /* search-entry-places-call setting */
1137 search_entry_places_call_changed(priv->settings, "search-entry-places-call", win);
1138 g_signal_connect(priv->settings, "changed::search-entry-places-call", G_CALLBACK(search_entry_places_call_changed), win);
1139
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001140 /* set window icon */
1141 GError *error = NULL;
1142 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
1143 if (icon == NULL) {
1144 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001145 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001146 } else
1147 gtk_window_set_icon(GTK_WINDOW(win), icon);
1148
1149 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001150 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001151 -1, 16, TRUE, &error);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001152 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001153 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001154 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001155 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001156 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001157
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001158 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001159 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
1160 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001161 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001162 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001163
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001164 /* settings icon */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001165 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 -04001166
1167 /* connect settings button signal */
1168 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
1169
1170 /* add the call view to the main stack */
1171 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
1172 priv->vbox_call_view,
1173 CALL_VIEW_NAME);
1174
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001175 if (has_ring_account()) {
1176 /* user has ring account, so show the call view right away */
1177 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
1178 } else {
1179 /* user has to create the ring account */
1180 show_account_creation(win);
1181 }
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001182
1183 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001184 priv->account_settings_view = account_view_new();
1185 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
1186
Stepan Salenikovich0bd53492015-05-11 14:28:52 -04001187 priv->media_settings_view = media_settings_view_new();
1188 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 -04001189
Stepan Salenikovichde896112015-05-11 16:46:33 -04001190 priv->general_settings_view = general_settings_view_new();
1191 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);
1192
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001193 /* make the setting we will show first the active one */
Stepan Salenikovichde896112015-05-11 16:46:33 -04001194 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
1195 priv->last_settings_view = priv->general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001196
1197 /* connect the settings button signals to switch settings views */
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001198 g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001199 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
Stepan Salenikovichde896112015-05-11 16:46:33 -04001200 g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001201
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001202 /* populate the notebook */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001203 priv->treeview_conversations = recent_contacts_view_new();
1204 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), priv->treeview_conversations);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -04001205
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001206 priv->treeview_contacts = contacts_view_new();
1207 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), priv->treeview_contacts);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001208
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001209 priv->treeview_history = history_view_new();
1210 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), priv->treeview_history);
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001211
Stepan Salenikovich2cde7612015-09-25 10:44:01 -04001212 /* welcome/default view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001213 priv->welcome_view = ring_welcome_view_new();
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001214 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 -04001215 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
1216 gtk_widget_show(priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001217
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001218 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1219 g_signal_connect(selection_conversations, "changed", G_CALLBACK(conversation_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001220
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001221 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1222 g_signal_connect(selection_contacts, "changed", G_CALLBACK(contact_selection_changed), win);
1223
1224 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
1225 g_signal_connect(selection_history, "changed", G_CALLBACK(history_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001226
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001227 g_signal_connect(priv->button_new_conversation, "clicked", G_CALLBACK(search_entry_activated), win);
1228 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_activated), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001229
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001230 /* autocompletion */
1231 priv->q_completion_model = new NumberCompletionModel();
1232
1233 /* autocompletion renderers */
1234 GtkCellArea *completion_area = gtk_cell_area_box_new();
1235
1236 /* photo renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001237 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001238 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1239 renderer,
1240 TRUE, /* expand */
1241 TRUE, /* align */
1242 TRUE); /* fixed size */
1243
1244 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1245 renderer,
1246 (GtkCellLayoutDataFunc)autocompletion_photo_render,
1247 NULL, NULL);
1248
1249 /* name renderer */
1250 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001251 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001252 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1253 renderer,
1254 TRUE, /* expand */
1255 TRUE, /* align */
1256 TRUE); /* fixed size */
1257
1258 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1259 renderer,
1260 (GtkCellLayoutDataFunc)autocompletion_name_render,
1261 NULL, NULL);
1262
1263 /* number renderer */
1264 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001265 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001266 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1267 renderer,
1268 TRUE, /* expand */
1269 TRUE, /* align */
1270 TRUE); /* fixed size */
1271
1272 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1273 renderer,
1274 (GtkCellLayoutDataFunc)autocompletion_number_render,
1275 NULL, NULL);
1276 /* account renderer */
1277 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich19e581d2015-06-18 11:44:45 -04001278 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001279 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
1280 renderer,
1281 TRUE, /* expand */
1282 TRUE, /* align */
1283 TRUE); /* fixed size */
1284
1285 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
1286 renderer,
1287 (GtkCellLayoutDataFunc)autocompletion_account_render,
1288 NULL, NULL);
1289
1290 GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);
1291
1292 GtkQTreeModel *completion_model = gtk_q_tree_model_new(
1293 (QAbstractItemModel *)priv->q_completion_model,
1294 1,
aviau9bbb19b2016-05-16 15:53:44 -04001295 0, Qt::DisplayRole, G_TYPE_STRING);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001296
1297 gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));
1298
1299 gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
1300 gtk_entry_completion_set_match_func(
1301 entry_completion,
1302 (GtkEntryCompletionMatchFunc) completion_match_func,
1303 NULL,
1304 NULL);
1305
1306 /* connect signal to when text is entered in the entry */
1307 g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
1308 g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001309
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001310 /* make sure the incoming call is the selected call in the CallModel */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001311 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -04001312 &CallModel::instance(),
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001313 &CallModel::incomingCall,
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001314 [](Call* call) {
1315 CallModel::instance().selectCall(call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001316 }
1317 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001318
Stepan Salenikovichb01d7362015-04-27 23:02:00 -04001319 /* react to digit key press events */
1320 g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
Stepan Salenikovich48b45c12015-09-25 15:49:51 -04001321
1322 /* set the search entry placeholder text */
1323 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
1324 C_("Please try to make the translation 50 chars or less so that it fits into the layout", "Search contacts or enter number"));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001325}
1326
1327static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001328ring_main_window_dispose(GObject *object)
1329{
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001330 RingMainWindow *self = RING_MAIN_WINDOW(object);
1331 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1332
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001333 QObject::disconnect(priv->selected_item_changed);
1334 QObject::disconnect(priv->selected_call_over);
1335 g_object_unref(priv->welcome_view); // can now be destroyed
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001336
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001337 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1338}
1339
1340static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001341ring_main_window_finalize(GObject *object)
1342{
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001343 RingMainWindow *self = RING_MAIN_WINDOW(object);
1344 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1345
1346 g_clear_object(&priv->settings);
1347
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001348 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001349}
1350
1351static void
1352ring_main_window_class_init(RingMainWindowClass *klass)
1353{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001354 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001355 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001356
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001357 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1358 "/cx/ring/RingGnome/ringmainwindow.ui");
1359
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001360 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001361 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001362 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
1363 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001364 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1365 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1366 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1367 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001368 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001369 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001370 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001371 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001372 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001373 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_new_conversation );
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001374 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001375 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001376 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001377
1378 /* account creation */
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -05001379 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001380 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -05001381 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_account_creation_entry);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001382 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -05001383 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_default_name);
1384 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_paceholder);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -04001385 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
1386 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
1387 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);
Nicolas Jagerb413b302016-05-06 11:41:32 -04001388 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, box_avatarselection);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001389}
1390
1391GtkWidget *
1392ring_main_window_new (GtkApplication *app)
1393{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001394 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1395
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001396 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001397}