ringmainwindow.cpp 113 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
private:
377 378 379 380
    CppImpl() = delete;
    CppImpl(const CppImpl&) = delete;
    CppImpl& operator=(const CppImpl&) = delete;

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

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

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

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

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
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);
}

452 453 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
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);
}

497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
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);
}

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

528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
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;
}

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

554 555
    auto accountComboBox = GTK_COMBO_BOX(priv->combobox_account_selector);
    auto model = gtk_combo_box_get_model(accountComboBox);
556

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

576 577 578 579 580
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));
581

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

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

603

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

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

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

625 626 627
    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;
628
    }
629
}
630

631 632 633 634 635
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));
636

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

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

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

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

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

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

670 671
    return GDK_EVENT_PROPAGATE;
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
672

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

682 683 684 685 686 687
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));

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

    (void)user_data;

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

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

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

739 740 741 742 743 744 745 746 747
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;
}

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

754 755 756
    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
    auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));

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

764 765 766 767 768 769 770 771 772 773 774 775
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;
}

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

782 783 784 785
    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);
786
}
787

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

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

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

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 843 844 845
    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);
        }
846
    }
847 848
    gtk_widget_show(priv->ring_settings);
    priv->cpp->showAccountSelectorWidget();
849
}
850

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

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

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

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

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

}

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

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

1002
} // namespace gtk_callbacks
aviau's avatar
aviau committed
1003

1004 1005
CppImpl::CppImpl(RingMainWindow& widget)
    : self {&widget}
1006
    , widgets {RING_MAIN_WINDOW_GET_PRIVATE(&widget)}
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 1036 1037 1038
{
    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_);
    });
}