/*
 * Copyright © 2004-2010 Jens Oknelid, paskharen@gmail.com
 *
 * 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 2 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, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, compiling, linking, and/or
 * using OpenSSL with this program is allowed.
 */

#include "privatemessage.hh"
#include <dcpp/version.h>
#include <dcpp/ClientManager.h>
#include <dcpp/FavoriteManager.h>
#include "settingsmanager.hh"
#include "emoticonsdialog.hh"
#include "emoticons.hh"
#include "wulformanager.hh"
#include "WulforUtil.hh"
#include "search.hh"
#include "sound.hh"

using namespace std;
using namespace dcpp;

PrivateMessage::PrivateMessage(const string &_cid, const string &_hubUrl):
    BookEntry(Entry::PRIVATE_MESSAGE, WulforUtil::getNicks(_cid, _hubUrl), "privatemessage.ui", _cid),
    cid(_cid),
    hubUrl(_hubUrl),
    historyIndex(0),
    sentAwayMessage(false),
    scrollToBottom(true),
    offline(false)
{
#if !GTK_CHECK_VERSION(3,0,0)
    gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR(getWidget("status")),false);
#endif

    // Initialize the chat window
    if (WGETB("use-oem-monofont"))
    {
        PangoFontDescription *fontDesc = pango_font_description_new();
        pango_font_description_set_family(fontDesc, "Mono");
#if GTK_CHECK_VERSION(3, 0, 0)
        gtk_widget_override_font(getWidget("text"), fontDesc);
#else
        gtk_widget_modify_font(getWidget("text"), fontDesc);
#endif
        pango_font_description_free(fontDesc);
    }

    // the reference count on the buffer is not incremented and caller of this function won't own a new reference.
    messageBuffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(getWidget("text")));

    /* initial markers */
    GtkTextIter iter;
    gtk_text_buffer_get_end_iter(messageBuffer, &iter);

    mark = gtk_text_buffer_create_mark(messageBuffer, NULL, &iter, false);
    start_mark = gtk_text_buffer_create_mark(messageBuffer, NULL, &iter, true);
    end_mark = gtk_text_buffer_create_mark(messageBuffer, NULL, &iter, true);
    tag_mark = gtk_text_buffer_create_mark(messageBuffer, NULL, &iter, false);
    emot_mark = gtk_text_buffer_create_mark(messageBuffer, NULL, &iter, true);

    handCursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_HAND2);

    GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(getWidget("scroll")));

    // menu
    g_object_ref_sink(getWidget("magnetMenu"));
    g_object_ref_sink(getWidget("linkMenu"));
    g_object_ref_sink(getWidget("hubMenu"));
    g_object_ref_sink(getWidget("chatCommandsMenu"));

    // Emoticons dialog
    emotdialog = new EmoticonsDialog(getWidget("entry"), getWidget("emotButton"), getWidget("emotMenu"));
    if (!WGETB("emoticons-use"))
        gtk_widget_set_sensitive(getWidget("emotButton"), false);
    useEmoticons = true;

    // PM commands
    g_object_set_data_full(G_OBJECT(getWidget("awayCommandItem")), "command", g_strdup("/away"), g_free);
    g_signal_connect(getWidget("awayCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("backCommandItem")), "command", g_strdup("/back"), g_free);
    g_signal_connect(getWidget("backCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("clearCommandItem")), "command", g_strdup("/clear"), g_free);
    g_signal_connect(getWidget("clearCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("closeCommandItem")), "command", g_strdup("/close"), g_free);
    g_signal_connect(getWidget("closeCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("fuserCommandItem")), "command", g_strdup("/fuser"), g_free);
    g_signal_connect(getWidget("fuserCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("rmfuCommandItem")), "command", g_strdup("/rmfu"), g_free);
    g_signal_connect(getWidget("rmfuCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("versionCommandItem")), "command", g_strdup("/version"), g_free);
    g_signal_connect(getWidget("versionCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("ratioCommandItem")), "command", g_strdup("/ratio"), g_free);
    g_signal_connect(getWidget("ratioCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("helpCommandItem")), "command", g_strdup("/help"), g_free);
    g_signal_connect(getWidget("helpCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("getlistCommandItem")), "command", g_strdup("/getlist"), g_free);
    g_signal_connect(getWidget("getlistCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    g_object_set_data_full(G_OBJECT(getWidget("grantCommandItem")), "command", g_strdup("/grant"), g_free);
    g_signal_connect(getWidget("grantCommandItem"), "activate", G_CALLBACK(onCommandClicked_gui), (gpointer)this);

    // chat commands button
    g_signal_connect(getWidget("chatCommandsButton"), "button-release-event", G_CALLBACK(onChatCommandButtonRelease_gui), (gpointer)this);

    // Connect the signals to their callback functions.
    g_signal_connect(getContainer(), "focus-in-event", G_CALLBACK(onFocusIn_gui), (gpointer)this);
    g_signal_connect(getWidget("entry"), "activate", G_CALLBACK(onSendMessage_gui), (gpointer)this);
    g_signal_connect(getWidget("entry"), "key-press-event", G_CALLBACK(onKeyPress_gui), (gpointer)this);
    g_signal_connect(getWidget("text"), "motion-notify-event", G_CALLBACK(onChatPointerMoved_gui), (gpointer)this);
    g_signal_connect(getWidget("text"), "visibility-notify-event", G_CALLBACK(onChatVisibilityChanged_gui), (gpointer)this);
    g_signal_connect(adjustment, "value_changed", G_CALLBACK(onChatScroll_gui), (gpointer)this);
    g_signal_connect(adjustment, "changed", G_CALLBACK(onChatResize_gui), (gpointer)this);
    g_signal_connect(getWidget("copyLinkItem"), "activate", G_CALLBACK(onCopyURIClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("openLinkItem"), "activate", G_CALLBACK(onOpenLinkClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("copyhubItem"), "activate", G_CALLBACK(onCopyURIClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("openhubItem"), "activate", G_CALLBACK(onOpenHubClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("copyMagnetItem"), "activate", G_CALLBACK(onCopyURIClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("searchMagnetItem"), "activate", G_CALLBACK(onSearchMagnetClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("magnetPropertiesItem"), "activate", G_CALLBACK(onMagnetPropertiesClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("emotButton"), "button-release-event", G_CALLBACK(onEmotButtonRelease_gui), (gpointer)this);
    g_signal_connect(getWidget("downloadBrowseItem"), "activate", G_CALLBACK(onDownloadToClicked_gui), (gpointer)this);
    g_signal_connect(getWidget("downloadItem"), "activate", G_CALLBACK(onDownloadClicked_gui), (gpointer)this);

    gtk_widget_grab_focus(getWidget("entry"));
    history.push_back("");
    const UserPtr user = ClientManager::getInstance()->findUser(CID(_cid));
    isBot = user ? user->isSet(User::BOT) : false;

    setLabel_gui(WulforUtil::getNicks(_cid, _hubUrl) + " [" + WulforUtil::getHubNames(_cid, _hubUrl) + "]");

    /* initial tags map */
    TagsMap[Tag::TAG_PRIVATE] = createTag_gui("TAG_PRIVATE", Tag::TAG_PRIVATE);
    TagsMap[Tag::TAG_MYOWN] = createTag_gui("TAG_MYOWN", Tag::TAG_MYOWN);
    TagsMap[Tag::TAG_SYSTEM] = createTag_gui("TAG_SYSTEM", Tag::TAG_SYSTEM);
    TagsMap[Tag::TAG_STATUS] = createTag_gui("TAG_STATUS", Tag::TAG_STATUS);
    TagsMap[Tag::TAG_TIMESTAMP] = createTag_gui("TAG_TIMESTAMP", Tag::TAG_TIMESTAMP);
    /*-*/
    TagsMap[Tag::TAG_MYNICK] = createTag_gui("TAG_MYNICK", Tag::TAG_MYNICK);
    TagsMap[Tag::TAG_NICK] = createTag_gui("TAG_NICK", Tag::TAG_NICK);
    TagsMap[Tag::TAG_OPERATOR] = createTag_gui("TAG_OPERATOR", Tag::TAG_OPERATOR);
    TagsMap[Tag::TAG_FAVORITE] = createTag_gui("TAG_FAVORITE", Tag::TAG_FAVORITE);
    TagsMap[Tag::TAG_URL] = createTag_gui("TAG_URL", Tag::TAG_URL);

    // set default select tag (fix error show cursor in neutral space)
    selectedTag = TagsMap[Tag::TAG_PRIVATE];
}

PrivateMessage::~PrivateMessage()
{
    ClientManager::getInstance()->removeListener(this);

    if (handCursor)
    {
#if GTK_CHECK_VERSION(3, 0, 0)
        g_object_unref(handCursor);
#else
        gdk_cursor_unref(handCursor);
#endif
        handCursor = NULL;
    }

    g_object_unref(getWidget("magnetMenu"));
    g_object_unref(getWidget("linkMenu"));
    g_object_unref(getWidget("hubMenu"));
    g_object_unref(getWidget("chatCommandsMenu"));

    delete emotdialog;
}

void PrivateMessage::show()
{
    ClientManager::getInstance()->addListener(this);
}

void PrivateMessage::addMessage_gui(string message, Msg::TypeMsg typemsg)
{
    addLine_gui(typemsg, message);

    if (BOOLSETTING(LOG_PRIVATE_CHAT))
    {
        StringMap params;
        params["message"] = message;
        params["hubNI"] = WulforUtil::getHubNames(cid, hubUrl);
        params["hubURL"] = hubUrl;
        params["userCID"] = cid;
        params["userNI"] = ClientManager::getInstance()->getNicks(CID(cid), hubUrl)[0];
        params["myCID"] = ClientManager::getInstance()->getMe()->getCID().toBase32();
        LOG(LogManager::PM, params);
    }

    if (WGETB("bold-pm"))
        setUrgent_gui();

    // Send an away message, but only the first time after setting away mode.
    if (!Util::getAway())
    {
        sentAwayMessage = false;
    }
    else if (!sentAwayMessage && !(BOOLSETTING(NO_AWAYMSG_TO_BOTS) && isBot))
    {
        sentAwayMessage = true;
        typedef Func1<PrivateMessage, string> F1;
        F1 *func = new F1(this, &PrivateMessage::sendMessage_client, Util::getAwayMessage());
        WulforManager::get()->dispatchClientFunc(func);
    }

    if (WGETB("sound-pm"))
    {
        MainWindow *mw = WulforManager::get()->getMainWindow();
#if GTK_CHECK_VERSION(3, 0, 0)
        GdkWindowState state = gdk_window_get_state(gtk_widget_get_window(mw->getContainer())  );
#else
        GdkWindowState state = gdk_window_get_state(mw->getContainer()->window);
#endif
        if ((state & GDK_WINDOW_STATE_ICONIFIED) || mw->currentPage_gui() != getContainer())
            Sound::get()->playSound(Sound::PRIVATE_MESSAGE);
        else if (WGETB("sound-pm-open")) Sound::get()->playSound(Sound::PRIVATE_MESSAGE);
    }
}

void PrivateMessage::addStatusMessage_gui(string message, Msg::TypeMsg typemsg)
{
    setStatus_gui(message);
    addLine_gui(typemsg, "*** " + message);
}

void PrivateMessage::preferences_gui()
{
    WulforSettingsManager *wsm = WulforSettingsManager::getInstance();
    string fore, back;
    bool bold = false, italic = false;

    for (int i = Tag::TAG_FIRST; i < Tag::TAG_LAST; ++i)
    {
        if(i == Tag::TAG_GENERAL)
            continue;

        getSettingTag_gui(wsm, (Tag::TypeTag)i, fore, back, bold, italic);

        WGETB("use-native-back-color-for-text") ?
                    g_object_set(TagsMap[i],
                                 "foreground", fore.c_str(),
                                 "weight", bold ? TEXT_WEIGHT_BOLD : TEXT_WEIGHT_NORMAL,
                                 "style", italic ? TEXT_STYLE_ITALIC : TEXT_STYLE_NORMAL,
                                 NULL) :
                    g_object_set(TagsMap[i],
                                 "foreground", fore.c_str(),
                                 "background", back.c_str(),
                                 "weight", bold ? TEXT_WEIGHT_BOLD : TEXT_WEIGHT_NORMAL,
                                 "style", italic ? TEXT_STYLE_ITALIC : TEXT_STYLE_NORMAL,
                                 NULL);
    }

    gtk_widget_queue_draw(getWidget("text"));
    gtk_widget_queue_draw(getWidget("emotButton"));

    if (!WGETB("emoticons-use"))
    {
        if (gtk_widget_is_sensitive(getWidget("emotButton")))
            gtk_widget_set_sensitive(getWidget("emotButton"), false);
    }
    else if (!gtk_widget_is_sensitive(getWidget("emotButton")))
    {
        gtk_widget_set_sensitive(getWidget("emotButton"), true);
    }
}

void PrivateMessage::setStatus_gui(string text)
{
    if (!text.empty())
    {
        text = "[" + Util::getShortTimeString() + "] " + text;
        gtk_statusbar_pop(GTK_STATUSBAR(getWidget("status")), 0);
        gtk_statusbar_push(GTK_STATUSBAR(getWidget("status")), 0, text.c_str());
    }
}

void PrivateMessage::addLine_gui(Msg::TypeMsg typemsg, const string &message)
{
    if (message.empty())
        return;

    GtkTextIter iter;
    string line = "";

    if (BOOLSETTING(TIME_STAMPS))
        line += "[" + Util::getShortTimeString() + "] ";

    line += message + "\n";

    gtk_text_buffer_get_end_iter(messageBuffer, &iter);
    gtk_text_buffer_insert(messageBuffer, &iter, line.c_str(), line.size());

    switch (typemsg)
    {
    case Msg::MYOWN:

        tagMsg = Tag::TAG_MYOWN;
        tagNick = Tag::TAG_MYNICK;
        break;

    case Msg::SYSTEM:

        tagMsg = Tag::TAG_SYSTEM;
        tagNick = Tag::TAG_NICK;
        break;

    case Msg::STATUS:

        tagMsg = Tag::TAG_STATUS;
        tagNick = Tag::TAG_NICK;
        break;

    case Msg::OPERATOR:

        tagMsg = Tag::TAG_PRIVATE;
        tagNick = Tag::TAG_OPERATOR;
        break;

    case Msg::FAVORITE:

        tagMsg = Tag::TAG_PRIVATE;
        tagNick = Tag::TAG_FAVORITE;
        break;

    case Msg::PRIVATE:

    default:

        tagMsg = Tag::TAG_PRIVATE;
        tagNick = Tag::TAG_NICK;
    }

    totalEmoticons = 0;

    applyTags_gui(line);

    gtk_text_buffer_get_end_iter(messageBuffer, &iter);

    // Limit size of chat text
    if (gtk_text_buffer_get_line_count(messageBuffer) > maxLines + 1)
    {
        GtkTextIter next;
        gtk_text_buffer_get_start_iter(messageBuffer, &iter);
        gtk_text_buffer_get_iter_at_line(messageBuffer, &next, 1);
        gtk_text_buffer_delete(messageBuffer, &iter, &next);
    }
}

void PrivateMessage::applyTags_gui(const string &line)
{
    GtkTextIter start_iter;
    gtk_text_buffer_get_end_iter(messageBuffer, &start_iter);

    string::size_type begin = 0;

    // apply timestamp tag
    if (BOOLSETTING(TIME_STAMPS))
    {
        string ts = Util::getShortTimeString();
        gtk_text_iter_backward_chars(&start_iter, g_utf8_strlen(line.c_str(), -1) - g_utf8_strlen(ts.c_str(), -1) - 2);

        GtkTextIter ts_start_iter, ts_end_iter;
        ts_end_iter = start_iter;

        gtk_text_buffer_get_end_iter(messageBuffer, &ts_start_iter);
        gtk_text_iter_backward_chars(&ts_start_iter, g_utf8_strlen(line.c_str(), -1));

        gtk_text_buffer_apply_tag(messageBuffer, TagsMap[Tag::TAG_TIMESTAMP], &ts_start_iter, &ts_end_iter);

        begin = ts.size() + 2 + 1;
    }
    else
        gtk_text_iter_backward_chars(&start_iter, g_utf8_strlen(line.c_str(), -1));

    dcassert(begin < line.size());

    // apply nick tag
    if (line[begin] == '<')
    {
        string::size_type end = line.find_first_of('>', begin);

        if (end != string::npos)
        {
            GtkTextIter nick_start_iter, nick_end_iter;

            gtk_text_buffer_get_end_iter(messageBuffer, &nick_start_iter);
            gtk_text_buffer_get_end_iter(messageBuffer, &nick_end_iter);

            gtk_text_iter_backward_chars(&nick_start_iter, g_utf8_strlen(line.c_str() + begin, -1));
            gtk_text_iter_backward_chars(&nick_end_iter, g_utf8_strlen(line.c_str() + end, -1) - 1);

            dcassert(tagNick >= TAG_MYNICK && tagNick < TAG_URL);
            gtk_text_buffer_apply_tag(messageBuffer, TagsMap[tagNick], &nick_start_iter, &nick_end_iter);

            start_iter = nick_end_iter;
        }
    }

    // apply tags: link, hub-url, magnet
    GtkTextIter tag_start_iter, tag_end_iter;

    gtk_text_buffer_move_mark(messageBuffer, start_mark, &start_iter);
    gtk_text_buffer_move_mark(messageBuffer, end_mark, &start_iter);

    string tagName;
    bool start = false;

    while(true)
    {
        do {
            gunichar ch = gtk_text_iter_get_char(&start_iter);

            if (!g_unichar_isspace(ch))
                break;

        }
        while (gtk_text_iter_forward_char(&start_iter));

        if(!start)
        {
            gtk_text_buffer_move_mark(messageBuffer, start_mark, &start_iter);
            gtk_text_buffer_move_mark(messageBuffer, end_mark, &start_iter);

            start = true;
        }

        tag_start_iter = start_iter;

        for(;gtk_text_iter_forward_char(&start_iter);)
        {
            gunichar ch = gtk_text_iter_get_char(&start_iter);

            if (g_unichar_isspace(ch))
                break;
        }

        tag_end_iter = start_iter;

        GCallback callback = NULL;
        gchar *temp = gtk_text_iter_get_text(&tag_start_iter, &tag_end_iter);

        if (!temp)
            continue;

        if (temp[0] != '\0')
        {
            tagName = temp;

            if (WulforUtil::isLink(tagName))
                callback = G_CALLBACK(onLinkTagEvent_gui);
            else if (WulforUtil::isHubURL(tagName))
                callback = G_CALLBACK(onHubTagEvent_gui);
            else if (WulforUtil::isMagnet(tagName))
                callback = G_CALLBACK(onMagnetTagEvent_gui);
        }

        g_free(temp);

        if (callback)
        {
            gtk_text_buffer_move_mark(messageBuffer, tag_mark, &tag_end_iter);

            // check for the tags in our buffer
            GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(messageBuffer), tagName.c_str());

            if (!tag)
            {
                tag = gtk_text_buffer_create_tag(messageBuffer, tagName.c_str(), "underline", PANGO_UNDERLINE_SINGLE, NULL);
                g_signal_connect(tag, "event", callback, (gpointer)this);
            }

            // apply tags
            if (callback == G_CALLBACK(onMagnetTagEvent_gui) && WGETB("use-magnet-split"))
            {
                string line;

                if (WulforUtil::splitMagnet(tagName, line))
                {
                    gtk_text_buffer_delete(messageBuffer, &tag_start_iter, &tag_end_iter);

                    gtk_text_buffer_insert_with_tags(messageBuffer, &tag_start_iter,
                                                     line.c_str(), line.size(), tag, TagsMap[Tag::TAG_URL], NULL);
                }
            }
            else
            {
                gtk_text_buffer_apply_tag(messageBuffer, tag, &tag_start_iter, &tag_end_iter);
                gtk_text_buffer_apply_tag(messageBuffer, TagsMap[Tag::TAG_URL], &tag_start_iter, &tag_end_iter);
            }

            applyEmoticons_gui();

            gtk_text_buffer_get_iter_at_mark(messageBuffer, &start_iter, tag_mark);

            if (gtk_text_iter_is_end(&start_iter))
                return;

            start = false;
        }
        else
        {
            if (gtk_text_iter_is_end(&start_iter))
            {
                if (!gtk_text_iter_equal(&tag_start_iter, &tag_end_iter))
                    gtk_text_buffer_move_mark(messageBuffer, end_mark, &tag_end_iter);

                applyEmoticons_gui();

                break;
            }

            gtk_text_buffer_move_mark(messageBuffer, end_mark, &tag_end_iter);
        }
    }
}

void PrivateMessage::applyEmoticons_gui()
{
    GtkTextIter start_iter, end_iter;
    gtk_text_buffer_get_iter_at_mark(messageBuffer, &start_iter, start_mark);
    gtk_text_buffer_get_iter_at_mark(messageBuffer, &end_iter, end_mark);

    if(gtk_text_iter_equal(&start_iter, &end_iter))
        return;

    dcassert(tagMsg >= TAG_PRIVATE && tagMsg < TAG_TIMESTAMP);
    gtk_text_buffer_apply_tag(messageBuffer, TagsMap[tagMsg], &start_iter, &end_iter);

    /* emoticons */
    if (tagMsg == Tag::TAG_SYSTEM || tagMsg == Tag::TAG_STATUS)
    {
        return;
    }
    else if (!Emoticons::get()->useEmoticons_gui())
    {
        if (WGETB("emoticons-use"))
            setStatus_gui(_(" *** Emoticons not loaded"));
        return;
    }
    else if (!useEmoticons)
    {
        setStatus_gui(_(" *** Emoticons mode off"));
        return;
    }
    else if (totalEmoticons >= EMOTICONS_MAX)
    {
        setStatus_gui(_(" *** Emoticons limit"));
        return;
    }

    bool search;
    gint searchEmoticons = 0;

    GtkTextIter tmp_end_iter,
            match_start,
            match_end,
            p_start,
            p_end;

    Emot::Iter p_it;
    gint set_start, new_start;
    Emot::List &list = Emoticons::get()->getPack_gui();

    /* set start mark */
    gtk_text_buffer_move_mark(messageBuffer, emot_mark, &start_iter);

    for (;;)
    {
        /* get start and end iter positions at marks */
        gtk_text_buffer_get_iter_at_mark(messageBuffer, &start_iter, emot_mark);
        gtk_text_buffer_get_iter_at_mark(messageBuffer, &end_iter, end_mark);

        search = false;
        set_start = gtk_text_iter_get_offset(&end_iter);

        for (Emot::Iter it = list.begin(); it != list.end(); ++it)
        {
            GList *names = (*it)->getNames();

            for (GList *p = names; p ; p = p->next)
            {
                if (gtk_text_iter_forward_search(&start_iter,
                                                 (gchar *)p->data,
                                                 GTK_TEXT_SEARCH_VISIBLE_ONLY,
                                                 &match_start,
                                                 &match_end,
                                                 &end_iter))
                {
                    if (!search)
                    {
                        search = true;
                        end_iter = match_start;

                        /* set new limit search */
                        gtk_text_buffer_get_iter_at_mark(messageBuffer, &tmp_end_iter, end_mark);
                        for (int i = 1; !gtk_text_iter_equal(&end_iter, &tmp_end_iter) && i <= Emot::SIZE_NAME;
                             gtk_text_iter_forward_chars(&end_iter, 1), i++);

                    }

                    new_start = gtk_text_iter_get_offset(&match_start);

                    if (new_start < set_start)
                    {
                        set_start = new_start;

                        p_start = match_start;
                        p_end = match_end;

                        p_it = it;

                        if (gtk_text_iter_equal(&start_iter, &match_start))
                        {
                            it = list.end() - 1;
                            break;
                        }
                    }
                }
            }
        }

        if (search)
        {
            if (totalEmoticons >= EMOTICONS_MAX)
            {
                setStatus_gui(_(" *** Emoticons limit"));
                return;
            }

            /* delete text-emoticon and insert pixbuf-emoticon */
            gtk_text_buffer_delete(messageBuffer, &p_start, &p_end);
            gtk_text_buffer_insert_pixbuf(messageBuffer, &p_start, (*p_it)->getPixbuf());

            searchEmoticons++;
            totalEmoticons++;

            /* set emoticon mark to start */
            gtk_text_buffer_move_mark(messageBuffer, emot_mark, &p_start);

            /* check full emoticons */
            gtk_text_buffer_get_iter_at_mark(messageBuffer, &start_iter, start_mark);
            gtk_text_buffer_get_iter_at_mark(messageBuffer, &end_iter, end_mark);

            if (gtk_text_iter_get_offset(&end_iter) - gtk_text_iter_get_offset(&start_iter) == searchEmoticons - 1)
                return;
        }
        else
            return;
    }
}

void PrivateMessage::getSettingTag_gui(WulforSettingsManager *wsm, const Tag::TypeTag type, string &fore, string &back, bool &bold, bool &italic)
{
    switch (type)
    {
    case Tag::TAG_MYOWN:

        fore = wsm->getString("text-myown-fore-color");
        back = wsm->getString("text-myown-back-color");
        bold = (bool)wsm->getInt("text-myown-bold");
        italic = (bool)wsm->getInt("text-myown-italic");
        break;

    case Tag::TAG_SYSTEM:

        fore = wsm->getString("text-system-fore-color");
        back = wsm->getString("text-system-back-color");
        bold = (bool)wsm->getInt("text-system-bold");
        italic = (bool)wsm->getInt("text-system-italic");
        break;

    case Tag::TAG_STATUS:

        fore = wsm->getString("text-status-fore-color");
        back = wsm->getString("text-status-back-color");
        bold = (bool)wsm->getInt("text-status-bold");
        italic = (bool)wsm->getInt("text-status-italic");
        break;

    case Tag::TAG_TIMESTAMP:

        fore = wsm->getString("text-timestamp-fore-color");
        back = wsm->getString("text-timestamp-back-color");
        bold = (bool)wsm->getInt("text-timestamp-bold");
        italic = (bool)wsm->getInt("text-timestamp-italic");
        break;

    case Tag::TAG_MYNICK:

        fore = wsm->getString("text-mynick-fore-color");
        back = wsm->getString("text-mynick-back-color");
        bold = (bool)wsm->getInt("text-mynick-bold");
        italic = (bool)wsm->getInt("text-mynick-italic");
        break;

    case Tag::TAG_OPERATOR:

        fore = wsm->getString("text-op-fore-color");
        back = wsm->getString("text-op-back-color");
        bold = (bool)wsm->getInt("text-op-bold");
        italic = (bool)wsm->getInt("text-op-italic");
        break;

    case Tag::TAG_FAVORITE:

        fore = wsm->getString("text-fav-fore-color");
        back = wsm->getString("text-fav-back-color");
        bold = (bool)wsm->getInt("text-fav-bold");
        italic = (bool)wsm->getInt("text-fav-italic");
        break;

    case Tag::TAG_URL:

        fore = wsm->getString("text-url-fore-color");
        back = wsm->getString("text-url-back-color");
        bold = (bool)wsm->getInt("text-url-bold");
        italic = (bool)wsm->getInt("text-url-italic");
        break;

    case Tag::TAG_NICK:

        fore = wsm->getString("text-private-fore-color");
        back = wsm->getString("text-private-back-color");
        italic = (bool)wsm->getInt("text-private-italic");
        bold = wsm->getBool("text-bold-autors");
        break;

    case Tag::TAG_PRIVATE:

    default:

        fore = wsm->getString("text-private-fore-color");
        back = wsm->getString("text-private-back-color");
        bold = (bool)wsm->getInt("text-private-bold");
        italic = (bool)wsm->getInt("text-private-italic");
    }
}

GtkTextTag* PrivateMessage::createTag_gui(const string &tagname, Tag::TypeTag type)
{
    WulforSettingsManager *wsm = WulforSettingsManager::getInstance();
    GtkTextTag *tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(messageBuffer), tagname.c_str());

    if (!tag)
    {
        string fore, back;
        bool bold = false, italic = false;

        getSettingTag_gui(wsm, type, fore, back, bold, italic);

        tag = WGETB("use-native-back-color-for-text") ?
                    gtk_text_buffer_create_tag(messageBuffer, tagname.c_str(),
                                               "foreground", fore.c_str(),
                                               "weight", bold ? TEXT_WEIGHT_BOLD : TEXT_WEIGHT_NORMAL,
                                               "style", italic ? TEXT_STYLE_ITALIC : TEXT_STYLE_NORMAL,
                                               NULL) :
                    gtk_text_buffer_create_tag(messageBuffer, tagname.c_str(),
                                               "foreground", fore.c_str(),
                                               "background", back.c_str(),
                                               "weight", bold ? TEXT_WEIGHT_BOLD : TEXT_WEIGHT_NORMAL,
                                               "style", italic ? TEXT_STYLE_ITALIC : TEXT_STYLE_NORMAL,
                                               NULL);
    }

    return tag;
}

void PrivateMessage::updateCursor(GtkWidget *widget)
{
    gint x, y, buf_x, buf_y;
    GtkTextIter iter;
    GSList *tagList = NULL;
    GtkTextTag *newTag = NULL;
#if GTK_CHECK_VERSION(3, 0, 0)
    //gdk_window_get_pointer(gtk_widget_get_window(widget), &x, &y, NULL);
    GdkDeviceManager *device_manager = gdk_display_get_device_manager(gdk_window_get_display(gtk_widget_get_window(widget)));
    GdkDevice *pointer = gdk_device_manager_get_client_pointer (device_manager);
    gdk_window_get_device_position(gtk_widget_get_window(widget), pointer, &x,&y, NULL);
#else
    gdk_window_get_pointer(widget->window, &x, &y, NULL);
#endif

    // Check for tags under the cursor, and change mouse cursor appropriately
    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_WIDGET, x, y, &buf_x, &buf_y);
    gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(widget), &iter, buf_x, buf_y);
    tagList = gtk_text_iter_get_tags(&iter);

    if (tagList)
    {
        newTag = GTK_TEXT_TAG(tagList->data);

        if (newTag == TagsMap[Tag::TAG_URL])
        {
            GSList *nextList = g_slist_next(tagList);

            if (nextList)
                newTag = GTK_TEXT_TAG(nextList->data);
            else
                newTag = NULL;
        }

        g_slist_free(tagList);
    }


    if (newTag != selectedTag)
    {
        // Cursor is in transition.
        if (newTag)
        {
            // Cursor is entering a tag.
#if GTK_CHECK_VERSION(3, 0, 0)
            gchar *tmp = NULL;
            g_object_get(G_OBJECT(newTag),"name",&tmp,NULL);
            selectedTagStr = string(tmp);
            g_free(tmp);
#else
            selectedTagStr = newTag->name;
#endif

            if (find(TagsMap, TagsMap + Tag::TAG_URL, newTag) == TagsMap + Tag::TAG_URL)
            {
                // Cursor was in neutral space.
                gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT), handCursor);
            }
            else
                gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT), NULL);
        }
        else
        {
            // Cursor is entering neutral space.
            gdk_window_set_cursor(gtk_text_view_get_window(GTK_TEXT_VIEW(widget), GTK_TEXT_WINDOW_TEXT), NULL);
        }

        selectedTag = newTag;
    }
}

gboolean PrivateMessage::onFocusIn_gui(GtkWidget *widget, GdkEventFocus *event, gpointer data)
{
    (void)widget;
    (void)event;

    PrivateMessage *pm = (PrivateMessage *)data;

    gtk_widget_grab_focus(pm->getWidget("entry"));

    // fix select text
    gtk_editable_set_position(GTK_EDITABLE(pm->getWidget("entry")), -1);

    return true;
}

void PrivateMessage::onSendMessage_gui(GtkEntry *entry, gpointer data)
{
    string text = gtk_entry_get_text(entry);
    if (text.empty())
        return;

    PrivateMessage *pm = (PrivateMessage *)data;
    gtk_entry_set_text(entry, "");

    // Store line in chat history
    pm->history.pop_back();
    pm->history.push_back(text);
    pm->history.push_back("");
    pm->historyIndex = pm->history.size() - 1;
    if (pm->history.size() > maxHistory + 1)
        pm->history.erase(pm->history.begin());

    // Process special commands
    if (text[0] == '/')
    {
        string command, param;
        string::size_type separator = text.find_first_of(' ');
        if (separator != string::npos && text.size() > separator + 1)
        {
            command = text.substr(1, separator - 1);
            param = text.substr(separator + 1);
        }
        else
        {
            command = text.substr(1);
        }
        std::transform(command.begin(), command.end(), command.begin(), (int(*)(int))tolower);

        if (command == "away")
        {
            if (Util::getAway() && param.empty())
            {
                Util::setAway(false);
                Util::setManualAway(false);
                pm->addStatusMessage_gui(_("Away mode off"), Msg::SYSTEM);
                pm->sentAwayMessage = false;
            }
            else
            {
                Util::setAway(true);
                Util::setManualAway(true);
                Util::setAwayMessage(param);
                pm->addStatusMessage_gui(_("Away mode on: ") + Util::getAwayMessage(), Msg::SYSTEM);
            }
        }
        else if (command == "back")
        {
            Util::setAway(false);
            pm->addStatusMessage_gui(_("Away mode off"), Msg::SYSTEM);
        }
        else if (command == "clear")
        {
            GtkTextIter startIter, endIter;
            gtk_text_buffer_get_start_iter(pm->messageBuffer, &startIter);
            gtk_text_buffer_get_end_iter(pm->messageBuffer, &endIter);
            gtk_text_buffer_delete(pm->messageBuffer, &startIter, &endIter);
        }
        else if (command == "close")
        {
            WulforManager::get()->getMainWindow()->removeBookEntry_gui(pm);
        }
        else if (command == "fuser" || command == "fu")
        {
            typedef Func0<PrivateMessage> F0;
            F0 *func = new F0(pm, &PrivateMessage::addFavoriteUser_client);
            WulforManager::get()->dispatchClientFunc(func);
        }
        else if (command == "removefu" || command == "rmfu")
        {
            typedef Func0<PrivateMessage> F0;
            F0 *func = new F0(pm, &PrivateMessage::removeFavoriteUser_client);
            WulforManager::get()->dispatchClientFunc(func);
        }
        else if (command == "getlist")
        {
            typedef Func0<PrivateMessage> F0;
            F0 *func = new F0(pm, &PrivateMessage::getFileList_client);
            WulforManager::get()->dispatchClientFunc(func);
        }
        else if (command == "grant")
        {
            typedef Func0<PrivateMessage> F0;
            F0 *func = new F0(pm, &PrivateMessage::grantSlot_client);
            WulforManager::get()->dispatchClientFunc(func);
        }
        else if (command == "emoticons" || command == "emot")
        {
            if (pm->useEmoticons)
            {
                pm->useEmoticons = false;
                pm->addStatusMessage_gui(_("Emoticons mode off"), Msg::SYSTEM);
            }
            else
            {
                pm->useEmoticons = true;
                pm->addStatusMessage_gui(_("Emoticons mode on"), Msg::SYSTEM);
            }
        }
        else if (command == "version")
        {
            pm->addStatusMessage_gui(eiskaltdcppAppNameString + " " +
                                     eiskaltdcppVersionString +
                                     _("project home: ") +
                                     "https://github.com/eiskaltdcpp/eiskaltdcpp",
                                     Msg::SYSTEM);
        }
        else if (command == "ratio")
        {
            double ratio;
            double up   = static_cast<double>(SETTING(TOTAL_UPLOAD));
            double down = static_cast<double>(SETTING(TOTAL_DOWNLOAD));

            if (down > 0)
                ratio = up / down;
            else
                ratio = 0;

            char ratio_c[32];
            sprintf(ratio_c,"%.3f", ratio);

            string uploaded = Util::formatBytes(up);
            string downloaded = Util::formatBytes(down);

            string line = str(dcpp_fmt(gettext("ratio: %1% (uploads: %2%, downloads: %3% )"))
                              % string(ratio_c)
                              % uploaded
                              % downloaded);

            if (param.compare("show") == 0)
            {
                typedef Func1<PrivateMessage, string> F1;
                F1 *func = new F1(pm, &PrivateMessage::sendMessage_client, line);
                WulforManager::get()->dispatchClientFunc(func);
            }
            else
            {
                pm->addStatusMessage_gui(line, Msg::SYSTEM);
            }
        }
        else if (command == "ws" && !param.empty())
        {
            string msg = WSCMD(param);
            pm->addStatusMessage_gui(msg, Msg::SYSTEM);
        }
        else if (command == "dcpps" && !param.empty())
        {
            string msg = SettingsManager::getInstance()->parseCoreCmd (param);
            pm->addStatusMessage_gui(msg, Msg::SYSTEM);
        }
        else if (command == "help")
        {
            pm->addLine_gui(Msg::SYSTEM, string(_("*** Available commands:")) + "\n\n" +
                            "/away <message>\t - " + _("Away mode message on/off") + "\n" +
                            "/back\t\t\t\t - " + _("Away mode off") + "\n" +
                            "/clear\t\t\t\t - " + _("Clear PM") + "\n" +
                            "/close\t\t\t\t - " + _("Close PM") + "\n" +
                            "/fuser, /fu\t\t\t - " + _("Add user to favorites list") + "\n" +
                            "/removefu, /rmfu\t\t - " + _("Remove user favorite") + "\n" +
                            "/getlist\t\t\t\t - " + _("Get file list") + "\n" +
                            "/grant\t\t\t\t - " + _("Grant extra slot") + "\n" +
                            "/emoticons, /emot\t - " + _("Emoticons on/off") + "\n" +
                            "/version\t\t\t\t - " + _("Show version") + "\n" +
                            "/ratio [show]\t\t\t - " + _("Show ratio [send in chat]") + "\n" +
                            "/help\t\t\t\t - " + _("Show help") + "\n");
        }
        else
        {
            pm->addStatusMessage_gui(_("Unknown command ") + text + _(": type /help for a list of available commands"), Msg::SYSTEM);
        }
    }
    else
    {
        typedef Func1<PrivateMessage, string> F1;
        F1 *func = new F1(pm, &PrivateMessage::sendMessage_client, text);
        WulforManager::get()->dispatchClientFunc(func);
    }
}

gboolean PrivateMessage::onKeyPress_gui(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;
    string text;
    size_t index;

    if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up)
    {
        index = pm->historyIndex - 1;
        if (index >= 0 && index < pm->history.size())
        {
            text = pm->history[index];
            pm->historyIndex = index;
            gtk_entry_set_text(GTK_ENTRY(widget), text.c_str());
        }
        return true;
    }
    else if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
    {
        index = pm->historyIndex + 1;
        if (index >= 0 && index < pm->history.size())
        {
            text = pm->history[index];
            pm->historyIndex = index;
            gtk_entry_set_text(GTK_ENTRY(widget), text.c_str());
        }
        return true;
    }

    return false;
}

gboolean PrivateMessage::onLinkTagEvent_gui(GtkTextTag *tag, GObject*, GdkEvent *event, GtkTextIter*, gpointer data)
{
    if (event->type == GDK_BUTTON_PRESS)
    {
        PrivateMessage *pm = (PrivateMessage *)data;
#if GTK_CHECK_VERSION(3, 0, 0)
        gchar *tmp = NULL;
        g_object_get(G_OBJECT(tag),"name",&tmp,NULL);
        pm->selectedTagStr = string(tmp);
        g_free(tmp);
#else
        pm->selectedTagStr = tag->name;
#endif

        switch (event->button.button)
        {
        case 1:
            onOpenLinkClicked_gui(NULL, data);
            break;
        case 3:
            // Pop-up link context menu
            gtk_widget_show_all(pm->getWidget("linkMenu"));
#if GTK_CHECK_VERSION(3,22,0)
            gtk_menu_popup_at_pointer(GTK_MENU(pm->getWidget("linkMenu")),NULL);
#else
            gtk_menu_popup(GTK_MENU(pm->getWidget("linkMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
#endif
            break;
        }
        return true;
    }
    return false;
}

gboolean PrivateMessage::onHubTagEvent_gui(GtkTextTag *tag, GObject*, GdkEvent *event, GtkTextIter*, gpointer data)
{
    if (event->type == GDK_BUTTON_PRESS)
    {
        PrivateMessage *pm = (PrivateMessage *)data;
#if GTK_CHECK_VERSION(3, 0, 0)
        gchar *tmp = NULL;
        g_object_get(G_OBJECT(tag),"name",&tmp,NULL);
        pm->selectedTagStr = string(tmp);
        g_free(tmp);
#else
        pm->selectedTagStr = tag->name;
#endif

        switch (event->button.button)
        {
        case 1:
            onOpenHubClicked_gui(NULL, data);
            break;
        case 3:
            // Popup hub context menu
            gtk_widget_show_all(pm->getWidget("hubMenu"));
#if GTK_CHECK_VERSION(3,22,0)
            gtk_menu_popup_at_pointer(GTK_MENU(pm->getWidget("hubMenu")),NULL);
#else
            gtk_menu_popup(GTK_MENU(pm->getWidget("hubMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
#endif
            break;
        }
        return true;
    }
    return false;
}

gboolean PrivateMessage::onMagnetTagEvent_gui(GtkTextTag *tag, GObject*, GdkEvent *event, GtkTextIter*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    if (event->type == GDK_BUTTON_PRESS)
    {
#if GTK_CHECK_VERSION(3, 0, 0)
        gchar *tmp = NULL;
        g_object_get(G_OBJECT(tag),"name",&tmp,NULL);
        pm->selectedTagStr = string(tmp);
        g_free(tmp);
#else
        pm->selectedTagStr = tag->name;
#endif

        switch (event->button.button)
        {
        case 1:
            // Search for magnet
            WulforManager::get()->getMainWindow()->actionMagnet_gui(pm->selectedTagStr);
            break;
        case 3:
            // Popup magnet context menu
            gtk_widget_show_all(pm->getWidget("magnetMenu"));
#if GTK_CHECK_VERSION(3,22,0)
            gtk_menu_popup_at_pointer(GTK_MENU(pm->getWidget("magnetMenu")),NULL);
#else
            gtk_menu_popup(GTK_MENU(pm->getWidget("magnetMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
#endif
            break;
        }
        return true;
    }
    return false;
}

gboolean PrivateMessage::onChatPointerMoved_gui(GtkWidget* widget, GdkEventMotion*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    pm->updateCursor(widget);

    return false;
}

gboolean PrivateMessage::onChatVisibilityChanged_gui(GtkWidget* widget, GdkEventVisibility* event, gpointer data)
{
    (void)event;
    PrivateMessage *pm = (PrivateMessage *)data;

    pm->updateCursor(widget);

    return false;
}

gboolean PrivateMessage::onEmotButtonRelease_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    (void)widget;
    PrivateMessage *pm = (PrivateMessage *)data;

    switch (event->button)
    {
    case 1: //show emoticons dialog

        pm->emotdialog->showEmotDialog_gui();
        break;

    case 3: //show emoticons menu

        pm->emotdialog->buildEmotMenu_gui();

        GtkWidget *check_item = NULL;
        GtkWidget *emot_menu = pm->getWidget("emotMenu");

        check_item = gtk_separator_menu_item_new();
        gtk_menu_shell_append(GTK_MENU_SHELL(emot_menu), check_item);
        gtk_widget_show(check_item);

        check_item = gtk_check_menu_item_new_with_label(_("Use Emoticons"));
        gtk_menu_shell_append(GTK_MENU_SHELL(emot_menu), check_item);

        if (pm->useEmoticons)
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(check_item), true);

        g_signal_connect(check_item, "activate", G_CALLBACK(onUseEmoticons_gui), data);

        gtk_widget_show_all(emot_menu);
#if GTK_CHECK_VERSION(3,22,0)
        gtk_menu_popup_at_widget(GTK_MENU(emot_menu),widget,GDK_GRAVITY_SOUTH_WEST,GDK_GRAVITY_NORTH_WEST,NULL);
#else
        gtk_menu_popup(GTK_MENU(emot_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
#endif
        break;
    }

    return false;
}

void PrivateMessage::onChatScroll_gui(GtkAdjustment *adjustment, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;
    gdouble value = gtk_adjustment_get_value(adjustment);
#if GTK_CHECK_VERSION(3, 0, 0)
    pm->scrollToBottom = value >= (gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size (adjustment));
#else
    pm->scrollToBottom = value >= (adjustment->upper-adjustment->page_size);
#endif
}

void PrivateMessage::onChatResize_gui(GtkAdjustment *adjustment, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;
    gdouble value = gtk_adjustment_get_value(adjustment);

#if GTK_CHECK_VERSION(3, 0, 0)
    if (pm->scrollToBottom && value < (gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size (adjustment)))
#else
    if (pm->scrollToBottom && value < (adjustment->upper-adjustment->page_size))
#endif
    {
        GtkTextIter iter;

        gtk_text_buffer_get_end_iter(pm->messageBuffer, &iter);
        gtk_text_buffer_move_mark(pm->messageBuffer, pm->mark, &iter);
        gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(pm->getWidget("text")), pm->mark, 0, false, 0, 0);
    }
}

void PrivateMessage::onCopyURIClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), pm->selectedTagStr.c_str(), pm->selectedTagStr.length());
}

void PrivateMessage::onOpenLinkClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    WulforUtil::openURI(pm->selectedTagStr);
}

void PrivateMessage::onOpenHubClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    WulforManager::get()->getMainWindow()->showHub_gui(pm->selectedTagStr);
}

void PrivateMessage::onSearchMagnetClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    WulforManager::get()->getMainWindow()->addSearch_gui(pm->selectedTagStr);
}

void PrivateMessage::onDownloadClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;
    WulforManager::get()->getMainWindow()->fileToDownload_gui(pm->selectedTagStr, SETTING(DOWNLOAD_DIRECTORY));
}

void PrivateMessage::onDownloadToClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    GtkWidget *dialog = WulforManager::get()->getMainWindow()->getChooserDialog_gui();
    gtk_window_set_title(GTK_WINDOW(dialog), _("Choose a directory"));
    gtk_file_chooser_set_action(GTK_FILE_CHOOSER(dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
    gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), Text::fromUtf8(WGETS("magnet-choose-dir")).c_str());
    gint response = gtk_dialog_run(GTK_DIALOG(dialog));

    // if the dialog gets programmatically destroyed.
    if (response == GTK_RESPONSE_NONE)
        return;

    if (response == GTK_RESPONSE_OK)
    {
        gchar *temp = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog));

        if (temp)
        {
            string path = Text::toUtf8(temp) + G_DIR_SEPARATOR_S;
            g_free(temp);

            WulforManager::get()->getMainWindow()->fileToDownload_gui(pm->selectedTagStr, path);
        }
    }
    gtk_widget_hide(dialog);
}

void PrivateMessage::onMagnetPropertiesClicked_gui(GtkMenuItem*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    WulforManager::get()->getMainWindow()->propertiesMagnetDialog_gui(pm->selectedTagStr);
}

void PrivateMessage::onCommandClicked_gui(GtkWidget *widget, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    string command = (gchar*) g_object_get_data(G_OBJECT(widget), "command");

    gint pos = 0;
    GtkWidget *entry = pm->getWidget("entry");
    if (!gtk_widget_is_focus(entry))
        gtk_widget_grab_focus(entry);
    gtk_editable_delete_text(GTK_EDITABLE(entry), pos, -1);
    gtk_editable_insert_text(GTK_EDITABLE(entry), command.c_str(), -1, &pos);
    gtk_editable_set_position(GTK_EDITABLE(entry), pos);
}

gboolean PrivateMessage::onChatCommandButtonRelease_gui(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
    if (event->button == 1)
    {
        PrivateMessage *pm = (PrivateMessage *)data;
#if GTK_CHECK_VERSION(3,22,0)
        gtk_menu_popup_at_widget(GTK_MENU(pm->getWidget("chatCommandsMenu")),widget,GDK_GRAVITY_SOUTH_WEST,GDK_GRAVITY_NORTH_WEST,NULL);
#else
        (void)widget;
        gtk_menu_popup(GTK_MENU(pm->getWidget("chatCommandsMenu")), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
#endif
    }

    return false;
}

void PrivateMessage::onUseEmoticons_gui(GtkWidget*, gpointer data)
{
    PrivateMessage *pm = (PrivateMessage *)data;

    pm->useEmoticons = !pm->useEmoticons;
}

void PrivateMessage::updateOnlineStatus_gui(bool online)
{
    setIcon_gui(online ? WGETS("icon-pm-online") : WGETS("icon-pm-offline"));
}

void PrivateMessage::sendMessage_client(string message)
{
    UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
    if (user && user->isOnline())
    {
        // FIXME: WTF does the 3rd param (bool thirdPerson) do? A: Used for /me stuff
        ClientManager::getInstance()->privateMessage(HintedUser(user, hubUrl), message, false);
    }
    else
    {
        typedef Func2<PrivateMessage, string, Msg::TypeMsg> F2;
        F2 *func = new F2(this, &PrivateMessage::addStatusMessage_gui, _("User went offline"), Msg::STATUS);
        WulforManager::get()->dispatchGuiFunc(func);
    }
}

void PrivateMessage::addFavoriteUser_client()
{
    UserPtr user = ClientManager::getInstance()->findUser(CID(cid));

    if (user && FavoriteManager::getInstance()->isFavoriteUser(user))
    {
        typedef Func2<PrivateMessage, string, Msg::TypeMsg> F2;
        F2 *func = new F2(this, &PrivateMessage::addStatusMessage_gui, WulforUtil::getNicks(user, hubUrl) + _(" is favorite user"),
                          Msg::STATUS);
        WulforManager::get()->dispatchGuiFunc(func);
    }
    else
    {
        FavoriteManager::getInstance()->addFavoriteUser(user);
    }
}

void PrivateMessage::removeFavoriteUser_client()
{
    UserPtr user = ClientManager::getInstance()->findUser(CID(cid));

    if (user && FavoriteManager::getInstance()->isFavoriteUser(user))
    {
        FavoriteManager::getInstance()->removeFavoriteUser(user);
    }
    else
    {
        typedef Func2<PrivateMessage, string, Msg::TypeMsg> F2;
        F2 *func = new F2(this, &PrivateMessage::addStatusMessage_gui, WulforUtil::getNicks(user, hubUrl) + _(" is not favorite user"),
                          Msg::STATUS);
        WulforManager::get()->dispatchGuiFunc(func);
    }
}

void PrivateMessage::getFileList_client()
{
    try
    {
        UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
        if (user)
            QueueManager::getInstance()->addList(HintedUser(user, hubUrl), QueueItem::FLAG_CLIENT_VIEW);
    }
    catch (const Exception& e)
    {
        typedef Func2<PrivateMessage, string, Msg::TypeMsg> F2;
        F2 *func = new F2(this, &PrivateMessage::addStatusMessage_gui, e.getError(), Msg::STATUS);
        WulforManager::get()->dispatchGuiFunc(func);
    }
}

void PrivateMessage::grantSlot_client()
{
    UserPtr user = ClientManager::getInstance()->findUser(CID(cid));
    if (user)
    {
        UploadManager::getInstance()->reserveSlot(HintedUser(user, hubUrl));
    }
    else
    {
        typedef Func2<PrivateMessage, string, Msg::TypeMsg> F2;
        F2 *func = new F2(this, &PrivateMessage::addStatusMessage_gui, _("Slot granted"), Msg::STATUS);
        WulforManager::get()->dispatchGuiFunc(func);
    }
}

void PrivateMessage::on(ClientManagerListener::UserConnected, const UserPtr& aUser) noexcept
{
    if (aUser->getCID() == CID(cid))
    {
        typedef Func1<PrivateMessage, bool> F1;
        F1 *func = new F1(this, &PrivateMessage::updateOnlineStatus_gui, aUser->isOnline());
        WulforManager::get()->dispatchGuiFunc(func);
        offline = false;
    }
}

void PrivateMessage::on(ClientManagerListener::UserDisconnected, const UserPtr& aUser) noexcept
{
    if (aUser->getCID() == CID(cid))
    {
        typedef Func1<PrivateMessage, bool> F1;
        F1 *func = new F1(this, &PrivateMessage::updateOnlineStatus_gui, aUser->isOnline());
        WulforManager::get()->dispatchGuiFunc(func);
        offline = true;
    }
}
