blob: 055f9b27ef6888b6b54b97fef1873ff1a57a617e [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 <categorizedcontactmodel.h>
44#include <recentmodel.h>
45#include <profilemodel.h>
46#include <profile.h>
Stepan Salenikovichf53128f2016-10-07 10:32:16 -040047#include <phonedirectorymodel.h>
Stepan Salenikovich7b7be482016-11-03 16:57:48 -040048#include <availableaccountmodel.h>
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040049
50// Ring client
51#include "models/gtkqtreemodel.h"
52#include "incomingcallview.h"
53#include "currentcallview.h"
Stepan Salenikovichf6078222016-10-03 17:31:16 -040054#include "models/gtkqtreemodel.h"
Stepan Salenikovich5fd97bc2016-08-30 09:40:38 -040055#include "accountview.h"
56#include "dialogs.h"
57#include "mediasettingsview.h"
58#include "utils/drawing.h"
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040059#include "native/pixbufmanipulator.h"
Stepan Salenikovich6f687072015-03-26 10:43:37 -040060#include "models/activeitemproxymodel.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040061#include "utils/calling.h"
62#include "contactsview.h"
63#include "historyview.h"
64#include "utils/models.h"
Stepan Salenikovichde896112015-05-11 16:46:33 -040065#include "generalsettingsview.h"
Stepan Salenikovich75a39172015-07-10 13:21:08 -040066#include "utils/accounts.h"
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040067#include "ringwelcomeview.h"
aviau6aeb4852016-08-18 16:01:09 -040068#include "accountmigrationview.h"
69#include "accountcreationwizard.h"
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040070#include "recentcontactsview.h"
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -050071#include "chatview.h"
Nicolas Jagerb413b302016-05-06 11:41:32 -040072#include "avatarmanipulation.h"
Stepan Salenikovichba87ae22016-09-13 14:27:20 -040073#include "utils/files.h"
Nicolas Jagerb413b302016-05-06 11:41:32 -040074
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040075static constexpr const char* CALL_VIEW_NAME = "calls";
aviau6aeb4852016-08-18 16:01:09 -040076static constexpr const char* ACCOUNT_CREATION_WIZARD_VIEW_NAME = "account-creation-wizard";
77static constexpr const char* ACCOUNT_MIGRATION_VIEW_NAME = "account-migration-view";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040078static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
79static constexpr const char* AUDIO_SETTINGS_VIEW_NAME = "audio";
80static constexpr const char* MEDIA_SETTINGS_VIEW_NAME = "media";
81static constexpr const char* ACCOUNT_SETTINGS_VIEW_NAME = "accounts";
Stepan Salenikovich2cde7612015-09-25 10:44:01 -040082static constexpr const char* DEFAULT_VIEW_NAME = "welcome";
Stepan Salenikovich9ffad5e2015-09-25 13:16:50 -040083static constexpr const char* VIEW_CONTACTS = "contacts";
84static constexpr const char* VIEW_HISTORY = "history";
85static constexpr const char* VIEW_PRESENCE = "presence";
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050086
87struct _RingMainWindow
88{
89 GtkApplicationWindow parent;
90};
91
92struct _RingMainWindowClass
93{
94 GtkApplicationWindowClass parent_class;
95};
96
97typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
98
99struct _RingMainWindowPrivate
100{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400101 GtkWidget *ring_menu;
102 GtkWidget *image_ring;
103 GtkWidget *ring_settings;
104 GtkWidget *image_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400105 GtkWidget *hbox_settings;
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400106 GtkWidget *scrolled_window_smartview;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400107 GtkWidget *treeview_conversations;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400108 GtkWidget *scrolled_window_contacts;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400109 GtkWidget *treeview_contacts;
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -0400110 GtkWidget *scrolled_window_history;
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400111 GtkWidget *treeview_history;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400112 GtkWidget *vbox_left_pane;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500113 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500114 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400115 GtkWidget *vbox_call_view;
Stepan Salenikovich3034d922015-10-09 10:11:42 -0400116 GtkWidget *frame_call;
117 GtkWidget *welcome_view;
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400118 GtkWidget *button_new_conversation ;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400119 GtkWidget *account_settings_view;
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400120 GtkWidget *media_settings_view;
Stepan Salenikovichde896112015-05-11 16:46:33 -0400121 GtkWidget *general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400122 GtkWidget *last_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400123 GtkWidget *radiobutton_general_settings;
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400124 GtkWidget *radiobutton_media_settings;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400125 GtkWidget *radiobutton_account_settings;
aviau69081842016-10-14 14:51:54 -0400126 GtkWidget *account_creation_wizard;
127 GtkWidget *account_migration_view;
Nicolas Jager55903142017-02-01 14:43:45 -0500128 GtkWidget *spinner_lookup;
Nicolas Jager15a8b902017-03-21 07:53:06 -0400129 GtkWidget *combobox_account_selector;
Stepan Salenikovich5eab3032015-03-28 10:50:51 -0400130
aviau8c0b2422016-10-31 16:06:44 -0400131 /* Pending ring usernames lookup for the search entry */
132 QMetaObject::Connection username_lookup;
Guillaume Rogueze7221ed2017-03-17 20:09:56 -0400133 std::string* pending_username_lookup;
aviau8c0b2422016-10-31 16:06:44 -0400134
aviau039001d2016-09-29 16:39:05 -0400135 /* The webkit_chat_container is created once, then reused for all chat views */
136 GtkWidget *webkit_chat_container;
137
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400138 QMetaObject::Connection selected_item_changed;
139 QMetaObject::Connection selected_call_over;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400140
141 gboolean show_settings;
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400142
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500143 /* fullscreen */
144 gboolean is_fullscreen;
Stepan Salenikovichba87ae22016-09-13 14:27:20 -0400145
146 GSettings *settings;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500147};
148
149G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
150
151#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
152
aviau039001d2016-09-29 16:39:05 -0400153static gboolean selection_changed(RingMainWindow *win);
154
155static WebKitChatContainer*
156get_webkit_chat_container(RingMainWindow *win)
157{
158 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
159 if (!priv->webkit_chat_container)
160 {
161 priv->webkit_chat_container = webkit_chat_container_new();
162
163 //We don't want it to be deleted, ever.
164 g_object_ref(priv->webkit_chat_container);
165 }
166 return WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container);
167}
168
Stepan Salenikovich69771842015-02-24 18:11:45 -0500169static void
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500170enter_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_hide(priv->vbox_left_pane);
177 gtk_window_fullscreen(GTK_WINDOW(self));
178 priv->is_fullscreen = TRUE;
179 }
180}
181
182static void
183leave_full_screen(RingMainWindow *self)
184{
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 gtk_widget_show(priv->vbox_left_pane);
190 gtk_window_unfullscreen(GTK_WINDOW(self));
191 priv->is_fullscreen = FALSE;
192 }
193}
194
195static void
Stepan Salenikovich5e431cf2015-12-24 14:09:01 -0500196video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500197{
198 g_return_if_fail(IS_RING_MAIN_WINDOW(self));
199 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
200
201 if (priv->is_fullscreen) {
202 leave_full_screen(self);
203 } else {
204 enter_full_screen(self);
205 }
206}
207
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400208static void
209hide_view_clicked(G_GNUC_UNUSED GtkWidget *view, RingMainWindow *self)
210{
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400211 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400212
213 /* clear selection */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400214 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
215 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
216 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
217 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
218 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
219 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
Stepan Salenikovich8043a562016-03-18 13:56:40 -0400220}
221
aviau039001d2016-09-29 16:39:05 -0400222static void
223change_view(RingMainWindow *self, GtkWidget* old, QObject *object, GType type)
224{
225 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
226 leave_full_screen(self);
227 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old);
228
229 GtkWidget *new_view = nullptr;
230
231 QObject::disconnect(priv->selected_item_changed);
232 QObject::disconnect(priv->selected_call_over);
233
234 if (g_type_is_a(INCOMING_CALL_VIEW_TYPE, type)) {
235 if (auto call = qobject_cast<Call *>(object)) {
236 new_view = incoming_call_view_new(call, get_webkit_chat_container(self));
237 priv->selected_item_changed = QObject::connect(
238 call,
239 &Call::lifeCycleStateChanged,
240 [self] (Call::LifeCycleState, Call::LifeCycleState)
241 {g_idle_add((GSourceFunc)selection_changed, self);}
242 );
243 priv->selected_call_over = QObject::connect(
244 call,
245 &Call::isOver,
246 [self] ()
247 {g_idle_add((GSourceFunc)selection_changed, self);}
248 );
249 } else
250 g_warning("Trying to display a view of type IncomingCallView, but the object is not of type Call");
251 } else if (g_type_is_a(CURRENT_CALL_VIEW_TYPE, type)) {
252 if (auto call = qobject_cast<Call *>(object)) {
253 new_view = current_call_view_new(call, get_webkit_chat_container(self));
254 g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), self);
255 priv->selected_item_changed = QObject::connect(
256 call,
257 &Call::lifeCycleStateChanged,
258 [self] (Call::LifeCycleState, Call::LifeCycleState)
259 {g_idle_add((GSourceFunc)selection_changed, self);}
260 );
261 priv->selected_call_over = QObject::connect(
262 call,
263 &Call::isOver,
264 [self] ()
265 {g_idle_add((GSourceFunc)selection_changed, self);}
266 );
267 } else
268 g_warning("Trying to display a view of type CurrentCallView, but the object is not of type Call");
269 } else if (g_type_is_a(CHAT_VIEW_TYPE, type)) {
270 if (auto person = qobject_cast<Person *>(object)) {
271 new_view = chat_view_new_person(get_webkit_chat_container(self), person);
272 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), self);
273
274 /* connect to the Person's callAdded signal, because we want to switch to the call view
275 * in this case */
276 priv->selected_item_changed = QObject::connect(
277 person,
278 &Person::callAdded,
279 [self] (Call*)
280 {g_idle_add((GSourceFunc)selection_changed, self);}
281 );
282 } else if (auto cm = qobject_cast<ContactMethod *>(object)) {
283 new_view = chat_view_new_cm(get_webkit_chat_container(self), cm);
284 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), self);
285
286 /* connect to the ContactMethod's callAdded signal, because we want to switch to the
287 * call view in this case */
288 priv->selected_item_changed = QObject::connect(
289 cm,
290 &ContactMethod::callAdded,
291 [self] (Call*)
292 {g_idle_add((GSourceFunc)selection_changed, self);}
293 );
294 } else {
295 g_warning("Trying to display a veiw of type ChatView, but the object is neither a Person nor a ContactMethod");
296 }
297 } else {
298 // display the welcome view
299 new_view = priv->welcome_view;
300 }
301
302 gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
303 gtk_widget_show(new_view);
304}
305
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500306/**
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400307 * This function determines which view to display in the right panel of the main window based on
308 * which item is selected in one of the 3 contact list views (Conversations, Contacts, History). The
309 * possible views ares:
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500310 * - incoming call view
311 * - current call view
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500312 * - chat view
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400313 * - welcome view (if no valid item is selected)
314 *
315 * There should never be a conflict of which item should be displayed (ie: which item is selected),
316 * as there should only ever be one selection at a time in the 3 views except in the case that the
317 * same item is selected in more than one view (see the compare_treeview_selection() function).
318 *
319 * This function could be called from a g_idle source, so it returns a boolean to remove itself from
320 * being called again. The boolean doesn't reflect success nor failure.
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500321 */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400322static gboolean
323selection_changed(RingMainWindow *win)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500324{
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500325 // g_debug("selection changed");
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400326
327 g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500328 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
329
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400330 auto old_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500331
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400332 /* if we're showing the settings, then we need to make sure to remove any possible ongoing call
333 * view so that we don't have more than one VideoWidget at a time */
334 if (priv->show_settings) {
335 if (old_view != priv->welcome_view) {
336 leave_full_screen(win);
337 gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
338 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
339 gtk_widget_show(priv->welcome_view);
340 }
341 return G_SOURCE_REMOVE;
342 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500343
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400344 /* there should only be one item selected at a time, get which one is selected */
345 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
346 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
347 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
348
349 GtkTreeModel *model = nullptr;
350 GtkTreeIter iter;
351 QModelIndex idx;
352
353 if (gtk_tree_selection_get_selected(selection_conversations, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400354 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400355 } else if (gtk_tree_selection_get_selected(selection_contacts, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400356 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400357 } else if (gtk_tree_selection_get_selected(selection_history, &model, &iter)) {
Stepan Salenikovichf6078222016-10-03 17:31:16 -0400358 idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400359 }
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500360
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500361 /* check which object type is selected */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400362 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
363 auto object = idx.data(static_cast<int>(Ring::Role::Object));
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500364
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400365 if (idx.isValid() && type.isValid() && object.isValid()) {
Stepan Salenikovich69771842015-02-24 18:11:45 -0500366
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400367 /* we prioritize the views in the following order:
368 * - call view (if there is an ongoing call associated with the item)
369 * - chat view built from Person
370 * - chat view built from ContactMethod
371 */
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500372
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400373 Person *person = nullptr;
374 ContactMethod *cm = nullptr;
375 Call *call = nullptr;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500376
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400377 /* use the RecentModel to see if there are any ongoing calls associated with the selected item */
378 switch(type.value<Ring::ObjectType>()) {
379 case Ring::ObjectType::Person:
380 {
381 person = object.value<Person *>();
382 auto recent_idx = RecentModel::instance().getIndex(person);
383 if (recent_idx.isValid()) {
384 call = RecentModel::instance().getActiveCall(recent_idx);
385 }
386 }
387 break;
388 case Ring::ObjectType::ContactMethod:
389 {
390 cm = object.value<ContactMethod *>();
391 auto recent_idx = RecentModel::instance().getIndex(cm);
392 if (recent_idx.isValid()) {
393 call = RecentModel::instance().getActiveCall(recent_idx);
394 }
395 }
396 break;
397 case Ring::ObjectType::Call:
398 {
399 /* if the call is over (eg: if it comes from the history model) then we first check
400 * if there is an ongoing call with the same ContactMethod, otherwise we need to get
401 * the associated Person or ContactMethod */
402 call = object.value<Call *>();
403 if (call->isHistory()) {
404 cm = call->peerContactMethod();
405 call = nullptr;
406 if (cm) {
407 auto recent_idx = RecentModel::instance().getIndex(cm);
408 if (recent_idx.isValid()) {
409 call = RecentModel::instance().getActiveCall(recent_idx);
410 }
411 person = cm->contact();
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500412 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500413 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400414 }
415 break;
416 case Ring::ObjectType::Media:
417 case Ring::ObjectType::Certificate:
Nicolas Jagerc74f7612017-03-28 09:48:18 -0400418 case Ring::ObjectType::ContactRequest:
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400419 case Ring::ObjectType::COUNT__:
420 // nothing to do
421 break;
422 }
423
424 /* we prioritize showing the call view */
425 if (call) {
426 auto state = call->lifeCycleState();
427
428 Call *current_call = nullptr;
429
430 switch(state) {
431 case Call::LifeCycleState::CREATION:
432 case Call::LifeCycleState::INITIALIZATION:
433 case Call::LifeCycleState::FINISHED:
434 // check if we're already displaying this call
435 if (IS_INCOMING_CALL_VIEW(old_view))
436 current_call = incoming_call_view_get_call(INCOMING_CALL_VIEW(old_view));
437 if (current_call != call)
aviau039001d2016-09-29 16:39:05 -0400438 change_view(win, old_view, call, INCOMING_CALL_VIEW_TYPE);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400439 break;
440 case Call::LifeCycleState::PROGRESS:
441 // check if we're already displaying this call
442 if (IS_CURRENT_CALL_VIEW(old_view))
443 current_call = current_call_view_get_call(CURRENT_CALL_VIEW(old_view));
aviau039001d2016-09-29 16:39:05 -0400444 if (current_call != call)
445 change_view(win, old_view, call, CURRENT_CALL_VIEW_TYPE);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400446 break;
447 case Call::LifeCycleState::COUNT__:
448 g_warning("LifeCycleState should never be COUNT");
449 break;
450 }
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500451
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400452 } else if (person) {
453 /* show chat view constructed from Person object */
454
455 // check if we're already displaying the chat view for this person
456 Person *current_person = nullptr;
457 if (IS_CHAT_VIEW(old_view))
458 current_person = chat_view_get_person(CHAT_VIEW(old_view));
459
aviau039001d2016-09-29 16:39:05 -0400460 if (current_person != person)
461 change_view(win, old_view, person, CHAT_VIEW_TYPE);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400462 } else if (cm) {
463 /* show chat view constructed from CM */
464
465 // check if we're already displaying the chat view for this cm
466 ContactMethod *current_cm = nullptr;
467 if (IS_CHAT_VIEW(old_view))
468 current_cm = chat_view_get_cm(CHAT_VIEW(old_view));
469
aviau039001d2016-09-29 16:39:05 -0400470 if (current_cm != cm)
471 change_view(win, old_view, cm, CHAT_VIEW_TYPE);
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400472 } else {
473 /* not a supported object type, display the welcome view */
aviau039001d2016-09-29 16:39:05 -0400474 if (old_view != priv->welcome_view)
475 change_view(win, old_view, nullptr, RING_WELCOME_VIEW_TYPE);
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500476 }
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400477 } else {
478 /* selection isn't valid, display the welcome view */
aviau039001d2016-09-29 16:39:05 -0400479 if (old_view != priv->welcome_view)
480 change_view(win, old_view, nullptr, RING_WELCOME_VIEW_TYPE);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500481 }
Stepan Salenikovich504f7722016-01-20 17:10:45 -0500482
483 return G_SOURCE_REMOVE;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500484}
485
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500486static void
Nicolas Jager65d6e1f2017-03-27 16:57:55 -0400487process_search_entry_contact_method(RingMainWindow *self, const URI& uri, Account* accountOrNullptr)
aviau8c0b2422016-10-31 16:06:44 -0400488{
489 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
490
Nicolas Jager65d6e1f2017-03-27 16:57:55 -0400491 auto cm = PhoneDirectoryModel::instance().getNumber(uri, accountOrNullptr);
aviau8c0b2422016-10-31 16:06:44 -0400492
493 if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
494 place_new_call(cm);
495
496 /* move focus away from entry so that DTMF tones can be entered via the keyboard */
497 gtk_widget_child_focus(GTK_WIDGET(self), GTK_DIR_TAB_FORWARD);
498 } else {
499 // if its a new CM, bring it to the top
500 if (cm->lastUsed() == 0)
501 cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
502
503 // select cm
504 RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
505 }
506 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
507}
508
509static void
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400510search_entry_activated(RingMainWindow *self)
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500511{
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -0400512 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500513
Nicolas Jager55903142017-02-01 14:43:45 -0500514 const auto *querry = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500515
Nicolas Jager55903142017-02-01 14:43:45 -0500516 URI uri = URI(querry);
Stepan Salenikovich7b7be482016-11-03 16:57:48 -0400517
518
519 // make sure the userinfo part isn't empty, only specifying a protocol isn't enough
520 if (!uri.userinfo().isEmpty()) {
521
522 gboolean lookup_username = FALSE;
523
Stepan Salenikovichefd8cae2016-11-10 13:41:30 -0500524 /* get the account with which we will do the lookup */
525 auto ring_account = AvailableAccountModel::instance().currentDefaultAccount(URI::SchemeType::RING);
526
Stepan Salenikovich7b7be482016-11-03 16:57:48 -0400527 if (uri.protocolHint() == URI::ProtocolHint::RING_USERNAME ) {
528 lookup_username = TRUE;
529 } else if (
530 uri.protocolHint() != URI::ProtocolHint::RING && // not a RingID
531 uri.schemeType() == URI::SchemeType::NONE // scheme type not specified
532 ) {
533 // if no scheme type has been specified, determine ring vs sip by the first available account
534 auto idx = AvailableAccountModel::instance().index(0, 0);
535 if (idx.isValid()) {
536 auto account = idx.data((int)Ring::Role::Object).value<Account *>();
537 if (account && account->protocol() == Account::Protocol::RING)
538 lookup_username = TRUE;
539 }
540 }
541
542 if (lookup_username)
aviau8c0b2422016-10-31 16:06:44 -0400543 {
Nicolas Jager55903142017-02-01 14:43:45 -0500544 gtk_spinner_start(GTK_SPINNER(priv->spinner_lookup));
Guillaume Rogueze7221ed2017-03-17 20:09:56 -0400545 *priv->pending_username_lookup = querry;
Nicolas Jager55903142017-02-01 14:43:45 -0500546 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400547
aviau8f436a32016-11-05 01:49:55 -0400548 QString username_to_lookup = uri.format(
549 URI::Section::USER_INFO |
550 URI::Section::HOSTNAME |
551 URI::Section::PORT
552 );
Stepan Salenikovich895a70b2015-03-27 16:02:27 -0400553
aviau8c0b2422016-10-31 16:06:44 -0400554 QObject::disconnect(priv->username_lookup);
555 priv->username_lookup = QObject::connect(
556 &NameDirectory::instance(),
557 &NameDirectory::registeredNameFound,
Nicolas Jager65d6e1f2017-03-27 16:57:55 -0400558 [self, priv, username_to_lookup] (Account* account, NameDirectory::LookupStatus status, const QString& address, const QString& name) {
Stepan Salenikovich1564bba2015-04-29 11:28:43 -0400559
Nicolas Jager55903142017-02-01 14:43:45 -0500560 auto name_qbarray = name.toLatin1();
Guillaume Rogueze7221ed2017-03-17 20:09:56 -0400561 if ( strcmp(priv->pending_username_lookup->data(), name_qbarray.data()) != 0 )
Nicolas Jager55903142017-02-01 14:43:45 -0500562 return;
563
aviau8c0b2422016-10-31 16:06:44 -0400564 gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
565
566 switch(status)
567 {
568 case NameDirectory::LookupStatus::SUCCESS:
569 {
570 URI uri = URI("ring:" + address);
Nicolas Jager65d6e1f2017-03-27 16:57:55 -0400571 process_search_entry_contact_method(self, uri, account);
aviau8c0b2422016-10-31 16:06:44 -0400572 break;
573 }
574 case NameDirectory::LookupStatus::INVALID_NAME:
575 {
576 //Can't be a ring username, could be something else.
577 auto dialog = gtk_message_dialog_new(
578 GTK_WINDOW(self),
579 GTK_DIALOG_DESTROY_WITH_PARENT,
580 GTK_MESSAGE_ERROR,
581 GTK_BUTTONS_CLOSE,
582 _("Invalid Ring username: %s"),
583 name.toUtf8().constData()
584 );
585
586 gtk_dialog_run (GTK_DIALOG (dialog));
587 gtk_widget_destroy (dialog);
588 break;
589 }
590 case NameDirectory::LookupStatus::ERROR:
Stepan Salenikovichfa24b932016-11-21 15:19:57 -0500591 {
592 auto dialog = gtk_message_dialog_new(
593 GTK_WINDOW(self),
594 GTK_DIALOG_DESTROY_WITH_PARENT,
595 GTK_MESSAGE_ERROR,
596 GTK_BUTTONS_CLOSE,
597 _("Error resolving username, nameserver is possibly unreachable")
598 );
599
600 gtk_dialog_run(GTK_DIALOG (dialog));
601 gtk_widget_destroy(dialog);
602 break;
603 }
aviau8c0b2422016-10-31 16:06:44 -0400604 case NameDirectory::LookupStatus::NOT_FOUND:
605 {
606 auto dialog = gtk_message_dialog_new(
607 GTK_WINDOW(self),
608 GTK_DIALOG_DESTROY_WITH_PARENT,
609 GTK_MESSAGE_ERROR,
610 GTK_BUTTONS_CLOSE,
611 _("Could not resolve Ring username: %s"),
612 name.toUtf8().constData()
613 );
614
615 gtk_dialog_run(GTK_DIALOG (dialog));
616 gtk_widget_destroy(dialog);
617 break;
618 }
619 }
Guillaume Rogueze7221ed2017-03-17 20:09:56 -0400620 *priv->pending_username_lookup = "";
Nicolas Jager55903142017-02-01 14:43:45 -0500621 gtk_spinner_stop(GTK_SPINNER(priv->spinner_lookup));
aviau8c0b2422016-10-31 16:06:44 -0400622 }
623 );
624
Stepan Salenikovichefd8cae2016-11-10 13:41:30 -0500625 NameDirectory::instance().lookupName(ring_account, QString(), username_to_lookup);
Stepan Salenikoviche9933242016-06-21 18:08:48 -0400626 }
aviau8c0b2422016-10-31 16:06:44 -0400627 else
628 {
Guillaume Rogueze7221ed2017-03-17 20:09:56 -0400629 *priv->pending_username_lookup = "";
Nicolas Jager55903142017-02-01 14:43:45 -0500630 gtk_spinner_stop(GTK_SPINNER(priv->spinner_lookup));
Nicolas Jager65d6e1f2017-03-27 16:57:55 -0400631 process_search_entry_contact_method(self, uri, nullptr);
aviau8c0b2422016-10-31 16:06:44 -0400632 }
Stepan Salenikovich2e6ab562015-05-07 12:33:07 -0400633 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500634}
635
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400636static gboolean
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400637save_accounts(GtkWidget *working_dialog)
638{
639 /* save changes to accounts */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400640 AccountModel::instance().save();
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400641
642 if (working_dialog)
643 gtk_widget_destroy(working_dialog);
644
645 return G_SOURCE_REMOVE;
646}
647
Stepan Salenikovich15142182015-03-11 17:15:26 -0400648static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400649settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
650{
651 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
652 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
653
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400654 /* check which view to show */
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500655 if (!priv->show_settings) {
656 /* show the settings */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400657 priv->show_settings = TRUE;
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500658
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500659 /* 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 -0400660 selection_changed(win);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500661
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400662 /* show settings */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400663 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 -0400664
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400665 gtk_widget_show(priv->hbox_settings);
666
Stepan Salenikovich41118912015-05-01 11:25:46 -0400667 /* make sure to start preview if we're showing the video settings */
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400668 if (priv->last_settings_view == priv->media_settings_view)
669 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich41118912015-05-01 11:25:46 -0400670
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400671 /* make sure to show the profile if we're showing the general settings */
672 if (priv->last_settings_view == priv->general_settings_view)
673 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
674
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400675 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400676 } else {
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500677 /* hide the settings */
678 priv->show_settings = FALSE;
679
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400680 /* show working dialog in case save operation takes time */
681 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
682 gtk_window_present(GTK_WINDOW(working));
683
684 /* now save after the time it takes to transition back to the call view (400ms)
685 * the save doesn't happen before the "working" dialog is presented
686 * the timeout function should destroy the "working" dialog when done saving
687 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400688 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400689
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400690 /* show calls */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -0400691 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 -0400692
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400693 gtk_widget_hide(priv->hbox_settings);
694
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500695 /* make sure video preview is stopped, in case it was started */
696 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400697 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichbd4b3772015-12-13 23:28:06 -0500698
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400699 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400700
Stepan Salenikovichc1323422016-01-06 10:54:44 -0500701 /* show the view which was selected previously */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400702 selection_changed(win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400703 }
704}
705
706static void
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400707show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400708{
709 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
710 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
711
712 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400713 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -0400714 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400715 priv->last_settings_view = priv->media_settings_view;
Stepan Salenikovich41118912015-05-01 11:25:46 -0400716 } else {
Stepan Salenikovich0bd53492015-05-11 14:28:52 -0400717 media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400718 }
719}
720
721static void
722show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
723{
724 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
725 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
726
727 if (gtk_toggle_button_get_active(navbutton)) {
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -0400728 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
729 priv->last_settings_view = priv->account_settings_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400730 }
731}
732
Stepan Salenikovichde896112015-05-11 16:46:33 -0400733static void
734show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
735{
736 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
737 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
738
739 if (gtk_toggle_button_get_active(navbutton)) {
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400740 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), TRUE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400741 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
742 priv->last_settings_view = priv->general_settings_view;
Nicolas Jager6f5e04a2016-05-18 15:04:59 -0400743 } else {
744 general_settings_view_show_profile(GENERAL_SETTINGS_VIEW(priv->general_settings_view), FALSE);
Stepan Salenikovichde896112015-05-11 16:46:33 -0400745 }
746}
747
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400748static void
aviau6aeb4852016-08-18 16:01:09 -0400749on_account_creation_completed(RingMainWindow *win)
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400750{
Stepan Salenikovichbabe26f2016-02-12 16:47:07 -0500751 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400752 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
753
aviau6aeb4852016-08-18 16:01:09 -0400754 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
755
756 /* destroy the wizard */
aviau69081842016-10-14 14:51:54 -0400757 if (priv->account_creation_wizard)
aviau6aeb4852016-08-18 16:01:09 -0400758 {
aviau69081842016-10-14 14:51:54 -0400759 gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), priv->account_creation_wizard);
760 gtk_widget_destroy(priv->account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400761 }
aviau6aeb4852016-08-18 16:01:09 -0400762
763 /* show the settings button*/
764 gtk_widget_show(priv->ring_settings);
Nicolas Jager15a8b902017-03-21 07:53:06 -0400765
766 /* show the account selector */
767 gtk_widget_show(priv->combobox_account_selector);
768
769 /* init the selection for the account selector */
770 gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_account_selector), 0);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400771}
772
773static void
aviau6aeb4852016-08-18 16:01:09 -0400774show_account_creation_wizard(RingMainWindow *win)
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400775{
776 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
777
aviau61d05b32016-10-18 16:47:53 -0400778 if (!priv->account_creation_wizard)
aviau6aeb4852016-08-18 16:01:09 -0400779 {
aviau69081842016-10-14 14:51:54 -0400780 priv->account_creation_wizard = account_creation_wizard_new(false);
781 g_object_add_weak_pointer(G_OBJECT(priv->account_creation_wizard), (gpointer *)&priv->account_creation_wizard);
782 g_signal_connect_swapped(priv->account_creation_wizard, "account-creation-completed", G_CALLBACK(on_account_creation_completed), win);
Stepan Salenikovich419414c2016-06-07 10:35:07 -0400783
aviau6aeb4852016-08-18 16:01:09 -0400784 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
aviau69081842016-10-14 14:51:54 -0400785 priv->account_creation_wizard,
aviau6aeb4852016-08-18 16:01:09 -0400786 ACCOUNT_CREATION_WIZARD_VIEW_NAME);
787 }
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400788
Stepan Salenikovich64505672015-09-24 10:46:07 -0400789 /* hide settings button until account creation is complete */
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400790 gtk_widget_hide(priv->ring_settings);
Nicolas Jager15a8b902017-03-21 07:53:06 -0400791 gtk_widget_hide(priv->combobox_account_selector);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400792
aviau69081842016-10-14 14:51:54 -0400793 gtk_widget_show(priv->account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400794
aviau69081842016-10-14 14:51:54 -0400795 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->account_creation_wizard);
Stepan Salenikovichb8e41272015-03-27 14:31:54 -0400796}
797
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400798static void
Stepan Salenikovicha06ce192016-11-01 14:26:43 -0400799search_entry_text_changed(GtkSearchEntry *search_entry, RingMainWindow *self)
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400800{
Stepan Salenikovichdcfb5032016-10-26 18:57:56 -0400801 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
802
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400803 /* get the text from the entry */
804 const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
805
Stepan Salenikovichf53128f2016-10-07 10:32:16 -0400806 RecentModel::instance().peopleProxy()->setFilterRegExp(QRegExp(text, Qt::CaseInsensitive, QRegExp::FixedString));
Stepan Salenikovichdcfb5032016-10-26 18:57:56 -0400807 contacts_view_set_filter_string(CONTACTS_VIEW(priv->treeview_contacts), text);
808 history_view_set_filter_string(HISTORY_VIEW(priv->treeview_history), text);
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -0400809}
810
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400811static gboolean
Stepan Salenikovichcac35042016-10-28 12:07:38 -0400812search_entry_key_released(GtkEntry *search_entry, GdkEventKey *key, RingMainWindow *self)
813{
814 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
815
816 // if esc key pressed, clear the regex (keep the text, the user might not want to actually delete it)
817 if (key->keyval == GDK_KEY_Escape) {
818 RecentModel::instance().peopleProxy()->setFilterRegExp(QRegExp());
819 contacts_view_set_filter_string(CONTACTS_VIEW(priv->treeview_contacts), "");
820 history_view_set_filter_string(HISTORY_VIEW(priv->treeview_history), "");
821 return GDK_EVENT_STOP;
822 }
823
824 return GDK_EVENT_PROPAGATE;
825}
826
827static gboolean
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400828dtmf_pressed(RingMainWindow *win,
829 GdkEventKey *event,
830 G_GNUC_UNUSED gpointer user_data)
831{
832 g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
833
834 /* we want to react to digit key presses, as long as a GtkEntry is not the
835 * input focus
836 */
837 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
838 if (GTK_IS_ENTRY(focus))
839 return GDK_EVENT_PROPAGATE;
840
841 /* make sure that a call is selected*/
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400842 QItemSelectionModel *selection = CallModel::instance().selectionModel();
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400843 QModelIndex idx = selection->currentIndex();
844 if (!idx.isValid())
845 return GDK_EVENT_PROPAGATE;
846
847 /* make sure that the selected call is in progress */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400848 Call *call = CallModel::instance().getCall(idx);
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400849 Call::LifeCycleState state = call->lifeCycleState();
850 if (state != Call::LifeCycleState::PROGRESS)
851 return GDK_EVENT_PROPAGATE;
852
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400853 /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
854 * does not result in a 'c' being played.
855 * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
856 if ( event->state
857 & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
858 return GDK_EVENT_PROPAGATE;
859
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400860 /* pass the character that was entered to be played by the daemon;
861 * the daemon will filter out invalid DTMF characters */
862 guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
863 QString val = QString::fromUcs4(&unicode_val, 1);
864 call->playDTMF(val);
865 g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
Stepan Salenikovich447f4a72015-04-29 11:09:28 -0400866
867 /* always propogate the key, so we don't steal accelerators/shortcuts */
868 return GDK_EVENT_PROPAGATE;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400869}
870
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400871/**
872 * This function determines if two different contact list views (Conversations, Contacts, History)
873 * have the same item selected. Note that the same item does not necessarily mean the same object.
874 * For example, if the history view has a Call selected and the Conversations view has a Person
875 * selected which is associated with the ContactMethod to which that Call was made, then this will
876 * evaluate to TRUE.
877 */
878static gboolean
879compare_treeview_selection(GtkTreeSelection *selection1, GtkTreeSelection *selection2)
880{
881 g_return_val_if_fail(selection1 && selection2, FALSE);
882
883 if (selection1 == selection2) return TRUE;
884
885 auto idx1 = get_index_from_selection(selection1);
886 auto type1 = idx1.data(static_cast<int>(Ring::Role::ObjectType));
887 auto object1 = idx1.data(static_cast<int>(Ring::Role::Object));
888
889 auto idx2 = get_index_from_selection(selection2);
890 auto type2 = idx2.data(static_cast<int>(Ring::Role::ObjectType));
891 auto object2 = idx2.data(static_cast<int>(Ring::Role::Object));
892
893 if (idx1.isValid() && type1.isValid() && object1.isValid() && idx2.isValid() && type2.isValid() && object2.isValid()) {
894 Call *call1 = nullptr;
895 ContactMethod *cm1 = nullptr;
896 Person *person1 = nullptr;
897
898 Call *call2 = nullptr;
899 ContactMethod *cm2 = nullptr;
900 Person *person2 = nullptr;
901
902 switch(type1.value<Ring::ObjectType>()) {
903 case Ring::ObjectType::Person:
904 person1 = object1.value<Person *>();
905 break;
906 case Ring::ObjectType::ContactMethod:
907 cm1 = object1.value<ContactMethod *>();
908 person1 = cm1->contact();
909 break;
910 case Ring::ObjectType::Call:
911 call1 = object1.value<Call *>();
912 cm1 = call1->peerContactMethod();
913 person1 = cm1->contact();
914 break;
915 case Ring::ObjectType::Media:
916 case Ring::ObjectType::Certificate:
Nicolas Jagerc74f7612017-03-28 09:48:18 -0400917 case Ring::ObjectType::ContactRequest:
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400918 case Ring::ObjectType::COUNT__:
919 // nothing to do
920 break;
921 }
922
923 switch(type2.value<Ring::ObjectType>()) {
924 case Ring::ObjectType::Person:
925 person2 = object2.value<Person *>();
926 break;
927 case Ring::ObjectType::ContactMethod:
928 cm2 = object2.value<ContactMethod *>();
929 person2 = cm2->contact();
930 break;
931 case Ring::ObjectType::Call:
932 call2 = object2.value<Call *>();
933 cm2 = call2->peerContactMethod();
934 person2 = cm2->contact();
935 break;
936 case Ring::ObjectType::Media:
937 case Ring::ObjectType::Certificate:
Nicolas Jagerc74f7612017-03-28 09:48:18 -0400938 case Ring::ObjectType::ContactRequest:
Stepan Salenikovich09e0b782016-09-07 16:28:50 -0400939 case Ring::ObjectType::COUNT__:
940 // nothing to do
941 break;
942 }
943
944 if (person1 != nullptr && person1 == person2)
945 return TRUE;
946 if (cm1 != nullptr && cm1 == cm2)
947 return TRUE;
948 if (call1 != nullptr && call1 == call2)
949 return TRUE;
950 }
951
952 return FALSE;
953}
954
955static void
956conversation_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
957{
958 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
959 GtkTreeIter iter;
960 GtkTreeModel *model = nullptr;
961 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
962 /* something is selected in the conversations view, clear the selection in the other views;
963 * unless the current selection is of the same item; we don't try to match the selection in
964 * all views since not all items exist in all 3 contact list views
965 */
966
967 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
968 if (!compare_treeview_selection(selection, selection_contacts))
969 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
970
971 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
972 if (!compare_treeview_selection(selection, selection_history))
973 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
974 }
975
976 selection_changed(self);
977}
978
979static void
980contact_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
981{
982 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
983 GtkTreeIter iter;
984 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
985 /* something is selected in the contacts view, clear the selection in the other views;
986 * unless the current selection is of the same item; we don't try to match the selection in
987 * all views since not all items exist in all 3 contact list views
988 */
989 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
990 if (!compare_treeview_selection(selection, selection_conversations))
991 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
992
993 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
994 if (!compare_treeview_selection(selection, selection_history))
995 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
996 }
997
998 selection_changed(self);
999}
1000
1001static void
1002history_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
1003{
1004 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1005 GtkTreeIter iter;
1006 if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
1007 /* something is selected in the history view, clear the selection in the other views;
1008 * unless the current selection is of the same item; we don't try to match the selection in
1009 * all views since not all items exist in all 3 contact list views
1010 */
1011
1012 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1013 if (!compare_treeview_selection(selection, selection_conversations))
1014 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
1015
1016 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1017 if (!compare_treeview_selection(selection, selection_contacts))
1018 gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
1019 }
1020
1021 selection_changed(self);
1022}
1023
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001024static gboolean
1025window_size_changed(GtkWidget *win, GdkEventConfigure *event, gpointer)
1026{
1027 auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1028
1029 g_settings_set_int(priv->settings, "window-width", event->width);
1030 g_settings_set_int(priv->settings, "window-height", event->height);
1031
1032 return GDK_EVENT_PROPAGATE;
1033}
1034
Stepan Salenikovicheafc3dd2015-04-16 18:38:01 -04001035static void
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001036search_entry_places_call_changed(GSettings *settings, const gchar *key, RingMainWindow *self)
1037{
1038 auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1039
1040 if (g_settings_get_boolean(settings, key)) {
1041 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will place a new call", "place call"));
1042 } else {
1043 gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will open chat", "open chat"));
1044 }
1045}
1046
1047static void
aviau6aeb4852016-08-18 16:01:09 -04001048handle_account_migrations(RingMainWindow *win)
1049{
1050 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1051
1052 /* If there is an existing migration view, remove it */
aviau69081842016-10-14 14:51:54 -04001053 if (priv->account_migration_view)
aviau6aeb4852016-08-18 16:01:09 -04001054 {
Stepan Salenikovich56530b12016-10-20 17:51:34 -04001055 gtk_widget_destroy(priv->account_migration_view);
1056 priv->account_migration_view = nullptr;
aviau6aeb4852016-08-18 16:01:09 -04001057 }
1058
1059 QList<Account*> accounts = AccountModel::instance().accountsToMigrate();
1060 if (!accounts.isEmpty())
1061 {
1062 Account* account = accounts.first();
aviau6aeb4852016-08-18 16:01:09 -04001063
aviau69081842016-10-14 14:51:54 -04001064 priv->account_migration_view = account_migration_view_new(account);
aviau69081842016-10-14 14:51:54 -04001065 g_signal_connect_swapped(priv->account_migration_view, "account-migration-completed", G_CALLBACK(handle_account_migrations), win);
1066 g_signal_connect_swapped(priv->account_migration_view, "account-migration-failed", G_CALLBACK(handle_account_migrations), win);
aviau6aeb4852016-08-18 16:01:09 -04001067
1068 gtk_widget_hide(priv->ring_settings);
Nicolas Jager15a8b902017-03-21 07:53:06 -04001069 gtk_widget_hide(priv->combobox_account_selector);
aviau69081842016-10-14 14:51:54 -04001070 gtk_widget_show(priv->account_migration_view);
aviau6aeb4852016-08-18 16:01:09 -04001071 gtk_stack_add_named(
1072 GTK_STACK(priv->stack_main_view),
aviau69081842016-10-14 14:51:54 -04001073 priv->account_migration_view,
aviau6aeb4852016-08-18 16:01:09 -04001074 ACCOUNT_MIGRATION_VIEW_NAME
1075 );
1076 gtk_stack_set_visible_child_name(
1077 GTK_STACK(priv->stack_main_view),
1078 ACCOUNT_MIGRATION_VIEW_NAME
1079 );
1080 }
1081 else
1082 {
1083 gtk_widget_show(priv->ring_settings);
Nicolas Jager15a8b902017-03-21 07:53:06 -04001084 gtk_widget_show(priv->combobox_account_selector);
1085
1086 /* init the selection for the account selector */
1087 gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_account_selector), 0);
aviau6aeb4852016-08-18 16:01:09 -04001088 }
1089}
1090
1091static void
Nicolas Jager15a8b902017-03-21 07:53:06 -04001092selected_account_changed(GtkComboBox *gtk_combo_box, RingMainWindow *self)
1093{
1094 int nbr = gtk_combo_box_get_active(gtk_combo_box);
1095
1096 QModelIndex idx = AccountModel::instance().userSelectionModel()->model()->index(nbr, 0);
1097 auto account = AccountModel::instance().getAccountByModelIndex(idx);
1098
1099 AccountModel::instance().setUserChosenAccount(account);
1100
1101 // we closing any view opened to avoid confusion (especially between SIP and Ring protocols).
1102 hide_view_clicked(nullptr, self);
1103}
1104
1105static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001106ring_main_window_init(RingMainWindow *win)
1107{
1108 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
1109 gtk_widget_init_template(GTK_WIDGET(win));
1110
Guillaume Rogueze7221ed2017-03-17 20:09:56 -04001111 priv->pending_username_lookup = new std::string; // see object finilize for delete
1112
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001113 /* bind to window size settings */
1114 priv->settings = g_settings_new_full(get_ring_schema(), nullptr, nullptr);
1115 auto width = g_settings_get_int(priv->settings, "window-width");
1116 auto height = g_settings_get_int(priv->settings, "window-height");
1117 gtk_window_set_default_size(GTK_WINDOW(win), width, height);
1118 g_signal_connect(win, "configure-event", G_CALLBACK(window_size_changed), nullptr);
1119
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001120 /* search-entry-places-call setting */
1121 search_entry_places_call_changed(priv->settings, "search-entry-places-call", win);
1122 g_signal_connect(priv->settings, "changed::search-entry-places-call", G_CALLBACK(search_entry_places_call_changed), win);
1123
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001124 /* set window icon */
1125 GError *error = NULL;
1126 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
1127 if (icon == NULL) {
1128 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001129 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001130 } else
1131 gtk_window_set_icon(GTK_WINDOW(win), icon);
1132
1133 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001134 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001135 -1, 16, TRUE, &error);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001136 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001137 g_debug("Could not load icon: %s", error->message);
Stepan Salenikovich8a287fc2015-05-01 16:53:20 -04001138 g_clear_error(&error);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001139 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001140 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001141
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001142 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001143 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
1144 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001145 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001146 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -05001147
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001148 /* settings icon */
Stepan Salenikovichd61974b2016-03-18 16:36:27 -04001149 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 -04001150
1151 /* connect settings button signal */
1152 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
1153
1154 /* add the call view to the main stack */
1155 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
1156 priv->vbox_call_view,
1157 CALL_VIEW_NAME);
1158
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001159 /* init the settings views */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001160 priv->account_settings_view = account_view_new();
1161 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
1162
Stepan Salenikovich0bd53492015-05-11 14:28:52 -04001163 priv->media_settings_view = media_settings_view_new();
1164 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 -04001165
Stepan Salenikovichde896112015-05-11 16:46:33 -04001166 priv->general_settings_view = general_settings_view_new();
1167 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);
1168
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001169 /* make the setting we will show first the active one */
Stepan Salenikovichde896112015-05-11 16:46:33 -04001170 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
1171 priv->last_settings_view = priv->general_settings_view;
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001172
1173 /* connect the settings button signals to switch settings views */
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001174 g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001175 g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
Stepan Salenikovichde896112015-05-11 16:46:33 -04001176 g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
Stepan Salenikovichf903d1b2015-03-25 14:51:45 -04001177
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001178 /* populate the notebook */
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001179 priv->treeview_conversations = recent_contacts_view_new();
1180 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), priv->treeview_conversations);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -04001181
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001182 priv->treeview_contacts = contacts_view_new();
1183 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), priv->treeview_contacts);
Stepan Salenikovich15142182015-03-11 17:15:26 -04001184
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001185 priv->treeview_history = history_view_new();
1186 gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), priv->treeview_history);
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001187
Nicolas Jager15a8b902017-03-21 07:53:06 -04001188 /* use this event to refresh the treeview_conversations when account selection changed */
1189 QObject::connect(AccountModel::instance().userSelectionModel(), &QItemSelectionModel::currentChanged, [priv](const QModelIndex& idx){
1190 // next line will refresh the recentmodel so the treeview will do
1191 RecentModel::instance().peopleProxy()->setFilterRegExp("");
1192
1193 // set the good index to the combox. Required when the selected account changed by different way than selecting
1194 // from the combo box.
1195 gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combobox_account_selector), idx.row());
1196
1197 });
1198
Stepan Salenikovich2cde7612015-09-25 10:44:01 -04001199 /* welcome/default view */
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001200 priv->welcome_view = ring_welcome_view_new();
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001201 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 -04001202 gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
1203 gtk_widget_show(priv->welcome_view);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001204
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001205 auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
1206 g_signal_connect(selection_conversations, "changed", G_CALLBACK(conversation_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001207
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001208 auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
1209 g_signal_connect(selection_contacts, "changed", G_CALLBACK(contact_selection_changed), win);
1210
1211 auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
1212 g_signal_connect(selection_history, "changed", G_CALLBACK(history_selection_changed), win);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001213
Stepan Salenikovichb0ffc342016-10-13 13:10:55 -04001214 g_signal_connect_swapped(priv->button_new_conversation, "clicked", G_CALLBACK(search_entry_activated), win);
1215 g_signal_connect_swapped(priv->search_entry, "activate", G_CALLBACK(search_entry_activated), win);
Stepan Salenikovicha06ce192016-11-01 14:26:43 -04001216 g_signal_connect(priv->search_entry, "search-changed", G_CALLBACK(search_entry_text_changed), win);
Stepan Salenikovichcac35042016-10-28 12:07:38 -04001217 g_signal_connect(priv->search_entry, "key-release-event", G_CALLBACK(search_entry_key_released), win);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001218
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001219 /* make sure the incoming call is the selected call in the CallModel */
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001220 QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -04001221 &CallModel::instance(),
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001222 &CallModel::incomingCall,
Stepan Salenikovichdcfb5032016-10-26 18:57:56 -04001223 [priv](Call* call) {
Nicolas Jager85e992a2017-03-22 04:11:47 -04001224 // select the revelant account
1225 AccountModel::instance().setUserChosenAccount(call->account());
1226
Stepan Salenikovichf53128f2016-10-07 10:32:16 -04001227 // clear the regex to make sure the call is shown
1228 RecentModel::instance().peopleProxy()->setFilterRegExp(QRegExp());
Stepan Salenikovichdcfb5032016-10-26 18:57:56 -04001229 contacts_view_set_filter_string(CONTACTS_VIEW(priv->treeview_contacts),"");
1230 history_view_set_filter_string(HISTORY_VIEW(priv->treeview_history), "");
Stepan Salenikovichf53128f2016-10-07 10:32:16 -04001231
1232 // now make sure its selected
1233 RecentModel::instance().selectionModel()->clearCurrentIndex();
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001234 CallModel::instance().selectCall(call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001235 }
1236 );
Stepan Salenikovich5eab3032015-03-28 10:50:51 -04001237
Stepan Salenikovichb01d7362015-04-27 23:02:00 -04001238 /* react to digit key press events */
1239 g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
Stepan Salenikovich48b45c12015-09-25 15:49:51 -04001240
1241 /* set the search entry placeholder text */
1242 gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
1243 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 -04001244
aviau039001d2016-09-29 16:39:05 -04001245 /* init chat webkit container so that it starts loading before the first time we need it*/
1246 get_webkit_chat_container(win);
1247
Stepan Salenikovichcbc8dcc2017-04-05 11:27:10 -04001248 if (has_ring_account()) {
1249 /* user has ring account, so show the call view right away */
1250 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
1251
1252 handle_account_migrations(win);
1253 } else {
1254 /* user has to create the ring account */
1255 show_account_creation_wizard(win);
1256 }
Nicolas Jager15a8b902017-03-21 07:53:06 -04001257
1258 /* model for the combobox for Account chooser */
1259 auto account_model = gtk_q_tree_model_new(AccountModel::instance().userSelectionModel()->model(), 1,
1260 0, Account::Role::Alias, G_TYPE_STRING);
1261
1262 gtk_combo_box_set_model(GTK_COMBO_BOX(priv->combobox_account_selector), GTK_TREE_MODEL(account_model));
1263
1264 auto *renderer = gtk_cell_renderer_text_new();
1265
1266 /* layout */
1267 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combobox_account_selector), renderer, FALSE);
1268 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(priv->combobox_account_selector), renderer, "text", 0, NULL);
1269
1270
1271 g_signal_connect(priv->combobox_account_selector, "changed", G_CALLBACK(selected_account_changed), win);
1272
1273 /* init the selection for the account selector */
1274 selected_account_changed(GTK_COMBO_BOX(priv->combobox_account_selector), win);
1275
1276 g_object_unref(account_model);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001277}
1278
1279static void
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001280ring_main_window_dispose(GObject *object)
1281{
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001282 RingMainWindow *self = RING_MAIN_WINDOW(object);
1283 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1284
Stepan Salenikovich09e0b782016-09-07 16:28:50 -04001285 QObject::disconnect(priv->selected_item_changed);
1286 QObject::disconnect(priv->selected_call_over);
aviau8c0b2422016-10-31 16:06:44 -04001287 QObject::disconnect(priv->username_lookup);
aviau039001d2016-09-29 16:39:05 -04001288
1289 g_clear_object(&priv->welcome_view);
1290 g_clear_object(&priv->webkit_chat_container);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001291
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001292 G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
1293}
1294
1295static void
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001296ring_main_window_finalize(GObject *object)
1297{
Stepan Salenikovichba87ae22016-09-13 14:27:20 -04001298 RingMainWindow *self = RING_MAIN_WINDOW(object);
1299 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
1300
1301 g_clear_object(&priv->settings);
1302
Guillaume Rogueze7221ed2017-03-17 20:09:56 -04001303 delete priv->pending_username_lookup;
1304 priv->pending_username_lookup = nullptr;
1305
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001306 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001307}
1308
1309static void
1310ring_main_window_class_init(RingMainWindowClass *klass)
1311{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001312 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
Stepan Salenikovich6f687072015-03-26 10:43:37 -04001313 G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -05001314
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001315 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
1316 "/cx/ring/RingGnome/ringmainwindow.ui");
1317
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -04001318 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -04001319 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
Stepan Salenikovicha7a19bc2015-09-24 16:16:28 -04001320 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
1321 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001322 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
1323 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
1324 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
1325 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001326 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -05001327 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -05001328 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001329 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
Stepan Salenikovich3034d922015-10-09 10:11:42 -04001330 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikoviche9933242016-06-21 18:08:48 -04001331 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_new_conversation );
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001332 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
Stepan Salenikovich5d3506e2015-03-30 11:01:29 -04001333 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -04001334 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Nicolas Jager55903142017-02-01 14:43:45 -05001335 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_lookup);
Nicolas Jager15a8b902017-03-21 07:53:06 -04001336 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, combobox_account_selector);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001337}
1338
1339GtkWidget *
1340ring_main_window_new (GtkApplication *app)
1341{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001342 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
1343
Stepan Salenikovich61cbab02015-03-16 18:35:10 -04001344 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001345}