mainwindow.cpp 115 KB
Newer Older
1
/*
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
 *
 *  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.
 */

21
#include "mainwindow.h"
22

23
// GTK+ related
24
#include <glib/gi18n.h>
25 26
// std
#include <algorithm>
27
#include <sstream>
28

29 30 31
// Qt
#include <QSize>

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

46
// Jami Client
47
#include "config.h"
48
#include "newaccountsettingsview.h"
aviau's avatar
aviau committed
49 50
#include "accountmigrationview.h"
#include "accountcreationwizard.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
51
#include "chatview.h"
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"
59
#include "welcomeview.h"
60
#include "utils/drawing.h"
61
#include "utils/files.h"
62
#include "notifier.h"
63
#include "accountinfopointer.h"
64
#include "native/pixbufmanipulator.h"
65
#include "notifier.h"
66

67 68 69 70
#if USE_LIBNM
#include <NetworkManager.h>
#endif

71 72
//==============================================================================

73
namespace { namespace details
74 75
{
class CppImpl;
76
}}
77

78
struct _MainWindow
79 80 81 82
{
    GtkApplicationWindow parent;
};

83
struct _MainWindowClass
84 85 86 87
{
    GtkApplicationWindowClass parent_class;
};

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

121 122
    GtkWidget *notifier;

123
    GSettings *window_settings;
124
    bool useDarkTheme {false};
125

126
    details::CppImpl* cpp; ///< Non-UI and C++ only code
127 128

    gulong update_download_folder;
129 130 131 132 133
    gulong notif_chat_view;
    gulong notif_accept_pending;
    gulong notif_refuse_pending;
    gulong notif_accept_call;
    gulong notif_decline_call;
134
    gboolean set_top_account_flag = true;
135
    gboolean is_fullscreen_main_win = false;
136
    gboolean key_pressed = false;
137 138

    GCancellable *cancellable;
139 140

    GtkWidget* migratingDialog_;
141 142 143 144
#if USE_LIBNM
    /* NetworkManager */
    NMClient *nm_client;
#endif
145 146
};

147
G_DEFINE_TYPE_WITH_PRIVATE(MainWindow, main_window, GTK_TYPE_APPLICATION_WINDOW);
148

149
#define MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), MAIN_WINDOW_TYPE, MainWindowPrivate))
150

151 152
//==============================================================================

153
namespace { namespace details
154 155 156 157 158 159 160
{

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";
161
static constexpr const char* NEW_ACCOUNT_SETTINGS_VIEW_NAME    = "account";
162

163 164 165 166 167 168 169
inline namespace helpers
{

/**
 * set the column value by printing the alias and the state of an account in combobox_account_selector.
 */
static void
170
print_account_and_state(GtkCellLayout*,
171 172 173
                        GtkCellRenderer* cell,
                        GtkTreeModel* model,
                        GtkTreeIter* iter,
174
                        gpointer*)
175
{
176
    gchar *id;
177
    gchar *alias;
178 179
    gchar *registeredName;
    gchar *uri;
180 181 182
    gchar *text;

    gtk_tree_model_get (model, iter,
183 184 185 186
                        0 /* col# */, &id /* data */,
                        3 /* col# */, &uri /* data */,
                        4 /* col# */, &alias /* data */,
                        5 /* col# */, &registeredName /* data */,
187 188
                        -1);

189 190 191
    if (g_strcmp0("", id) == 0) {
        text = g_markup_printf_escaped(
            "<span font=\"10\">%s</span>",
192
            _("Add account…")
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
        );
    } 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
            );
        }
    }
219 220

    g_object_set(G_OBJECT(cell), "markup", text, NULL);
221 222
    g_object_set(G_OBJECT(cell), "height", 17, NULL);
    g_object_set(G_OBJECT(cell), "ypad", 0, NULL);
223 224

    g_free(id);
225
    g_free(alias);
226 227 228
    g_free(registeredName);
    g_free(uri);
    g_free(text);
229 230
}

231
static void
232
render_account_avatar(GtkCellLayout*,
233 234 235
                      GtkCellRenderer *cell,
                      GtkTreeModel *model,
                      GtkTreeIter *iter,
236
                      gpointer)
237 238 239
{
    gchar *id;
    gchar* avatar;
240
    gchar* status;
241 242 243

    gtk_tree_model_get (model, iter,
                        0 /* col# */, &id /* data */,
244
                        1 /* col# */, &status /* data */,
245 246 247 248
                        2 /* col# */, &avatar /* data */,
                        -1);

    if (g_strcmp0("", id) == 0) {
249
        g_free(status);
250
        g_free(avatar);
251 252
        g_free(id);

253
        GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/net/jami/JamiGnome/add-device", nullptr);
254 255 256 257 258 259
        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;
    }

260 261 262 263 264 265 266 267 268
    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;
    }
269
    auto default_avatar = Interfaces::PixbufManipulator().generateAvatar("", "");
270
    auto default_scaled = Interfaces::PixbufManipulator().scaleAndFrame(default_avatar.get(), QSize(32, 32), true, iconStatus);
271
    auto photo = default_scaled;
272

273 274 275 276
    std::string photostr = avatar;
    if (!photostr.empty()) {
        QByteArray byteArray(photostr.c_str(), photostr.length());
        QVariant avatar = Interfaces::PixbufManipulator().personPhoto(byteArray);
277
        auto pixbuf_photo = Interfaces::PixbufManipulator().scaleAndFrame(avatar.value<std::shared_ptr<GdkPixbuf>>().get(), QSize(32, 32), true, iconStatus);
278 279 280 281 282 283 284 285 286
        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);

287
    g_free(status);
288
    g_free(avatar);
289
    g_free(id);
290 291
}

292
inline static void
293 294
foreachLrcAccount(const lrc::api::Lrc& lrc,
                  const std::function<void(const lrc::api::account::Info&)>& func)
295
{
296 297 298 299
    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);
300 301 302 303 304
    }
}

} // namespace helpers

305 306 307
class CppImpl
{
public:
308
    explicit CppImpl(MainWindow& widget);
309
    ~CppImpl();
310

311
    void init();
312
    void updateLrc(const std::string& accountId, const std::string& accountIdToFlagFreeable = "");
313
    void changeView(GType type, lrc::api::conversation::Info conversation = {});
314 315 316
    void enterFullScreen();
    void leaveFullScreen();
    void toggleFullScreen();
317
    void resetToWelcome();
318
    void refreshPendingContactRequestTab();
319 320
    void changeAccountSelection(const std::string& id);
    void onAccountSelectionChange(const std::string& id);
321
    void enterAccountCreationWizard(bool showControls = false);
322 323 324
    void leaveAccountCreationWizard();
    void enterSettingsView();
    void leaveSettingsView();
325

326 327 328 329
    int getCurrentUid();
    void forCurrentConversation(const std::function<void(const lrc::api::conversation::Info&)>& func);
    bool showOkCancelDialog(const std::string& title, const std::string& text);

330 331
    lrc::api::conversation::Info getCurrentConversation(GtkWidget* frame_call);

332
    void showAccountSelectorWidget(bool show = true);
333
    std::size_t refreshAccountSelectorWidget(int selection_row = -1, const std::string& selected = "");
334

335
    WebKitChatContainer* webkitChatContainer(bool redraw_webview = false);
336

337 338
    MainWindow* self = nullptr; // The GTK widget itself
    MainWindowPrivate* widgets = nullptr;
339

340
    std::unique_ptr<lrc::api::Lrc> lrc_;
341
    AccountInfoPointer accountInfo_ = nullptr;
342
    AccountInfoPointer accountInfoForMigration_ = nullptr;
343
    std::unique_ptr<lrc::api::conversation::Info> chatViewConversation_;
344 345 346
    lrc::api::profile::Type currentTypeFilter_;
    bool show_settings = false;
    bool is_fullscreen = false;
347
    bool has_cleared_all_history = false;
348
    guint inhibitionCookie = 0;
349

350 351 352
    int smartviewPageNum = 0;
    int contactRequestsPageNum = 0;

353
    QMetaObject::Connection showChatViewConnection_;
354
    QMetaObject::Connection showLeaveMessageViewConnection_;
355 356
    QMetaObject::Connection showCallViewConnection_;
    QMetaObject::Connection showIncomingViewConnection_;
357 358 359 360
    QMetaObject::Connection newTrustRequestNotification_;
    QMetaObject::Connection closeTrustRequestNotification_;
    QMetaObject::Connection slotNewInteraction_;
    QMetaObject::Connection slotReadInteraction_;
361 362 363
    QMetaObject::Connection changeAccountConnection_;
    QMetaObject::Connection newAccountConnection_;
    QMetaObject::Connection rmAccountConnection_;
364
    QMetaObject::Connection invalidAccountConnection_;
365 366
    QMetaObject::Connection historyClearedConnection_;
    QMetaObject::Connection modelSortedConnection_;
367
    QMetaObject::Connection callChangedConnection_;
368 369
    QMetaObject::Connection callStartedConnection_;
    QMetaObject::Connection callEndedConnection_;
370
    QMetaObject::Connection newIncomingCallConnection_;
371 372 373 374
    QMetaObject::Connection filterChangedConnection_;
    QMetaObject::Connection newConversationConnection_;
    QMetaObject::Connection conversationRemovedConnection_;
    QMetaObject::Connection accountStatusChangedConnection_;
375
    QMetaObject::Connection profileUpdatedConnection_;
376

377
    std::string eventUid_;
378
    std::string eventBody_;
379 380

    bool isCreatingAccount {false};
381
    QHash<QString, QMetaObject::Connection> pendingConferences_;
382
private:
383 384 385 386
    CppImpl() = delete;
    CppImpl(const CppImpl&) = delete;
    CppImpl& operator=(const CppImpl&) = delete;

387
    GtkWidget* displayWelcomeView(lrc::api::conversation::Info);
388 389 390
    GtkWidget* displayIncomingView(lrc::api::conversation::Info, bool redraw_webview);
    GtkWidget* displayCurrentCallView(lrc::api::conversation::Info, bool redraw_webview);
    GtkWidget* displayChatView(lrc::api::conversation::Info, bool redraw_webview);
391

392 393 394
    // Callbacks used as LRC Qt slot
    void slotAccountAddedFromLrc(const std::string& id);
    void slotAccountRemovedFromLrc(const std::string& id);
395
    void slotInvalidAccountFromLrc(const std::string& id);
396
    void slotAccountNeedsMigration(const std::string& id);
397 398 399
    void slotAccountStatusChanged(const std::string& id);
    void slotConversationCleared(const std::string& uid);
    void slotModelSorted();
400 401
    void slotNewIncomingCall(const std::string& callId);
    void slotCallStatusChanged(const std::string& callId);
402 403
    void slotCallStarted(const std::string& callId);
    void slotCallEnded(const std::string& callId);
404 405 406 407
    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);
408
    void slotShowLeaveMessageView(lrc::api::conversation::Info conv);
409 410
    void slotShowCallView(const std::string& id, lrc::api::conversation::Info origin);
    void slotShowIncomingCallView(const std::string& id, lrc::api::conversation::Info origin);
411 412 413
    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,
414 415
                                uint64_t interactionId, const lrc::api::interaction::Info& interaction);
    void slotCloseInteraction(const std::string& accountId, const std::string& conversation, uint64_t interactionId);
416
    void slotProfileUpdated(const std::string& id);
417 418
};

419 420
inline namespace gtk_callbacks
{
421
static void
422
on_video_double_clicked(MainWindow* self)
423
{
424 425
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
426 427
    priv->cpp->toggleFullScreen();
}
aviau's avatar
aviau committed
428

429
static void
430
on_hide_view_clicked(MainWindow* self)
431
{
432 433
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
434
    priv->cpp->resetToWelcome();
435 436
}

437
static gboolean
438
place_call_event(MainWindow* self)
439
{
440 441
    g_return_val_if_fail(IS_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
442 443 444 445 446 447 448 449

    if (!priv->cpp->eventUid_.empty())
        priv->cpp->accountInfo_->conversationModel->placeCall(priv->cpp->eventUid_);

    return G_SOURCE_REMOVE;
}

static void
450
on_place_call_clicked(G_GNUC_UNUSED GtkWidget*, gchar *uid, MainWindow* self)
451
{
452 453
    g_return_if_fail(IS_MAIN_WINDOW(self) && uid);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
454 455 456 457 458

    priv->cpp->eventUid_ = uid;
    g_idle_add((GSourceFunc)place_call_event, self);
}

459
static gboolean
460
add_conversation_event(MainWindow* self)
461
{
462 463
    g_return_val_if_fail(IS_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
464 465 466 467 468 469 470 471

    if (!priv->cpp->eventUid_.empty())
        priv->cpp->accountInfo_->conversationModel->makePermanent(priv->cpp->eventUid_);

    return G_SOURCE_REMOVE;
}

static void
472
on_add_conversation_clicked(G_GNUC_UNUSED GtkWidget*, gchar *uid, MainWindow* self)
473
{
474 475
    g_return_if_fail(IS_MAIN_WINDOW(self) && uid);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
476 477 478 479 480 481

    priv->cpp->eventUid_ = uid;
    g_idle_add((GSourceFunc)add_conversation_event, self);
}

static gboolean
482
send_text_event(MainWindow* self)
483
{
484 485
    g_return_val_if_fail(IS_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
486 487 488 489 490 491 492

    if (!priv->cpp->eventUid_.empty())
        priv->cpp->accountInfo_->conversationModel->sendMessage(priv->cpp->eventUid_, priv->cpp->eventBody_);

    return G_SOURCE_REMOVE;
}

493
gboolean
494
on_redraw(GtkWidget*, cairo_t*, MainWindow* self)
495
{
496 497
    g_return_val_if_fail(IS_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
498 499 500 501 502

    auto color = get_ambient_color(GTK_WIDGET(self));
    bool current_theme = (color.red + color.green + color.blue) / 3 < .5;
    conversations_view_set_theme(CONVERSATIONS_VIEW(priv->treeview_conversations), current_theme);
    if (priv->useDarkTheme != current_theme) {
503 504
        welcome_set_theme(WELCOME_VIEW(priv->welcome_view), current_theme);
        welcome_update_view(WELCOME_VIEW(priv->welcome_view));
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531

        gtk_image_set_from_resource(GTK_IMAGE(priv->image_contact_requests_list), current_theme?
            "/net/jami/JamiGnome/contact_requests_list_white" : "/net/jami/JamiGnome/contact_requests_list");
        gtk_image_set_from_resource(GTK_IMAGE(priv->image_conversations_list), current_theme?
            "/net/jami/JamiGnome/conversations_list_white" : "/net/jami/JamiGnome/conversations_list");

        priv->useDarkTheme = current_theme;

        std::stringstream background;
        background << "#" << std::hex << (int)(color.red * 256)
                          << (int)(color.green * 256)
                          << (int)(color.blue * 256);

        auto provider = gtk_css_provider_new();
        std::string background_search_entry = "background: " + background.str() + ";";
        std::string css_style = ".search-entry-style { border: 0; border-radius: 0; } \
        .spinner-style { border: 0; background: white; } \
        .new-conversation-style { border: 0; " + background_search_entry + " transition: all 0.3s ease; border-radius: 0; } \
        .new-conversation-style:hover {  background: " + (priv->useDarkTheme ? "#003b4e" : "#bae5f0") + "; }";
        gtk_css_provider_load_from_data(provider,
            css_style.c_str(),
            -1, nullptr
        );
        gtk_style_context_add_provider_for_screen(gdk_display_get_default_screen(gdk_display_get_default()),
                                                  GTK_STYLE_PROVIDER(provider),
                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    }
532
    return false;
533 534
}

535
static void
536
on_send_text_clicked(G_GNUC_UNUSED GtkWidget*, gchar* uid, gchar* body, MainWindow* self)
537
{
538 539
    g_return_if_fail(IS_MAIN_WINDOW(self) && uid);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
540 541 542 543 544 545

    priv->cpp->eventUid_ = uid;
    priv->cpp->eventBody_ = body;
    g_idle_add((GSourceFunc)send_text_event, self);
}

546
static gboolean
547
place_audio_call_event(MainWindow* self)
548
{
549 550
    g_return_val_if_fail(IS_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
551 552 553 554 555 556 557 558

    if (!priv->cpp->eventUid_.empty())
        priv->cpp->accountInfo_->conversationModel->placeAudioOnlyCall(priv->cpp->eventUid_);

    return G_SOURCE_REMOVE;
}

static void
559
on_place_audio_call_clicked(G_GNUC_UNUSED GtkWidget*, gchar *uid, MainWindow* self)
560
{
561 562
    g_return_if_fail(IS_MAIN_WINDOW(self) && uid);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
563 564 565 566 567

    priv->cpp->eventUid_ = uid;
    g_idle_add((GSourceFunc)place_audio_call_event, self);
}

568
static void
569
on_account_creation_completed(MainWindow* self)
570
{
571 572
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
573
    priv->cpp->isCreatingAccount = false;
574 575
    priv->cpp->leaveAccountCreationWizard();
}
576

577
static void
578
on_account_creation_unlock(MainWindow* self)
579
{
580 581
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
582 583 584 585
    priv->cpp->isCreatingAccount = false;
}

static void
586
on_account_creation_lock(MainWindow* self)
587
{
588 589
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
590 591 592
    priv->cpp->isCreatingAccount = true;
}

593
static void
594
on_account_changed(MainWindow* self)
595
{
596 597
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
598 599 600 601
    auto changeTopAccount = priv->set_top_account_flag;
    if (!priv->set_top_account_flag) {
        priv->set_top_account_flag = true;
    }
602

603 604
    auto accountComboBox = GTK_COMBO_BOX(priv->combobox_account_selector);
    auto model = gtk_combo_box_get_model(accountComboBox);
605

606 607 608 609
    GtkTreeIter iter;
    if (gtk_combo_box_get_active_iter(accountComboBox, &iter)) {
        gchar* accountId;
        gtk_tree_model_get(model, &iter, 0 /* col# */, &accountId /* data */, -1);
610 611 612
        if (g_strcmp0("", accountId) == 0) {
            priv->cpp->enterAccountCreationWizard(true);
        } else {
613
            if (!priv->cpp->isCreatingAccount) priv->cpp->leaveAccountCreationWizard();
614
            if (priv->cpp->accountInfo_ && changeTopAccount) {
615 616
                priv->cpp->accountInfo_->accountModel->setTopAccount(accountId);
            }
617 618 619 620
            priv->cpp->onAccountSelectionChange(accountId);
            gtk_notebook_set_show_tabs(GTK_NOTEBOOK(priv->notebook_contacts),
                priv->cpp->accountInfo_->contactModel->hasPendingRequests());
        }
621 622 623
        g_free(accountId);
    }
}
624

625
static void
626
on_settings_clicked(MainWindow* self)
627
{
628 629
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
630

631 632 633 634
    if (!priv->cpp->show_settings)
        priv->cpp->enterSettingsView();
    else
        priv->cpp->leaveSettingsView();
635
}
636

637
static void
638
on_show_media_settings(GtkToggleButton* navbutton, MainWindow* self)
639
{
640 641
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
642 643 644 645 646 647 648 649 650

    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
651

652

653
static void
654
on_show_new_account_settings(GtkToggleButton* navbutton, MainWindow* self)
655
{
656 657
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
658

659
    if (gtk_toggle_button_get_active(navbutton)) {
660 661 662 663 664
        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);
665 666
    }
}
667

668
static void
669
on_show_general_settings(GtkToggleButton* navbutton, MainWindow* self)
670
{
671 672
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
673

674 675 676
    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;
677
    }
678
}
679

680
static void
681
on_search_entry_text_changed(GtkSearchEntry* search_entry, MainWindow* self)
682
{
683 684
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
685

686 687
    // Filter model
    const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
688
    priv->cpp->accountInfo_->conversationModel->setFilter(text);
689
}
690

691
static void
692
on_search_entry_activated(MainWindow* self)
693
{
694 695
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
696

697
    // Select the first conversation of the list
698
    auto& conversationModel = priv->cpp->accountInfo_->conversationModel;
699
    auto conversations = conversationModel->allFilteredConversations();
700 701 702 703

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

    if (!conversations.empty() && text && !std::string(text).empty())
704 705
        conversationModel->selectConversation(conversations[0].uid);
}
706

707
static gboolean
708
on_search_entry_key_released(G_GNUC_UNUSED GtkEntry* search_entry, GdkEventKey* key, MainWindow* self)
709
{
710 711
    g_return_val_if_fail(IS_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
712 713 714

    // 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) {
715
        priv->cpp->accountInfo_->conversationModel->setFilter("");
716
        return GDK_EVENT_STOP;
717
    }
718

719 720
    return GDK_EVENT_PROPAGATE;
}
721

722 723 724 725 726 727 728 729 730
static gboolean
on_current_call_clicked(GtkWidget *widget, G_GNUC_UNUSED GdkEventButton *event)
{
    // once mouse is clicked, grab the focus
    gtk_widget_set_can_focus (widget, true);
    gtk_widget_grab_focus(widget);
    return GDK_EVENT_PROPAGATE;
}

731
static gboolean
732
on_dtmf_pressed(MainWindow* self, GdkEventKey* event, gpointer user_data)
733
{
734 735
    g_return_val_if_fail(IS_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
736

737
    if(priv->key_pressed && !(event->state & GDK_SHIFT_MASK)){
738
        return GDK_EVENT_PROPAGATE;
739
    }
740 741 742

    (void)user_data;

743 744 745 746 747 748 749 750 751 752
    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;

753 754
    if (priv->cpp->accountInfo_ &&
        priv->cpp->accountInfo_->profileInfo.type != lrc::api::profile::Type::SIP)
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
        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);
779
    g_debug("attempting to play DTMF tone during ongoing call: %s", val.toUtf8().constData());
780
    priv->cpp->accountInfo_->callModel->playDTMF(current_item.callId, val.toStdString());
781 782 783

    // set keyPressed to True
    priv->key_pressed = true;
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
784
    // always propagate the key, so we don't steal accelerators/shortcuts
785
    return GDK_EVENT_PROPAGATE;
786
}
787

788
static gboolean
789
on_dtmf_released(MainWindow* self, GdkEventKey* event, gpointer user_data)
790
{
791 792
    g_return_val_if_fail(IS_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
793 794 795 796
    priv->key_pressed = false;
    return GDK_EVENT_PROPAGATE;
}

797
static void
798
on_tab_changed(GtkNotebook* notebook, GtkWidget* page, guint page_num, MainWindow* self)
799
{
800 801
    (void)notebook;
    (void)page;
802

803 804
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
805

806
    auto newType = page_num == 0 ? priv->cpp->accountInfo_->profileInfo.type : lrc::api::profile::Type::PENDING;
807 808
    if (priv->cpp->currentTypeFilter_ != newType) {
        priv->cpp->currentTypeFilter_ = newType;
809
        priv->cpp->accountInfo_->conversationModel->setFilter(priv->cpp->currentTypeFilter_);
810
    }
811
}
812

813 814 815
static gboolean
on_window_state_event(GtkWidget *widget, GdkEventWindowState *event)
{
816 817
    g_return_val_if_fail(IS_MAIN_WINDOW(widget), GDK_EVENT_PROPAGATE);
    auto *priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(widget));
818

819 820
    g_settings_set_boolean(priv->window_settings, "window-maximized", ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0));
    g_settings_set_boolean(priv->window_settings, "window-fullscreen", ((event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0));
821 822 823 824

    return GDK_EVENT_PROPAGATE;
}

825 826
static void
on_window_size_changed(GtkWidget *self, G_GNUC_UNUSED GdkRectangle*, G_GNUC_UNUSED gpointer)
827
{
828 829
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto *priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
830

831 832
    int new_width, new_height;
    gtk_window_get_size(GTK_WINDOW(self), &new_width, &new_height);
833 834
    g_settings_set_int(priv->window_settings, "window-width", new_width);
    g_settings_set_int(priv->window_settings, "window-height", new_height);
835
}
836

837
static void
838
on_search_entry_places_call_changed(GSettings* settings, const gchar* key, MainWindow* self)
839
{
840 841
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
842

843 844 845 846 847 848 849
    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"));
850
    }
851
}
852

853
static void
854
on_handle_account_migrations(MainWindow* self)
855
{
856 857
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
858 859 860 861 862

    /* 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;
863
    }
864

865 866 867 868 869 870 871 872 873 874
    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);

875
            gtk_widget_hide(priv->settings);
876 877 878 879 880 881 882 883 884 885 886 887 888 889
            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;
890 891
    on_account_changed(self);
    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
892
    gtk_widget_show(priv->settings);
893
}
894

895 896 897 898 899 900 901
enum class Action {
    SELECT,
    ACCEPT,
    REFUSE
};

static void
902
action_notification(gchar* title, MainWindow* self, Action action)
903
{
904 905
    g_return_if_fail(IS_MAIN_WINDOW(self) && title);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
906 907 908 909 910 911 912 913 914 915
    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;
916
    auto thirdMarker = titleStr.find(":", secondMarker + 1);
917 918 919

    auto id = titleStr.substr(0, firstMarker);
    auto type = titleStr.substr(firstMarker + 1, secondMarker - firstMarker - 1);
920
    auto information = titleStr.substr(secondMarker + 1, thirdMarker - secondMarker - 1);
921 922 923 924 925 926 927 928 929

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

        if (priv->cpp->accountInfo_->id != id) {
            priv->cpp->updateLrc(id);
930
            priv->cpp->refreshAccountSelectorWidget(-1, id);
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
        }

        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) {
961
            g_warning("Can't get account %s: %s", id.c_str(), e.what());
962 963 964 965 966 967
        }
    }

}

static void
968
on_notification_chat_clicked(G_GNUC_UNUSED GtkWidget* notifier,
969
                             gchar *title, MainWindow* self)
970 971 972 973 974
{
    action_notification(title, self, Action::SELECT);
}

static void
975
on_notification_accept_pending(GtkWidget*, gchar *title, MainWindow* self)
976 977 978 979 980
{
    action_notification(title, self, Action::ACCEPT);
}

static void
981
on_notification_refuse_pending(GtkWidget*, gchar *title, MainWindow* self)
982 983 984 985 986
{
    action_notification(title, self, Action::REFUSE);
}

static void
987
on_notification_accept_call(GtkWidget*, gchar *title, MainWindow* self)
988
{
989 990
    g_return_if_fail(IS_MAIN_WINDOW(self) && title);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
    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) {
1014
        g_warning("Can't get account %s: %s", id.c_str(), e.what());
1015 1016 1017 1018
    }
}

static void
1019
on_notification_decline_call(GtkWidget*, gchar *title, MainWindow* self)
1020
{
1021 1022
    g_return_if_fail(IS_MAIN_WINDOW(self) && title);
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
    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) {
1042
        g_warning("Can't get account %s: %s", id.c_str(), e.what());
1043 1044 1045
    }
}

1046
} // namespace gtk_callbacks
1047

1048
CppImpl::CppImpl(MainWindow& widget)
1049
    : self {&widget}
1050
    , widgets {MAIN_WINDOW_GET_PRIVATE(&widget)}
1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
{
    lrc_ = std::make_unique<lrc::api::Lrc>([this](){
        widgets->migratingDialog_ = gtk_message_dialog_new(
            GTK_WINDOW(self), GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_INFO, GTK_BUTTONS_NONE, nullptr);
        gtk_window_set_title(GTK_WINDOW(widgets->migratingDialog_), _("Jami - Migration needed"));

        auto* content_area = gtk_dialog_get_content_area(GTK_DIALOG(widgets->migratingDialog_));
        GError *error = nullptr;

        GdkPixbuf* logo_jami = gdk_pixbuf_new_from_resource_at_scale("/net/jami/JamiGnome/jami-logo-blue",
                                                                    -1, 128, TRUE, &error);
        if (!logo_jami) {
            g_debug("Could not load logo: %s", error->message);
            g_clear_error(&error);
            return;
        }
        auto* image = gtk_image_new_from_pixbuf(logo_jami);
        auto* label = gtk_label_new(_("Migration in progress... please do not close this window."));
        gtk_container_add(GTK_CONTAINER(content_area), image);
        gtk_container_add(GTK_CONTAINER(content_area), label);
        gtk_widget_set_margin_left(content_area, 32);
        gtk_widget_set_margin_right(content_area, 32);

        gtk_widget_show_all(widgets->migratingDialog_);

        gtk_dialog_run(GTK_DIALOG(widgets->migratingDialog_));
    },
    [this](){
        gtk_widget_destroy(widgets->migratingDialog_);
    });
}
1083

1084
static void
1085
on_clear_all_history_clicked(MainWindow* self)
1086
{
1087 1088
    g_return_if_fail(IS_MAIN_WINDOW(self));
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
1089
    g_return_if_fail(priv && priv->cpp);
1090

1091 1092 1093 1094 1095 1096 1097 1098
    for (const auto &account_id : priv->cpp->lrc_->getAccountModel().getAccountList()) {
        try {
            auto &accountInfo = priv->cpp->lrc_->getAccountModel().getAccountInfo(account_id);
            accountInfo.conversationModel->clearAllHistory();
        } catch (const std::out_of_range &e) {
            g_warning("Can't get account %s: %s", account_id.c_str(), e.what());
        }
    }
1099 1100 1101 1102

    priv->cpp->has_cleared_all_history = true;
}

1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115
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
1116
update_download_folder(MainWindow* self)
1117
{
1118
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
1119 1120
    g_return_if_fail(priv);

1121
    update_data_transfer(priv->cpp->lrc_->getDataTransferModel(), priv->window_settings);
1122 1123
}

1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
#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
1139
primary_connection_changed(NMClient *nm,  GParamSpec*, MainWindow* self)
1140
{
1141
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
1142
    auto connection = nm_client_get_primary_connection(nm);
1143 1144
    log_connection_info(connection);
    priv->cpp->lrc_->connectivityChanged();
1145 1146 1147
}

static void
1148
nm_client_cb(G_GNUC_UNUSED GObject *source_object, GAsyncResult *result,  MainWindow* self)
1149
{
1150
    auto* priv = MAIN_WINDOW_GET_PRIVATE(MAIN_WINDOW(self));
1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161

    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);
1162
        g_signal_connect(nm_client, "notify::active-connections", G_CALLBACK(primary_connection_changed), self);
1163 1164 1165 1166 1167 1168 1169 1170 1171

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

#endif /* USE_LIBNM */

1172 1173 1174
void
CppImpl::init()
{
1175 1176 1177 1178 1179
    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
1180

1181 1182 1183 1184 1185 1186
    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);