ringmainwindow.cpp 114 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
    gboolean is_fullscreen_main_win = false;
131
    gboolean key_pressed = false;
132 133

    GCancellable *cancellable;
134 135

    GtkWidget* migratingDialog_;
136 137 138 139
#if USE_LIBNM
    /* NetworkManager */
    NMClient *nm_client;
#endif
140 141 142 143 144 145
};

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

146 147
//==============================================================================

148
namespace { namespace details
149 150 151 152 153 154 155
{

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

158 159 160 161 162 163 164
inline namespace helpers
{

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

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

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

    g_object_set(G_OBJECT(cell), "markup", text, NULL);
216 217
    g_object_set(G_OBJECT(cell), "height", 17, NULL);
    g_object_set(G_OBJECT(cell), "ypad", 0, NULL);
218 219

    g_free(id);
220
    g_free(alias);
221 222 223
    g_free(registeredName);
    g_free(uri);
    g_free(text);
224 225
}

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

    gtk_tree_model_get (model, iter,
                        0 /* col# */, &id /* data */,
239
                        1 /* col# */, &status /* data */,
240 241 242 243
                        2 /* col# */, &avatar /* data */,
                        -1);

    if (g_strcmp0("", id) == 0) {
244
        g_free(status);
245
        g_free(avatar);
246 247
        g_free(id);

248
        GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/net/jami/JamiGnome/add-device", nullptr);
249 250 251 252 253 254
        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;
    }

255 256 257 258 259 260 261 262 263
    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;
    }
264
    auto default_avatar = Interfaces::PixbufManipulator().generateAvatar("", "");
265
    auto default_scaled = Interfaces::PixbufManipulator().scaleAndFrame(default_avatar.get(), QSize(32, 32), true, iconStatus);
266
    auto photo = default_scaled;
267

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

282
    g_free(status);
283
    g_free(avatar);
284
    g_free(id);
285 286
}

287
inline static void
288 289
foreachLrcAccount(const lrc::api::Lrc& lrc,
                  const std::function<void(const lrc::api::account::Info&)>& func)
290
{
291 292 293 294
    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);
295 296 297 298 299
    }
}

} // namespace helpers

300 301 302 303
class CppImpl
{
public:
    explicit CppImpl(RingMainWindow& widget);
304
    ~CppImpl();
305

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

321 322 323 324
    int getCurrentUid();
    void forCurrentConversation(const std::function<void(const lrc::api::conversation::Info&)>& func);
    bool showOkCancelDialog(const std::string& title, const std::string& text);

325 326
    lrc::api::conversation::Info getCurrentConversation(GtkWidget* frame_call);

327
    void showAccountSelectorWidget(bool show = true);
328
    std::size_t refreshAccountSelectorWidget(int selection_row = -1, const std::string& selected = "");
329

330
    WebKitChatContainer* webkitChatContainer(bool redraw_webview = false);
331

332
    RingMainWindow* self = nullptr; // The GTK widget itself
333
    RingMainWindowPrivate* widgets = nullptr;
334

335
    std::unique_ptr<lrc::api::Lrc> lrc_;
336
    AccountInfoPointer accountInfo_ = nullptr;
337
    AccountInfoPointer accountInfoForMigration_ = nullptr;
338
    std::unique_ptr<lrc::api::conversation::Info> chatViewConversation_;
339 340 341
    lrc::api::profile::Type currentTypeFilter_;
    bool show_settings = false;
    bool is_fullscreen = false;
342
    bool has_cleared_all_history = false;
343
    guint inhibitionCookie = 0;
344

345 346 347
    int smartviewPageNum = 0;
    int contactRequestsPageNum = 0;

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

372
    std::string eventUid_;
373
    std::string eventBody_;
374 375

    bool isCreatingAccount {false};
376
    QHash<QString, QMetaObject::Connection> pendingConferences_;
377
private:
378 379 380 381
    CppImpl() = delete;
    CppImpl(const CppImpl&) = delete;
    CppImpl& operator=(const CppImpl&) = delete;

382
    GtkWidget* displayWelcomeView(lrc::api::conversation::Info);
383 384 385
    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);
386

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

414 415
inline namespace gtk_callbacks
{
416
static void
417
on_video_double_clicked(RingMainWindow* self)
418 419 420 421 422
{
    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
423

424
static void
425
on_hide_view_clicked(RingMainWindow* self)
426 427
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
428
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
429
    priv->cpp->resetToWelcome();
430 431
}

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
static gboolean
place_call_event(RingMainWindow* self)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

    return G_SOURCE_REMOVE;
}

static void
on_place_call_clicked(G_GNUC_UNUSED GtkWidget*, gchar *uid, RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && uid);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
static gboolean
add_conversation_event(RingMainWindow* self)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

    return G_SOURCE_REMOVE;
}

static void
on_add_conversation_clicked(G_GNUC_UNUSED GtkWidget*, gchar *uid, RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && uid);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

static gboolean
send_text_event(RingMainWindow* self)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

    return G_SOURCE_REMOVE;
}

static void
on_send_text_clicked(G_GNUC_UNUSED GtkWidget*, gchar* uid, gchar* body, RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && uid);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
static gboolean
place_audio_call_event(RingMainWindow* self)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), G_SOURCE_REMOVE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

    return G_SOURCE_REMOVE;
}

static void
on_place_audio_call_clicked(G_GNUC_UNUSED GtkWidget*, gchar *uid, RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self) && uid);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

521 522 523 524 525
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));
526
    priv->cpp->isCreatingAccount = false;
527 528
    priv->cpp->leaveAccountCreationWizard();
}
529

530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545
static void
on_account_creation_unlock(RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    priv->cpp->isCreatingAccount = false;
}

static void
on_account_creation_lock(RingMainWindow* self)
{
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    priv->cpp->isCreatingAccount = true;
}

546
static void
547
on_account_changed(RingMainWindow* self)
548
{
549 550
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
551 552 553 554
    auto changeTopAccount = priv->set_top_account_flag;
    if (!priv->set_top_account_flag) {
        priv->set_top_account_flag = true;
    }
555

556 557
    auto accountComboBox = GTK_COMBO_BOX(priv->combobox_account_selector);
    auto model = gtk_combo_box_get_model(accountComboBox);
558

559 560 561 562
    GtkTreeIter iter;
    if (gtk_combo_box_get_active_iter(accountComboBox, &iter)) {
        gchar* accountId;
        gtk_tree_model_get(model, &iter, 0 /* col# */, &accountId /* data */, -1);
563 564 565
        if (g_strcmp0("", accountId) == 0) {
            priv->cpp->enterAccountCreationWizard(true);
        } else {
566
            if (!priv->cpp->isCreatingAccount) priv->cpp->leaveAccountCreationWizard();
567
            if (priv->cpp->accountInfo_ && changeTopAccount) {
568 569
                priv->cpp->accountInfo_->accountModel->setTopAccount(accountId);
            }
570 571 572 573
            priv->cpp->onAccountSelectionChange(accountId);
            gtk_notebook_set_show_tabs(GTK_NOTEBOOK(priv->notebook_contacts),
                priv->cpp->accountInfo_->contactModel->hasPendingRequests());
        }
574 575 576
        g_free(accountId);
    }
}
577

578 579 580 581 582
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));
583

584 585 586 587
    if (!priv->cpp->show_settings)
        priv->cpp->enterSettingsView();
    else
        priv->cpp->leaveSettingsView();
588
}
589

590
static void
591 592 593 594 595 596 597 598 599 600 601 602 603
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
604

605

606
static void
607
on_show_new_account_settings(GtkToggleButton* navbutton, RingMainWindow* self)
608
{
609 610
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
611

612
    if (gtk_toggle_button_get_active(navbutton)) {
613 614 615 616 617
        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);
618 619
    }
}
620

621
static void
622
on_show_general_settings(GtkToggleButton* navbutton, RingMainWindow* self)
623
{
624 625
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
626

627 628 629
    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;
630
    }
631
}
632

633 634 635 636 637
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));
638

639 640
    // Filter model
    const gchar *text = gtk_entry_get_text(GTK_ENTRY(search_entry));
641
    priv->cpp->accountInfo_->conversationModel->setFilter(text);
642
}
643

644
static void
645
on_search_entry_activated(RingMainWindow* self)
646
{
647 648
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
649

650
    // Select the first conversation of the list
651
    auto& conversationModel = priv->cpp->accountInfo_->conversationModel;
652
    auto conversations = conversationModel->allFilteredConversations();
653 654 655 656

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

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

660 661 662 663 664 665 666 667
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) {
668
        priv->cpp->accountInfo_->conversationModel->setFilter("");
669
        return GDK_EVENT_STOP;
670
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
671

672 673
    return GDK_EVENT_PROPAGATE;
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
674

675 676 677 678 679 680 681 682 683
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;
}

684 685 686 687 688 689
static gboolean
on_dtmf_pressed(RingMainWindow* self, GdkEventKey* event, gpointer user_data)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

690
    if(priv->key_pressed && !(event->state & GDK_SHIFT_MASK)){
691
        return GDK_EVENT_PROPAGATE;
692
    }
693 694 695

    (void)user_data;

696 697 698 699 700 701 702 703 704 705
    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;

706 707
    if (priv->cpp->accountInfo_ &&
        priv->cpp->accountInfo_->profileInfo.type != lrc::api::profile::Type::SIP)
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
        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);
732
    g_debug("attempting to play DTMF tone during ongoing call: %s", val.toUtf8().constData());
733
    priv->cpp->accountInfo_->callModel->playDTMF(current_item.callId, val.toStdString());
734 735 736

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

741 742 743 744 745 746 747 748 749
static gboolean
on_dtmf_released(RingMainWindow* self, GdkEventKey* event, gpointer user_data)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), GDK_EVENT_PROPAGATE);
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
    priv->key_pressed = false;
    return GDK_EVENT_PROPAGATE;
}

750
static void
751
on_tab_changed(GtkNotebook* notebook, GtkWidget* page, guint page_num, RingMainWindow* self)
752
{
753 754
    (void)notebook;
    (void)page;
755

756 757 758
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

759
    auto newType = page_num == 0 ? priv->cpp->accountInfo_->profileInfo.type : lrc::api::profile::Type::PENDING;
760 761
    if (priv->cpp->currentTypeFilter_ != newType) {
        priv->cpp->currentTypeFilter_ = newType;
762
        priv->cpp->accountInfo_->conversationModel->setFilter(priv->cpp->currentTypeFilter_);
763
    }
764
}
765

766 767 768 769 770 771 772 773 774 775 776 777
static gboolean
on_window_state_event(GtkWidget *widget, GdkEventWindowState *event)
{
    g_return_val_if_fail(IS_RING_MAIN_WINDOW(widget), GDK_EVENT_PROPAGATE);
    auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(widget));

    g_settings_set_boolean(priv->settings, "window-maximized", ((event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0));
    g_settings_set_boolean(priv->settings, "window-fullscreen", ((event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0));

    return GDK_EVENT_PROPAGATE;
}

778 779
static void
on_window_size_changed(GtkWidget *self, G_GNUC_UNUSED GdkRectangle*, G_GNUC_UNUSED gpointer)
780
{
781 782
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
783

784 785 786 787
    int new_width, new_height;
    gtk_window_get_size(GTK_WINDOW(self), &new_width, &new_height);
    g_settings_set_int(priv->settings, "window-width", new_width);
    g_settings_set_int(priv->settings, "window-height", new_height);
788
}
789

790
static void
791
on_search_entry_places_call_changed(GSettings* settings, const gchar* key, RingMainWindow* self)
792
{
793 794
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
795

796 797 798 799 800 801 802
    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"));
803
    }
804
}
805

806 807 808 809 810 811 812 813 814 815
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;
816
    }
817

818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
    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;
843 844
    on_account_changed(self);
    gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
845
    gtk_widget_show(priv->ring_settings);
846
}
847

848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868
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;
869
    auto thirdMarker = titleStr.find(":", secondMarker + 1);
870 871 872

    auto id = titleStr.substr(0, firstMarker);
    auto type = titleStr.substr(firstMarker + 1, secondMarker - firstMarker - 1);
873
    auto information = titleStr.substr(secondMarker + 1, thirdMarker - secondMarker - 1);
874 875 876 877 878 879 880 881 882

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

        if (priv->cpp->accountInfo_->id != id) {
            priv->cpp->updateLrc(id);
883
            priv->cpp->refreshAccountSelectorWidget(-1, id);
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913
        }

        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) {
914
            g_warning("Can't get account %s: %s", id.c_str(), e.what());
915 916 917 918 919 920
        }
    }

}

static void
921 922
on_notification_chat_clicked(G_GNUC_UNUSED GtkWidget* notifier,
                             gchar *title, RingMainWindow* self)
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
{
    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
940
on_notification_accept_call(GtkWidget*, gchar *title, RingMainWindow* self)
941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966
{
    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) {
967
        g_warning("Can't get account %s: %s", id.c_str(), e.what());
968 969 970 971
    }
}

static void
972
on_notification_decline_call(GtkWidget*, gchar *title, RingMainWindow* self)
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994
{
    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) {
995
        g_warning("Can't get account %s: %s", id.c_str(), e.what());
996 997 998
    }
}

999
} // namespace gtk_callbacks
aviau's avatar
aviau committed
1000

1001 1002
CppImpl::CppImpl(RingMainWindow& widget)
    : self {&widget}
1003
    , widgets {RING_MAIN_WINDOW_GET_PRIVATE(&widget)}
1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
{
    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_));
    },