ringmainwindow.cpp 92.9 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2015-2019 Savoir-faire Linux Inc.
3
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 *  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.
 */

#include "ringmainwindow.h"

Stepan Salenikovich's avatar
Stepan Salenikovich committed
23
// GTK+ related
24
#include <glib/gi18n.h>
25 26
// std
#include <algorithm>
27

28 29 30
// Qt
#include <QSize>

Stepan Salenikovich's avatar
Stepan Salenikovich committed
31
// LRC
32
#include <api/account.h>
33
#include <api/avmodel.h>
34
#include <api/behaviorcontroller.h>
35 36 37 38
#include <api/contact.h>
#include <api/contactmodel.h>
#include <api/conversation.h>
#include <api/conversationmodel.h>
39
#include <api/datatransfermodel.h>
40 41 42
#include <api/lrc.h>
#include <api/newaccountmodel.h>
#include <api/newcallmodel.h>
43 44
#include <api/profile.h>

Stepan Salenikovich's avatar
Stepan Salenikovich committed
45
// Ring client
46
#include "config.h"
47
#include "newaccountsettingsview.h"
aviau's avatar
aviau committed
48 49
#include "accountmigrationview.h"
#include "accountcreationwizard.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
50
#include "chatview.h"
51 52 53 54 55 56 57 58
#include "conversationsview.h"
#include "currentcallview.h"
#include "dialogs.h"
#include "generalsettingsview.h"
#include "incomingcallview.h"
#include "mediasettingsview.h"
#include "models/gtkqtreemodel.h"
#include "ringwelcomeview.h"
59
#include "utils/files.h"
60
#include "ringnotify.h"
61
#include "accountinfopointer.h"
62
#include "native/pixbufmanipulator.h"
63
#include "ringnotify.h"
64

65 66 67 68
#if USE_LIBNM
#include <NetworkManager.h>
#endif

69 70
//==============================================================================

71
namespace { namespace details
72 73
{
class CppImpl;
74
}}
75 76 77 78 79 80 81 82 83 84 85

struct _RingMainWindow
{
    GtkApplicationWindow parent;
};

struct _RingMainWindowClass
{
    GtkApplicationWindowClass parent_class;
};

86
struct RingMainWindowPrivate
87
{
88 89 90 91 92
    GtkWidget *ring_menu;
    GtkWidget *image_ring;
    GtkWidget *ring_settings;
    GtkWidget *image_settings;
    GtkWidget *hbox_settings;
93
    GtkWidget *notebook_contacts;
94
    GtkWidget *scrolled_window_smartview;
95
    GtkWidget *treeview_conversations;
96
    GtkWidget *vbox_left_pane;
97
    GtkWidget *search_entry;
98
    GtkWidget *stack_main_view;
99
    GtkWidget *vbox_call_view;
100 101
    GtkWidget *frame_call;
    GtkWidget *welcome_view;
102
    GtkWidget *button_new_conversation;
103
    GtkWidget *media_settings_view;
104
    GtkWidget *new_account_settings_view;
105
    GtkWidget *general_settings_view;
106
    GtkWidget *last_settings_view;
107
    GtkWidget *radiobutton_new_account_settings;
108
    GtkWidget *radiobutton_general_settings;
109
    GtkWidget *radiobutton_media_settings;
110 111
    GtkWidget *account_creation_wizard;
    GtkWidget *account_migration_view;
112
    GtkWidget *combobox_account_selector;
113 114
    GtkWidget *treeview_contact_requests;
    GtkWidget *scrolled_window_contact_requests;
115
    GtkWidget *webkit_chat_container; ///< The webkit_chat_container is created once, then reused for all chat views
116

117 118
    GtkWidget *notifier;

119
    GSettings *settings;
120

121
    details::CppImpl* cpp; ///< Non-UI and C++ only code
122 123

    gulong update_download_folder;
124 125 126 127 128
    gulong notif_chat_view;
    gulong notif_accept_pending;
    gulong notif_refuse_pending;
    gulong notif_accept_call;
    gulong notif_decline_call;
129
    gboolean set_top_account_flag = true;
130 131 132 133 134 135 136

    GCancellable *cancellable;
#if USE_LIBNM
    /* NetworkManager */
    NMClient *nm_client;
    NMActiveConnection *primary_connection;
#endif
137 138 139 140 141 142
};

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))

143 144
//==============================================================================

145
namespace { namespace details
146 147 148 149 150 151 152
{

static constexpr const char* CALL_VIEW_NAME                    = "calls";
static constexpr const char* ACCOUNT_CREATION_WIZARD_VIEW_NAME = "account-creation-wizard";
static constexpr const char* ACCOUNT_MIGRATION_VIEW_NAME       = "account-migration-view";
static constexpr const char* GENERAL_SETTINGS_VIEW_NAME        = "general";
static constexpr const char* MEDIA_SETTINGS_VIEW_NAME          = "media";
153
static constexpr const char* NEW_ACCOUNT_SETTINGS_VIEW_NAME    = "account";
154

155 156 157 158 159 160 161
inline namespace helpers
{

/**
 * set the column value by printing the alias and the state of an account in combobox_account_selector.
 */
static void
162
print_account_and_state(GtkCellLayout*,
163 164 165
                        GtkCellRenderer* cell,
                        GtkTreeModel* model,
                        GtkTreeIter* iter,
166
                        gpointer*)
167
{
168
    gchar *id;
169
    gchar *alias;
170 171
    gchar *registeredName;
    gchar *uri;
172 173 174
    gchar *text;

    gtk_tree_model_get (model, iter,
175 176 177 178
                        0 /* col# */, &id /* data */,
                        3 /* col# */, &uri /* data */,
                        4 /* col# */, &alias /* data */,
                        5 /* col# */, &registeredName /* data */,
179 180
                        -1);

181 182 183
    if (g_strcmp0("", id) == 0) {
        text = g_markup_printf_escaped(
            "<span font=\"10\">%s</span>",
184
            _("Add account…")
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
        );
    } else if (g_strcmp0("", registeredName) == 0) {
        if (g_strcmp0(uri, alias) == 0) {
            text = g_markup_printf_escaped(
                "<span font=\"10\">%s</span>",
                alias
            );
        } else {
            text = g_markup_printf_escaped(
                "<span font=\"10\">%s</span>\n<span font=\"7\">%s</span>",
                alias, uri
            );
        }
    } else {
        if (g_strcmp0(alias, registeredName) == 0) {
            text = g_markup_printf_escaped(
                "<span font=\"10\">%s</span>",
                alias
            );
        } else {
            text = g_markup_printf_escaped(
                "<span font=\"10\">%s</span>\n<span font=\"7\">%s</span>",
                alias, registeredName
            );
        }
    }
211 212

    g_object_set(G_OBJECT(cell), "markup", text, NULL);
213 214
    g_object_set(G_OBJECT(cell), "height", 17, NULL);
    g_object_set(G_OBJECT(cell), "ypad", 0, NULL);
215 216

    g_free(id);
217
    g_free(alias);
218 219 220
    g_free(registeredName);
    g_free(uri);
    g_free(text);
221 222
}

223
static void
224
render_account_avatar(GtkCellLayout*,
225 226 227
                      GtkCellRenderer *cell,
                      GtkTreeModel *model,
                      GtkTreeIter *iter,
228
                      gpointer)
229 230 231
{
    gchar *id;
    gchar* avatar;
232
    gchar* status;
233 234 235

    gtk_tree_model_get (model, iter,
                        0 /* col# */, &id /* data */,
236
                        1 /* col# */, &status /* data */,
237 238 239 240
                        2 /* col# */, &avatar /* data */,
                        -1);

    if (g_strcmp0("", id) == 0) {
241
        g_free(status);
242
        g_free(avatar);
243 244
        g_free(id);

245
        GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/net/jami/JamiGnome/add-device", nullptr);
246 247 248 249 250 251
        g_object_set(G_OBJECT(cell), "width", 32, nullptr);
        g_object_set(G_OBJECT(cell), "height", 32, nullptr);
        g_object_set(G_OBJECT(cell), "pixbuf", icon, nullptr);
        return;
    }

252 253 254 255 256 257 258 259 260
    IconStatus iconStatus = IconStatus::INVALID;
    std::string statusStr = status? status : "";
    if (statusStr == "DISCONNECTED") {
        iconStatus = IconStatus::DISCONNECTED;
    } else if (statusStr == "TRYING") {
        iconStatus = IconStatus::TRYING;
    } else if (statusStr == "CONNECTED") {
        iconStatus = IconStatus::CONNECTED;
    }
261
    auto default_avatar = Interfaces::PixbufManipulator().generateAvatar("", "");
262
    auto default_scaled = Interfaces::PixbufManipulator().scaleAndFrame(default_avatar.get(), QSize(32, 32), true, iconStatus);
263
    auto photo = default_scaled;
264

265 266 267 268
    std::string photostr = avatar;
    if (!photostr.empty()) {
        QByteArray byteArray(photostr.c_str(), photostr.length());
        QVariant avatar = Interfaces::PixbufManipulator().personPhoto(byteArray);
269
        auto pixbuf_photo = Interfaces::PixbufManipulator().scaleAndFrame(avatar.value<std::shared_ptr<GdkPixbuf>>().get(), QSize(32, 32), true, iconStatus);
270 271 272 273 274 275 276 277 278
        if (avatar.isValid()) {
            photo = pixbuf_photo;
        }
    }

    g_object_set(G_OBJECT(cell), "width", 32, nullptr);
    g_object_set(G_OBJECT(cell), "height", 32, nullptr);
    g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), nullptr);

279
    g_free(status);
280
    g_free(avatar);
281
    g_free(id);
282 283
}

284
inline static void
285 286
foreachLrcAccount(const lrc::api::Lrc& lrc,
                  const std::function<void(const lrc::api::account::Info&)>& func)
287
{
288 289 290 291
    auto& account_model = lrc.getAccountModel();
    for (const auto& account_id : account_model.getAccountList()) {
        const auto& account_info = account_model.getAccountInfo(account_id);
            func(account_info);
292 293 294 295 296
    }
}

} // namespace helpers

297 298 299 300
class CppImpl
{
public:
    explicit CppImpl(RingMainWindow& widget);
301
    ~CppImpl();
302

303
    void init();
304
    void updateLrc(const std::string& accountId, const std::string& accountIdToFlagFreeable = "");
305
    void changeView(GType type, lrc::api::conversation::Info conversation = {});
306 307 308
    void enterFullScreen();
    void leaveFullScreen();
    void toggleFullScreen();
309
    void resetToWelcome();
310
    void refreshPendingContactRequestTab();
311 312
    void changeAccountSelection(const std::string& id);
    void onAccountSelectionChange(const std::string& id);
313
    void enterAccountCreationWizard(bool showControls = false);
314 315 316
    void leaveAccountCreationWizard();
    void enterSettingsView();
    void leaveSettingsView();
317

318 319
    lrc::api::conversation::Info getCurrentConversation(GtkWidget* frame_call);

320
    void showAccountSelectorWidget(bool show = true);
321
    std::size_t refreshAccountSelectorWidget(int selection_row = -1, const std::string& selected = "");
322 323 324

    WebKitChatContainer* webkitChatContainer() const;

325
    RingMainWindow* self = nullptr; // The GTK widget itself
326
    RingMainWindowPrivate* widgets = nullptr;
327

328
    std::unique_ptr<lrc::api::Lrc> lrc_;
329
    AccountInfoPointer accountInfo_ = nullptr;
330
    AccountInfoPointer accountInfoForMigration_ = nullptr;
331
    std::unique_ptr<lrc::api::conversation::Info> chatViewConversation_;
332 333 334
    lrc::api::profile::Type currentTypeFilter_;
    bool show_settings = false;
    bool is_fullscreen = false;
335
    bool has_cleared_all_history = false;
336

337 338 339
    int smartviewPageNum = 0;
    int contactRequestsPageNum = 0;

340
    QMetaObject::Connection showChatViewConnection_;
341
    QMetaObject::Connection showLeaveMessageViewConnection_;
342 343
    QMetaObject::Connection showCallViewConnection_;
    QMetaObject::Connection showIncomingViewConnection_;
344 345 346 347
    QMetaObject::Connection newTrustRequestNotification_;
    QMetaObject::Connection closeTrustRequestNotification_;
    QMetaObject::Connection slotNewInteraction_;
    QMetaObject::Connection slotReadInteraction_;
348 349 350
    QMetaObject::Connection changeAccountConnection_;
    QMetaObject::Connection newAccountConnection_;
    QMetaObject::Connection rmAccountConnection_;
351
    QMetaObject::Connection invalidAccountConnection_;
352 353
    QMetaObject::Connection historyClearedConnection_;
    QMetaObject::Connection modelSortedConnection_;
354 355
    QMetaObject::Connection callChangedConnection_;
    QMetaObject::Connection newIncomingCallConnection_;
356 357 358 359
    QMetaObject::Connection filterChangedConnection_;
    QMetaObject::Connection newConversationConnection_;
    QMetaObject::Connection conversationRemovedConnection_;
    QMetaObject::Connection accountStatusChangedConnection_;
360
    QMetaObject::Connection profileUpdatedConnection_;
361

362
private:
363 364 365 366
    CppImpl() = delete;
    CppImpl(const CppImpl&) = delete;
    CppImpl& operator=(const CppImpl&) = delete;

367
    GtkWidget* displayWelcomeView(lrc::api::conversation::Info);
368 369 370 371
    GtkWidget* displayIncomingView(lrc::api::conversation::Info);
    GtkWidget* displayCurrentCallView(lrc::api::conversation::Info);
    GtkWidget* displayChatView(lrc::api::conversation::Info);

372 373 374
    // Callbacks used as LRC Qt slot
    void slotAccountAddedFromLrc(const std::string& id);
    void slotAccountRemovedFromLrc(const std::string& id);
375
    void slotInvalidAccountFromLrc(const std::string& id);
376 377 378
    void slotAccountStatusChanged(const std::string& id);
    void slotConversationCleared(const std::string& uid);
    void slotModelSorted();
379 380
    void slotNewIncomingCall(const std::string& callId);
    void slotCallStatusChanged(const std::string& callId);
381 382 383 384
    void slotFilterChanged();
    void slotNewConversation(const std::string& uid);
    void slotConversationRemoved(const std::string& uid);
    void slotShowChatView(const std::string& id, lrc::api::conversation::Info origin);
385
    void slotShowLeaveMessageView(lrc::api::conversation::Info conv);
386 387
    void slotShowCallView(const std::string& id, lrc::api::conversation::Info origin);
    void slotShowIncomingCallView(const std::string& id, lrc::api::conversation::Info origin);
388 389 390 391 392
    void slotNewTrustRequest(const std::string& id, const std::string& contactUri);
    void slotCloseTrustRequest(const std::string& id, const std::string& contactUri);
    void slotNewInteraction(const std::string& accountId, const std::string& conversation,
                                uint64_t, const lrc::api::interaction::Info& interaction);
    void slotCloseInteraction(const std::string& accountId, const std::string& conversation, uint64_t);
393
    void slotProfileUpdated(const std::string& id);
394 395
};

396 397
inline namespace gtk_callbacks
{
398
static void
399
on_video_double_clicked(RingMainWindow* self)
400 401 402 403 404
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    priv->cpp->toggleFullScreen();
}
aviau's avatar
aviau committed
405

406
static void
407
on_hide_view_clicked(RingMainWindow* self)
408 409
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
410
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
411
    priv->cpp->resetToWelcome();
412 413
}

414 415 416 417 418 419 420
static void
on_account_creation_completed(RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    priv->cpp->leaveAccountCreationWizard();
}
421

422
static void
423
on_account_changed(RingMainWindow* self)
424
{
425 426
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
427 428 429 430
    auto changeTopAccount = priv->set_top_account_flag;
    if (!priv->set_top_account_flag) {
        priv->set_top_account_flag = true;
    }
431

432 433
    auto accountComboBox = GTK_COMBO_BOX(priv->combobox_account_selector);
    auto model = gtk_combo_box_get_model(accountComboBox);
434

435 436 437 438
    GtkTreeIter iter;
    if (gtk_combo_box_get_active_iter(accountComboBox, &iter)) {
        gchar* accountId;
        gtk_tree_model_get(model, &iter, 0 /* col# */, &accountId /* data */, -1);
439 440 441 442
        if (g_strcmp0("", accountId) == 0) {
            priv->cpp->enterAccountCreationWizard(true);
        } else {
            priv->cpp->leaveAccountCreationWizard();
443
            if (priv->cpp->accountInfo_ && changeTopAccount) {
444 445
                priv->cpp->accountInfo_->accountModel->setTopAccount(accountId);
            }
446 447 448 449
            priv->cpp->onAccountSelectionChange(accountId);
            gtk_notebook_set_show_tabs(GTK_NOTEBOOK(priv->notebook_contacts),
                priv->cpp->accountInfo_->contactModel->hasPendingRequests());
        }
450 451 452
        g_free(accountId);
    }
}
453

454 455 456 457 458
static void
on_settings_clicked(RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
459

460 461 462 463
    if (!priv->cpp->show_settings)
        priv->cpp->enterSettingsView();
    else
        priv->cpp->leaveSettingsView();
464
}
465

466
static void
467 468 469 470 471 472 473 474 475 476 477 478 479
on_show_media_settings(GtkToggleButton* navbutton, RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    if (gtk_toggle_button_get_active(navbutton)) {
        media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), TRUE);
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), MEDIA_SETTINGS_VIEW_NAME);
        priv->last_settings_view = priv->media_settings_view;
    } else {
        media_settings_view_show_preview(MEDIA_SETTINGS_VIEW(priv->media_settings_view), FALSE);
    }
}
aviau's avatar
aviau committed
480

481

482
static void
483
on_show_new_account_settings(GtkToggleButton* navbutton, RingMainWindow* self)
484
{
485 486
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
487

488
    if (gtk_toggle_button_get_active(navbutton)) {
489 490 491 492 493
        new_account_settings_view_show(NEW_ACCOUNT_SETTINGS_VIEW(priv->new_account_settings_view), TRUE);
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), NEW_ACCOUNT_SETTINGS_VIEW_NAME);
        priv->last_settings_view = priv->new_account_settings_view;
    } else {
        new_account_settings_view_show(NEW_ACCOUNT_SETTINGS_VIEW(priv->new_account_settings_view), FALSE);
494 495
    }
}
496

497
static void
498
on_show_general_settings(GtkToggleButton* navbutton, RingMainWindow* self)
499
{
500 501
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
502

503 504 505
    if (gtk_toggle_button_get_active(navbutton)) {
        gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), GENERAL_SETTINGS_VIEW_NAME);
        priv->last_settings_view = priv->general_settings_view;
506
    }
507
}
508

509 510 511 512 513
static void
on_search_entry_text_changed(GtkSearchEntry* search_entry, RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
514

515 516
    // Filter model
    const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
517
    priv->cpp->accountInfo_->conversationModel->setFilter(text);
518
}
519

520
static void
521
on_search_entry_activated(RingMainWindow* self)
522
{
523 524
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
525

526
    // Select the first conversation of the list
527
    auto& conversationModel = priv->cpp->accountInfo_->conversationModel;
528
    auto conversations = conversationModel->allFilteredConversations();
529 530 531 532

    const gchar *text = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));

    if (!conversations.empty() && text && !std::string(text).empty())
533 534
        conversationModel->selectConversation(conversations[0].uid);
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
535

536 537 538 539 540 541 542 543
static gboolean
on_search_entry_key_released(G_GNUC_UNUSED GtkEntry* search_entry, GdkEventKey* key, RingMainWindow* self)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    // if esc key pressed, clear the regex (keep the text, the user might not want to actually delete it)
    if (key->keyval == GDK_KEY_Escape) {
544
        priv->cpp->accountInfo_->conversationModel->setFilter("");
545
        return GDK_EVENT_STOP;
546
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
547

548 549
    return GDK_EVENT_PROPAGATE;
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
550

551 552 553 554
static gboolean
on_dtmf_pressed(RingMainWindow* self, GdkEventKey* event, gpointer user_data)
{
    (void)user_data;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
555

556 557 558 559 560 561 562 563 564 565 566 567 568
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    g_return_val_if_fail(event->type == GDK_KEY_PRESS, GDK_EVENT_PROPAGATE);
    g_return_val_if_fail(priv, 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(self));
    if (GTK_IS_ENTRY(focus))
        return GDK_EVENT_PROPAGATE;

569 570
    if (priv->cpp->accountInfo_ &&
        priv->cpp->accountInfo_->profileInfo.type != lrc::api::profile::Type::SIP)
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
        return GDK_EVENT_PROPAGATE;

    /* 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;

    // Get current conversation
    auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
    lrc::api::conversation::Info current_item;
    if (IS_CURRENT_CALL_VIEW(current_view))
       current_item = current_call_view_get_conversation(CURRENT_CALL_VIEW(current_view));
    else
       return GDK_EVENT_PROPAGATE;

    if (current_item.callId.empty())
       return GDK_EVENT_PROPAGATE;

    // 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);
595
    g_debug("attempting to play DTMF tone during ongoing call: %s", val.toUtf8().constData());
596
    priv->cpp->accountInfo_->callModel->playDTMF(current_item.callId, val.toStdString());
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
597
    // always propagate the key, so we don't steal accelerators/shortcuts
598
    return GDK_EVENT_PROPAGATE;
599
}
600

601
static void
602
on_tab_changed(GtkNotebook* notebook, GtkWidget* page, guint page_num, RingMainWindow* self)
603
{
604 605
    (void)notebook;
    (void)page;
606

607 608 609
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

610
    auto newType = page_num == 0 ? priv->cpp->accountInfo_->profileInfo.type : lrc::api::profile::Type::PENDING;
611 612
    if (priv->cpp->currentTypeFilter_ != newType) {
        priv->cpp->currentTypeFilter_ = newType;
613
        priv->cpp->accountInfo_->conversationModel->setFilter(priv->cpp->currentTypeFilter_);
614
    }
615
}
616

617 618 619 620
static gboolean
on_window_size_changed(GtkWidget* self, GdkEventConfigure* event, gpointer user_data)
{
    (void)user_data;
621

622 623 624 625 626 627 628
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    g_settings_set_int(priv->settings, "window-width", event->width);
    g_settings_set_int(priv->settings, "window-height", event->height);

    return GDK_EVENT_PROPAGATE;
629
}
630

631
static void
632
on_search_entry_places_call_changed(GSettings* settings, const gchar* key, RingMainWindow* self)
633
{
634 635
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
636

637 638 639 640 641 642 643
    if (g_settings_get_boolean(settings, key)) {
        gtk_widget_set_tooltip_text(priv->button_new_conversation,
                                    C_("button next to search entry will place a new call",
                                       "place call"));
    } else {
        gtk_widget_set_tooltip_text(priv->button_new_conversation,
                                    C_("button next to search entry will open chat", "open chat"));
644
    }
645
}
646

647 648 649 650 651 652 653 654 655 656
static void
on_handle_account_migrations(RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    /* If there is an existing migration view, remove it */
    if (priv->account_migration_view) {
        gtk_widget_destroy(priv->account_migration_view);
        priv->account_migration_view = nullptr;
657
    }
658

659 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
    auto accounts = priv->cpp->lrc_->getAccountModel().getAccountList();
    for (const auto& accountId : accounts) {
        priv->cpp->accountInfoForMigration_ = &priv->cpp->lrc_->getAccountModel().getAccountInfo(accountId);
        if (priv->cpp->accountInfoForMigration_->status == lrc::api::account::Status::ERROR_NEED_MIGRATION) {
            priv->account_migration_view = account_migration_view_new(priv->cpp->accountInfoForMigration_);
            g_signal_connect_swapped(priv->account_migration_view, "account-migration-completed",
                                     G_CALLBACK(on_handle_account_migrations), self);
            g_signal_connect_swapped(priv->account_migration_view, "account-migration-failed",
                                     G_CALLBACK(on_handle_account_migrations), self);

            gtk_widget_hide(priv->ring_settings);
            priv->cpp->showAccountSelectorWidget(false);
            gtk_widget_show(priv->account_migration_view);
            gtk_stack_add_named(
                GTK_STACK(priv->stack_main_view),
                priv->account_migration_view,
                ACCOUNT_MIGRATION_VIEW_NAME);
            gtk_stack_set_visible_child_name(
                GTK_STACK(priv->stack_main_view),
                ACCOUNT_MIGRATION_VIEW_NAME);
            return;
        }
    }

    priv->cpp->accountInfoForMigration_ = nullptr;
    for (const auto& accountId : accounts) {
        auto* accountInfo = &priv->cpp->lrc_->getAccountModel().getAccountInfo(accountId);
        if (accountInfo->enabled) {
            priv->cpp->updateLrc(accountId);
        }
689
    }
690 691
    gtk_widget_show(priv->ring_settings);
    priv->cpp->showAccountSelectorWidget();
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 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
enum class Action {
    SELECT,
    ACCEPT,
    REFUSE
};

static void
action_notification(gchar* title, RingMainWindow* self, Action action)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && title);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    if (!priv->cpp->accountInfo_) {
        g_warning("Notification clicked but accountInfo_ currently empty");
        return;
    }

    std::string titleStr = title;
    auto firstMarker = titleStr.find(":");
    if (firstMarker == std::string::npos) return;
    auto secondMarker = titleStr.find(":", firstMarker + 1);
    if (secondMarker == std::string::npos) return;

    auto id = titleStr.substr(0, firstMarker);
    auto type = titleStr.substr(firstMarker + 1, secondMarker - firstMarker - 1);
    auto information = titleStr.substr(secondMarker + 1);

    if (action == Action::SELECT) {
        // Select conversation
        if (priv->cpp->show_settings) {
            priv->cpp->leaveSettingsView();
        }

        if (priv->cpp->accountInfo_->id != id) {
            priv->cpp->updateLrc(id);
        }

        if (type == "interaction") {
            priv->cpp->accountInfo_->conversationModel->selectConversation(information);
            conversations_view_select_conversation(CONVERSATIONS_VIEW(priv->treeview_conversations), information);
        } else if (type == "request") {
            for (const auto& conversation : priv->cpp->accountInfo_->conversationModel->getFilteredConversations(lrc::api::profile::Type::PENDING)) {
                auto contactRequestsPageNum = gtk_notebook_page_num(GTK_NOTEBOOK(priv->notebook_contacts),
                                                               priv->scrolled_window_contact_requests);
                gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook_contacts), contactRequestsPageNum);
                if (!conversation.participants.empty() && conversation.participants.front() == information) {
                    priv->cpp->accountInfo_->conversationModel->selectConversation(conversation.uid);
                }
                conversations_view_select_conversation(CONVERSATIONS_VIEW(priv->treeview_conversations), conversation.uid);
            }
        }
    } else {
        // accept or refuse notifiation
        try {
            auto& accountInfo = priv->cpp->lrc_->getAccountModel().getAccountInfo(id);
            for (const auto& conversation : accountInfo.conversationModel->getFilteredConversations(lrc::api::profile::Type::PENDING)) {
                if (!conversation.participants.empty() && conversation.participants.front() == information) {
                    if (action == Action::ACCEPT) {
                        accountInfo.conversationModel->makePermanent(conversation.uid);
                    } else {
                        accountInfo.conversationModel->removeConversation(conversation.uid);
                    }
                }
            }
        } catch (const std::out_of_range& e) {
758
            g_warning("Can't get account %s: %s", id.c_str(), e.what());
759 760 761 762 763 764
        }
    }

}

static void
765 766
on_notification_chat_clicked(G_GNUC_UNUSED GtkWidget* notifier,
                             gchar *title, RingMainWindow* self)
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783
{
    action_notification(title, self, Action::SELECT);
}

static void
on_notification_accept_pending(GtkWidget*, gchar *title, RingMainWindow* self)
{
    action_notification(title, self, Action::ACCEPT);
}

static void
on_notification_refuse_pending(GtkWidget*, gchar *title, RingMainWindow* self)
{
    action_notification(title, self, Action::REFUSE);
}

static void
784
on_notification_accept_call(GtkWidget*, gchar *title, RingMainWindow* self)
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && title);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    if (!priv->cpp->accountInfo_) {
        g_warning("Notification clicked but accountInfo_ currently empty");
        return;
    }

    std::string titleStr = title;
    auto firstMarker = titleStr.find(":");
    if (firstMarker == std::string::npos) return;
    auto secondMarker = titleStr.find(":", firstMarker + 1);
    if (secondMarker == std::string::npos) return;

    auto id = titleStr.substr(0, firstMarker);
    auto type = titleStr.substr(firstMarker + 1, secondMarker - firstMarker - 1);
    auto information = titleStr.substr(secondMarker + 1);

    if (priv->cpp->show_settings) {
        priv->cpp->leaveSettingsView();
    }

    try {
        auto& accountInfo = priv->cpp->lrc_->getAccountModel().getAccountInfo(id);
        accountInfo.callModel->accept(information);
    } catch (const std::out_of_range& e) {
811
        g_warning("Can't get account %s: %s", id.c_str(), e.what());
812 813 814 815
    }
}

static void
816
on_notification_decline_call(GtkWidget*, gchar *title, RingMainWindow* self)
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && title);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    if (!priv->cpp->accountInfo_) {
        g_warning("Notification clicked but accountInfo_ currently empty");
        return;
    }

    std::string titleStr = title;
    auto firstMarker = titleStr.find(":");
    if (firstMarker == std::string::npos) return;
    auto secondMarker = titleStr.find(":", firstMarker + 1);
    if (secondMarker == std::string::npos) return;

    auto id = titleStr.substr(0, firstMarker);
    auto type = titleStr.substr(firstMarker + 1, secondMarker - firstMarker - 1);
    auto information = titleStr.substr(secondMarker + 1);

    try {
        auto& accountInfo = priv->cpp->lrc_->getAccountModel().getAccountInfo(id);
        accountInfo.callModel->hangUp(information);
    } catch (const std::out_of_range& e) {
839
        g_warning("Can't get account %s: %s", id.c_str(), e.what());
840 841 842
    }
}

843
} // namespace gtk_callbacks
aviau's avatar
aviau committed
844

845 846
CppImpl::CppImpl(RingMainWindow& widget)
    : self {&widget}
847
    , widgets {RING_MAIN_WINDOW_GET_PRIVATE(&widget)}
848
    , lrc_ {std::make_unique<lrc::api::Lrc>()}
849
{}
850

851
static gboolean
852
on_clear_all_history_foreach(GtkTreeModel *model, GtkTreePath*, GtkTreeIter *iter, gpointer self)
853 854 855 856
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), TRUE);

    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
857
    gchar* account_id;
858 859 860 861 862 863

    gtk_tree_model_get(model, iter, 0 /* col# */, &account_id /* data */, -1);

    auto& accountInfo = priv->cpp->lrc_->getAccountModel().getAccountInfo(account_id);
    accountInfo.conversationModel->clearAllHistory();

864
    g_free(account_id);
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880
    return FALSE;
}

static void
on_clear_all_history_clicked(RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    auto accountComboBox = GTK_COMBO_BOX(priv->combobox_account_selector);
    auto model = gtk_combo_box_get_model(accountComboBox);

    gtk_tree_model_foreach (model, on_clear_all_history_foreach, self);

    priv->cpp->has_cleared_all_history = true;
}

881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
static void
update_data_transfer(lrc::api::DataTransferModel& model, GSettings* settings)
{
    char* download_directory_value;
    auto* download_directory_variant = g_settings_get_value(settings, "download-folder");
    g_variant_get(download_directory_variant, "&s", &download_directory_value);
    std::string download_dir = download_directory_value;
    if (!download_dir.empty() && download_dir.back() != '/')
        download_dir += "/";
    model.downloadDirectory = download_dir;
}

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

    update_data_transfer(priv->cpp->lrc_->getDataTransferModel(), priv->settings);
}

902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
#if USE_LIBNM

static void
log_connection_info(NMActiveConnection *connection)
{
    if (connection) {
        g_debug("primary network connection: %s, default: %s",
                nm_active_connection_get_uuid(connection),
                nm_active_connection_get_default(connection) ? "yes" : "no");
    } else {
        g_warning("no primary network connection detected, check network settings");
    }
}

static void
primary_connection_changed(NMClient *nm,  GParamSpec*, RingMainWindow* self)
{
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    auto connection = nm_client_get_primary_connection(nm);

    if (priv->primary_connection != connection) {
        /* make sure the connection really changed
         * on client start it seems to always emit the notify::primary-connection signal though it
         * hasn't changed */
        log_connection_info(connection);
        priv->primary_connection = connection;
        priv->cpp->lrc_->connectivityChanged();
    }
}

static void
nm_client_cb(G_GNUC_UNUSED GObject *source_object, GAsyncResult *result,  RingMainWindow* self)
{
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

    GError* error = nullptr;
    if (auto nm_client = nm_client_new_finish(result, &error)) {
        priv->nm_client = nm_client;
        g_debug("NetworkManager client initialized, version: %s\ndaemon running: %s\nnnetworking enabled: %s",
                nm_client_get_version(nm_client),
                nm_client_get_nm_running(nm_client) ? "yes" : "no",
                nm_client_networking_get_enabled(nm_client) ? "yes" : "no");

        auto connection = nm_client_get_primary_connection(nm_client);
        log_connection_info(connection);
        priv->primary_connection = connection;

        /* We monitor the primary connection and notify the daemon to re-load its connections
         * (accounts, UPnP, ...) when it changes. For example, on most systems, if we have an
         * ethernet connection and then also connect to wifi, the primary connection will not change;
         * however it will change in the opposite case because an ethernet connection is preferred.
         */
        g_signal_connect(nm_client, "notify::primary-connection", G_CALLBACK(primary_connection_changed), self);

    } else {
        g_warning("error initializing NetworkManager client: %s", error->message);
        g_clear_error(&error);
    }
}

#endif /* USE_LIBNM */

964 965 966
void
CppImpl::init()
{
967 968 969
    try {
        lrc_->getAVModel().deactivateOldVideoModels();
    } catch (...) {}
970 971 972 973 974
    widgets->cancellable = g_cancellable_new();
#if USE_LIBNM
     // monitor the network using libnm to notify the daemon about connectivity changes
     nm_client_new_async(widgets->cancellable, (GAsyncReadyCallback)nm_client_cb, self);
#endif
975

976 977 978 979 980 981
    smartviewPageNum = gtk_notebook_page_num(GTK_NOTEBOOK(widgets->notebook_contacts),
                                             widgets->scrolled_window_smartview);
    contactRequestsPageNum = gtk_notebook_page_num(GTK_NOTEBOOK(widgets->notebook_contacts),
                                                   widgets->scrolled_window_contact_requests);
    g_assert(smartviewPageNum != contactRequestsPageNum);

982 983 984 985
    // NOTE: When new models will be fully implemented, we need to move this
    // in rign_client.cpp->
    // Init LRC and the vew
    const auto accountIds = lrc_->getAccountModel().getAccountList();
986 987
    decltype(accountIds)::value_type activeAccountId; // non-empty if a enabled account is found below

988
    if (not accountIds.empty()) {
989
        on_handle_account_migrations(self);
990 991
        for (const auto& id : accountIds) {
            const auto& accountInfo = lrc_->getAccountModel().getAccountInfo(id);
992
            if (accountInfo.enabled) {
993
                activeAccountId = id;
994 995 996
                break;
            }
        }
997 998 999 1000 1001 1002
    }

    if (!activeAccountId.empty()) {
        updateLrc(activeAccountId);
    } else {
        // No enabled account: create empty widgets
1003
        widgets->treeview_conversations = conversations_view_new(accountInfo_);
1004
        gtk_container_add(GTK_CONTAINER(widgets->scrolled_window_smartview), widgets->treeview_conversations);
1005
        widgets->treeview_contact_requests = conversations_view_new(accountInfo_);
Guillaume Roguez's avatar