start attachments (image paste and upload)

This commit is contained in:
ouwou
2022-06-05 21:41:57 -04:00
parent 4ec5c1dfcc
commit 270730d9b3
15 changed files with 418 additions and 54 deletions

View File

@@ -1,6 +1,10 @@
#include "chatinput.hpp"
#include "abaddon.hpp"
#include "constants.hpp"
#include <filesystem>
#include <utility>
ChatInput::ChatInput() {
ChatInputText::ChatInputText() {
get_style_context()->add_class("message-input");
set_propagate_natural_height(true);
set_min_content_height(20);
@@ -20,22 +24,26 @@ ChatInput::ChatInput() {
add(m_textview);
}
void ChatInput::InsertText(const Glib::ustring &text) {
void ChatInputText::InsertText(const Glib::ustring &text) {
GetBuffer()->insert_at_cursor(text);
m_textview.grab_focus();
}
Glib::RefPtr<Gtk::TextBuffer> ChatInput::GetBuffer() {
Glib::RefPtr<Gtk::TextBuffer> ChatInputText::GetBuffer() {
return m_textview.get_buffer();
}
// this isnt connected directly so that the chat window can handle stuff like the completer first
bool ChatInput::ProcessKeyPress(GdkEventKey *event) {
bool ChatInputText::ProcessKeyPress(GdkEventKey *event) {
if (event->keyval == GDK_KEY_Escape) {
m_signal_escape.emit();
return true;
}
if ((event->state & GDK_CONTROL_MASK) && event->keyval == GDK_KEY_v) {
return CheckHandleClipboardPaste();
}
if (event->keyval == GDK_KEY_Return) {
if (event->state & GDK_SHIFT_MASK)
return false;
@@ -53,10 +61,196 @@ bool ChatInput::ProcessKeyPress(GdkEventKey *event) {
return false;
}
void ChatInput::on_grab_focus() {
void ChatInputText::on_grab_focus() {
m_textview.grab_focus();
}
bool ChatInputText::CheckHandleClipboardPaste() {
auto clip = Gtk::Clipboard::get();
if (!clip->wait_is_image_available()) return false;
const auto pb = clip->wait_for_image();
std::array<char, L_tmpnam> dest_name {};
if (std::tmpnam(dest_name.data()) == nullptr) {
fprintf(stderr, "failed to get temporary path\n");
return true;
}
// stinky
std::filesystem::path part1(std::filesystem::temp_directory_path() / "abaddon-cache");
std::filesystem::path part2(dest_name.data());
const auto dest_path = (part1 / part2.relative_path()).string();
try {
pb->save(dest_path, "png");
} catch (...) {
fprintf(stderr, "pasted image save error\n");
return true;
}
m_signal_image_paste.emit(pb, dest_path);
return true;
}
ChatInputText::type_signal_submit ChatInputText::signal_submit() {
return m_signal_submit;
}
ChatInputText::type_signal_escape ChatInputText::signal_escape() {
return m_signal_escape;
}
ChatInputText::type_signal_image_paste ChatInputText::signal_image_paste() {
return m_signal_image_paste;
}
ChatInputAttachmentContainer::ChatInputAttachmentContainer()
: m_box(Gtk::ORIENTATION_HORIZONTAL) {
get_style_context()->add_class("attachment-container");
add(m_box);
m_box.show();
set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER);
set_vexpand(true);
set_size_request(-1, AttachmentItemSize + 10);
}
void ChatInputAttachmentContainer::Clear() {
for (auto *x : m_attachments)
delete x;
m_attachments.clear();
}
bool ChatInputAttachmentContainer::AddImage(const Glib::RefPtr<Gdk::Pixbuf> &pb, const std::string &path) {
if (m_attachments.size() == 10) return false;
auto *item = Gtk::make_managed<ChatInputAttachmentItem>(path, pb);
item->show();
item->set_valign(Gtk::ALIGN_CENTER);
m_box.add(*item);
m_attachments.insert(item);
item->signal_remove().connect([this, item] {
m_attachments.erase(item);
delete item;
if (m_attachments.empty())
m_signal_emptied.emit();
});
return true;
}
std::vector<std::string> ChatInputAttachmentContainer::GetFilePaths() const {
std::vector<std::string> ret;
for (auto *x : m_attachments)
ret.push_back(x->GetPath());
return ret;
}
ChatInputAttachmentContainer::type_signal_emptied ChatInputAttachmentContainer::signal_emptied() {
return m_signal_emptied;
}
ChatInputAttachmentItem::ChatInputAttachmentItem(std::string path, const Glib::RefPtr<Gdk::Pixbuf> &pb)
: m_path(std::move(path))
, m_img(Gtk::make_managed<Gtk::Image>()) {
get_style_context()->add_class("attachment-item");
int outw, outh;
GetImageDimensions(pb->get_width(), pb->get_height(), outw, outh, AttachmentItemSize, AttachmentItemSize);
m_img->property_pixbuf() = pb->scale_simple(outw, outh, Gdk::INTERP_BILINEAR);
set_size_request(AttachmentItemSize, AttachmentItemSize);
m_box.add(*m_img);
add(m_box);
show_all_children();
SetupMenu();
}
std::string ChatInputAttachmentItem::GetPath() const {
return m_path;
}
void ChatInputAttachmentItem::SetupMenu() {
m_menu_remove.set_label("Remove");
m_menu_remove.signal_activate().connect([this] {
m_signal_remove.emit();
});
m_menu.add(m_menu_remove);
m_menu.show_all();
signal_button_press_event().connect([this](GdkEventButton *ev) -> bool {
if (ev->button == GDK_BUTTON_SECONDARY) {
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
return true;
}
return false;
});
}
ChatInputAttachmentItem::type_signal_remove ChatInputAttachmentItem::signal_remove() {
return m_signal_remove;
}
ChatInput::ChatInput()
: Gtk::Box(Gtk::ORIENTATION_VERTICAL) {
m_input.signal_escape().connect([this] {
m_attachments.Clear();
m_attachments_revealer.set_reveal_child(false);
m_signal_escape.emit();
});
m_input.signal_submit().connect([this](const Glib::ustring &input) -> bool {
const auto attachments = m_attachments.GetFilePaths();
bool b = m_signal_submit.emit(input, attachments);
if (b) {
m_attachments.Clear();
m_attachments_revealer.set_reveal_child(false);
}
return b;
});
m_attachments.set_vexpand(false);
m_attachments_revealer.set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP);
m_attachments_revealer.add(m_attachments);
add(m_attachments_revealer);
add(m_input);
show_all_children();
m_input.signal_image_paste().connect([this](const Glib::RefPtr<Gdk::Pixbuf> &pb, const std::string &path) {
if (m_attachments.AddImage(pb, path))
m_attachments_revealer.set_reveal_child(true);
});
// double hack !
auto cb = [this](GdkEventKey *e) -> bool {
return event(reinterpret_cast<GdkEvent *>(e));
};
m_input.signal_key_press_event().connect(cb, false);
m_attachments.signal_emptied().connect([this] {
m_attachments_revealer.set_reveal_child(false);
});
}
void ChatInput::InsertText(const Glib::ustring &text) {
m_input.InsertText(text);
}
Glib::RefPtr<Gtk::TextBuffer> ChatInput::GetBuffer() {
return m_input.GetBuffer();
}
bool ChatInput::ProcessKeyPress(GdkEventKey *event) {
return m_input.ProcessKeyPress(event);
}
ChatInput::type_signal_submit ChatInput::signal_submit() {
return m_signal_submit;
}

View File

@@ -1,9 +1,57 @@
#pragma once
#include <gtkmm.h>
class ChatInput : public Gtk::ScrolledWindow {
class ChatInputAttachmentItem : public Gtk::EventBox {
public:
ChatInput();
ChatInputAttachmentItem(std::string path, const Glib::RefPtr<Gdk::Pixbuf> &pb);
[[nodiscard]] std::string GetPath() const;
private:
void SetupMenu();
Gtk::Menu m_menu;
Gtk::MenuItem m_menu_remove;
Gtk::Box m_box;
Gtk::Image *m_img = nullptr;
std::string m_path;
private:
using type_signal_remove = sigc::signal<void>;
type_signal_remove m_signal_remove;
public:
type_signal_remove signal_remove();
};
class ChatInputAttachmentContainer : public Gtk::ScrolledWindow {
public:
ChatInputAttachmentContainer();
void Clear();
bool AddImage(const Glib::RefPtr<Gdk::Pixbuf> &pb, const std::string &path);
[[nodiscard]] std::vector<std::string> GetFilePaths() const;
private:
std::set<ChatInputAttachmentItem *> m_attachments;
Gtk::Box m_box;
private:
using type_signal_emptied = sigc::signal<void>;
type_signal_emptied m_signal_emptied;
public:
type_signal_emptied signal_emptied();
};
class ChatInputText : public Gtk::ScrolledWindow {
public:
ChatInputText();
void InsertText(const Glib::ustring &text);
Glib::RefPtr<Gtk::TextBuffer> GetBuffer();
@@ -15,9 +63,42 @@ protected:
private:
Gtk::TextView m_textview;
bool CheckHandleClipboardPaste();
void HandleNewPastedImage(const Glib::RefPtr<Gdk::Pixbuf> &pb, const std::string &filename);
public:
typedef sigc::signal<bool, Glib::ustring> type_signal_submit;
typedef sigc::signal<void> type_signal_escape;
using type_signal_submit = sigc::signal<bool, Glib::ustring>;
using type_signal_escape = sigc::signal<void>;
using type_signal_image_paste = sigc::signal<void, Glib::RefPtr<Gdk::Pixbuf>, std::string>;
type_signal_submit signal_submit();
type_signal_escape signal_escape();
type_signal_image_paste signal_image_paste();
private:
type_signal_submit m_signal_submit;
type_signal_escape m_signal_escape;
type_signal_image_paste m_signal_image_paste;
};
class ChatInput : public Gtk::Box {
public:
ChatInput();
void InsertText(const Glib::ustring &text);
Glib::RefPtr<Gtk::TextBuffer> GetBuffer();
bool ProcessKeyPress(GdkEventKey *event);
private:
Gtk::Revealer m_attachments_revealer;
ChatInputAttachmentContainer m_attachments;
ChatInputText m_input;
public:
// text, attachments -> request sent
// maybe this should be reduced to a single struct, its bound to get more complicated (application commands?)
using type_signal_submit = sigc::signal<bool, Glib::ustring, std::vector<std::string>>;
using type_signal_escape = sigc::signal<void>;
type_signal_submit signal_submit();
type_signal_escape signal_escape();

View File

@@ -352,10 +352,6 @@ ChatList::type_signal_action_message_edit ChatList::signal_action_message_edit()
return m_signal_action_message_edit;
}
ChatList::type_signal_action_chat_submit ChatList::signal_action_chat_submit() {
return m_signal_action_chat_submit;
}
ChatList::type_signal_action_chat_load_history ChatList::signal_action_chat_load_history() {
return m_signal_action_chat_load_history;
}

View File

@@ -63,7 +63,6 @@ private:
public:
// these are all forwarded by the parent
using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>;
using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
using type_signal_action_channel_click = sigc::signal<void, Snowflake>;
using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;
@@ -73,7 +72,6 @@ public:
using type_signal_action_reply_to = sigc::signal<void, Snowflake>;
type_signal_action_message_edit signal_action_message_edit();
type_signal_action_chat_submit signal_action_chat_submit();
type_signal_action_chat_load_history signal_action_chat_load_history();
type_signal_action_channel_click signal_action_channel_click();
type_signal_action_insert_mention signal_action_insert_mention();
@@ -84,7 +82,6 @@ public:
private:
type_signal_action_message_edit m_signal_action_message_edit;
type_signal_action_chat_submit m_signal_action_chat_submit;
type_signal_action_chat_load_history m_signal_action_chat_load_history;
type_signal_action_channel_click m_signal_action_channel_click;
type_signal_action_insert_mention m_signal_action_insert_mention;

View File

@@ -45,6 +45,8 @@ ChatWindow::ChatWindow() {
m_topic_text.set_halign(Gtk::ALIGN_START);
m_topic_text.show();
m_input->set_valign(Gtk::ALIGN_END);
m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit));
m_input->signal_escape().connect([this]() {
if (m_is_replying)
@@ -70,9 +72,6 @@ ChatWindow::ChatWindow() {
m_chat->signal_action_chat_load_history().connect([this](Snowflake id) {
m_signal_action_chat_load_history.emit(id);
});
m_chat->signal_action_chat_submit().connect([this](const std::string &str, Snowflake channel_id, Snowflake referenced_id) {
m_signal_action_chat_submit.emit(str, channel_id, referenced_id);
});
m_chat->signal_action_insert_mention().connect([this](Snowflake id) {
// lowkey gross
m_signal_action_insert_mention.emit(id);
@@ -210,15 +209,15 @@ Snowflake ChatWindow::GetActiveChannel() const {
return m_active_channel;
}
bool ChatWindow::OnInputSubmit(const Glib::ustring &text) {
bool ChatWindow::OnInputSubmit(const Glib::ustring &text, const std::vector<std::string> &attachment_paths) {
if (!m_rate_limit_indicator->CanSpeak())
return false;
if (text.empty())
if (text.empty() && attachment_paths.empty())
return false;
if (m_active_channel.IsValid())
m_signal_action_chat_submit.emit(text, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler
m_signal_action_chat_submit.emit(text, attachment_paths, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler
if (m_is_replying)
StopReplying();

View File

@@ -55,7 +55,7 @@ protected:
Snowflake m_active_channel;
bool OnInputSubmit(const Glib::ustring &text);
bool OnInputSubmit(const Glib::ustring &text, const std::vector<std::string> &attachment_paths);
bool OnKeyPressEvent(GdkEventKey *e);
void OnScrollEdgeOvershot(Gtk::PositionType pos);
@@ -84,7 +84,7 @@ protected:
public:
using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>;
using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
using type_signal_action_chat_submit = sigc::signal<void, std::string, std::vector<std::string>, Snowflake, Snowflake>;
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
using type_signal_action_channel_click = sigc::signal<void, Snowflake, bool>;
using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;