blob: 699ff65844c04ff9be64a8fdd7f5499cfa84d232 [file] [log] [blame]
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001/*
2 * Copyright (C) 2015 Savoir-Faire Linux Inc.
3 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "ringmainwindow.h"
32
33#include <gtk/gtk.h>
Stepan Salenikovicha3557452015-02-20 14:14:12 -050034#include "models/gtkqtreemodel.h"
35#include <callmodel.h>
Stepan Salenikovich69771842015-02-24 18:11:45 -050036#include <call.h>
Stepan Salenikovichd2dbcee2015-02-27 16:52:28 -050037#include <QtCore/QItemSelectionModel>
Stepan Salenikovich69771842015-02-24 18:11:45 -050038#include "incomingcallview.h"
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050039#include "currentcallview.h"
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050040#include <string.h>
Stepan Salenikovichdd84cf92015-03-19 21:38:19 -040041#include <categorizedhistorymodel.h>
Stepan Salenikovich15142182015-03-11 17:15:26 -040042#include <contactmethod.h>
Stepan Salenikovich7ff47962015-03-16 15:10:14 -040043#include <QtCore/QSortFilterProxyModel>
44#include "models/gtkqsortfiltertreemodel.h"
Stepan Salenikovich61cbab02015-03-16 18:35:10 -040045#include "accountview.h"
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040046#include <accountmodel.h>
47#include <audio/codecmodel.h>
Stepan Salenikovichbd029582015-03-24 11:00:56 -040048#include "dialogs.h"
Stepan Salenikovich69771842015-02-24 18:11:45 -050049
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040050#define CALL_VIEW_NAME "calls"
51#define GENERAL_SETTINGS_VIEW_NAME "general"
52#define AUDIO_SETTINGS_VIEW_NAME "audio"
53#define VIDEO_SETTINGS_VIEW_NAME "video"
54#define ACCOUNT_SETTINGS_VIEW_NAME "accounts"
Stepan Salenikovich69771842015-02-24 18:11:45 -050055#define DEFAULT_VIEW_NAME "placeholder"
Stepan Salenikovich15142182015-03-11 17:15:26 -040056#define VIEW_CONTACTS "contacts"
57#define VIEW_HISTORY "history"
58#define VIEW_PRESENCE "presence"
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050059
60struct _RingMainWindow
61{
62 GtkApplicationWindow parent;
63};
64
65struct _RingMainWindowClass
66{
67 GtkApplicationWindowClass parent_class;
68};
69
70typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
71
72struct _RingMainWindowPrivate
73{
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040074 GtkWidget *ring_menu;
75 GtkWidget *image_ring;
76 GtkWidget *ring_settings;
77 GtkWidget *image_settings;
78 GtkWidget *hbox_search;
79 GtkWidget *hbox_settings;
Stepan Salenikovich15142182015-03-11 17:15:26 -040080 GtkWidget *stack_contacts_history_presence;
81 GtkWidget *radiobutton_contacts;
82 GtkWidget *radiobutton_history;
83 GtkWidget *radiobutton_presence;
Stepan Salenikovicha3557452015-02-20 14:14:12 -050084 GtkWidget *treeview_call;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -050085 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -050086 GtkWidget *stack_main_view;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040087 GtkWidget *vbox_call_view;
88 GtkWidget *stack_call_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050089 GtkWidget *button_placecall;
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -040090 GtkWidget *account_settings_view;
91 GtkWidget *radiobutton_audio_settings;
92 GtkWidget *radiobutton_general_settings;
93 GtkWidget *radiobutton_video_settings;
94 GtkWidget *radiobutton_account_settings;
95
96 gboolean show_settings;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050097};
98
99G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
100
101#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
102
Stepan Salenikovich69771842015-02-24 18:11:45 -0500103static QModelIndex
104get_index_from_selection(GtkTreeSelection *selection)
105{
106 GtkTreeIter iter;
107 GtkTreeModel *model = NULL;
108
109 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
110 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
111 } else {
112 return QModelIndex();
113 }
114}
115
116static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500117update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich69771842015-02-24 18:11:45 -0500118{
Stepan Salenikovich69771842015-02-24 18:11:45 -0500119 QModelIndex current = get_index_from_selection(selection);
120 if (current.isValid())
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500121 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500122 else
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500123 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500124}
125
126static void
127call_selection_changed(GtkTreeSelection *selection, gpointer win)
128{
129 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
130
131 /* get the current visible stack child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400132 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovich69771842015-02-24 18:11:45 -0500133
134 QModelIndex idx = get_index_from_selection(selection);
135 if (idx.isValid()) {
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400136 QVariant state = CallModel::instance()->data(idx, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500137 GtkWidget *new_call_view = NULL;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500138 char* new_call_view_name = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500139
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500140 /* FIXME: change when fixed
141 * switch(state.value<Call::LifeCycleState>()) { */
142 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
143 switch(lifecyclestate) {
144 case Call::LifeCycleState::INITIALIZATION:
145 case Call::LifeCycleState::FINISHED:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500146 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500147 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500148 /* use the pointer of the call as a unique name */
149 new_call_view_name = g_strdup_printf("%p_incoming", (void *)CallModel::instance()->getCall(idx));
150 break;
151 case Call::LifeCycleState::PROGRESS:
152 new_call_view = current_call_view_new();
153 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx);
154 /* use the pointer of the call as a unique name */
155 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx));
156 break;
157 case Call::LifeCycleState::COUNT__:
158 g_warning("LifeCycleState should never be COUNT");
159 break;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500160 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500161
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400162 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
163 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500164 g_free(new_call_view_name);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500165
Stepan Salenikovich69771842015-02-24 18:11:45 -0500166 } else {
167 /* nothing selected in the call model, so show the default screen */
168
169 /* TODO: replace stack paceholder view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400170 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
171 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_call_view), DEFAULT_VIEW_NAME);
172 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500173
174 }
175
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500176 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400177 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500178 if (current_call_view != old_call_view && old_call_view != NULL) {
179 /* if the previous child was a call view, then remove it from
Stepan Salenikovich69771842015-02-24 18:11:45 -0500180 * the stack; removing it should destory it since there should not
181 * be any other references to it */
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500182 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400183 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500184 }
185 }
186}
187
188static void
189call_state_changed(Call *call, gpointer win)
190{
191 g_debug("call state changed");
192 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
193
194 /* check if the call that changed state is the same as the selected call */
195 QModelIndex idx_selected = CallModel::instance()->selectionModel()->currentIndex();
196
197 if( idx_selected.isValid() && call == CallModel::instance()->getCall(idx_selected)) {
198 g_debug("selected call state changed");
199 /* check if we need to change the view */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400200 GtkWidget *old_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500201 GtkWidget *new_call_view = NULL;
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400202 QVariant state = CallModel::instance()->data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500203
204 /* check what the current state is vs what is displayed */
205
206 /* FIXME: change when fixed
207 * switch(state.value<Call::LifeCycleState>()) { */
208 Call::LifeCycleState lifecyclestate = (Call::LifeCycleState)state.toUInt();
209 switch(lifecyclestate) {
210 case Call::LifeCycleState::INITIALIZATION:
211 /* LifeCycleState cannot go backwards, so it should not be possible
212 * that the call is displayed as current (meaning that its in progress)
213 * but have the state 'initialization' */
214 if (IS_CURRENT_CALL_VIEW(old_call_view))
215 g_warning("call displayed as current, but is in state of initialization");
216 break;
217 case Call::LifeCycleState::PROGRESS:
218 if (IS_INCOMING_CALL_VIEW(old_call_view)) {
219 /* change from incoming to current */
220 new_call_view = current_call_view_new();
221 current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), idx_selected);
222 /* use the pointer of the call as a unique name */
223 char* new_call_view_name = NULL;
224 new_call_view_name = g_strdup_printf("%p_current", (void *)CallModel::instance()->getCall(idx_selected));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400225 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), new_call_view, new_call_view_name);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500226 g_free(new_call_view_name);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400227 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
228 gtk_stack_set_visible_child(GTK_STACK(priv->stack_call_view), new_call_view);
229 gtk_stack_set_transition_type(GTK_STACK(priv->stack_call_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500230 }
231 break;
232 case Call::LifeCycleState::FINISHED:
233 /* do nothing, either call view is valid for this state */
234 break;
235 case Call::LifeCycleState::COUNT__:
236 g_warning("LifeCycleState should never be COUNT");
237 break;
238 }
239
240 /* check if we changed the visible child */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400241 GtkWidget *current_call_view = gtk_stack_get_visible_child(GTK_STACK(priv->stack_call_view));
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500242 if (current_call_view != old_call_view && old_call_view != NULL) {
243 /* if the previous child was a call view, then remove it from
244 * the stack; removing it should destory it since there should not
245 * be any other references to it */
246 if (IS_INCOMING_CALL_VIEW(old_call_view) || IS_CURRENT_CALL_VIEW(old_call_view)) {
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400247 gtk_container_remove(GTK_CONTAINER(priv->stack_call_view), old_call_view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500248 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500249 }
250 }
251}
252
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500253static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500254search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
255{
256 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
257
258 const gchar *number = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
259
260 if (number && strlen(number) > 0) {
261 g_debug("dialing to number: %s", number);
262 Call *call = CallModel::instance()->dialingCall();
263 call->setDialNumber(number);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500264 call->performAction(Call::Action::ACCEPT);
265
266 /* make this the currently selected call */
267 QModelIndex idx = CallModel::instance()->getIndex(call);
268 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
269 }
270}
271
272static void
Stepan Salenikovich15142182015-03-11 17:15:26 -0400273call_history_item(GtkTreeView *tree_view,
274 GtkTreePath *path,
275 G_GNUC_UNUSED GtkTreeViewColumn *column,
276 G_GNUC_UNUSED gpointer user_data)
277{
278 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
279
280 /* get iter */
281 GtkTreeIter iter;
282 if (gtk_tree_model_get_iter(model, &iter, path)) {
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400283 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400284
285 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
286 /* create new call */
287 if (contact_method.value<ContactMethod*>()) {
288 Call *call = CallModel::instance()->dialingCall();
289 call->setDialNumber(contact_method.value<ContactMethod*>());
290 call->performAction(Call::Action::ACCEPT);
291
292 /* make this the currently selected call */
293 QModelIndex call_idx = CallModel::instance()->getIndex(call);
294 CallModel::instance()->selectionModel()->setCurrentIndex(call_idx, QItemSelectionModel::ClearAndSelect);
295 } else
296 g_warning("contact method is empty");
297 }
298}
299
300static void
301navbutton_contacts_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
302{
303 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
304 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
305
306 if (gtk_toggle_button_get_active(navbutton)) {
307
308 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
309
310 if (visible) {
311 /* contacts is left of both history and presence, so always slide right to show it */
312 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
313 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
314 } else {
315 g_warning("should always have a visible child in the nav stack");
316 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
317 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_CONTACTS);
318 }
319 }
320}
321
322static void
323navbutton_presence_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
324{
325 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
326 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
327
328 if (gtk_toggle_button_get_active(navbutton)) {
329
330 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
331 if (visible) {
332 /* presence is right of both history and contacts, so always slide left to show it */
333 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
334 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
335 } else {
336 g_warning("should always have a visible child in the nav stack");
337 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
338 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_PRESENCE);
339 }
340 }
341}
342
343static void
344navbutton_history_toggled(GtkToggleButton *navbutton, RingMainWindow *win)
345{
346 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
347 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
348
349 if (gtk_toggle_button_get_active(navbutton)) {
350
351 const gchar *visible = gtk_stack_get_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence));
352 if (visible) {
353 if (strcmp(visible, VIEW_CONTACTS) == 0) {
354 /* history is right of contacts, so slide left to show it */
355 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
356 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
357 } else if (strcmp(visible, VIEW_PRESENCE) == 0) {
358 /* history is left of presence, so slide right to show it */
359 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
360 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
361 }
362 } else {
363 g_warning("should always have a visible child in the nav stack");
364 gtk_stack_set_transition_type(GTK_STACK(priv->stack_contacts_history_presence), GTK_STACK_TRANSITION_TYPE_NONE);
365 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_contacts_history_presence), VIEW_HISTORY);
366 }
367 }
368}
369
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400370static gboolean
371save_accounts(GtkWidget *working_dialog)
372{
373 /* save changes to accounts */
374 AccountModel::instance()->save();
375 /* save changes to codecs */
376 for (int i = 0; i < AccountModel::instance()->rowCount(); i++) {
377 QModelIndex idx = AccountModel::instance()->index(i, 0);
378 AccountModel::instance()->getAccountByModelIndex(idx)->codecModel()->save();
379 }
380
381 if (working_dialog)
382 gtk_widget_destroy(working_dialog);
383
384 return G_SOURCE_REMOVE;
385}
386
Stepan Salenikovich15142182015-03-11 17:15:26 -0400387static void
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400388settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
389{
390 g_return_if_fail(IS_RING_MAIN_WINDOW(win));
391 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
392
393 /* toggle show settings */
394 priv->show_settings = !priv->show_settings;
395
396 /* check which view to show */
397 if (priv->show_settings) {
398 /* show settings */
399 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
400
401 gtk_widget_hide(priv->hbox_search);
402 gtk_widget_show(priv->hbox_settings);
403
404 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
405 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
406 } else {
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400407 /* show working dialog in case save operation takes time */
408 GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
409 gtk_window_present(GTK_WINDOW(working));
410
411 /* now save after the time it takes to transition back to the call view (400ms)
412 * the save doesn't happen before the "working" dialog is presented
413 * the timeout function should destroy the "working" dialog when done saving
414 */
Stepan Salenikovich12fee942015-03-25 18:38:47 -0400415 g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
Stepan Salenikovichbd029582015-03-24 11:00:56 -0400416
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400417 /* show calls */
418 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
419
420 gtk_widget_show(priv->hbox_search);
421 gtk_widget_hide(priv->hbox_settings);
422
423 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
424 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400425 }
426}
427
428static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500429ring_main_window_init(RingMainWindow *win)
430{
431 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
432 gtk_widget_init_template(GTK_WIDGET(win));
433
434 /* set window icon */
435 GError *error = NULL;
436 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
437 if (icon == NULL) {
438 g_debug("Could not load icon: %s", error->message);
439 g_error_free(error);
440 } else
441 gtk_window_set_icon(GTK_WINDOW(win), icon);
442
443 /* set menu icon */
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400444 GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
445 -1, 24, TRUE, &error);
446 if (image_ring == NULL) {
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500447 g_debug("Could not load icon: %s", error->message);
448 g_error_free(error);
449 } else
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400450 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500451
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400452 /* ring menu */
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500453 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
454 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400455 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500456 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500457
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400458 /* settings icon */
459 gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);
460
461 /* connect settings button signal */
462 g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);
463
464 /* add the call view to the main stack */
465 gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
466 priv->vbox_call_view,
467 CALL_VIEW_NAME);
468
469 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
470
471 /* init the settings views */
472 /* make the setting we will show first the active one */
473 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_account_settings), TRUE);
474
475 priv->account_settings_view = account_view_new();
476 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);
477
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500478 /* call model */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500479 GtkQTreeModel *call_model;
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500480 GtkCellRenderer *renderer;
481 GtkTreeViewColumn *column;
482
Stepan Salenikovich69771842015-02-24 18:11:45 -0500483 call_model = gtk_q_tree_model_new(CallModel::instance(), 4,
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500484 Call::Role::Name, G_TYPE_STRING,
485 Call::Role::Number, G_TYPE_STRING,
486 Call::Role::Length, G_TYPE_STRING,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400487 Call::Role::State, G_TYPE_STRING);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500488 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_call), GTK_TREE_MODEL(call_model));
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500489
490 renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400491 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500492 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400493 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500494 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
495
496 renderer = gtk_cell_renderer_text_new();
497 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400498 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500499 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
500
Stepan Salenikovich69771842015-02-24 18:11:45 -0500501 /* connect signals to and from UserActionModel to sync selection betwee
502 * the QModel and the GtkTreeView */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500503 QObject::connect(
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500504 CallModel::instance()->selectionModel(),
Stepan Salenikovich69771842015-02-24 18:11:45 -0500505 &QItemSelectionModel::currentChanged,
506 [=](const QModelIndex & current, const QModelIndex & previous) {
507 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
508
509 /* first unselect the previous */
510 if (previous.isValid()) {
511 GtkTreeIter old_iter;
512 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
513 gtk_tree_selection_unselect_iter(selection, &old_iter);
514 } else {
515 g_warning("Trying to unselect invalid GtkTreeIter");
516 }
517 }
518
519 /* select the current */
520 if (current.isValid()) {
521 GtkTreeIter new_iter;
522 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
523 gtk_tree_selection_select_iter(selection, &new_iter);
524 } else {
525 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
526 }
527 }
528 }
529 );
530
531 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500532 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500533
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500534 /* connect to call state changes to update relevant view(s) */
535 QObject::connect(
536 CallModel::instance(),
537 &CallModel::callStateChanged,
538 [=](Call* call, G_GNUC_UNUSED Call::State previousState) {
539 call_state_changed(call, win);
540 }
541 );
542
Stepan Salenikovich15142182015-03-11 17:15:26 -0400543 /* contacts view/model */
544 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
545 GtkWidget *treeview_contacts = gtk_tree_view_new();
546 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_contacts), FALSE);
547 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_contacts);
548 gtk_widget_show_all(scrolled_window);
549 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
550 scrolled_window,
551 VIEW_CONTACTS);
552
553 /* history view/model */
554 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
555 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400556 /* make headers visible to allow column resizing */
557 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400558 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_history);
559 gtk_widget_show_all(scrolled_window);
560 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
561 scrolled_window,
562 VIEW_HISTORY);
563 /* TODO: make this linked to the client settings so that the last shown view is the same on startup */
564 gtk_stack_set_visible_child(GTK_STACK(priv->stack_contacts_history_presence),
565 scrolled_window);
566
Stepan Salenikovich15142182015-03-11 17:15:26 -0400567
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400568 /* sort the history in descending order by date */
Stepan Salenikovichdd84cf92015-03-19 21:38:19 -0400569 QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(CategorizedHistoryModel::instance());
570 proxyModel->setSourceModel(CategorizedHistoryModel::instance());
Stepan Salenikovich7ff47962015-03-16 15:10:14 -0400571 proxyModel->setSortRole(static_cast<int>(Call::Role::Date));
572 proxyModel->sort(0,Qt::DescendingOrder);
573
574 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new((QSortFilterProxyModel *)proxyModel, 4,
Stepan Salenikovich15142182015-03-11 17:15:26 -0400575 Qt::DisplayRole, G_TYPE_STRING,
576 Call::Role::Number, G_TYPE_STRING,
577 Call::Role::FormattedDate, G_TYPE_STRING,
578 Call::Role::Direction, G_TYPE_INT);
579 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
580
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400581 /* name or time category column */
582 renderer = gtk_cell_renderer_text_new();
583 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
584 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400585 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400586 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400587
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400588 /* "number" column */
589 renderer = gtk_cell_renderer_text_new();
590 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
591 column = gtk_tree_view_column_new_with_attributes("Number", renderer, "text", 1, NULL);
592 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
593 gtk_tree_view_column_set_resizable(column, TRUE);
594
595 /* date column */
Stepan Salenikovich15142182015-03-11 17:15:26 -0400596 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400597 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400598 column = gtk_tree_view_column_new_with_attributes ("Date", renderer, "text", 2, NULL);
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400599 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
600 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400601
Stepan Salenikovich2efd1982015-03-22 12:10:23 -0400602 /* expand the first row, which should be the most recent calls */
603 gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_history),
604 gtk_tree_path_new_from_string("0"),
605 FALSE);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400606
607 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(call_history_item), NULL);
608
609 /* presence view/model */
610 scrolled_window = gtk_scrolled_window_new(NULL, NULL);
611 GtkWidget *treeview_presence = gtk_tree_view_new();
612 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_presence), FALSE);
613 gtk_container_add(GTK_CONTAINER(scrolled_window), treeview_presence);
614 gtk_widget_show_all(scrolled_window);
615 gtk_stack_add_named(GTK_STACK(priv->stack_contacts_history_presence),
616 scrolled_window,
617 VIEW_PRESENCE);
618
619 /* connect signals to change the contacts/history/presence stack view */
620 g_signal_connect(priv->radiobutton_contacts, "toggled", G_CALLBACK(navbutton_contacts_toggled), win);
621 g_signal_connect(priv->radiobutton_history, "toggled", G_CALLBACK(navbutton_history_toggled), win);
622 g_signal_connect(priv->radiobutton_presence, "toggled", G_CALLBACK(navbutton_presence_toggled), win);
623
Stepan Salenikovich69771842015-02-24 18:11:45 -0500624 /* TODO: replace stack paceholder view */
625 GtkWidget *placeholder_view = gtk_tree_view_new();
626 gtk_widget_show(placeholder_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400627 gtk_stack_add_named(GTK_STACK(priv->stack_call_view), placeholder_view, DEFAULT_VIEW_NAME);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500628
629 /* connect signals */
630 g_signal_connect(call_selection, "changed", G_CALLBACK(call_selection_changed), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500631 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
632 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
633
634 /* style of search entry */
635 gtk_widget_override_font(priv->search_entry, pango_font_description_from_string("monospace 15"));
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500636
637 /* connect to incoming call and focus */
638 QObject::connect(
639 CallModel::instance(),
640 &CallModel::incomingCall,
641 [=](Call* call) {
642 CallModel::instance()->selectionModel()->setCurrentIndex(
643 CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect);
644 }
645 );
646}
647
648static void
649ring_main_window_finalize(GObject *object)
650{
651 G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500652}
653
654static void
655ring_main_window_class_init(RingMainWindowClass *klass)
656{
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500657 G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
658
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500659 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
660 "/cx/ring/RingGnome/ringmainwindow.ui");
661
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500662 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, treeview_call);
Stepan Salenikovich15142182015-03-11 17:15:26 -0400663 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_contacts_history_presence);
664 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_contacts);
665 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_history);
666 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_presence);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400667 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
668 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
669 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
670 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
671 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_search);
672 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500673 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500674 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400675 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
676 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_call_view);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500677 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich2d63d5e2015-03-22 23:23:54 -0400678 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_audio_settings);
679 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
680 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_video_settings);
681 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500682}
683
684GtkWidget *
685ring_main_window_new (GtkApplication *app)
686{
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400687 gpointer win = g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
688
Stepan Salenikovich61cbab02015-03-16 18:35:10 -0400689 return (GtkWidget *)win;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500690}