diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6cb83ea5..8c492299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,8 +14,6 @@
### Other changes
- Removed room re-ordering option.
-- Removed built-in emoji picker.
-- Removed bundled fonts.
## [0.6.1] - 2018-09-26
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 45a63829..ae6edb87 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -176,6 +176,10 @@ set(SRC_FILES
src/dialogs/RoomSettings.cpp
# Emoji
+ src/emoji/Category.cpp
+ src/emoji/ItemDelegate.cpp
+ src/emoji/Panel.cpp
+ src/emoji/PickButton.cpp
src/emoji/Provider.cpp
# Timeline
@@ -301,6 +305,12 @@ qt5_wrap_cpp(MOC_HEADERS
src/dialogs/ReCaptcha.h
src/dialogs/RoomSettings.h
+ # Emoji
+ src/emoji/Category.h
+ src/emoji/ItemDelegate.h
+ src/emoji/Panel.h
+ src/emoji/PickButton.h
+
# Timeline
src/timeline/TimelineItem.h
src/timeline/TimelineView.h
diff --git a/README.md b/README.md
index 66675d61..f3fad169 100644
--- a/README.md
+++ b/README.md
@@ -264,5 +264,11 @@ Here is a screen shot to get a feel for the UI, but things will probably change.
![nheko](https://dl.dropboxusercontent.com/s/3zjcezdtk8kqe4i/nheko-v0.4.0.png)
+### Third party
+
+- [Emoji One](http://emojione.com)
+- [Font Awesome](http://fontawesome.io/)
+- [Open Sans](https://fonts.google.com/specimen/Open+Sans)
+
[Matrix]:https://matrix.org
[Riot]:https://riot.im
diff --git a/resources/fonts/EmojiOne/emojione-android.ttf b/resources/fonts/EmojiOne/emojione-android.ttf
new file mode 100644
index 00000000..4cd640d0
Binary files /dev/null and b/resources/fonts/EmojiOne/emojione-android.ttf differ
diff --git a/resources/icons/emoji-categories/activity.png b/resources/icons/emoji-categories/activity.png
new file mode 100644
index 00000000..2d360762
Binary files /dev/null and b/resources/icons/emoji-categories/activity.png differ
diff --git a/resources/icons/emoji-categories/activity@2x.png b/resources/icons/emoji-categories/activity@2x.png
new file mode 100644
index 00000000..d8f88711
Binary files /dev/null and b/resources/icons/emoji-categories/activity@2x.png differ
diff --git a/resources/icons/emoji-categories/flags.png b/resources/icons/emoji-categories/flags.png
new file mode 100644
index 00000000..9a52000f
Binary files /dev/null and b/resources/icons/emoji-categories/flags.png differ
diff --git a/resources/icons/emoji-categories/flags@2x.png b/resources/icons/emoji-categories/flags@2x.png
new file mode 100644
index 00000000..45350593
Binary files /dev/null and b/resources/icons/emoji-categories/flags@2x.png differ
diff --git a/resources/icons/emoji-categories/foods.png b/resources/icons/emoji-categories/foods.png
new file mode 100644
index 00000000..15c31069
Binary files /dev/null and b/resources/icons/emoji-categories/foods.png differ
diff --git a/resources/icons/emoji-categories/foods@2x.png b/resources/icons/emoji-categories/foods@2x.png
new file mode 100644
index 00000000..bbdd2a3c
Binary files /dev/null and b/resources/icons/emoji-categories/foods@2x.png differ
diff --git a/resources/icons/emoji-categories/nature.png b/resources/icons/emoji-categories/nature.png
new file mode 100644
index 00000000..eb1786cf
Binary files /dev/null and b/resources/icons/emoji-categories/nature.png differ
diff --git a/resources/icons/emoji-categories/nature@2x.png b/resources/icons/emoji-categories/nature@2x.png
new file mode 100644
index 00000000..81db5c08
Binary files /dev/null and b/resources/icons/emoji-categories/nature@2x.png differ
diff --git a/resources/icons/emoji-categories/objects.png b/resources/icons/emoji-categories/objects.png
new file mode 100644
index 00000000..45c6eb37
Binary files /dev/null and b/resources/icons/emoji-categories/objects.png differ
diff --git a/resources/icons/emoji-categories/objects@2x.png b/resources/icons/emoji-categories/objects@2x.png
new file mode 100644
index 00000000..01fd5cb4
Binary files /dev/null and b/resources/icons/emoji-categories/objects@2x.png differ
diff --git a/resources/icons/emoji-categories/people.png b/resources/icons/emoji-categories/people.png
new file mode 100644
index 00000000..710e808a
Binary files /dev/null and b/resources/icons/emoji-categories/people.png differ
diff --git a/resources/icons/emoji-categories/people@2x.png b/resources/icons/emoji-categories/people@2x.png
new file mode 100644
index 00000000..142ba09e
Binary files /dev/null and b/resources/icons/emoji-categories/people@2x.png differ
diff --git a/resources/icons/emoji-categories/symbols.png b/resources/icons/emoji-categories/symbols.png
new file mode 100644
index 00000000..08184de1
Binary files /dev/null and b/resources/icons/emoji-categories/symbols.png differ
diff --git a/resources/icons/emoji-categories/symbols@2x.png b/resources/icons/emoji-categories/symbols@2x.png
new file mode 100644
index 00000000..b5e7cc6c
Binary files /dev/null and b/resources/icons/emoji-categories/symbols@2x.png differ
diff --git a/resources/icons/emoji-categories/travel.png b/resources/icons/emoji-categories/travel.png
new file mode 100644
index 00000000..93da773e
Binary files /dev/null and b/resources/icons/emoji-categories/travel.png differ
diff --git a/resources/icons/emoji-categories/travel@2x.png b/resources/icons/emoji-categories/travel@2x.png
new file mode 100644
index 00000000..2f72a281
Binary files /dev/null and b/resources/icons/emoji-categories/travel@2x.png differ
diff --git a/resources/res.qrc b/resources/res.qrc
index 559d6def..cef55773 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -63,6 +63,22 @@
icons/ui/edit.png
icons/ui/edit@2x.png
+ icons/emoji-categories/people.png
+ icons/emoji-categories/people@2x.png
+ icons/emoji-categories/nature.png
+ icons/emoji-categories/nature@2x.png
+ icons/emoji-categories/foods.png
+ icons/emoji-categories/foods@2x.png
+ icons/emoji-categories/activity.png
+ icons/emoji-categories/activity@2x.png
+ icons/emoji-categories/travel.png
+ icons/emoji-categories/travel@2x.png
+ icons/emoji-categories/objects.png
+ icons/emoji-categories/objects@2x.png
+ icons/emoji-categories/symbols.png
+ icons/emoji-categories/symbols@2x.png
+ icons/emoji-categories/flags.png
+ icons/emoji-categories/flags@2x.png
nheko.png
@@ -83,6 +99,13 @@
nheko-32.png
nheko-16.png
+
+ fonts/OpenSans/OpenSans-Regular.ttf
+ fonts/OpenSans/OpenSans-Italic.ttf
+ fonts/OpenSans/OpenSans-Bold.ttf
+ fonts/OpenSans/OpenSans-Semibold.ttf
+ fonts/EmojiOne/emojione-android.ttf
+
styles/system.qss
styles/nheko.qss
diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss
index 0abd8415..5567f32c 100644
--- a/resources/styles/nheko-dark.qss
+++ b/resources/styles/nheko-dark.qss
@@ -193,6 +193,18 @@ RegisterPage {
color: #caccd1;
}
+emoji--Panel,
+emoji--Panel > * {
+ background-color: #202228;
+ color: #caccd1;
+}
+
+emoji--Category,
+emoji--Category > * {
+ background-color: #2d3139;
+ color: #caccd1;
+}
+
FloatingButton {
qproperty-backgroundColor: #2d3139;
qproperty-foregroundColor: white;
diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss
index 3e47c49b..58e83c22 100644
--- a/resources/styles/nheko.qss
+++ b/resources/styles/nheko.qss
@@ -190,6 +190,18 @@ RegisterPage {
color: #333;
}
+emoji--Panel,
+emoji--Panel > * {
+ background-color: #eee;
+ color: #333;
+}
+
+emoji--Category,
+emoji--Category > * {
+ background-color: white;
+ color: #ccc;
+}
+
FloatingButton {
qproperty-backgroundColor: #efefef;
qproperty-foregroundColor: black;
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 89513037..5fcba7a9 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -513,8 +513,22 @@ TextInputWidget::TextInputWidget(QWidget *parent)
sendMessageBtn_->setIcon(send_message_icon);
sendMessageBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight));
+ emojiBtn_ = new emoji::PickButton(this);
+ emojiBtn_->setToolTip(tr("Emoji"));
+
+#if defined(Q_OS_MAC)
+ // macOS has a native emoji picker.
+ emojiBtn_->hide();
+#endif
+
+ QIcon emoji_icon;
+ emoji_icon.addFile(":/icons/icons/ui/smile.png");
+ emojiBtn_->setIcon(emoji_icon);
+ emojiBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight));
+
topLayout_->addWidget(sendFileBtn_);
topLayout_->addWidget(input_);
+ topLayout_->addWidget(emojiBtn_);
topLayout_->addWidget(sendMessageBtn_);
setLayout(topLayout_);
@@ -527,6 +541,11 @@ TextInputWidget::TextInputWidget(QWidget *parent)
connect(input_, &FilteredTextEdit::audio, this, &TextInputWidget::uploadAudio);
connect(input_, &FilteredTextEdit::video, this, &TextInputWidget::uploadVideo);
connect(input_, &FilteredTextEdit::file, this, &TextInputWidget::uploadFile);
+ connect(emojiBtn_,
+ SIGNAL(emojiSelected(const QString &)),
+ this,
+ SLOT(addSelectedEmoji(const QString &)));
+
connect(input_, &FilteredTextEdit::startedTyping, this, &TextInputWidget::startedTyping);
connect(input_, &FilteredTextEdit::stoppedTyping, this, &TextInputWidget::stoppedTyping);
@@ -535,6 +554,22 @@ TextInputWidget::TextInputWidget(QWidget *parent)
input_, &FilteredTextEdit::startedUpload, this, &TextInputWidget::showUploadSpinner);
}
+void
+TextInputWidget::addSelectedEmoji(const QString &emoji)
+{
+ QTextCursor cursor = input_->textCursor();
+
+ QTextCharFormat charfmt;
+ input_->setCurrentCharFormat(charfmt);
+
+ input_->insertPlainText(emoji);
+ cursor.movePosition(QTextCursor::End);
+
+ input_->setCurrentCharFormat(charfmt);
+
+ input_->show();
+}
+
void
TextInputWidget::command(QString command, QString args)
{
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index 1fb6d7f2..8f634f6b 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -30,6 +30,7 @@
#include "SuggestionsPopup.h"
#include "dialogs/PreviewUploadOverlay.h"
+#include "emoji/PickButton.h"
namespace dialogs {
class PreviewUploadOverlay;
@@ -159,6 +160,9 @@ public slots:
void focusLineEdit() { input_->setFocus(); }
void addReply(const QString &username, const QString &msg);
+private slots:
+ void addSelectedEmoji(const QString &emoji);
+
signals:
void sendTextMessage(QString msg);
void sendEmoteMessage(QString msg);
@@ -189,6 +193,7 @@ private:
FlatButton *sendFileBtn_;
FlatButton *sendMessageBtn_;
+ emoji::PickButton *emojiBtn_;
QColor borderColor_;
};
diff --git a/src/emoji/Category.cpp b/src/emoji/Category.cpp
new file mode 100644
index 00000000..fbfbf4fc
--- /dev/null
+++ b/src/emoji/Category.cpp
@@ -0,0 +1,90 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#include
+#include
+#include
+
+#include "Config.h"
+
+#include "emoji/Category.h"
+
+using namespace emoji;
+
+Category::Category(QString category, std::vector emoji, QWidget *parent)
+ : QWidget(parent)
+{
+ mainLayout_ = new QVBoxLayout(this);
+ mainLayout_->setMargin(0);
+ mainLayout_->setSpacing(0);
+
+ emojiListView_ = new QListView();
+ itemModel_ = new QStandardItemModel(this);
+
+ delegate_ = new ItemDelegate(this);
+ data_ = new Emoji;
+
+ emojiListView_->setItemDelegate(delegate_);
+ emojiListView_->setModel(itemModel_);
+ emojiListView_->setViewMode(QListView::IconMode);
+ emojiListView_->setFlow(QListView::LeftToRight);
+ emojiListView_->setResizeMode(QListView::Adjust);
+ emojiListView_->verticalScrollBar()->setEnabled(false);
+ emojiListView_->horizontalScrollBar()->setEnabled(false);
+
+ const int cols = 7;
+ const int rows = emoji.size() / 7;
+
+ // TODO: Be precise here. Take the parent into consideration.
+ emojiListView_->setFixedSize(cols * 50 + 20, rows * 50 + 20);
+ emojiListView_->setGridSize(QSize(50, 50));
+ emojiListView_->setDragEnabled(false);
+ emojiListView_->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+ for (const auto &e : emoji) {
+ data_->unicode = e.unicode;
+
+ auto item = new QStandardItem;
+ item->setSizeHint(QSize(24, 24));
+
+ QVariant unicode(data_->unicode);
+ item->setData(unicode.toString(), Qt::UserRole);
+
+ itemModel_->appendRow(item);
+ }
+
+ QFont font;
+ font.setWeight(QFont::Medium);
+
+ category_ = new QLabel(category, this);
+ category_->setFont(font);
+ category_->setStyleSheet("margin: 20px 0 20px 8px;");
+
+ mainLayout_->addWidget(category_);
+ mainLayout_->addWidget(emojiListView_);
+
+ connect(emojiListView_, &QListView::clicked, this, &Category::clickIndex);
+}
+
+void
+Category::paintEvent(QPaintEvent *)
+{
+ QStyleOption opt;
+ opt.init(this);
+ QPainter p(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+}
diff --git a/src/emoji/Category.h b/src/emoji/Category.h
new file mode 100644
index 00000000..a14029c8
--- /dev/null
+++ b/src/emoji/Category.h
@@ -0,0 +1,59 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "ItemDelegate.h"
+
+namespace emoji {
+
+class Category : public QWidget
+{
+ Q_OBJECT
+
+public:
+ Category(QString category, std::vector emoji, QWidget *parent = nullptr);
+
+signals:
+ void emojiSelected(const QString &emoji);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+
+private slots:
+ void clickIndex(const QModelIndex &index)
+ {
+ emit emojiSelected(index.data(Qt::UserRole).toString());
+ };
+
+private:
+ QVBoxLayout *mainLayout_;
+
+ QStandardItemModel *itemModel_;
+ QListView *emojiListView_;
+
+ emoji::Emoji *data_;
+ emoji::ItemDelegate *delegate_;
+
+ QLabel *category_;
+};
+} // namespace emoji
diff --git a/src/emoji/ItemDelegate.cpp b/src/emoji/ItemDelegate.cpp
new file mode 100644
index 00000000..50a1b7ed
--- /dev/null
+++ b/src/emoji/ItemDelegate.cpp
@@ -0,0 +1,48 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#include
+#include
+
+#include "emoji/ItemDelegate.h"
+
+using namespace emoji;
+
+ItemDelegate::ItemDelegate(QObject *parent)
+ : QStyledItemDelegate(parent)
+{
+ data_ = new Emoji;
+}
+
+ItemDelegate::~ItemDelegate() { delete data_; }
+
+void
+ItemDelegate::paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ Q_UNUSED(index);
+
+ QStyleOptionViewItem viewOption(option);
+
+ auto emoji = index.data(Qt::UserRole).toString();
+
+ QFont font("Emoji One");
+
+ painter->setFont(font);
+ painter->drawText(viewOption.rect, Qt::AlignCenter, emoji);
+}
diff --git a/src/emoji/ItemDelegate.h b/src/emoji/ItemDelegate.h
new file mode 100644
index 00000000..e0456308
--- /dev/null
+++ b/src/emoji/ItemDelegate.h
@@ -0,0 +1,43 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "Provider.h"
+
+namespace emoji {
+
+class ItemDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ explicit ItemDelegate(QObject *parent = nullptr);
+ ~ItemDelegate();
+
+ void paint(QPainter *painter,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+
+private:
+ Emoji *data_;
+};
+} // namespace emoji
diff --git a/src/emoji/Panel.cpp b/src/emoji/Panel.cpp
new file mode 100644
index 00000000..710b501e
--- /dev/null
+++ b/src/emoji/Panel.cpp
@@ -0,0 +1,236 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#include
+#include
+#include
+
+#include "ui/DropShadow.h"
+#include "ui/FlatButton.h"
+
+#include "emoji/Category.h"
+#include "emoji/Panel.h"
+
+using namespace emoji;
+
+Panel::Panel(QWidget *parent)
+ : QWidget(parent)
+ , shadowMargin_{2}
+ , width_{370}
+ , height_{350}
+ , categoryIconSize_{20}
+{
+ setStyleSheet("QWidget {border: none;}"
+ "QScrollBar:vertical { width: 0px; margin: 0px; }"
+ "QScrollBar::handle:vertical { min-height: 30px; }");
+
+ setAttribute(Qt::WA_ShowWithoutActivating, true);
+ setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
+
+ auto mainWidget = new QWidget(this);
+ mainWidget->setMaximumSize(width_, height_);
+
+ auto topLayout = new QVBoxLayout(this);
+ topLayout->addWidget(mainWidget);
+ topLayout->setMargin(shadowMargin_);
+ topLayout->setSpacing(0);
+
+ auto contentLayout = new QVBoxLayout(mainWidget);
+ contentLayout->setMargin(0);
+ contentLayout->setSpacing(0);
+
+ auto emojiCategories = new QFrame(mainWidget);
+
+ auto categoriesLayout = new QHBoxLayout(emojiCategories);
+ categoriesLayout->setSpacing(0);
+ categoriesLayout->setMargin(0);
+
+ QIcon icon;
+
+ auto peopleCategory = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/people.png");
+ peopleCategory->setIcon(icon);
+ peopleCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto natureCategory_ = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/nature.png");
+ natureCategory_->setIcon(icon);
+ natureCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto foodCategory_ = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/foods.png");
+ foodCategory_->setIcon(icon);
+ foodCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto activityCategory = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/activity.png");
+ activityCategory->setIcon(icon);
+ activityCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto travelCategory = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/travel.png");
+ travelCategory->setIcon(icon);
+ travelCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto objectsCategory = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/objects.png");
+ objectsCategory->setIcon(icon);
+ objectsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto symbolsCategory = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/symbols.png");
+ symbolsCategory->setIcon(icon);
+ symbolsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ auto flagsCategory = new FlatButton(emojiCategories);
+ icon.addFile(":/icons/icons/emoji-categories/flags.png");
+ flagsCategory->setIcon(icon);
+ flagsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
+
+ categoriesLayout->addWidget(peopleCategory);
+ categoriesLayout->addWidget(natureCategory_);
+ categoriesLayout->addWidget(foodCategory_);
+ categoriesLayout->addWidget(activityCategory);
+ categoriesLayout->addWidget(travelCategory);
+ categoriesLayout->addWidget(objectsCategory);
+ categoriesLayout->addWidget(symbolsCategory);
+ categoriesLayout->addWidget(flagsCategory);
+
+ scrollArea_ = new QScrollArea(this);
+ scrollArea_->setWidgetResizable(true);
+ scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ auto scrollWidget = new QWidget(this);
+ auto scrollLayout = new QVBoxLayout(scrollWidget);
+
+ scrollLayout->setMargin(0);
+ scrollLayout->setSpacing(0);
+ scrollArea_->setWidget(scrollWidget);
+
+ auto peopleEmoji =
+ new Category(tr("Smileys & People"), emoji_provider_.people, scrollWidget);
+ scrollLayout->addWidget(peopleEmoji);
+
+ auto natureEmoji =
+ new Category(tr("Animals & Nature"), emoji_provider_.nature, scrollWidget);
+ scrollLayout->addWidget(natureEmoji);
+
+ auto foodEmoji = new Category(tr("Food & Drink"), emoji_provider_.food, scrollWidget);
+ scrollLayout->addWidget(foodEmoji);
+
+ auto activityEmoji = new Category(tr("Activity"), emoji_provider_.activity, scrollWidget);
+ scrollLayout->addWidget(activityEmoji);
+
+ auto travelEmoji =
+ new Category(tr("Travel & Places"), emoji_provider_.travel, scrollWidget);
+ scrollLayout->addWidget(travelEmoji);
+
+ auto objectsEmoji = new Category(tr("Objects"), emoji_provider_.objects, scrollWidget);
+ scrollLayout->addWidget(objectsEmoji);
+
+ auto symbolsEmoji = new Category(tr("Symbols"), emoji_provider_.symbols, scrollWidget);
+ scrollLayout->addWidget(symbolsEmoji);
+
+ auto flagsEmoji = new Category(tr("Flags"), emoji_provider_.flags, scrollWidget);
+ scrollLayout->addWidget(flagsEmoji);
+
+ contentLayout->addWidget(scrollArea_);
+ contentLayout->addWidget(emojiCategories);
+
+ connect(peopleEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(peopleCategory, &QPushButton::clicked, [this, peopleEmoji]() {
+ this->showCategory(peopleEmoji);
+ });
+
+ connect(natureEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(natureCategory_, &QPushButton::clicked, [this, natureEmoji]() {
+ this->showCategory(natureEmoji);
+ });
+
+ connect(foodEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(foodCategory_, &QPushButton::clicked, [this, foodEmoji]() {
+ this->showCategory(foodEmoji);
+ });
+
+ connect(activityEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(activityCategory, &QPushButton::clicked, [this, activityEmoji]() {
+ this->showCategory(activityEmoji);
+ });
+
+ connect(travelEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(travelCategory, &QPushButton::clicked, [this, travelEmoji]() {
+ this->showCategory(travelEmoji);
+ });
+
+ connect(objectsEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(objectsCategory, &QPushButton::clicked, [this, objectsEmoji]() {
+ this->showCategory(objectsEmoji);
+ });
+
+ connect(symbolsEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(symbolsCategory, &QPushButton::clicked, [this, symbolsEmoji]() {
+ this->showCategory(symbolsEmoji);
+ });
+
+ connect(flagsEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
+ connect(flagsCategory, &QPushButton::clicked, [this, flagsEmoji]() {
+ this->showCategory(flagsEmoji);
+ });
+}
+
+void
+Panel::showCategory(const Category *category)
+{
+ auto posToGo = category->mapToParent(QPoint()).y();
+ auto current = scrollArea_->verticalScrollBar()->value();
+
+ if (current == posToGo)
+ return;
+
+ // HACK
+ // If we want to go to a previous category and position the label at the top
+ // the 6*50 offset won't work because not all the categories have the same
+ // height. To ensure the category is at the top, we move to the top and go as
+ // normal to the next category.
+ if (current > posToGo)
+ this->scrollArea_->ensureVisible(0, 0, 0, 0);
+
+ posToGo += 6 * 50;
+ this->scrollArea_->ensureVisible(0, posToGo, 0, 0);
+}
+
+void
+Panel::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+
+ QStyleOption opt;
+ opt.init(this);
+ QPainter p(this);
+ style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
+
+ DropShadow::draw(p,
+ shadowMargin_,
+ 4.0,
+ QColor(120, 120, 120, 92),
+ QColor(255, 255, 255, 0),
+ 0.0,
+ 1.0,
+ 0.6,
+ width(),
+ height());
+}
diff --git a/src/emoji/Panel.h b/src/emoji/Panel.h
new file mode 100644
index 00000000..ad233c27
--- /dev/null
+++ b/src/emoji/Panel.h
@@ -0,0 +1,66 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#pragma once
+
+#include
+
+#include "Provider.h"
+
+namespace emoji {
+
+class Category;
+
+class Panel : public QWidget
+{
+ Q_OBJECT
+
+public:
+ Panel(QWidget *parent = nullptr);
+
+signals:
+ void mouseLeft();
+ void emojiSelected(const QString &emoji);
+
+protected:
+ void leaveEvent(QEvent *event) override
+ {
+ emit leaving();
+ QWidget::leaveEvent(event);
+ }
+
+ void paintEvent(QPaintEvent *event) override;
+
+signals:
+ void leaving();
+
+private:
+ void showCategory(const Category *category);
+
+ Provider emoji_provider_;
+
+ QScrollArea *scrollArea_;
+
+ int shadowMargin_;
+
+ // Panel dimensions.
+ int width_;
+ int height_;
+
+ int categoryIconSize_;
+};
+} // namespace emoji
diff --git a/src/emoji/PickButton.cpp b/src/emoji/PickButton.cpp
new file mode 100644
index 00000000..608b4fa2
--- /dev/null
+++ b/src/emoji/PickButton.cpp
@@ -0,0 +1,82 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#include
+
+#include "emoji/Panel.h"
+#include "emoji/PickButton.h"
+
+using namespace emoji;
+
+// Number of milliseconds after which the panel will be hidden
+// if the mouse cursor is not on top of the widget.
+constexpr int HIDE_TIMEOUT = 300;
+
+PickButton::PickButton(QWidget *parent)
+ : FlatButton(parent)
+ , panel_{nullptr}
+{
+ connect(&hideTimer_, &QTimer::timeout, this, &PickButton::hidePanel);
+ connect(this, &QPushButton::clicked, this, [this]() {
+ if (panel_ && panel_->isVisible()) {
+ hidePanel();
+ return;
+ }
+
+ showPanel();
+ });
+}
+
+void
+PickButton::hidePanel()
+{
+ if (panel_ && !panel_->underMouse()) {
+ hideTimer_.stop();
+ panel_->hide();
+ }
+}
+
+void
+PickButton::showPanel()
+{
+ if (panel_.isNull()) {
+ panel_ = QSharedPointer(new Panel(this));
+ connect(panel_.data(), &Panel::emojiSelected, this, &PickButton::emojiSelected);
+ connect(panel_.data(), &Panel::leaving, this, [this]() { panel_->hide(); });
+ }
+
+ if (panel_->isVisible())
+ return;
+
+ QPoint pos(rect().x(), rect().y());
+ pos = this->mapToGlobal(pos);
+
+ auto panel_size = panel_->sizeHint();
+
+ auto x = pos.x() - panel_size.width() + horizontal_distance_;
+ auto y = pos.y() - panel_size.height() - vertical_distance_;
+
+ panel_->move(x, y);
+ panel_->show();
+}
+
+void
+PickButton::leaveEvent(QEvent *e)
+{
+ hideTimer_.start(HIDE_TIMEOUT);
+ FlatButton::leaveEvent(e);
+}
diff --git a/src/emoji/PickButton.h b/src/emoji/PickButton.h
new file mode 100644
index 00000000..97ed8c37
--- /dev/null
+++ b/src/emoji/PickButton.h
@@ -0,0 +1,55 @@
+/*
+ * nheko Copyright (C) 2017 Konstantinos Sideris
+ *
+ * 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, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "ui/FlatButton.h"
+
+namespace emoji {
+
+class Panel;
+
+class PickButton : public FlatButton
+{
+ Q_OBJECT
+public:
+ explicit PickButton(QWidget *parent = nullptr);
+
+signals:
+ void emojiSelected(const QString &emoji);
+
+protected:
+ void leaveEvent(QEvent *e) override;
+
+private:
+ void showPanel();
+ void hidePanel();
+
+ // Vertical distance from panel's bottom.
+ int vertical_distance_ = 10;
+
+ // Horizontal distance from panel's bottom right corner.
+ int horizontal_distance_ = 70;
+
+ QSharedPointer panel_;
+ QTimer hideTimer_;
+};
+} // namespace emoji
diff --git a/src/main.cpp b/src/main.cpp
index fe7ea2ff..0c196a33 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -127,6 +127,12 @@ main(int argc, char *argv[])
parser.addVersionOption();
parser.process(app);
+ QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Bold.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Semibold.ttf");
+ QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
+
app.setWindowIcon(QIcon(":/logos/nheko.png"));
http::init();
diff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp
index a0a1759e..8d2343d0 100644
--- a/src/timeline/TimelineItem.cpp
+++ b/src/timeline/TimelineItem.cpp
@@ -595,7 +595,7 @@ TimelineItem::markReceived(bool isEncrypted)
void
TimelineItem::generateBody(const QString &body)
{
- body_ = new TextLabel(body, this);
+ body_ = new TextLabel(replaceEmoji(body), this);
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
connect(body_, &TextLabel::userProfileTriggered, this, [](const QString &user_id) {
@@ -698,6 +698,25 @@ TimelineItem::generateTimestamp(const QDateTime &time)
QString(" %1 ").arg(time.toString("HH:mm")));
}
+QString
+TimelineItem::replaceEmoji(const QString &body)
+{
+ QString fmtBody = "";
+
+ QVector utf32_string = body.toUcs4();
+
+ for (auto &code : utf32_string) {
+ // TODO: Be more precise here.
+ if (code > 9000)
+ fmtBody += QString("") +
+ QString::fromUcs4(&code, 1) + "";
+ else
+ fmtBody += QString::fromUcs4(&code, 1);
+ }
+
+ return fmtBody;
+}
+
void
TimelineItem::setupAvatarLayout(const QString &userName)
{
diff --git a/src/timeline/TimelineItem.h b/src/timeline/TimelineItem.h
index 6ed3325f..f81aa658 100644
--- a/src/timeline/TimelineItem.h
+++ b/src/timeline/TimelineItem.h
@@ -264,6 +264,7 @@ private:
//! has been acknowledged by the server.
bool isReceived_ = false;
+ QString replaceEmoji(const QString &body);
QString event_id_;
QString room_id_;