summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/chatmessage.cpp146
-rw-r--r--components/chatmessage.hpp21
-rw-r--r--components/chatwindow.cpp15
-rw-r--r--css/main.css8
-rw-r--r--discord/message.cpp6
-rw-r--r--discord/message.hpp4
-rw-r--r--filecache.cpp9
-rw-r--r--filecache.hpp1
-rw-r--r--imgmanager.cpp19
-rw-r--r--util.hpp30
10 files changed, 239 insertions, 20 deletions
diff --git a/components/chatmessage.cpp b/components/chatmessage.cpp
index 599f64d..363b733 100644
--- a/components/chatmessage.cpp
+++ b/components/chatmessage.cpp
@@ -30,23 +30,44 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) {
container->ChannelID = data->ChannelID;
if (data->Content.size() > 0 || data->Type != MessageType::DEFAULT) {
- container->m_text_component = CreateTextComponent(data);
+ container->m_text_component = container->CreateTextComponent(data);
container->AttachMenuHandler(container->m_text_component);
container->m_main->add(*container->m_text_component);
}
// there should only ever be 1 embed (i think?)
if (data->Embeds.size() == 1) {
- container->m_embed_component = CreateEmbedComponent(data);
+ container->m_embed_component = container->CreateEmbedComponent(data);
container->AttachMenuHandler(container->m_embed_component);
container->m_main->add(*container->m_embed_component);
}
+ // i dont think attachments can be edited
+ // also this can definitely be done much better holy shit
+ for (const auto &a : data->Attachments) {
+ const auto last3 = a.ProxyURL.substr(a.ProxyURL.length() - 3);
+ if (last3 == "png" || last3 == "jpg") {
+ auto *widget = container->CreateImageComponent(a);
+ auto *ev = Gtk::manage(new Gtk::EventBox);
+ ev->add(*widget);
+ container->AttachMenuHandler(ev);
+ container->AddClickHandler(ev, a.URL);
+ container->m_main->add(*ev);
+ container->HandleImage(a, widget, a.ProxyURL);
+ } else {
+ auto *widget = container->CreateAttachmentComponent(a);
+ container->AttachMenuHandler(widget);
+ container->AddClickHandler(widget, a.URL);
+ container->m_main->add(*widget);
+ }
+ }
+
container->UpdateAttributes();
return container;
}
+// this doesnt rly make sense
void ChatMessageItemContainer::UpdateContent() {
const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
if (m_text_component != nullptr)
@@ -58,12 +79,37 @@ void ChatMessageItemContainer::UpdateContent() {
if (data->Embeds.size() == 1) {
m_embed_component = CreateEmbedComponent(data);
+ if (m_embed_imgurl.size() > 0) {
+ m_signal_image_load.emit(m_embed_imgurl);
+ }
AttachMenuHandler(m_embed_component);
m_main->add(*m_embed_component);
}
}
}
+void ChatMessageItemContainer::UpdateImage() {
+ for (auto it = m_img_loadmap.cbegin(); it != m_img_loadmap.cend();) {
+ auto buf = Abaddon::Get().GetImageManager().GetFromURLIfCached(it->first);
+ if (buf) {
+ int w, h;
+ std::tie(w, h) = GetImageDimensions(it->second.second.Width, it->second.second.Height);
+ it->second.first->property_pixbuf() = buf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
+ it = m_img_loadmap.erase(it);
+ } else
+ it++;
+ }
+
+ if (m_embed_img != nullptr) {
+ auto buf = Abaddon::Get().GetImageManager().GetFromURLIfCached(m_embed_imgurl);
+ if (buf) {
+ int w, h;
+ m_embed_img->get_size_request(w, h);
+ m_embed_img->property_pixbuf() = buf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
+ }
+ }
+}
+
void ChatMessageItemContainer::UpdateAttributes() {
const auto *data = Abaddon::Get().GetDiscordClient().GetMessage(ID);
if (data == nullptr) return;
@@ -86,6 +132,18 @@ void ChatMessageItemContainer::UpdateAttributes() {
m_attrib_label->set_markup("<span color='#999999'>[edited]</span>");
}
+void ChatMessageItemContainer::AddClickHandler(Gtk::Widget *widget, std::string url) {
+ // clang-format off
+ widget->signal_button_press_event().connect([url](GdkEventButton *event) -> bool {
+ if (event->type == Gdk::BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
+ LaunchBrowser(url);
+ return false;
+ }
+ return true;
+ }, false);
+ // clang-format on
+}
+
Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message *data) {
auto *tv = Gtk::manage(new Gtk::TextView);
@@ -200,6 +258,29 @@ Gtk::EventBox *ChatMessageItemContainer::CreateEmbedComponent(const Message *dat
}
}
+ bool img = embed.Image.URL.size() > 0;
+ bool thumb = embed.Thumbnail.URL.size() > 0;
+ if (img || thumb) {
+ auto *img = Gtk::manage(new Gtk::Image);
+ img->set_halign(Gtk::ALIGN_CENTER);
+ int w, h;
+ if (img)
+ std::tie(w, h) = GetImageDimensions(embed.Image.Width, embed.Image.Height, 200, 150);
+ else
+ std::tie(w, h) = GetImageDimensions(embed.Thumbnail.Width, embed.Thumbnail.Height, 200, 150);
+ img->set_size_request(w, h);
+ main->pack_start(*img);
+ m_embed_img = img;
+ if (img)
+ m_embed_imgurl = embed.Image.ProxyURL;
+ else
+ m_embed_imgurl = embed.Thumbnail.ProxyURL;
+ Glib::signal_idle().connect([this]() -> bool {
+ m_signal_image_load.emit(m_embed_imgurl);
+ return false;
+ });
+ }
+
if (embed.Footer.Text.length() > 0) {
auto *footer_lbl = Gtk::manage(new Gtk::Label);
footer_lbl->set_halign(Gtk::ALIGN_START);
@@ -234,6 +315,63 @@ Gtk::EventBox *ChatMessageItemContainer::CreateEmbedComponent(const Message *dat
return ev;
}
+Gtk::Image *ChatMessageItemContainer::CreateImageComponent(const AttachmentData &data) {
+ int w, h;
+ std::tie(w, h) = GetImageDimensions(data.Width, data.Height);
+
+ auto &im = Abaddon::Get().GetImageManager();
+ Gtk::Image *widget = Gtk::manage(new Gtk::Image);
+ widget->set_halign(Gtk::ALIGN_START);
+ widget->set_size_request(w, h);
+
+ // clang-format off
+ const auto url = data.URL;
+ widget->signal_button_press_event().connect([url](GdkEventButton *event) -> bool {
+ if (event->type == Gdk::BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
+ LaunchBrowser(url);
+ return false;
+ }
+ return true;
+ }, false);
+ // clang-format on
+
+ return widget;
+}
+
+Gtk::Box *ChatMessageItemContainer::CreateAttachmentComponent(const AttachmentData &data) {
+ auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ auto *ev = Gtk::manage(new Gtk::EventBox);
+ auto *btn = Gtk::manage(new Gtk::Label(data.Filename + " " + HumanReadableBytes(data.Bytes))); // Gtk::LinkButton flat out doesn't work :D
+ box->get_style_context()->add_class("message-attachment-box");
+ ev->add(*btn);
+ box->add(*ev);
+ return box;
+}
+
+std::pair<int, int> ChatMessageItemContainer::GetImageDimensions(int width, int height, int clampw, int clamph) {
+ const auto frac = static_cast<float>(width) / height;
+
+ if (width > clampw) {
+ width = clampw;
+ height = clampw / frac;
+ }
+
+ if (height > clamph) {
+ height = clamph;
+ width = clamph * frac;
+ }
+
+ return std::make_pair(static_cast<int>(width), static_cast<int>(height));
+}
+
+void ChatMessageItemContainer::HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url) {
+ m_img_loadmap[url] = std::make_pair(img, data);
+ Glib::signal_idle().connect([this, url]() -> bool {
+ m_signal_image_load.emit(url); // ask the chatwindow to call UpdateImage because dealing with lifetimes sucks
+ return false;
+ });
+}
+
void ChatMessageItemContainer::ShowMenu(GdkEvent *event) {
const auto &client = Abaddon::Get().GetDiscordClient();
const auto *data = client.GetMessage(ID);
@@ -270,6 +408,10 @@ ChatMessageItemContainer::type_signal_action_edit ChatMessageItemContainer::sign
return m_signal_action_edit;
}
+ChatMessageItemContainer::type_signal_image_load ChatMessageItemContainer::signal_image_load() {
+ return m_signal_image_load;
+}
+
// clang-format off
void ChatMessageItemContainer::AttachMenuHandler(Gtk::Widget *widget) {
widget->signal_button_press_event().connect([this](GdkEventButton *event) -> bool {
diff --git a/components/chatmessage.hpp b/components/chatmessage.hpp
index bf4f0da..f105354 100644
--- a/components/chatmessage.hpp
+++ b/components/chatmessage.hpp
@@ -13,10 +13,19 @@ public:
// attributes = edited, deleted
void UpdateAttributes();
void UpdateContent();
+ void UpdateImage();
protected:
- static Gtk::TextView *CreateTextComponent(const Message *data); // Message.Content
- static Gtk::EventBox *CreateEmbedComponent(const Message *data); // Message.Embeds[0]
+ void AddClickHandler(Gtk::Widget *widget, std::string);
+ Gtk::TextView *CreateTextComponent(const Message *data); // Message.Content
+ Gtk::EventBox *CreateEmbedComponent(const Message *data); // Message.Embeds[0]
+ Gtk::Image *CreateImageComponent(const AttachmentData &data);
+ Gtk::Box *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments
+ void HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url);
+ static std::pair<int, int> GetImageDimensions(int width, int height, int clampw = 400, int clamph = 300);
+
+ std::unordered_map<std::string, std::pair<Gtk::Image *, AttachmentData>> m_img_loadmap;
+
void AttachMenuHandler(Gtk::Widget *widget);
void ShowMenu(GdkEvent *event);
@@ -31,20 +40,28 @@ protected:
Gtk::Box *m_main;
Gtk::Label *m_attrib_label = nullptr;
+ Gtk::Image *m_embed_img = nullptr; // yes this is hacky no i dont care (for now)
+ std::string m_embed_imgurl;
Gtk::TextView *m_text_component = nullptr;
Gtk::EventBox *m_embed_component = nullptr;
public:
+ typedef sigc::signal<void, std::string> type_signal_image_load;
+
typedef sigc::signal<void> type_signal_action_delete;
typedef sigc::signal<void> type_signal_action_edit;
type_signal_action_delete signal_action_delete();
type_signal_action_edit signal_action_edit();
+ type_signal_image_load signal_image_load();
+
private:
type_signal_action_delete m_signal_action_delete;
type_signal_action_edit m_signal_action_edit;
+
+ type_signal_image_load m_signal_image_load;
};
class ChatMessageHeader : public Gtk::ListBoxRow {
diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp
index ee4aea9..f466852 100644
--- a/components/chatwindow.cpp
+++ b/components/chatwindow.cpp
@@ -199,14 +199,25 @@ void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
auto *content = CreateMessageComponent(id);
if (content != nullptr) {
+ header->AddContent(content, prepend);
+ m_id_to_widget[id] = content;
+
content->signal_action_delete().connect([this, id] {
m_signal_action_message_delete.emit(m_active_channel, id);
});
content->signal_action_edit().connect([this, id] {
m_signal_action_message_edit.emit(m_active_channel, id);
});
- header->AddContent(content, prepend);
- m_id_to_widget[id] = content;
+ content->signal_image_load().connect([this, id](std::string url) {
+ auto &mgr = Abaddon::Get().GetImageManager();
+ mgr.LoadFromURL(url, [this, id](Glib::RefPtr<Gdk::Pixbuf> buf) {
+ if (m_id_to_widget.find(id) != m_id_to_widget.end()) {
+ auto *x = dynamic_cast<ChatMessageItemContainer *>(m_id_to_widget.at(id));
+ if (x != nullptr)
+ x->UpdateImage();
+ }
+ });
+ });
}
header->set_margin_left(5);
diff --git a/css/main.css b/css/main.css
index 64b334a..c048533 100644
--- a/css/main.css
+++ b/css/main.css
@@ -85,8 +85,14 @@
padding-left: 15px;
}
+.message-attachment-box {
+ color: #aaaaaa;
+ border: 1px solid #aaaaaa;
+ padding: 2px 5px 2px 5px;
+}
+
paned separator {
- background: #37474f;
+ background:#37474f;
}
scrollbar {
diff --git a/discord/message.cpp b/discord/message.cpp
index 29f3dd2..428281d 100644
--- a/discord/message.cpp
+++ b/discord/message.cpp
@@ -66,8 +66,8 @@ void from_json(const nlohmann::json &j, AttachmentData &m) {
JS_D("size", m.Bytes);
JS_D("url", m.URL);
JS_D("proxy_url", m.ProxyURL);
- JS_N("height", m.Height);
- JS_N("width", m.Width);
+ JS_ON("height", m.Height);
+ JS_ON("width", m.Width);
}
void from_json(const nlohmann::json &j, Message &m) {
@@ -86,7 +86,7 @@ void from_json(const nlohmann::json &j, Message &m) {
JS_D("mentions", m.Mentions);
// JS_D("mention_roles", m.MentionRoles);
// JS_O("mention_channels", m.MentionChannels);
- // JS_D("attachments", m.Attachments);
+ JS_D("attachments", m.Attachments);
JS_D("embeds", m.Embeds);
// JS_O("reactions", m.Reactions);
JS_O("nonce", m.Nonce);
diff --git a/discord/message.hpp b/discord/message.hpp
index d288cda..a609742 100644
--- a/discord/message.hpp
+++ b/discord/message.hpp
@@ -113,8 +113,8 @@ struct AttachmentData {
int Bytes; //
std::string URL; //
std::string ProxyURL; //
- int Height = -1; // null
- int Width = -1; // null
+ int Height = -1; // opt, null
+ int Width = -1; // opt, null
friend void from_json(const nlohmann::json &j, AttachmentData &m);
};
diff --git a/filecache.cpp b/filecache.cpp
index 7d1bdb4..9220ae8 100644
--- a/filecache.cpp
+++ b/filecache.cpp
@@ -51,6 +51,15 @@ void Cache::GetFileFromURL(std::string url, callback_type cb) {
}
}
+std::string Cache::GetPathIfCached(std::string url) {
+ auto cache_path = m_tmp_path / SanitizeString(url);
+ if (std::filesystem::exists(cache_path)) {
+ return cache_path.string();
+ }
+
+ return "";
+}
+
// this just seems really yucky
void Cache::CleanupFutures() {
for (auto it = m_futures.begin(); it != m_futures.end();) {
diff --git a/filecache.hpp b/filecache.hpp
index dfc8638..b54f842 100644
--- a/filecache.hpp
+++ b/filecache.hpp
@@ -15,6 +15,7 @@ public:
using callback_type = std::function<void(std::string)>;
void GetFileFromURL(std::string url, callback_type cb);
+ std::string GetPathIfCached(std::string url);
private:
std::string SanitizeString(std::string str);
diff --git a/imgmanager.cpp b/imgmanager.cpp
index ce26b0f..2e582c3 100644
--- a/imgmanager.cpp
+++ b/imgmanager.cpp
@@ -5,15 +5,15 @@ Cache &ImageManager::GetCache() {
}
void ImageManager::LoadFromURL(std::string url, std::function<void(Glib::RefPtr<Gdk::Pixbuf>)> cb) {
- if (m_pixs.find(url) != m_pixs.end()) {
+ /*if (m_pixs.find(url) != m_pixs.end()) {
cb(m_pixs.at(url));
return;
- }
+ }*/
m_cache.GetFileFromURL(url, [this, url, cb](std::string path) {
try {
auto buf = Gdk::Pixbuf::create_from_file(path);
- m_pixs[url] = buf;
+ //m_pixs[url] = buf;
cb(buf);
} catch (std::exception &e) {
fprintf(stderr, "err loading pixbuf from %s: %s\n", path.c_str(), e.what());
@@ -22,20 +22,23 @@ void ImageManager::LoadFromURL(std::string url, std::function<void(Glib::RefPtr<
}
Glib::RefPtr<Gdk::Pixbuf> ImageManager::GetFromURLIfCached(std::string url) {
- if (m_pixs.find(url) != m_pixs.end())
- return m_pixs.at(url);
+ std::string path = m_cache.GetPathIfCached(url);
+ if (path != "")
+ return Gdk::Pixbuf::create_from_file(path);
+ //if (m_pixs.find(url) != m_pixs.end())
+ // return m_pixs.at(url);
return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
}
Glib::RefPtr<Gdk::Pixbuf> ImageManager::GetPlaceholder(int size) {
std::string name = "/placeholder" + std::to_string(size);
- if (m_pixs.find(name) != m_pixs.end())
- return m_pixs.at(name);
+ //if (m_pixs.find(name) != m_pixs.end())
+ // return m_pixs.at(name);
try {
auto buf = Gdk::Pixbuf::create_from_file("res/decamarks.png", size, size);
- m_pixs[name] = buf;
+ // m_pixs[name] = buf;
return buf;
} catch (std::exception &e) {
fprintf(stderr, "error loading placeholder\n");
diff --git a/util.hpp b/util.hpp
index 6f35e03..0b80053 100644
--- a/util.hpp
+++ b/util.hpp
@@ -9,6 +9,36 @@
#include <string>
#include <iomanip>
+// gtkmm doesnt seem to work
+#ifdef _WIN32
+ #define WIN32_LEAN_AND_MEAN
+ #include <Windows.h>
+ #include <shellapi.h>
+#endif
+
+inline void LaunchBrowser(std::string url) {
+#if defined(_WIN32)
+ // wtf i love the win32 api now ???
+ ShellExecuteA(NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL);
+#elif defined(__APPLE__)
+ std::system(("open " + url).c_str());
+#elif defined(__linux__)
+ std::system(("xdg-open" + url).c_str());
+#else
+ printf("can't open url on this platform\n");
+#endif
+}
+
+inline std::string HumanReadableBytes(uint64_t bytes) {
+ constexpr static const char *x[] = { "B", "KB", "MB", "GB", "TB" };
+ int order = 0;
+ while (bytes >= 1000 && order < 4) { // 4=len(x)-1
+ order++;
+ bytes /= 1000;
+ }
+ return std::to_string(bytes) + x[order];
+}
+
template<typename T>
struct Bitwise {
static const bool enable = false;