aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-05-20 11:15:15 +0200
committerdec05eba <dec05eba@protonmail.com>2018-05-20 11:15:18 +0200
commit4c392178dac1de9a299beb78989c4e0f3fecade9 (patch)
tree552b7a7dfa58e8193705934059e28461815bb951
parent34e1d3d9d40f9b9139b801de99292a563c3c9a96 (diff)
Add image preview and url/image open in browser
m---------depends/odhtdb0
-rw-r--r--include/Channel.hpp2
-rw-r--r--include/Chatbar.hpp2
-rw-r--r--include/Gif.hpp2
-rw-r--r--include/ImagePreview.hpp52
-rw-r--r--include/Message.hpp2
-rw-r--r--include/MessageBoard.hpp4
-rw-r--r--include/StringUtils.hpp18
-rw-r--r--include/Text.hpp9
-rw-r--r--src/Cache.cpp24
-rw-r--r--src/Channel.cpp6
-rw-r--r--src/Chatbar.cpp4
-rw-r--r--src/Gif.cpp5
-rw-r--r--src/ImagePreview.cpp168
-rw-r--r--src/MessageBoard.cpp57
-rw-r--r--src/Text.cpp85
-rw-r--r--src/main.cpp39
17 files changed, 413 insertions, 66 deletions
diff --git a/depends/odhtdb b/depends/odhtdb
-Subproject 279969f2da1f334c77a38153ae809c985d060bb
+Subproject e46344de86b92da7bb8c6e82c8f668fa5d20357
diff --git a/include/Channel.hpp b/include/Channel.hpp
index 6e944c3..16c2fb3 100644
--- a/include/Channel.hpp
+++ b/include/Channel.hpp
@@ -55,7 +55,7 @@ namespace dchat
void replaceLocalUser(OnlineLocalUser *newOnlineLocalUser);
void changeNick(const std::string &newNick);
- void processEvent(const sf::Event &event);
+ void processEvent(const sf::Event &event, Cache &cache);
void draw(sf::RenderWindow &window, Cache &cache);
static void setCurrent(Channel *channel);
diff --git a/include/Chatbar.hpp b/include/Chatbar.hpp
index 5d69448..df492ac 100644
--- a/include/Chatbar.hpp
+++ b/include/Chatbar.hpp
@@ -21,7 +21,7 @@ namespace dchat
bool isFocused() const;
- void processEvent(const sf::Event &event, Channel *channel);
+ void processEvent(const sf::Event &event, Cache &cache, Channel *channel);
void draw(sf::RenderWindow &window, Cache &cache);
static float getHeight();
diff --git a/include/Gif.hpp b/include/Gif.hpp
index 8f5d4e7..1341049 100644
--- a/include/Gif.hpp
+++ b/include/Gif.hpp
@@ -31,6 +31,8 @@ namespace dchat
sf::Vector2u getSize() const;
void setPosition(const sf::Vector2f &position);
+ sf::Vector2f getPosition() const;
+
void setScale(const sf::Vector2f &scale);
void draw(sf::RenderTarget &target, const sf::RenderStates &renderStates = sf::RenderStates::Default);
diff --git a/include/ImagePreview.hpp b/include/ImagePreview.hpp
new file mode 100644
index 0000000..1d20fe8
--- /dev/null
+++ b/include/ImagePreview.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <SFML/Graphics/RenderWindow.hpp>
+#include <SFML/Graphics/Sprite.hpp>
+#include <SFML/Graphics/Texture.hpp>
+#include <SFML/Window/Event.hpp>
+#include <SFML/System/Clock.hpp>
+#include <string>
+
+namespace dchat
+{
+ class Gif;
+
+ class ImagePreview
+ {
+ public:
+ // set @texture to nullptr if you wish to end preview
+ static void preview(sf::Texture *texture, const std::string &url = "");
+ // set gif to nullptr if you wish to end preview
+ static void preview(Gif *gif, const std::string &url = "");
+ static void* getPreviewContentPtr();
+ static sf::Int32 getTimeSinceLastSeenMs();
+
+ static void processEvent(const sf::Event &event);
+ static void draw(sf::RenderWindow &window);
+ private:
+ enum class ContentType
+ {
+ NONE,
+ TEXTURE,
+ GIF
+ };
+
+ ImagePreview() : texture(nullptr), contentType(ContentType::NONE) {}
+ ImagePreview(const ImagePreview&) = delete;
+ ImagePreview& operator=(const ImagePreview&) = delete;
+ static ImagePreview* getInstance();
+
+ sf::Vector2u calculateImageSize(sf::Vector2u windowSize) const;
+ private:
+ sf::Sprite sprite;
+ sf::Vector2u size;
+ sf::Clock lastSeenTimer;
+
+ union
+ {
+ sf::Texture *texture;
+ Gif *gif;
+ };
+ ContentType contentType;
+ };
+}
diff --git a/include/Message.hpp b/include/Message.hpp
index 8c7f57a..a2d67e8 100644
--- a/include/Message.hpp
+++ b/include/Message.hpp
@@ -4,6 +4,7 @@
#include "Text.hpp"
#include <string>
#include <vector>
+#include <odhtdb/Hash.hpp>
namespace dchat
{
@@ -23,5 +24,6 @@ namespace dchat
Text text;
const u64 timestampSeconds;
Type type;
+ odhtdb::Hash id;
};
}
diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp
index a947c1b..861d195 100644
--- a/include/MessageBoard.hpp
+++ b/include/MessageBoard.hpp
@@ -22,7 +22,7 @@ namespace dchat
MessageBoard(Channel *channel);
~MessageBoard();
- void processEvent(const sf::Event &event);
+ void processEvent(const sf::Event &event, Cache &cache);
void draw(sf::RenderWindow &window, Cache &cache);
private:
usize findPositionToInsertMessageByTimestamp(Message *message);
@@ -47,5 +47,7 @@ namespace dchat
sf::Vector2f backgroundSize;
sf::Vector2f backgroundPos;
std::mutex messageProcessMutex;
+ usize visibleMessageStartIndex;
+ usize visibleMessageEndIndex;
};
}
diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp
new file mode 100644
index 0000000..6b237dd
--- /dev/null
+++ b/include/StringUtils.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <string>
+
+namespace dchat
+{
+ static std::string stringReplaceChar(const std::string &str, const std::string &from, const std::string &to)
+ {
+ std::string result = str;
+ size_t pos = 0;
+ while((pos = result.find(from, pos)) != std::string::npos)
+ {
+ result.replace(pos, from.size(), to);
+ pos += to.size();
+ }
+ return result;
+ }
+}
diff --git a/include/Text.hpp b/include/Text.hpp
index d7d1e06..925dc94 100644
--- a/include/Text.hpp
+++ b/include/Text.hpp
@@ -57,10 +57,13 @@ namespace dchat
// Warning: won't update until @draw is called
float getHeight() const;
- void processEvent(const sf::Event &event);
+ void processEvent(const sf::Event &event, Cache &cache);
- // Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible
- void draw(sf::RenderTarget &target, Cache &cache);
+ // Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible.
+ // Returns true if text was drawn on screen (if text is within window borders)
+ bool draw(sf::RenderTarget &target, Cache &cache);
+ private:
+ void onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache);
private:
enum class CaretMoveDirection : u8
{
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 074b7bc..e5d910c 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -5,6 +5,8 @@
#include "../include/Gif.hpp"
#include "../include/Chatbar.hpp"
#include "../include/WebPagePreview.hpp"
+#include "../include/ImagePreview.hpp"
+#include "../include/StringUtils.hpp"
#include <boost/filesystem/convenience.hpp>
#include <unordered_map>
#include <process.hpp>
@@ -267,11 +269,21 @@ namespace dchat
{
case ContentByUrlResult::CachedType::TEXTURE:
{
+ if(ImagePreview::getPreviewContentPtr() == it->second.texture)
+ {
+ ++it;
+ continue;
+ }
delete it->second.texture;
break;
}
case ContentByUrlResult::CachedType::GIF:
{
+ if(ImagePreview::getPreviewContentPtr() == it->second.texture)
+ {
+ ++it;
+ continue;
+ }
delete it->second.gif;
break;
}
@@ -311,18 +323,6 @@ namespace dchat
}
}
- static string stringReplaceChar(const string &str, const string &from, const string &to)
- {
- string result = str;
- size_t pos = 0;
- while((pos = result.find(from, pos)) != string::npos)
- {
- result.replace(pos, from.size(), to);
- pos += to.size();
- }
- return result;
- }
-
const ContentByUrlResult Cache::getContentByUrl(const string &url, int downloadLimitBytes)
{
lock_guard<mutex> lock(imageDownloadMutex);
diff --git a/src/Channel.cpp b/src/Channel.cpp
index f67eb4c..0bacbc3 100644
--- a/src/Channel.cpp
+++ b/src/Channel.cpp
@@ -196,10 +196,10 @@ namespace dchat
}
}
- void Channel::processEvent(const sf::Event &event)
+ void Channel::processEvent(const sf::Event &event, Cache &cache)
{
- chatbar.processEvent(event, this);
- messageBoard.processEvent(event);
+ chatbar.processEvent(event, cache, this);
+ messageBoard.processEvent(event, cache);
}
void Channel::draw(sf::RenderWindow &window, Cache &cache)
diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp
index bd510f7..aab13a0 100644
--- a/src/Chatbar.cpp
+++ b/src/Chatbar.cpp
@@ -155,11 +155,11 @@ namespace dchat
}
}
- void Chatbar::processEvent(const sf::Event &event, Channel *channel)
+ void Chatbar::processEvent(const sf::Event &event, Cache &cache, Channel *channel)
{
if(!focused) return;
- text.processEvent(event);
+ text.processEvent(event, cache);
if(event.type == sf::Event::TextEntered)
{
if(event.text.unicode == 13) // enter
diff --git a/src/Gif.cpp b/src/Gif.cpp
index afd5b9d..2bd18e8 100644
--- a/src/Gif.cpp
+++ b/src/Gif.cpp
@@ -144,6 +144,11 @@ namespace dchat
sprite.setPosition(position);
}
+ sf::Vector2f Gif::getPosition() const
+ {
+ return sprite.getPosition();
+ }
+
void Gif::setScale(const sf::Vector2f &scale)
{
sprite.setScale(scale);
diff --git a/src/ImagePreview.cpp b/src/ImagePreview.cpp
new file mode 100644
index 0000000..0b36dbd
--- /dev/null
+++ b/src/ImagePreview.cpp
@@ -0,0 +1,168 @@
+#include "../include/ImagePreview.hpp"
+#include "../include/Settings.hpp"
+#include "../include/StringUtils.hpp"
+#include "../include/Gif.hpp"
+#include <SFML/Graphics/RectangleShape.hpp>
+#include <cassert>
+
+namespace dchat
+{
+ static const float PADDING_VERTICAL = 40.0f;
+ static const float PADDING_HORIZONTAL = 40.0f;
+
+ static ImagePreview *instance = nullptr;
+ static std::string imagePreviewUrl;
+
+ ImagePreview* ImagePreview::getInstance()
+ {
+ if(!instance)
+ instance = new ImagePreview();
+ return instance;
+ }
+
+ void ImagePreview::preview(sf::Texture *texture, const std::string &url)
+ {
+ if(texture == getInstance()->texture) return;
+ getInstance()->texture = texture;
+ getInstance()->contentType = texture ? ContentType::TEXTURE : ContentType::NONE;
+ imagePreviewUrl = url;
+ if(texture)
+ getInstance()->sprite.setTexture(*texture, true);
+ else
+ getInstance()->sprite = sf::Sprite();
+ }
+
+ void ImagePreview::preview(Gif *gif, const std::string &url)
+ {
+ if(gif == getInstance()->gif) return;
+ getInstance()->gif = gif;
+ getInstance()->contentType = gif ? ContentType::GIF : ContentType::NONE;
+ imagePreviewUrl = url;
+ getInstance()->sprite = sf::Sprite();
+ }
+
+ void* ImagePreview::getPreviewContentPtr()
+ {
+ return getInstance()->texture;
+ }
+
+ sf::Int32 ImagePreview::getTimeSinceLastSeenMs()
+ {
+ return getInstance()->lastSeenTimer.getElapsedTime().asMilliseconds();
+ }
+
+ void ImagePreview::processEvent(const sf::Event &event)
+ {
+ if(getInstance()->contentType == ContentType::NONE) return;
+
+ if(event.mouseButton.button == sf::Mouse::Button::Left)
+ {
+ sf::Vector2f imagePos;
+ switch(getInstance()->contentType)
+ {
+ case ContentType::TEXTURE:
+ imagePos = getInstance()->sprite.getPosition();
+ break;
+ case ContentType::GIF:
+ imagePos = getInstance()->gif->getPosition();
+ break;
+ }
+ getInstance()->sprite.getPosition();
+ const auto &imageSize = getInstance()->size;
+ bool mouseInside = false;
+ if(event.mouseButton.x >= imagePos.x && event.mouseButton.x <= imagePos.x + imageSize.x &&
+ event.mouseButton.y >= imagePos.y && event.mouseButton.y <= imagePos.y + imageSize.y)
+ {
+ mouseInside = true;
+ }
+
+ if(event.type == sf::Event::MouseButtonPressed && mouseInside && !imagePreviewUrl.empty())
+ {
+ // TODO: Implement for other platforms than linux
+ std::string escapedUrl = stringReplaceChar(imagePreviewUrl, "'", "");
+ escapedUrl = stringReplaceChar(escapedUrl, "\\", "");
+ std::string cmd = "xdg-open '";
+ cmd += escapedUrl;
+ cmd += "'";
+ printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str());
+ system(cmd.c_str());
+ }
+ else if(event.type == sf::Event::MouseButtonReleased && !mouseInside)
+ {
+ ImagePreview::preview((sf::Texture*)nullptr);
+ }
+ }
+ else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
+ {
+ ImagePreview::preview((sf::Texture*)nullptr);
+ }
+ }
+
+ void ImagePreview::draw(sf::RenderWindow &window)
+ {
+ if(getInstance()->contentType == ContentType::NONE) return;
+
+ auto windowSize = window.getSize();
+ sf::RectangleShape background(sf::Vector2f(windowSize.x, windowSize.y));
+ background.setFillColor(sf::Color(0, 0, 0, 200));
+
+ auto imageSize = getInstance()->calculateImageSize(windowSize);
+ getInstance()->size = imageSize;
+
+ window.draw(background);
+ switch(getInstance()->contentType)
+ {
+ case ContentType::TEXTURE:
+ {
+ auto textureSize = getInstance()->sprite.getTexture()->getSize();
+ getInstance()->sprite.setPosition(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2);
+ getInstance()->sprite.setScale((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y);
+ window.draw(getInstance()->sprite);
+ break;
+ }
+ case ContentType::GIF:
+ {
+ auto textureSize = getInstance()->gif->getSize();
+ getInstance()->gif->setPosition(sf::Vector2f(windowSize.x / 2 - imageSize.x / 2, windowSize.y / 2 - imageSize.y / 2));
+ getInstance()->gif->setScale(sf::Vector2f((double)imageSize.x / (double)textureSize.x, (double)imageSize.y / (double)textureSize.y));
+ getInstance()->gif->draw(window);
+ break;
+ }
+ }
+ getInstance()->lastSeenTimer.restart();
+ }
+
+ sf::Vector2u ImagePreview::calculateImageSize(sf::Vector2u windowSize) const
+ {
+ assert(contentType != ContentType::NONE);
+ sf::Vector2u imageMaxSize(windowSize.x - PADDING_HORIZONTAL * Settings::getScaling() * 2.0f, windowSize.y - PADDING_VERTICAL * Settings::getScaling() * 2.0f);
+ sf::Vector2u textureSize;
+ switch(contentType)
+ {
+ case ContentType::TEXTURE:
+ textureSize = texture->getSize();
+ break;
+ case ContentType::GIF:
+ textureSize = gif->getSize();
+ break;
+ }
+ auto imageSize = textureSize;
+ double textureWidthHeightRatio = (double)imageSize.x / (double)imageSize.y;
+ double textureHeightWidthRatio = (double)imageSize.y / (double)imageSize.x;
+
+ int overflowVertical = (int)imageSize.y - (int)imageMaxSize.y;
+ if(overflowVertical > 0)
+ {
+ imageSize.y = imageMaxSize.y;
+ imageSize.x -= (overflowVertical * textureWidthHeightRatio);
+ }
+
+ int overflowHorizontal = (int)imageSize.x - (int)imageMaxSize.x;
+ if(overflowHorizontal > 0)
+ {
+ imageSize.x = imageMaxSize.x;
+ imageSize.y -= (overflowHorizontal * textureHeightWidthRatio);
+ }
+ return imageSize;
+ }
+}
diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp
index 8e1525f..2e92d7f 100644
--- a/src/MessageBoard.cpp
+++ b/src/MessageBoard.cpp
@@ -46,7 +46,9 @@ namespace dchat
scroll(0.0),
scrollSpeed(0.0),
totalHeight(0.0),
- scrollToBottom(false)
+ scrollToBottom(false),
+ visibleMessageStartIndex(-1),
+ visibleMessageEndIndex(-1)
{
}
@@ -81,8 +83,8 @@ namespace dchat
{
lock_guard<mutex> lock(messageProcessMutex);
messages.insert(messages.begin() + findPositionToInsertMessageByTimestamp(message), message);
- if(!id.isEmpty())
- messageIdMap[id] = message;
+ message->id = id;
+ messageIdMap[id] = message;
dirty = true;
scrollToBottom = true;
}
@@ -102,6 +104,7 @@ namespace dchat
}
}
+ // TODO: Instead of deleting message, cover it with black rectangle
for(usize i = 0; i < messages.size(); ++i)
{
Message *message = messages[i];
@@ -137,6 +140,9 @@ namespace dchat
sf::Vector2<double> position(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling(), ChannelTopPanel::getHeight() + PADDING_TOP);
double startHeight = position.y;
position.y += scroll;
+
+ visibleMessageStartIndex = -1;
+ visibleMessageEndIndex = -1;
usize numMessages = messages.size();
for(usize i = 0; i < numMessages; ++i)
{
@@ -156,11 +162,14 @@ namespace dchat
mergeTextWithNext = nextMessage->user == message->user && (nextMessage->timestampSeconds == 0 || nextMessage->timestampSeconds - message->timestampSeconds <= MERGE_TEXT_TIMESTAMP_DIFF_SEC);
}
+ bool visible = false;
+
if(!mergeTextWithPrev)
{
position.y += (MESSAGE_PADDING_TOP * Settings::getScaling());
if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y)
{
+ visible = true;
sf::Text usernameText(sf::String::fromUtf8(message->user->getName().begin(), message->user->getName().end()), *usernameFont, usernameTextCharacterSize);
usernameText.setFillColor(sf::Color(15, 192, 252));
usernameText.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
@@ -219,7 +228,9 @@ namespace dchat
message->text.setMaxWidth(backgroundSize.x - (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling());
message->text.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y)));
message->text.setLineSpacing(LINE_SPACING);
- message->text.draw(window, cache);
+ bool textDrawn = message->text.draw(window, cache);
+ if(!visible)
+ visible = textDrawn;
position.y += message->text.getHeight();
if(!mergeTextWithNext)
{
@@ -227,12 +238,20 @@ namespace dchat
if(position.y + LINE_HEIGHT > 0.0f && position.y < backgroundPos.y + backgroundSize.y && i + 1 != numMessages)
{
+ visible = true;
lineRect.setPosition(sf::Vector2f(position.x + LINE_SIDE_PADDING * Settings::getScaling() - PADDING_SIDE * Settings::getScaling(), floor(position.y)));
window.draw(lineRect);
//drawGradientLine(sf::Vector2f(position.x + LINE_SIDE_PADDING, floor(position.y)), sf::Vector2f(backgroundSizeWithoutPadding.x - LINE_SIDE_PADDING * 2.0f, LINE_HEIGHT), LINE_COLOR, window);
}
}
position.y += LINE_HEIGHT;
+
+ if(visible)
+ {
+ if(visibleMessageStartIndex == -1)
+ visibleMessageStartIndex = i;
+ visibleMessageEndIndex = i;
+ }
}
totalHeight = (position.y - scroll) - startHeight;
}
@@ -294,40 +313,42 @@ namespace dchat
totalHeight = (position.y - scroll) - startHeight;
}
- void MessageBoard::processEvent(const sf::Event &event)
+ void MessageBoard::processEvent(const sf::Event &event, Cache &cache)
{
lock_guard<mutex> lock(messageProcessMutex);
- for(Message *message : messages)
- {
- message->text.processEvent(event);
- }
OnlineLocalUser *onlineLocalUser = nullptr;
if(channel->getLocalUser()->type == User::Type::ONLINE_LOCAL_USER)
onlineLocalUser = static_cast<OnlineLocalUser*>(channel->getLocalUser());
+ bool openContextMenu = false;
if(onlineLocalUser && event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Button::Right)
+ openContextMenu = true;
+
+ if(visibleMessageStartIndex != -1)
{
- for(auto it : messageIdMap)
+ //printf("visibleMessageStartIndex: %u, visibleMessageEndIndex: %u\n", visibleMessageStartIndex, visibleMessageEndIndex);
+ for(usize i = visibleMessageStartIndex; i <= visibleMessageEndIndex; ++i)
{
- it.second->text.processEvent(event);
- auto textPos = it.second->text.getPosition();
- if(it.second->user == channel->getLocalUser() && 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())
+ Message *message = messages[i];
+ message->text.processEvent(event, cache);
+
+ auto textPos = message->text.getPosition();
+ if(openContextMenu && message->user == channel->getLocalUser() && event.mouseButton.x >= textPos.x && event.mouseButton.x <= textPos.x + message->text.getMaxWidth() && event.mouseButton.y >= textPos.y && event.mouseButton.y <= textPos.y + message->text.getHeight())
{
auto contextMenu = GlobalContextMenu::getEditMessageContextMenu();
contextMenu->setPosition(sf::Vector2f(event.mouseButton.x, event.mouseButton.y));
contextMenu->setVisible(true);
- GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, it, onlineLocalUser](ContextMenuItem *menuItem)
+ GlobalContextMenu::setClickDeleteMessageCallbackFunc([this, message, onlineLocalUser](ContextMenuItem *menuItem)
{
- channel->deleteMessage(it.first, onlineLocalUser->getPublicKey());
+ channel->deleteMessage(message->id, onlineLocalUser->getPublicKey());
GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr);
});
- return;
}
}
- GlobalContextMenu::setClickDeleteMessageCallbackFunc(nullptr);
}
- else if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel)
+
+ if(event.type == sf::Event::MouseWheelScrolled && event.mouseWheelScroll.wheel == sf::Mouse::Wheel::VerticalWheel)
{
scrollSpeed += (event.mouseWheelScroll.delta * 5.0);
if(scrollSpeed > SCROLL_MAX_SPEED)
diff --git a/src/Text.cpp b/src/Text.cpp
index 9688ad1..3aa1574 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -3,6 +3,8 @@
#include "../include/Gif.hpp"
#include "../include/WebPagePreview.hpp"
#include "../include/ColorScheme.hpp"
+#include "../include/ImagePreview.hpp"
+#include "../include/StringUtils.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <cmath>
#include <process.hpp>
@@ -439,7 +441,6 @@ namespace dchat
{
// If there was a space in the text and text width is too long, then we need to word wrap at space index instead,
// which means we need to change the position of all vertices after the space to the current vertex
- //printf("last spacing word wrap index: %zu\n", lastSpacingWordWrapIndex);
if(lastSpacingWordWrapIndex != -1)
{
for(size_t j = lastSpacingWordWrapIndex; j < i; ++j)
@@ -698,8 +699,81 @@ namespace dchat
return result;
}
- void Text::processEvent(const sf::Event &event)
+ void Text::onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache)
{
+ if(event.button != sf::Mouse::Button::Left) return;
+ float vspace = font->getLineSpacing(characterSize);
+
+ for(TextElement &textElement : textElements)
+ {
+ if(textElement.type == TextElement::Type::URL)
+ {
+ sf::Vector2f pos = position;
+ pos.y += floor(textElement.position.y);
+ float imageHeight = floor(vspace * IMAGE_HEIGHT_SCALE);
+
+ // TODO: Optimize this (add unordered_map that takes StringViewUtf32 as key)
+ auto u8Str = sf::String::fromUtf32(textElement.text.data, textElement.text.data + textElement.text.size).toUtf8();
+ const std::string &utf8Str = *(std::basic_string<char>*)&u8Str;
+ const ContentByUrlResult contentByUrlResult = cache.getContentByUrl(utf8Str);
+ if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
+ {
+ switch(contentByUrlResult.cachedType)
+ {
+ case ContentByUrlResult::CachedType::TEXTURE:
+ {
+ auto textureSize = contentByUrlResult.texture->getSize();
+ float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
+ float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth);
+ if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
+ {
+ ImagePreview::preview(contentByUrlResult.texture, utf8Str);
+ return;
+ }
+ break;
+ }
+ case ContentByUrlResult::CachedType::GIF:
+ {
+ auto textureSize = contentByUrlResult.gif->getSize();
+ float widthToHeightRatio = (float)textureSize.x / (float)textureSize.y;
+ float imageWidth = fmin(imageHeight * widthToHeightRatio, maxWidth);
+ if(event.x >= pos.x && event.x <= pos.x + imageWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
+ {
+ ImagePreview::preview(contentByUrlResult.gif, utf8Str);
+ return;
+ }
+ break;
+ }
+ case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW:
+ {
+ const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f));
+ if(event.x >= pos.x && event.x <= pos.x + previewWidth && event.y >= pos.y && event.y <= pos.y + imageHeight)
+ {
+ // TODO: Implement for other platforms than linux
+ std::string escapedUrl = stringReplaceChar(utf8Str, "'", "");
+ escapedUrl = stringReplaceChar(escapedUrl, "\\", "");
+ std::string cmd = "xdg-open '";
+ cmd += escapedUrl;
+ cmd += "'";
+ printf("Clicked on web page preview, opening web page by running command: %s\n", cmd.c_str());
+ system(cmd.c_str());
+ return;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void Text::processEvent(const sf::Event &event, Cache &cache)
+ {
+ if(event.type == sf::Event::MouseButtonReleased)
+ {
+ onMouseClick(event.mouseButton, cache);
+ }
+
if(!editable) return;
bool caretAtEnd = textElements.size() == 0 || textElements[0].text.size == 0 || caretIndex == textElements[0].text.size;
@@ -798,7 +872,7 @@ namespace dchat
}
}
- void Text::draw(sf::RenderTarget &target, Cache &cache)
+ bool Text::draw(sf::RenderTarget &target, Cache &cache)
{
if(dirtyText)
{
@@ -831,7 +905,7 @@ namespace dchat
//sf::FloatRect textRect(pos.x, pos.y, maxWidth, )
//colRect.contains()
//if(pos.x + maxWidth <= 0.0f || pos.x >= maxWidth || pos.y + totalHeight <= 0.0f || pos.y >= target.getSize().y) return;
- if(pos.y + getHeight() <= 0.0f || pos.y >= target.getSize().y) return;
+ if(pos.y + getHeight() <= 0.0f || pos.y >= target.getSize().y) return false;
/*
if(editable)
{
@@ -972,7 +1046,7 @@ namespace dchat
}
}
- if(!editable) return;
+ if(!editable) return true;
//float rows = floor(totalHeight / (vspace + lineSpacing));
const float caretRow = getRowByPosition(caretPosition);
@@ -981,5 +1055,6 @@ namespace dchat
caretRect.setFillColor(sf::Color::White);
caretRect.setPosition(sf::Vector2f(floor(pos.x + caretPosition.x), floor(pos.y + caretRow * (vspace + lineSpacing))));
target.draw(caretRect);
+ return true;
}
}
diff --git a/src/main.cpp b/src/main.cpp
index b5ec81e..a66945c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -9,6 +9,8 @@
#include "../include/Settings.hpp"
#include "../include/ColorScheme.hpp"
#include "../include/GlobalContextMenu.hpp"
+#include "../include/StringUtils.hpp"
+#include "../include/ImagePreview.hpp"
#include <string>
#include <SFML/Graphics.hpp>
#include <cstring>
@@ -19,7 +21,6 @@
#include <odhtdb/hex2bin.hpp>
#include <ntp/NtpClient.hpp>
#include <sibs/SafeSerializer.hpp>
-#include <process.hpp>
#include <X11/Xlib.h>
using namespace std;
@@ -46,16 +47,6 @@ static void channelChangeUserNickname(Channel *channel, const StringView data, c
// We dont care if there is more data to read (malicious packet), we already got all the data we need
}
-static void stringReplaceChar(string &str, const string &from, const string &to)
-{
- size_t pos = 0;
- while((pos = str.find(from, pos)) != string::npos)
- {
- str.replace(pos, from.size(), to);
- pos += to.size();
- }
-}
-
static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp)
{
User *user = channel->getUserByPublicKey(creatorPublicKey);
@@ -78,12 +69,11 @@ static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &reques
if(!focused)
{
stringReplaceChar(msg, "'", "");
- stringReplaceChar(msg, "\"", "");
stringReplaceChar(msg, "\\", "");
string cmd = "notify-send dchat '";
cmd += msg;
cmd += "'";
- TinyProcessLib::Process notifySend(cmd, "");
+ system(cmd.c_str());
}
break;
}
@@ -681,28 +671,36 @@ int main(int argc, char **argv)
else if(event.type == sf::Event::Resized)
{
sf::FloatRect viewRect(0.0f, 0.0f, event.size.width, event.size.height);
+ /* // TODO: Use xlib to set window minimum size instead
const int minWidth = 800;
if(event.size.width < minWidth)
{
viewRect.width = minWidth;
window.setSize(sf::Vector2u(minWidth, event.size.height));
}
-
+ */
sf::View view(viewRect);
window.setView(view);
}
- else if(event.type == sf::Event::GainedFocus)
+ else if(event.type == sf::Event::MouseEntered)
window.setFramerateLimit(FRAMERATE_FOCUSED);
- //else if(event.type == sf::Event::LostFocus)
+ //else if(event.type == sf::Event::MouseLeft)
// window.setFramerateLimit(FRAMERATE_NOT_FOCUSED);
- if(event.type == sf::Event::GainedFocus)
+ if(event.type == sf::Event::MouseEntered)
focused = true;
- else if(event.type == sf::Event::LostFocus)
+ else if(event.type == sf::Event::MouseLeft)
focused = false;
- GlobalContextMenu::processEvent(event);
- currentChannel->processEvent(event);
+ if(focused)
+ {
+ ImagePreview::processEvent(event);
+ if(!ImagePreview::getPreviewContentPtr() && ImagePreview::getTimeSinceLastSeenMs() > 250)
+ {
+ GlobalContextMenu::processEvent(event);
+ currentChannel->processEvent(event, cache);
+ }
+ }
}
window.clear(ColorScheme::getBackgroundColor());
@@ -711,6 +709,7 @@ int main(int argc, char **argv)
UsersSidePanel::draw(window, cache);
ChannelTopPanel::draw(window);
GlobalContextMenu::draw(window);
+ ImagePreview::draw(window);
if(waitingToJoin)
{