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_;