From 15c4434de0c2cd12e09c2f41e898c0b124194a97 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 8 May 2018 18:06:43 +0200 Subject: Add context menu, add context menu to delete message --- src/Channel.cpp | 42 ++++++++++++--- src/ColorScheme.cpp | 7 ++- src/ContextMenu.cpp | 128 ++++++++++++++++++++++++++++++++++++++++++++++ src/GlobalContextMenu.cpp | 42 +++++++++++++++ src/Message.cpp | 3 +- src/MessageBoard.cpp | 98 ++++++++++++++++++----------------- src/Text.cpp | 10 ++++ src/main.cpp | 21 ++++++-- 8 files changed, 288 insertions(+), 63 deletions(-) create mode 100644 src/ContextMenu.cpp create mode 100644 src/GlobalContextMenu.cpp (limited to 'src') diff --git a/src/Channel.cpp b/src/Channel.cpp index e433aee..678dddd 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -15,15 +15,15 @@ namespace dchat database(_database), databaseNodeInfo(_databaseNodeInfo), name(_name), - messageBoard(sf::Vector2u(1.0f, 1.0f)), + messageBoard(this), localUser(_localUser ? _localUser : new OfflineUser("You")) { addUserLocally(localUser); localUser->avatarUrl = "https://discordemoji.com/assets/emoji/HanekawaSmug.gif"; - addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif) deaf [emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser); - addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif)[emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser); - addLocalMessage(u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo", &systemUser); + addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif) deaf [emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash()); + addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif)[emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash()); + addLocalMessage(u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo", &systemUser, 0, odhtdb::Hash()); if(database) database->seed(databaseNodeInfo); @@ -79,13 +79,18 @@ namespace dchat } void Channel::addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds) + { + addLocalMessage(msg, owner, timestampSeconds, odhtdb::Hash()); + } + + void Channel::addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id) { assert(owner); if(timestampSeconds == 0) { timestampSeconds = time(NULL); } - messageBoard.addMessage(new Message(owner, msg, timestampSeconds)); + messageBoard.addMessage(new Message(owner, msg, timestampSeconds), id); } void Channel::addMessage(const std::string &msg) @@ -96,14 +101,37 @@ namespace dchat assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL); sibs::SafeSerializer serializer; - serializer.add(ChannelDataType::MESSAGE); + serializer.add(ChannelDataType::ADD_MESSAGE); serializer.add((const u8*)msg.data(), msg.size()); database->addData(databaseNodeInfo, static_cast(localOnlineUser->databaseUser), odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); database->commit(); } else - addLocalMessage(msg, localUser, 0); + addLocalMessage(msg, localUser, 0, odhtdb::Hash()); + } + + void Channel::deleteLocalMessage(const odhtdb::Hash &id) + { + messageBoard.deleteMessage(id); + } + + void Channel::deleteMessage(const odhtdb::Hash &id) + { + if(database && localUser->type == User::Type::ONLINE) + { + auto localOnlineUser = static_cast(localUser); + assert(localOnlineUser->databaseUser->getType() == odhtdb::User::Type::LOCAL); + + sibs::SafeSerializer serializer; + serializer.add(ChannelDataType::DELETE_MESSAGE); + serializer.add((const u8*)id.getData(), odhtdb::HASH_BYTE_SIZE); + + database->addData(databaseNodeInfo, static_cast(localOnlineUser->databaseUser), odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + database->commit(); + } + else + deleteLocalMessage(id); } void Channel::addUserLocally(User *user) diff --git a/src/ColorScheme.cpp b/src/ColorScheme.cpp index 09dde51..4790e80 100644 --- a/src/ColorScheme.cpp +++ b/src/ColorScheme.cpp @@ -14,7 +14,10 @@ namespace dchat colorSchemeType = type; } - sf::Color ColorScheme::getBackgroundColor(){ return sf::Color(54,57,62); } - sf::Color ColorScheme::getPanelColor() { return sf::Color(47,49,54); } + sf::Color ColorScheme::getBackgroundColor(){ return sf::Color(54, 57, 62); } + sf::Color ColorScheme::getPanelColor() { return sf::Color(47, 49, 54); } sf::Color ColorScheme::getTextRegularColor() { return sf::Color(255, 255, 255); } + sf::Color ColorScheme::getContextMenuBackgroundColor() { return sf::Color(47, 49, 54); } + sf::Color ColorScheme::getContextMenuTextColor() { return sf::Color(255, 255, 255); } + sf::Color ColorScheme::getContextMenuSelectedItemBackgroundColor() { return sf::Color(37, 39, 44); } } diff --git a/src/ContextMenu.cpp b/src/ContextMenu.cpp new file mode 100644 index 0000000..317d34a --- /dev/null +++ b/src/ContextMenu.cpp @@ -0,0 +1,128 @@ +#include "../include/ContextMenu.hpp" +#include "../include/ColorScheme.hpp" +#include "../include/ResourceCache.hpp" +#include "../include/Settings.hpp" +#include +#include +#include +#include + +namespace dchat +{ + const float PADDING_SIDE = 10.0f; + const float MENU_ITEM_FONT_SIZE = 18.0f; + const std::string MENU_FONT_PATH = "fonts/Roboto-Regular.ttf"; + + ContextMenuItem::ContextMenuItem(const std::string & _text, MenuItemClickCallbackFunc _clickCallbackFunc) : + text(sf::String::fromUtf8(_text.begin(), _text.end()), *ResourceCache::getFont(MENU_FONT_PATH), MENU_ITEM_FONT_SIZE * Settings::getScaling()), + clickCallbackFunc(_clickCallbackFunc) + { + text.setFillColor(ColorScheme::getContextMenuTextColor()); + } + + ContextMenu::ContextMenu() : + focusedItem(-1), + pressedItem(-1), + visible(true) + { + + } + + void ContextMenu::addItem(const ContextMenuItem &item) + { + menuItems.push_back(item); + } + + void ContextMenu::setPosition(const sf::Vector2f &position) + { + this->position = position; + } + + void ContextMenu::setVisible(bool visible) + { + this->visible = visible; + } + + ContextMenuItem& ContextMenu::getItemByIndex(usize index) + { + assert(index < menuItems.size()); + return menuItems[index]; + } + + void ContextMenu::processEvent(const sf::Event &event) + { + if(!visible) return; + + if(event.type == sf::Event::MouseButtonPressed) + { + if(event.mouseButton.button == sf::Mouse::Button::Left) + { + pressedItem = focusedItem; + if(pressedItem == -1) + setVisible(false); + } + else + { + focusedItem = -1; + pressedItem = -1; + setVisible(false); + } + } + else if(event.type == sf::Event::MouseButtonReleased) + { + if(pressedItem != -1 && pressedItem == focusedItem) + { + assert(pressedItem < menuItems.size()); + if(menuItems[pressedItem].clickCallbackFunc) + menuItems[pressedItem].clickCallbackFunc(&menuItems[pressedItem]); + setVisible(false); + } + pressedItem = -1; + } + } + + void ContextMenu::draw(sf::RenderWindow &window) + { + if(!visible) return; + + const sf::Font *menuItemFont = ResourceCache::getFont(MENU_FONT_PATH); + float boxHeight = menuItemFont->getLineSpacing(MENU_ITEM_FONT_SIZE) * 1.5f; + + sf::Vector2f menuSize; + for(ContextMenuItem &menuItem : menuItems) + { + menuItem.text.setCharacterSize(18 * Settings::getScaling()); + menuSize.x = std::max(menuSize.x, menuItem.text.getLocalBounds().width); + menuSize.y += boxHeight; + } + menuSize.x += PADDING_SIDE * 2.0f; + menuSize.x = floor(menuSize.x); + menuSize.y = floor(menuSize.y); + + sf::Vector2f pos(floor(position.x), floor(position.y)); + sf::RectangleShape background(menuSize); + background.setPosition(pos); + background.setFillColor(ColorScheme::getContextMenuBackgroundColor()); + window.draw(background); + + auto mousePos = sf::Mouse::getPosition(window); + + focusedItem = -1; + for(int i = 0; i < menuItems.size(); ++i) + { + ContextMenuItem &menuItem = menuItems[i]; + if(mousePos.x >= pos.x && mousePos.x <= pos.x + menuSize.x && mousePos.y >= pos.y && mousePos.y <= pos.y + boxHeight && (pressedItem == -1 || i == pressedItem)) + { + sf::RectangleShape selectedItemRect(sf::Vector2f(menuSize.x, floor(boxHeight))); + selectedItemRect.setFillColor(ColorScheme::getContextMenuSelectedItemBackgroundColor()); + selectedItemRect.setPosition(floor(pos.x), floor(pos.y)); + window.draw(selectedItemRect); + focusedItem = i; + } + + menuItem.text.setPosition(floor(pos.x + PADDING_SIDE), floor(pos.y + boxHeight * 0.5f - menuItemFont->getLineSpacing(MENU_ITEM_FONT_SIZE) * 0.5f)); + window.draw(menuItem.text); + pos.y += boxHeight; + } + } +} diff --git a/src/GlobalContextMenu.cpp b/src/GlobalContextMenu.cpp new file mode 100644 index 0000000..9eddb7f --- /dev/null +++ b/src/GlobalContextMenu.cpp @@ -0,0 +1,42 @@ +#include "../include/GlobalContextMenu.hpp" + +namespace dchat +{ + static ContextMenu *editMessageContextMenu = nullptr; + + ContextMenu* GlobalContextMenu::getEditMessageContextMenu() + { + if(!editMessageContextMenu) + { + editMessageContextMenu = new ContextMenu(); + editMessageContextMenu->setVisible(false); + + ContextMenuItem editMessageItem("Edit message", nullptr); + ContextMenuItem deleteMessageItem("Delete message", nullptr); + deleteMessageItem.text.setFillColor(sf::Color::Red); + editMessageContextMenu->addItem(editMessageItem); + editMessageContextMenu->addItem(deleteMessageItem); + } + return editMessageContextMenu; + } + + void GlobalContextMenu::setClickEditMessageCallbackFunc(MenuItemClickCallbackFunc callbackFunc) + { + getEditMessageContextMenu()->getItemByIndex(0).clickCallbackFunc = callbackFunc; + } + + void GlobalContextMenu::setClickDeleteMessageCallbackFunc(MenuItemClickCallbackFunc callbackFunc) + { + getEditMessageContextMenu()->getItemByIndex(1).clickCallbackFunc = callbackFunc; + } + + void GlobalContextMenu::processEvent(const sf::Event &event) + { + getEditMessageContextMenu()->processEvent(event); + } + + void GlobalContextMenu::draw(sf::RenderWindow &window) + { + getEditMessageContextMenu()->draw(window); + } +} diff --git a/src/Message.cpp b/src/Message.cpp index 77e3cfa..a408aee 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -10,7 +10,8 @@ namespace dchat Message::Message(User *_user, const std::string &_text, u64 _timestampSeconds) : user(_user), text(sf::String::fromUtf8(_text.begin(), _text.end()), ResourceCache::getFont("fonts/Roboto-Regular.ttf"), 18 * Settings::getScaling(), 0.0f, false), - timestampSeconds(_timestampSeconds) + timestampSeconds(_timestampSeconds), + type(Type::REGULAR) { text.setFillColor(ColorScheme::getTextRegularColor()); } diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index 49d84e8..bdaccf3 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -8,6 +8,8 @@ #include "../include/Chatbar.hpp" #include "../include/ColorScheme.hpp" #include "../include/Theme.hpp" +#include "../include/GlobalContextMenu.hpp" +#include "../include/Channel.hpp" #include #include #include @@ -39,15 +41,14 @@ namespace dchat // Merge messages from same user that are sent within one minute const int MERGE_TEXT_TIMESTAMP_DIFF_SEC = 60; - MessageBoard::MessageBoard(const sf::Vector2u &size) : - selectingText(false), - leftMouseButtonPressed(false), + MessageBoard::MessageBoard(Channel *_channel) : + channel(_channel), scroll(0.0), scrollSpeed(0.0), totalHeight(0.0), scrollToBottom(false) { - updateStaticContentTexture(size); + } MessageBoard::~MessageBoard() @@ -65,13 +66,35 @@ namespace dchat dirty = true; } - void MessageBoard::addMessage(Message *message) + void MessageBoard::addMessage(Message *message, const odhtdb::Hash &id) { + lock_guard lock(messageProcessMutex); messages.push_back(message); + if(!id.isEmpty()) + messageIdMap[id] = message; dirty = true; scrollToBottom = true; } + void MessageBoard::deleteMessage(const odhtdb::Hash &id) + { + lock_guard lock(messageProcessMutex); + auto it = messageIdMap.find(id); + if(it == messageIdMap.end()) return; + + for(usize i = 0; i < messages.size(); ++i) + { + Message *message = messages[i]; + if(message == it->second) + { + delete message; + messages.erase(messages.begin() + i); + messageIdMap.erase(it); + break; + } + } + } + void MessageBoard::drawDefault(sf::RenderWindow &window, Cache &cache) { const float LINE_SPACING = 5.0f * Settings::getScaling(); @@ -253,32 +276,32 @@ namespace dchat void MessageBoard::processEvent(const sf::Event &event) { - if(event.type == sf::Event::MouseButtonPressed) + lock_guard lock(messageProcessMutex); + for(Message *message : messages) { - if(event.mouseButton.button == sf::Mouse::Button::Left) - { - leftMouseButtonPressed = true; - mousePos.x = event.mouseButton.x; - mousePos.y = event.mouseButton.y; - } + message->text.processEvent(event); } - else if(event.type == sf::Event::MouseButtonReleased) + + if(event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right) { - if(event.mouseButton.button == sf::Mouse::Button::Left) + for(auto it : messageIdMap) { - leftMouseButtonPressed = false; - mousePos.x = event.mouseButton.x; - mousePos.y = event.mouseButton.y; + it.second->text.processEvent(event); + auto textPos = it.second->text.getPosition(); + if(event.mouseButton.x >= textPos.x && event.mouseButton.x <= textPos.x + it.second->text.getMaxWidth() && event.mouseButton.y >= textPos.y && event.mouseButton.y <= textPos.y + it.second->text.getHeight()) + { + auto contextMenu = GlobalContextMenu::getEditMessageContextMenu(); + contextMenu->setPosition(sf::Vector2f(event.mouseButton.x, event.mouseButton.y)); + contextMenu->setVisible(true); + GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, it](ContextMenuItem *menuItem) + { + channel->deleteMessage(it.first); + GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr); + }); + return; + } } - } - else if(event.type == sf::Event::LostFocus) - { - leftMouseButtonPressed = false; - } - else if(event.type == sf::Event::MouseMoved) - { - mousePos.x = event.mouseMove.x; - mousePos.y = event.mouseMove.y; + GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr); } else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel) { @@ -288,22 +311,6 @@ namespace dchat else if(scrollSpeed < -SCROLL_MAX_SPEED) scrollSpeed = -SCROLL_MAX_SPEED; } - - if(selectingText && !leftMouseButtonPressed) - { - selectingText = false; - } - else if(!selectingText && leftMouseButtonPressed) - { - selectingText = true; - selectingTextStart.x = mousePos.x; - selectingTextStart.y = mousePos.y; - } - - for(Message *message : messages) - { - message->text.processEvent(event); - } } void MessageBoard::draw(sf::RenderWindow &window, Cache &cache) @@ -331,6 +338,7 @@ namespace dchat if(dirty) { + lock_guard lock(messageProcessMutex); switch(Theme::getType()) { case Theme::Type::DEFAULT: @@ -385,11 +393,5 @@ namespace dchat //sf::Sprite textureSprite(staticContentTexture.getTexture()); //textureSprite.setPosition(backgroundPos); //window.draw(textureSprite); - - if(!selectingText) return; - - sf::Vector2f selectionRectStart(min((float)mousePos.x, selectingTextStart.x), min((float)mousePos.y, selectingTextStart.y)); - sf::Vector2f selectionRectEnd(max((float)mousePos.x, selectingTextStart.x), max((float)mousePos.y, selectingTextStart.y)); - sf::FloatRect selectionRect(selectionRectStart, selectionRectEnd - selectionRectStart); } } diff --git a/src/Text.cpp b/src/Text.cpp index 252d27f..5191ca5 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -84,6 +84,11 @@ namespace dchat this->position = position; } + sf::Vector2f Text::getPosition() const + { + return position; + } + void Text::setMaxWidth(float maxWidth) { if(maxWidth != this->maxWidth) @@ -93,6 +98,11 @@ namespace dchat } } + float Text::getMaxWidth() const + { + return maxWidth; + } + void Text::setCharacterSize(unsigned int characterSize) { if(characterSize != this->characterSize) diff --git a/src/main.cpp b/src/main.cpp index b1f63e9..83ef11a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "../include/Command.hpp" #include "../include/Settings.hpp" #include "../include/ColorScheme.hpp" +#include "../include/GlobalContextMenu.hpp" #include #include #include @@ -75,7 +76,7 @@ void channelChangeUserNickname(Channel *channel, const StringView data, const od // We dont care if there is more data to read (malicious packet), we already got all the data we need } -void channelAddStoredMessage(Channel *channel, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp) +void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp) { User *user = channel->getUserByPublicKey(creatorPublicKey); if(!user) @@ -90,10 +91,18 @@ void channelAddStoredMessage(Channel *channel, const odhtdb::Signature::PublicKe StringView decryptedData((const char*)decryptedObject.data + 1, decryptedObject.size - 1); switch(channelDataType) { - case ChannelDataType::MESSAGE: + case ChannelDataType::ADD_MESSAGE: { string msg(decryptedData.data, decryptedData.size); - channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(timestamp).seconds); + channel->addLocalMessage(msg, user, ntp::NtpTimestamp::fromCombined(timestamp).seconds, requestHash); + break; + } + case ChannelDataType::DELETE_MESSAGE: + { + sibs::SafeDeserializer deserializer((const u8*)decryptedData.data, decryptedData.size); + odhtdb::Hash messageId; + deserializer.extract((u8*)messageId.getData(), odhtdb::HASH_BYTE_SIZE); + channel->deleteLocalMessage(messageId); break; } case ChannelDataType::NICKNAME_CHANGE: @@ -127,7 +136,7 @@ void channelAddStoredMessages(Channel *channel, const odhtdb::DatabaseStorageObj if(nodeStorageAddedObject->decryptedObject.operation == odhtdb::DatabaseOperation::ADD_DATA) { StringView decryptedObject((const char*)nodeStorageAddedObject->decryptedObject.data.data, nodeStorageAddedObject->decryptedObject.data.size); - channelAddStoredMessage(channel, nodeStorageAddedObject->creatorPublicKey, decryptedObject, nodeStorageAddedObject->createdTimestamp); + channelAddStoredMessage(channel, nodeStorageAddedObject->requestHash, nodeStorageAddedObject->creatorPublicKey, decryptedObject, nodeStorageAddedObject->createdTimestamp); } } } @@ -200,7 +209,7 @@ int main(int argc, char **argv) { if(*request.nodeHash == *channel->getNodeInfo().getRequestHash()) { - channelAddStoredMessage(channel, request.creatorUser->getPublicKey(), StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp); + channelAddStoredMessage(channel, *request.requestHash, request.creatorUser->getPublicKey(), StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp); return; } } @@ -642,6 +651,7 @@ int main(int argc, char **argv) window.setFramerateLimit(FRAMERATE_FOCUSED); //else if(event.type == sf::Event::LostFocus) // window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); + GlobalContextMenu::processEvent(event); currentChannel->processEvent(event); } @@ -650,6 +660,7 @@ int main(int argc, char **argv) currentChannel->draw(window, cache); UsersSidePanel::draw(window); ChannelTopPanel::draw(window); + GlobalContextMenu::draw(window); if(waitingToJoin) { -- cgit v1.2.3