conversationpopupmenu.cpp 12.4 KB
Newer Older
1
/****************************************************************************
Sébastien Blin's avatar
Sébastien Blin committed
2
 *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                             *
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
 *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 of the License, or (at your option) any later version.     *
 *                                                                          *
 *   This library 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      *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU General Public License      *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 ***************************************************************************/

#include "conversationpopupmenu.h"

// GTK+ related
#include <glib/gi18n.h>

// Lrc
#include <api/contact.h>
27 28
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
29

30 31
#include "accountinfopointer.h"

32 33 34
// Qt
#include <QItemSelectionModel>

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
struct _ConversationPopupMenu
{
    GtkMenu parent;
};

struct _ConversationPopupMenuClass
{
    GtkMenuClass parent_class;
};

typedef struct _ConversationPopupMenuPrivate ConversationPopupMenuPrivate;

struct _ConversationPopupMenuPrivate
{
    GtkTreeView *treeview;

51
    AccountInfoPointer const *accountInfo_;
52 53 54 55 56 57 58
    int row_;
};

G_DEFINE_TYPE_WITH_PRIVATE(ConversationPopupMenu, conversation_popup_menu, GTK_TYPE_MENU);

#define CONVERSATION_POPUP_MENU_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CONVERSATION_POPUP_MENU_TYPE, ConversationPopupMenuPrivate))

59 60 61 62 63
static void
copy_contact_info(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
    try
    {
64
        auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
65
        if (conversation.participants.empty()) return;
66
        auto& contact = (*priv->accountInfo_)->contactModel->getContact(conversation.participants.front());
67 68 69 70 71 72 73 74 75 76 77 78 79
        auto bestName = contact.registeredName.empty() ? contact.profileInfo.uri : contact.registeredName;
        auto text = (gchar *)bestName.c_str();
        GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
        gtk_clipboard_set_text(clip, text, -1);
        clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
        gtk_clipboard_set_text(clip, text, -1);
    }
    catch (...)
    {
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

80
static void
81
remove_history_conversation(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
82 83 84
{
    try
    {
85 86
        auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
        (*priv->accountInfo_)->conversationModel->clearHistory(conversation.uid);
87 88 89 90 91 92 93 94 95 96 97 98
    }
    catch (...)
    {
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

static void
remove_conversation(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
    try
    {
99 100
        auto conversationUid = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_).uid;
        (*priv->accountInfo_)->conversationModel->removeConversation(conversationUid);
101 102 103 104 105 106 107
    }
    catch (...)
    {
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
static void
unblock_conversation(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
    try
    {
        auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
        auto uri = conversation.participants[0];

        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(uri);

        if (!contactInfo.isBanned) {
            g_debug("unblock_conversation: trying to unban a contact which isn't banned !");
            return;
        }

        (*priv->accountInfo_)->contactModel->addContact(contactInfo);
    }
    catch (...)
    {
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

131 132 133 134 135
static void
block_conversation(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
    try
    {
136 137
        auto conversationUid = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_).uid;
        (*priv->accountInfo_)->conversationModel->removeConversation(conversationUid, true);
138 139 140 141 142 143 144 145 146 147 148 149
    }
    catch (...)
    {
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

static void
add_conversation(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
    try
    {
150 151
        auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
        (*priv->accountInfo_)->conversationModel->makePermanent(conversation.uid);
152 153 154 155 156 157 158 159
    }
    catch (...)
    {
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

static void
160
place_video_call(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
161 162 163
{
    try
    {
164 165
        auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
        (*priv->accountInfo_)->conversationModel->placeCall(conversation.uid);
166 167
    } catch (...) {
        g_warning("Can't get conversation at row %i", priv->row_);
168
    }
169 170 171 172 173 174
}

static void
place_audio_call(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
    try
175
    {
176 177
        auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
        (*priv->accountInfo_)->conversationModel->placeAudioOnlyCall(conversation.uid);
178
    } catch (...) {
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
        g_warning("Can't get conversation at row %i", priv->row_);
    }
}

/**
 * Update the menu when the selected conversation in the treeview changes.
 */
static void
update(GtkTreeSelection *selection, ConversationPopupMenu *self)
{
    ConversationPopupMenuPrivate *priv = CONVERSATION_POPUP_MENU_GET_PRIVATE(self);
    /* clear the current menu */
    gtk_container_forall(GTK_CONTAINER(self), (GtkCallback)gtk_widget_destroy, nullptr);

    // Retrieve conversation
    GtkTreeIter iter;
    GtkTreeModel *model;
    if (!gtk_tree_selection_get_selected(selection, &model, &iter)) return;
    auto path = gtk_tree_model_get_path(model, &iter);
    auto idx = gtk_tree_path_get_indices(path);
199
    auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(idx[0]);
200
    priv->row_ = idx[0];
201
    try {
202
        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(conversation.participants.front());
203 204 205 206
        if (contactInfo.profileInfo.uri.empty()) return;

        // we always build a menu, however in some cases some or all of the conversations will be deactivated
        // we prefer this to having an empty menu because GTK+ behaves weird in the empty menu case
207
        auto callId = conversation.confId.empty() ? conversation.callId : conversation.confId;
208

209
        // Not in call
210
        if (!contactInfo.isBanned && (*priv->accountInfo_)->enabled) {
211 212 213 214 215 216 217 218
            auto place_video_call_conversation = gtk_menu_item_new_with_mnemonic(_("Place _video call"));
            gtk_menu_shell_append(GTK_MENU_SHELL(self), place_video_call_conversation);
            g_signal_connect(place_video_call_conversation, "activate", G_CALLBACK(place_video_call), priv);
            auto place_audio_call_conversation = gtk_menu_item_new_with_mnemonic(_("Place _audio call"));
            gtk_menu_shell_append(GTK_MENU_SHELL(self), place_audio_call_conversation);
            g_signal_connect(place_audio_call_conversation, "activate", G_CALLBACK(place_audio_call), priv);
        }

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
        if (contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY ||
            contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING) {
            // If we can add this conversation
            auto add_conversation_conversation = gtk_menu_item_new_with_mnemonic(_("_Add to conversations"));
            gtk_menu_shell_append(GTK_MENU_SHELL(self), add_conversation_conversation);
            g_signal_connect(add_conversation_conversation, "activate", G_CALLBACK(add_conversation), priv);
            if (contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING) {
                auto rm_conversation_item = gtk_menu_item_new_with_mnemonic(_("_Discard invitation"));
                gtk_menu_shell_append(GTK_MENU_SHELL(self), rm_conversation_item);
                g_signal_connect(rm_conversation_item, "activate", G_CALLBACK(remove_conversation), priv);
                auto block_conversation_item = gtk_menu_item_new_with_mnemonic(_("_Block invitations"));
                gtk_menu_shell_append(GTK_MENU_SHELL(self), block_conversation_item);
                g_signal_connect(block_conversation_item, "activate", G_CALLBACK(block_conversation), priv);
            }
        } else {
            auto rm_history_conversation = gtk_menu_item_new_with_mnemonic(_("C_lear history"));
            gtk_menu_shell_append(GTK_MENU_SHELL(self), rm_history_conversation);
            g_signal_connect(rm_history_conversation, "activate", G_CALLBACK(remove_history_conversation), priv);
            auto rm_conversation_item = gtk_menu_item_new_with_mnemonic(_("_Remove conversation"));
238 239
            gtk_menu_shell_append(GTK_MENU_SHELL(self), rm_conversation_item);
            g_signal_connect(rm_conversation_item, "activate", G_CALLBACK(remove_conversation), priv);
240 241 242 243 244 245 246 247 248 249

            if (!contactInfo.isBanned) {
                auto block_conversation_item = gtk_menu_item_new_with_mnemonic(_("_Block contact"));
                gtk_menu_shell_append(GTK_MENU_SHELL(self), block_conversation_item);
                g_signal_connect(block_conversation_item, "activate", G_CALLBACK(block_conversation), priv);
            } else {
                auto block_conversation_item = gtk_menu_item_new_with_mnemonic(_("_Unblock contact"));
                gtk_menu_shell_append(GTK_MENU_SHELL(self), block_conversation_item);
                g_signal_connect(block_conversation_item, "activate", G_CALLBACK(unblock_conversation), priv);
            }
250 251
        }

252 253 254 255
        auto copy_name = gtk_menu_item_new_with_mnemonic(_("_Copy name"));
        gtk_menu_shell_append(GTK_MENU_SHELL(self), copy_name);
        g_signal_connect(copy_name, "activate", G_CALLBACK(copy_contact_info), priv);

256 257 258 259 260
        /* show all conversations */
        gtk_widget_show_all(GTK_WIDGET(self));
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
    }
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
}

static void
conversation_popup_menu_dispose(GObject *object)
{
    G_OBJECT_CLASS(conversation_popup_menu_parent_class)->dispose(object);
}

static void
conversation_popup_menu_finalize(GObject *object)
{
    G_OBJECT_CLASS(conversation_popup_menu_parent_class)->finalize(object);
}

static void
conversation_popup_menu_class_init(ConversationPopupMenuClass *klass)
{
    G_OBJECT_CLASS(klass)->finalize = conversation_popup_menu_finalize;
    G_OBJECT_CLASS(klass)->dispose = conversation_popup_menu_dispose;
}


static void
conversation_popup_menu_init(G_GNUC_UNUSED ConversationPopupMenu *self)
{
    // nothing to do
}

GtkWidget *
290
conversation_popup_menu_new (GtkTreeView *treeview, AccountInfoPointer const & accountInfo)
291 292 293
{
    gpointer self = g_object_new(CONVERSATION_POPUP_MENU_TYPE, NULL);
    ConversationPopupMenuPrivate *priv = CONVERSATION_POPUP_MENU_GET_PRIVATE(self);
294
    priv->accountInfo_ = &accountInfo;
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316

    priv->treeview = treeview;
    GtkTreeSelection *selection = gtk_tree_view_get_selection(priv->treeview);

    // build the menu for the first time
    update(selection, CONVERSATION_POPUP_MENU(self));

    return (GtkWidget *)self;
}

gboolean
conversation_popup_menu_show(ConversationPopupMenu *self, GdkEventButton *event)
{
    if (!self) return GDK_EVENT_PROPAGATE;
    if (event->type == GDK_BUTTON_PRESS
        && event->button == GDK_BUTTON_SECONDARY) {
        // Show popup menu. Will be updated later.
        gtk_menu_popup(GTK_MENU(self), NULL, NULL, NULL, NULL, event->button, event->time);
    }

    return GDK_EVENT_PROPAGATE; // so that the conversation selection changes
}