ringmainwindow.cpp 46.8 KB
Newer Older
1
/*
2
 *  Copyright (C) 2015 Savoir-faire Linux Inc.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
24
 *  terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
25 26 27 28 29 30 31 32 33
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
 */

#include "ringmainwindow.h"

#include <gtk/gtk.h>
34
#include <glib/gi18n.h>
35 36
#include "models/gtkqtreemodel.h"
#include <callmodel.h>
37
#include <call.h>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
38
#include <QtCore/QItemSelectionModel>
39
#include "incomingcallview.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
40
#include "currentcallview.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
41
#include <string.h>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
42
#include <contactmethod.h>
43 44
#include <QtCore/QSortFilterProxyModel>
#include "models/gtkqsortfiltertreemodel.h"
45
#include "accountview.h"
46 47
#include <accountmodel.h>
#include <audio/codecmodel.h>
48
#include "dialogs.h"
49
#include "mediasettingsview.h"
50
#include <video/previewmanager.h>
51 52 53
#include <personmodel.h>
#include "utils/drawing.h"
#include <memory>
54 55
#include <globalinstances.h>
#include "native/pixbufmanipulator.h"
56
#include "models/activeitemproxymodel.h"
57
#include <numbercompletionmodel.h>
58 59
#include "utils/calling.h"
#include "contactsview.h"
60
#include <categorizedcontactmodel.h>
61 62
#include "historyview.h"
#include "utils/models.h"
63
#include "generalsettingsview.h"
64
#include "utils/accounts.h"
65
#include "ringwelcomeview.h"
66
#include "recentcontactsview.h"
67
#include <recentmodel.h>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
68
#include "chatview.h"
69

70 71 72 73 74 75 76
static constexpr const char* CALL_VIEW_NAME             = "calls";
static constexpr const char* CREATE_ACCOUNT_1_VIEW_NAME = "create1";
static constexpr const char* CREATE_ACCOUNT_2_VIEW_NAME = "create2";
static constexpr const char* GENERAL_SETTINGS_VIEW_NAME = "general";
static constexpr const char* AUDIO_SETTINGS_VIEW_NAME   = "audio";
static constexpr const char* MEDIA_SETTINGS_VIEW_NAME   = "media";
static constexpr const char* ACCOUNT_SETTINGS_VIEW_NAME = "accounts";
77
static constexpr const char* DEFAULT_VIEW_NAME          = "welcome";
78 79 80
static constexpr const char* VIEW_CONTACTS              = "contacts";
static constexpr const char* VIEW_HISTORY               = "history";
static constexpr const char* VIEW_PRESENCE              = "presence";
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95

struct _RingMainWindow
{
    GtkApplicationWindow parent;
};

struct _RingMainWindowClass
{
    GtkApplicationWindowClass parent_class;
};

typedef struct _RingMainWindowPrivate RingMainWindowPrivate;

struct _RingMainWindowPrivate
{
96 97 98 99 100
    GtkWidget *ring_menu;
    GtkWidget *image_ring;
    GtkWidget *ring_settings;
    GtkWidget *image_settings;
    GtkWidget *hbox_settings;
101
    GtkWidget *scrolled_window_smartview;
102 103
    GtkWidget *scrolled_window_contacts;
    GtkWidget *scrolled_window_history;
104
    GtkWidget *vbox_left_pane;
105
    GtkWidget *search_entry;
106
    GtkWidget *stack_main_view;
107
    GtkWidget *vbox_call_view;
108 109
    GtkWidget *frame_call;
    GtkWidget *welcome_view;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
110
    GtkWidget *button_placecall;
111
    GtkWidget *account_settings_view;
112
    GtkWidget *media_settings_view;
113
    GtkWidget *general_settings_view;
114
    GtkWidget *last_settings_view;
115
    GtkWidget *radiobutton_general_settings;
116
    GtkWidget *radiobutton_media_settings;
117
    GtkWidget *radiobutton_account_settings;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
118

119
    QMetaObject::Connection selection_updated;
120 121

    gboolean   show_settings;
122 123 124 125 126 127 128 129 130 131 132 133 134 135

    /* account creation */
    GtkWidget *account_creation_1;
    GtkWidget *image_ring_logo;
    GtkWidget *label_enter_alias;
    GtkWidget *entry_alias;
    GtkWidget *label_generating_account;
    GtkWidget *spinner_generating_account;
    GtkWidget *button_account_creation_next;
    GtkWidget *account_creation_2;
    GtkWidget *entry_hash;
    GtkWidget *button_account_creation_done;

    QMetaObject::Connection hash_updated;
136 137 138 139

    /* allocd qmodels */
    ActiveItemProxyModel *q_contact_model;
    QSortFilterProxyModel *q_history_model;
140
    NumberCompletionModel *q_completion_model;
141 142 143

    /* fullscreen */
    gboolean is_fullscreen;
144 145 146 147 148 149
};

G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);

#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))

150
static void
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
enter_full_screen(RingMainWindow *self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    if (!priv->is_fullscreen) {
        gtk_widget_hide(priv->vbox_left_pane);
        gtk_window_fullscreen(GTK_WINDOW(self));
        priv->is_fullscreen = TRUE;
    }
}

static void
leave_full_screen(RingMainWindow *self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    if (priv->is_fullscreen) {
        gtk_widget_show(priv->vbox_left_pane);
        gtk_window_unfullscreen(GTK_WINDOW(self));
        priv->is_fullscreen = FALSE;
    }
}

static void
177
video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
178 179 180 181 182 183 184 185 186 187 188
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    if (priv->is_fullscreen) {
        leave_full_screen(self);
    } else {
        enter_full_screen(self);
    }
}

189 190 191 192
/**
 * This takes the RecentModel index as the argument and displays the corresponding view:
 * - incoming call view
 * - current call view
Stepan Salenikovich's avatar
Stepan Salenikovich committed
193
 * - chat view
194 195
 * - welcome view (if no index is selected)
 */
196
static void
Stepan Salenikovich's avatar
Stepan Salenikovich committed
197
selection_changed(const QModelIndex& recent_idx, RingMainWindow *win)
198
{
Stepan Salenikovich's avatar
Stepan Salenikovich committed
199
    // g_debug("selection changed");
200
    g_return_if_fail(IS_RING_MAIN_WINDOW(win));
201 202
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));

203 204 205 206
    /* if we're showing the settings, then nothing needs to be done as the call
       view is not shown */
    if (priv->show_settings) return;

207
    /* get the current visible stack child */
208
    GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
209

210 211 212
    /* make sure we leave full screen, since the call selection is changing */
    leave_full_screen(win);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
213 214 215 216 217 218 219
    /* check which object type is selected */
    auto type = recent_idx.data(static_cast<int>(Ring::Role::ObjectType)).value<Ring::ObjectType>();
    auto object = recent_idx.data(static_cast<int>(Ring::Role::Object));
    /* try to get the call model index, in case its a call, since we're still using the CallModel as well */
    auto call_idx = CallModel::instance().getIndex(RecentModel::instance().getActiveCall(recent_idx));

    /* we prioritize showing the call view */
220
    if (call_idx.isValid()) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
221
        /* show the call view */
222
        QVariant state =  call_idx.data(static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
223
        GtkWidget *new_call_view = NULL;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
224

225
        switch(state.value<Call::LifeCycleState>()) {
226
            case Call::LifeCycleState::CREATION:
Stepan Salenikovich's avatar
Stepan Salenikovich committed
227 228
            case Call::LifeCycleState::INITIALIZATION:
            case Call::LifeCycleState::FINISHED:
Stepan Salenikovich's avatar
Stepan Salenikovich committed
229
                new_call_view = incoming_call_view_new();
230
                incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), call_idx);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
231 232 233
                break;
            case Call::LifeCycleState::PROGRESS:
                new_call_view = current_call_view_new();
234
                g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
235
                current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), call_idx);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
236 237 238 239
                break;
            case Call::LifeCycleState::COUNT__:
                g_warning("LifeCycleState should never be COUNT");
                break;
240
        }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
241

242 243 244
        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view);
        gtk_widget_show(new_call_view);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
245 246 247 248 249 250 251 252 253 254 255 256
    } else if (type == Ring::ObjectType::Person && object.isValid()) {
        /* show chat view constructed from Person object */
        auto new_chat_view = chat_view_new_person(object.value<Person *>());
        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_chat_view);
        gtk_widget_show(new_chat_view);
    } else if (type == Ring::ObjectType::ContactMethod && object.isValid()) {
        /* show chat view constructed from CM */
        auto new_chat_view = chat_view_new_cm(object.value<ContactMethod *>());
        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_chat_view);
        gtk_widget_show(new_chat_view);
257
    } else {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
258
        /* nothing selected that we can display, show the welcome view */
259 260
        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
        gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
261
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
262 263 264
}

static void
265
call_state_changed(Call *call, RingMainWindow *win)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
266
{
Stepan Salenikovich's avatar
Stepan Salenikovich committed
267
    // g_debug("call state changed");
Stepan Salenikovich's avatar
Stepan Salenikovich committed
268 269
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));

270 271 272 273
    /* if we're showing the settings, then nothing needs to be done as the call
       view is not shown */
    if (priv->show_settings) return;

Stepan Salenikovich's avatar
Stepan Salenikovich committed
274 275
    /* we prioritize showing the call view; but if the call is over we go back to showing the chat view */

Stepan Salenikovich's avatar
Stepan Salenikovich committed
276
    /* check if the call that changed state is the same as the selected call */
Stepan Salenikovich's avatar
Stepan Salenikovich committed
277
    auto idx_selected = RecentModel::instance().selectionModel()->currentIndex();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
278

Stepan Salenikovich's avatar
Stepan Salenikovich committed
279
    if(call == RecentModel::instance().getActiveCall(idx_selected)) {
Stepan Salenikovich's avatar
Stepan Salenikovich committed
280
        /* check if we need to change the view */
Stepan Salenikovich's avatar
Stepan Salenikovich committed
281
        auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
282
        QVariant state = CallModel::instance().data(idx_selected, static_cast<int>(Call::Role::LifeCycleState));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
283 284

        /* check what the current state is vs what is displayed */
285
        switch(state.value<Call::LifeCycleState>()) {
286
            case Call::LifeCycleState::CREATION:
Stepan Salenikovich's avatar
Stepan Salenikovich committed
287 288 289
            case Call::LifeCycleState::FINISHED:
            /* go back to incoming call view;
             * it will show that the call failed and offer to hang it up */
Stepan Salenikovich's avatar
Stepan Salenikovich committed
290
            case Call::LifeCycleState::INITIALIZATION:
Stepan Salenikovich's avatar
Stepan Salenikovich committed
291 292 293 294 295 296 297 298 299 300
                {
                    /* show the incoming call view */
                    if (!IS_INCOMING_CALL_VIEW(current_view)) {
                        auto new_view = incoming_call_view_new();
                        incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_view), CallModel::instance().getIndex(call));
                        gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
                        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
                        gtk_widget_show(new_view);
                    }
                }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
301 302
                break;
            case Call::LifeCycleState::PROGRESS:
Stepan Salenikovich's avatar
Stepan Salenikovich committed
303 304 305 306 307 308 309 310 311 312
                {
                    /* show the current call view */
                    if (!IS_CURRENT_CALL_VIEW(current_view)) {
                        auto new_view = current_call_view_new();
                        g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
                        current_call_view_set_call_info(CURRENT_CALL_VIEW(new_view), CallModel::instance().getIndex(call));
                        gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
                        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
                        gtk_widget_show(new_view);
                    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
313 314 315 316 317 318
                }
                break;
            case Call::LifeCycleState::COUNT__:
                g_warning("LifeCycleState should never be COUNT");
                break;
        }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
    } else if (idx_selected.isValid()) {
        /* otherwise, the call is over and is already removed from the RecentModel */
        auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
        leave_full_screen(win);

        /* show the chat view */
        if (!IS_CHAT_VIEW(current_view)) {
            auto type = idx_selected.data(static_cast<int>(Ring::Role::ObjectType)).value<Ring::ObjectType>();
            auto object = idx_selected.data(static_cast<int>(Ring::Role::Object));
            if (type == Ring::ObjectType::Person && object.isValid()) {
                /* show chat view constructed from Person object */
                auto new_view = chat_view_new_person(object.value<Person *>());
                gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
                gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
                gtk_widget_show(new_view);
            } else if (type == Ring::ObjectType::ContactMethod && object.isValid()) {
                /* show chat view constructed from CM */
                auto new_view = chat_view_new_cm(object.value<ContactMethod *>());
                gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
                gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
                gtk_widget_show(new_view);
            }
        }
342 343 344
    }
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
345 346 347 348 349
static void
search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
    const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));

    if (number_entered && strlen(number_entered) > 0) {
        /* detect Ring hash */
        gboolean is_ring_hash = FALSE;
        if (strlen(number_entered) == 40) {
            is_ring_hash = TRUE;
            /* must be 40 chars long and alphanumeric */
            for (int i = 0; i < 40 && is_ring_hash; ++i) {
                if (!g_ascii_isalnum(number_entered[i]))
                    is_ring_hash = FALSE;
            }
        }

        QString number = QString{number_entered};

        if (is_ring_hash)
            number = "ring:" + number;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
368

369
        g_debug("dialing to number: %s", number.toUtf8().constData());
370 371

        Call *call = priv->q_completion_model->call();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
372 373 374 375
        call->setDialNumber(number);
        call->performAction(Call::Action::ACCEPT);

        /* make this the currently selected call */
376 377
        QModelIndex idx = CallModel::instance().getIndex(call);
        CallModel::instance().selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
378

379 380 381 382 383
        /* move focus away from entry so that DTMF tones can be entered via the keyboard */
        gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
        /* clear the entry */
        gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
384 385
}

386 387 388 389 390 391 392
static gboolean
grab_focus_on_widget(GtkWidget *widget)
{
    gtk_widget_grab_focus(widget);
    return G_SOURCE_REMOVE;
}

393 394 395 396
static gboolean
save_accounts(GtkWidget *working_dialog)
{
    /* save changes to accounts */
397
    AccountModel::instance().save();
398 399 400 401 402 403 404

    if (working_dialog)
        gtk_widget_destroy(working_dialog);

    return G_SOURCE_REMOVE;
}

405 406 407 408 409 410 411
static void
settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(win));
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    /* check which view to show */
412 413 414
    if (!priv->show_settings) {
        /* show the settings */

415 416
        /* make sure we are not showing a call view so we don't have more than one clutter stage at a time */
        selection_changed(QModelIndex(), win);
417

418 419 420 421 422
        /* show settings */
        gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);

        gtk_widget_show(priv->hbox_settings);

423
        /* make sure to start preview if we're showing the video settings */
424 425
        if (priv->last_settings_view == priv->media_settings_view)
            media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
426

427
        gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
428
        gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
429 430

        priv->show_settings = TRUE;
431
    } else {
432 433 434
        /* hide the settings */
        priv->show_settings = FALSE;

435 436 437 438 439 440 441 442
        /* show working dialog in case save operation takes time */
        GtkWidget *working = ring_dialog_working(GTK_WIDGET(win), NULL);
        gtk_window_present(GTK_WINDOW(working));

        /* now save after the time it takes to transition back to the call view (400ms)
         * the save doesn't happen before the "working" dialog is presented
         * the timeout function should destroy the "working" dialog when done saving
         */
443
        g_timeout_add_full(G_PRIORITY_DEFAULT, 400, (GSourceFunc)save_accounts, working, NULL);
444

445 446 447 448 449
        /* show calls */
        gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);

        gtk_widget_hide(priv->hbox_settings);

450 451 452
        /* make sure video preview is stopped, in case it was started */
        media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);

453 454
        gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN);
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
455

456 457
        /* show the view which was selected previously */
        selection_changed(RecentModel::instance().selectionModel()->currentIndex(), win);
458 459 460 461
    }
}

static void
462
show_media_settings(GtkToggleButton *navbutton, RingMainWindow *win)
463 464 465 466 467
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(win));
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    if (gtk_toggle_button_get_active(navbutton)) {
468
        media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
469
        gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
470
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
471
        priv->last_settings_view = priv->media_settings_view;
472
    } else {
473
        media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
474 475 476 477 478 479 480 481 482 483 484 485 486
    }
}

static void
show_account_settings(GtkToggleButton *navbutton, RingMainWindow *win)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(win));
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    if (gtk_toggle_button_get_active(navbutton)) {
        gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), ACCOUNT_SETTINGS_VIEW_NAME);
        priv->last_settings_view = priv->account_settings_view;
487 488 489
    }
}

490 491 492 493 494 495 496 497 498 499 500 501 502
static void
show_general_settings(GtkToggleButton *navbutton, RingMainWindow *win)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(win));
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    if (gtk_toggle_button_get_active(navbutton)) {
        gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
        priv->last_settings_view = priv->general_settings_view;
    }
}

503 504 505 506 507 508 509 510
static gboolean
create_ring_account(RingMainWindow *win)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    /* create account and set UPnP enabled, as its not by default in the daemon */
    const gchar *alias = gtk_entry_get_text(GTK_ENTRY(priv->entry_alias));
511
    Account *account = AccountModel::instance().add(alias, Account::Protocol::RING);
512
    account->setDisplayName(alias); // set the display name to the same as the alias
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    account->setUpnpEnabled(TRUE);

    /* wait for hash to be generated to show the next view */
    priv->hash_updated = QObject::connect(
        account,
        &Account::changed,
        [=] (Account *a) {
            QString hash = a->username();
            if (!hash.isEmpty()) {
                /* set the hash */
                gtk_entry_set_text(GTK_ENTRY(priv->entry_hash), hash.toUtf8().constData());

                /* show the next accont creation view */
                gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
                gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_2_VIEW_NAME);

                /* select the hash text */
                gtk_widget_grab_focus(priv->entry_hash);
                gtk_editable_select_region(GTK_EDITABLE(priv->entry_hash), 0, -1);
            }
        }
    );

    account->performAction(Account::EditAction::SAVE);

    return G_SOURCE_REMOVE;
}

static void
alias_entry_changed(GtkEditable *entry, RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
    if (strlen(alias) > 0) {
        /* enable "next" button */
        gtk_widget_set_sensitive(priv->button_account_creation_next, TRUE);
    } else {
        /* disable "next" button, as we require an alias */
        gtk_widget_set_sensitive(priv->button_account_creation_next, FALSE);
    }
}

static void
account_creation_next_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    /* show/hide relevant widgets */
    gtk_widget_hide(priv->label_enter_alias);
    gtk_widget_hide(priv->entry_alias);
    gtk_widget_hide(priv->button_account_creation_next);
    gtk_widget_show(priv->label_generating_account);
    gtk_widget_show(priv->spinner_generating_account);

    /* now create account after a short timeout so that the the save doesn't
     * happen freeze the client before the widget changes happen;
     * the timeout function should then display the next step in account creation
     */
    g_timeout_add_full(G_PRIORITY_DEFAULT, 300, (GSourceFunc)create_ring_account, win, NULL);
}

static void
account_creation_done_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    QObject::disconnect(priv->hash_updated);

    /* show the call view */
    gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);

586
    /* show the settings button*/
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
    gtk_widget_show(priv->ring_settings);

}

static void
entry_alias_activated(GtkEntry *entry, RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    const gchar *alias = gtk_entry_get_text(GTK_ENTRY(entry));
    if (strlen(alias) > 0)
        gtk_button_clicked(GTK_BUTTON(priv->button_account_creation_next));
}

static void
show_account_creation(RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
                        priv->account_creation_1,
                        CREATE_ACCOUNT_1_VIEW_NAME);

    gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
                        priv->account_creation_2,
                        CREATE_ACCOUNT_2_VIEW_NAME);

614
    /* hide settings button until account creation is complete */
615 616 617 618 619 620 621 622
    gtk_widget_hide(priv->ring_settings);

    /* set ring logo */
    GError *error = NULL;
    GdkPixbuf* logo_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
                                                                  -1, 75, TRUE, &error);
    if (logo_ring == NULL) {
        g_debug("Could not load logo: %s", error->message);
623
        g_clear_error(&error);
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
    } else
        gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring_logo), logo_ring);

    /* style of alias and hash entry; give them a larger font */
    gtk_widget_override_font(priv->entry_alias, pango_font_description_from_string("15"));
    gtk_widget_override_font(priv->entry_hash, pango_font_description_from_string("monospace 15"));

    /* connect signals */
    g_signal_connect(priv->entry_alias, "changed", G_CALLBACK(alias_entry_changed), win);
    g_signal_connect(priv->button_account_creation_next, "clicked", G_CALLBACK(account_creation_next_clicked), win);
    g_signal_connect(priv->button_account_creation_done, "clicked", G_CALLBACK(account_creation_done_clicked), win);
    g_signal_connect(priv->entry_alias, "activate", G_CALLBACK(entry_alias_activated), win);
    g_signal_connect_swapped(priv->entry_hash, "activate", G_CALLBACK(gtk_button_clicked), priv->button_account_creation_done);

    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CREATE_ACCOUNT_1_VIEW_NAME);
}

641 642 643 644 645 646 647 648
static void
search_entry_text_changed(GtkEditable *search_entry, RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    /* get the text from the entry */
    const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));

649
    if (text && strlen(text) > 0) {
650
        /* edit the the dialing call (or create a new one) */
651
        if (auto call = CallModel::instance().dialingCall()) {
652 653 654
            call->setDialNumber(text);
            priv->q_completion_model->setCall(call);
        }
655
    } else {
656 657 658 659
        if (auto call = priv->q_completion_model->call()) {
            if (call->lifeCycleState() == Call::LifeCycleState::CREATION)
                call->performAction(Call::Action::REFUSE);
        }
660
    }
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
}

static gboolean
completion_match_func(G_GNUC_UNUSED GtkEntryCompletion *completion,
                      G_GNUC_UNUSED const gchar *key,
                      G_GNUC_UNUSED GtkTreeIter *iter,
                      G_GNUC_UNUSED RingMainWindow *win)
{
    /* the model is updated by lrc and should only every contain matching entries
     * so always return TRUE */
    return TRUE;
}

static QModelIndex
get_qidx_from_filter_model(GtkTreeModelFilter *filter_model,
                           GtkTreeIter *filter_iter)
{
    GtkTreeModel *child_model = gtk_tree_model_filter_get_model(filter_model);
    GtkTreeIter child_iter;
    gtk_tree_model_filter_convert_iter_to_child_iter(
        GTK_TREE_MODEL_FILTER(filter_model),
        &child_iter,
        filter_iter);

    return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(child_model), &child_iter);
}

static void
autocompletion_photo_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
                            GtkCellRenderer *cell,
                            GtkTreeModel *model,
                            GtkTreeIter *iter,
                            G_GNUC_UNUSED gpointer user_data)
{
    QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
    if (idx.isValid()) {
        QVariant photo_var = idx.sibling(idx.row(), 1).data(Qt::DecorationRole);
        if (photo_var.isValid()) {
            std::shared_ptr<GdkPixbuf> photo = photo_var.value<std::shared_ptr<GdkPixbuf>>();
            GdkPixbuf *scaled = gdk_pixbuf_scale_simple(photo.get(),
                                                        20, 20,
                                                        GDK_INTERP_BILINEAR);

            g_object_set(G_OBJECT(cell), "pixbuf", scaled, NULL);
            g_object_unref(scaled);
            return;
        }
    }

    g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
}

static void
autocompletion_name_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
                           GtkCellRenderer *cell,
                           GtkTreeModel *model,
                           GtkTreeIter *iter,
                           G_GNUC_UNUSED gpointer user_data)
{
    QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
    if (idx.isValid()) {
        QVariant name = idx.sibling(idx.row(), 1).data(Qt::DisplayRole);
723
        gchar *text = g_strdup_printf("<span weight=\"bold\">%s</span>",
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
                                      name.value<QString>().toUtf8().constData());

        g_object_set(G_OBJECT(cell), "markup", text, NULL);
        g_free(text);
        return;
    }

    g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
}

static void
autocompletion_number_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
                             GtkCellRenderer *cell,
                             GtkTreeModel *model,
                             GtkTreeIter *iter,
                             G_GNUC_UNUSED gpointer user_data)
{
    QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
    if (idx.isValid()) {
        QVariant uri = idx.data(Qt::DisplayRole);
744
        gchar *text = g_strdup_printf("%s", uri.value<QString>().toUtf8().constData());
745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763

        g_object_set(G_OBJECT(cell), "markup", text, NULL);
        g_free(text);
        return;
    }

    g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
}

static void
autocompletion_account_render(G_GNUC_UNUSED GtkCellLayout *cell_layout,
                              GtkCellRenderer *cell,
                              GtkTreeModel *model,
                              GtkTreeIter *iter,
                              G_GNUC_UNUSED gpointer user_data)
{
    QModelIndex idx = get_qidx_from_filter_model(GTK_TREE_MODEL_FILTER(model), iter);
    if (idx.isValid()) {
        QVariant alias = idx.sibling(idx.row(), 2).data(Qt::DisplayRole);
764
        gchar *text = g_strdup_printf("<span color=\"gray\">%s</span>",
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
                                      alias.value<QString>().toUtf8().constData());

        g_object_set(G_OBJECT(cell), "markup", text, NULL);
        g_free(text);
        return;
    }

    g_object_set(G_OBJECT(cell), "markup", NULL, NULL);
}

static gboolean
select_autocompletion(G_GNUC_UNUSED GtkEntryCompletion *widget,
                      GtkTreeModel *model,
                      GtkTreeIter *iter,
                      RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);

    QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), iter);
    if (idx.isValid()) {
785 786 787
        priv->q_completion_model->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
        Call *call = priv->q_completion_model->call();
        priv->q_completion_model->callSelectedNumber();
788

789
        /* make this the currently selected call */
790 791
        QModelIndex idx = CallModel::instance().getIndex(call);
        CallModel::instance().selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
792 793 794

        /* move focus away from entry so that DTMF tones can be entered via the keyboard */
        gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
795 796 797

        /* clear the entry */
        gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
798 799 800 801 802 803
    } else {
        g_warning("autocompletion selection is not a valid index!");
    }
    return TRUE;
}

804 805 806 807 808 809 810 811 812 813 814 815 816 817 818
static gboolean
dtmf_pressed(RingMainWindow *win,
              GdkEventKey *event,
              G_GNUC_UNUSED gpointer user_data)
{
    g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);

    /* we want to react to digit key presses, as long as a GtkEntry is not the
     * input focus
     */
    GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(win));
    if (GTK_IS_ENTRY(focus))
        return GDK_EVENT_PROPAGATE;

    /* make sure that a call is selected*/
819
    QItemSelectionModel *selection = CallModel::instance().selectionModel();
820 821 822 823 824
    QModelIndex idx = selection->currentIndex();
    if (!idx.isValid())
        return GDK_EVENT_PROPAGATE;

    /* make sure that the selected call is in progress */
825
    Call *call = CallModel::instance().getCall(idx);
826 827 828 829
    Call::LifeCycleState state = call->lifeCycleState();
    if (state != Call::LifeCycleState::PROGRESS)
        return GDK_EVENT_PROPAGATE;

830 831 832 833 834 835 836
    /* filter out cretain MOD masked key presses so that, for example, 'Ctrl+c'
     * does not result in a 'c' being played.
     * we filter Ctrl, Alt, and SUPER/HYPER/META keys */
    if ( event->state
        & ( GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_SUPER_MASK | GDK_HYPER_MASK | GDK_META_MASK ))
        return GDK_EVENT_PROPAGATE;

837 838 839 840 841 842
    /* pass the character that was entered to be played by the daemon;
     * the daemon will filter out invalid DTMF characters */
    guint32 unicode_val = gdk_keyval_to_unicode(event->keyval);
    QString val = QString::fromUcs4(&unicode_val, 1);
    call->playDTMF(val);
    g_debug("attemptingto play DTMF tone during ongoing call: %s", val.toUtf8().constData());
843 844 845

    /* always propogate the key, so we don't steal accelerators/shortcuts */
    return GDK_EVENT_PROPAGATE;
846 847
}

848 849 850 851 852 853 854 855 856 857 858
static void
ring_main_window_init(RingMainWindow *win)
{
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
    gtk_widget_init_template(GTK_WIDGET(win));

     /* set window icon */
    GError *error = NULL;
    GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
    if (icon == NULL) {
        g_debug("Could not load icon: %s", error->message);
859
        g_clear_error(&error);
860 861 862 863
    } else
        gtk_window_set_icon(GTK_WINDOW(win), icon);

    /* set menu icon */
864 865 866
    GdkPixbuf* image_ring = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-symbol-blue",
                                                                  -1, 24, TRUE, &error);
    if (image_ring == NULL) {
867
        g_debug("Could not load icon: %s", error->message);
868
        g_clear_error(&error);
869
    } else
870
        gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_ring), image_ring);
871

872
    /* ring menu */
873 874
    GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
    GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
875
    gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->ring_menu), menu);
876
    g_object_unref(builder);
877

878 879 880 881 882 883 884 885 886 887 888
    /* settings icon */
    gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-system-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR);

    /* connect settings button signal */
    g_signal_connect(priv->ring_settings, "clicked", G_CALLBACK(settings_clicked), win);

    /* add the call view to the main stack */
    gtk_stack_add_named(GTK_STACK(priv->stack_main_view),
                        priv->vbox_call_view,
                        CALL_VIEW_NAME);

889 890 891 892 893 894 895
    if (has_ring_account()) {
        /* user has ring account, so show the call view right away */
        gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->vbox_call_view);
    } else {
        /* user has to create the ring account */
        show_account_creation(win);
    }
896 897 898 899 900

    /* init the settings views */
    priv->account_settings_view = account_view_new();
    gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->account_settings_view, ACCOUNT_SETTINGS_VIEW_NAME);

901 902
    priv->media_settings_view = media_settings_view_new();
    gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->media_settings_view, MEDIA_SETTINGS_VIEW_NAME);
903

904 905 906
    priv->general_settings_view = general_settings_view_new();
    gtk_stack_add_named(GTK_STACK(priv->stack_main_view), priv->general_settings_view, GENERAL_SETTINGS_VIEW_NAME);

907
    /* make the setting we will show first the active one */
908 909
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->radiobutton_general_settings), TRUE);
    priv->last_settings_view = priv->general_settings_view;
910 911

    /* connect the settings button signals to switch settings views */
912
    g_signal_connect(priv->radiobutton_media_settings, "toggled", G_CALLBACK(show_media_settings), win);
913
    g_signal_connect(priv->radiobutton_account_settings, "toggled", G_CALLBACK(show_account_settings), win);
914
    g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
915

916
    /* populate the notebook */
917 918
    auto smart_view = recent_contacts_view_new();
    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), smart_view);
919 920 921 922 923 924

    auto contacts_view = contacts_view_new();
    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), contacts_view);

    auto history_view = history_view_new();
    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), history_view);
925

926
    /* welcome/default view */
927 928 929 930 931
    priv->welcome_view = ring_welcome_view_new();
    g_object_ref(priv->welcome_view);
    // gtk_stack_add_named(GTK_STACK(priv->stack_call_view), welcome_view, DEFAULT_VIEW_NAME);
    gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
    gtk_widget_show(priv->welcome_view);
932

933
    /* call/chat selection */
934
    QObject::connect(
935
       RecentModel::instance().selectionModel(),
936 937
       &QItemSelectionModel::currentChanged,
       [win](const QModelIndex current, G_GNUC_UNUSED const QModelIndex & previous) {
938
            if (auto call = RecentModel::instance().getActiveCall(current)) {
939 940 941 942 943
                /* if the call is on hold, we want to put it off hold automatically
                 * when switching to it */
                if (call->state() == Call::State::HOLD)
                    call << Call::Action::HOLD;
            }
944
            selection_changed(current, win);
945 946 947 948 949
        }
    );

    /* connect to call state changes to update relevant view(s) */
    QObject::connect(
950
        &CallModel::instance(),
951
        &CallModel::callStateChanged,
Stepan Salenikovich's avatar
Stepan Salenikovich committed
952 953 954 955 956 957 958 959 960 961
        [win](Call* call, G_GNUC_UNUSED Call::State previousState) {
            call_state_changed(call, win);
        }
    );

    /* also connect to the incoming call, in case the RecentModel item we already selected gets a call */
    QObject::connect(
        &CallModel::instance(),
        &CallModel::incomingCall,
        [win](Call* call) {
962 963 964 965
            call_state_changed(call, win);
        }
    );

Stepan Salenikovich's avatar
Stepan Salenikovich committed
966 967 968
    g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
    g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);

969 970 971 972 973 974 975
    /* autocompletion */
    priv->q_completion_model = new NumberCompletionModel();

    /* autocompletion renderers */
    GtkCellArea *completion_area = gtk_cell_area_box_new();

    /* photo renderer */
976
    GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
977 978 979 980 981 982 983 984 985 986 987 988 989
    gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
                                 renderer,
                                 TRUE,  /* expand */
                                 TRUE,  /* align */
                                 TRUE); /* fixed size */

    gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
                                       renderer,
                                       (GtkCellLayoutDataFunc)autocompletion_photo_render,
                                       NULL, NULL);

    /* name renderer */
    renderer = gtk_cell_renderer_text_new();
990
    g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
    gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
                                 renderer,
                                 TRUE,  /* expand */
                                 TRUE,  /* align */
                                 TRUE); /* fixed size */

    gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
                                       renderer,
                                       (GtkCellLayoutDataFunc)autocompletion_name_render,
                                       NULL, NULL);

    /* number renderer */
    renderer = gtk_cell_renderer_text_new();
1004
    g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
    gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
                                 renderer,
                                 TRUE,  /* expand */
                                 TRUE,  /* align */
                                 TRUE); /* fixed size */

    gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
                                       renderer,
                                       (GtkCellLayoutDataFunc)autocompletion_number_render,
                                       NULL, NULL);
    /* account renderer */
    renderer = gtk_cell_renderer_text_new();
1017
    g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
    gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(completion_area),
                                 renderer,
                                 TRUE,  /* expand */
                                 TRUE,  /* align */
                                 TRUE); /* fixed size */

    gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(completion_area),
                                       renderer,
                                       (GtkCellLayoutDataFunc)autocompletion_account_render,
                                       NULL, NULL);

    GtkEntryCompletion *entry_completion = gtk_entry_completion_new_with_area(completion_area);

    GtkQTreeModel *completion_model = gtk_q_tree_model_new(
        (QAbstractItemModel *)priv->q_completion_model,
        1,
        Qt::DisplayRole, G_TYPE_STRING);

    gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(completion_model));

    gtk_entry_set_completion(GTK_ENTRY(priv->search_entry), entry_completion);
    gtk_entry_completion_set_match_func(
        entry_completion,
        (GtkEntryCompletionMatchFunc) completion_match_func,
        NULL,
        NULL);

    /* connect signal to when text is entered in the entry */
    g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
    g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
1048 1049 1050

    /* connect to incoming call and focus */
    QObject::connect(
1051
        &CallModel::instance(),
1052 1053
        &CallModel::incomingCall,
        [=](Call* call) {
1054 1055
            CallModel::instance().selectionModel()->setCurrentIndex(
                CallModel::instance().getIndex(call), QItemSelectionModel::ClearAndSelect);
1056 1057
        }
    );
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1058

1059 1060
    /* react to digit key press events */
    g_signal_connect(win, "key-press-event", G_CALLBACK(dtmf_pressed), NULL);
1061 1062 1063 1064

    /* set the search entry placeholder text */
    gtk_entry_set_placeholder_text(GTK_ENTRY(priv->search_entry),
                                   C_("Please try to make the translation 50 chars or less so that it fits into the layout", "Search contacts or enter number"));
1065 1066
}

1067 1068 1069
static void
ring_main_window_dispose(GObject *object)
{
1070 1071 1072 1073 1074
    RingMainWindow *self = RING_MAIN_WINDOW(object);
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);

    QObject::disconnect(priv->selection_updated);

1075 1076 1077
    G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
}

1078 1079 1080
static void
ring_main_window_finalize(GObject *object)
{
1081 1082 1083 1084 1085
    RingMainWindow *self = RING_MAIN_WINDOW(object);
    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);

    delete priv->q_contact_model;
    delete priv->q_history_model;
1086
    delete priv->q_completion_model;
1087

1088
    G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
1089 1090 1091 1092 1093
}

static void
ring_main_window_class_init(RingMainWindowClass *klass)
{
1094
    G_OBJECT_CLASS(klass)->finalize = ring_main_window_finalize;
1095
    G_OBJECT_CLASS(klass)->dispose = ring_main_window_dispose;
1096

1097 1098 1099
    gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
                                                "/cx/ring/RingGnome/ringmainwindow.ui");

1100
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_left_pane);
1101
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_smartview);
1102 1103
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_contacts);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, scrolled_window_history);
1104 1105 1106 1107 1108
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_menu);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, ring_settings);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_settings);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, hbox_settings);
1109
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
1110
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
1111
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
1112
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1113
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
1114
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
1115
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
1116
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);
1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129

    /* account creation */
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_1);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, image_ring_logo);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_enter_alias);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_alias);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, label_generating_account);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, spinner_generating_account);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_next);

    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, account_creation_2);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, entry_hash);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_account_creation_done);
1130 1131 1132 1133 1134
}

GtkWidget *
ring_main_window_new (GtkApplication *app)
{