diff --git a/Applications/IRCClient/IRCAppWindow.cpp b/Applications/IRCClient/IRCAppWindow.cpp index 00ac55ff93..ddd160d719 100644 --- a/Applications/IRCClient/IRCAppWindow.cpp +++ b/Applications/IRCClient/IRCAppWindow.cpp @@ -44,11 +44,22 @@ void IRCAppWindow::setup_widgets() auto* subwindow_list = new GListBox(widget); subwindow_list->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill); subwindow_list->set_preferred_size({ 120, 0 }); - subwindow_list->add_item("test1"); - subwindow_list->add_item("test2"); - subwindow_list->add_item("test3"); + subwindow_list->add_item("Server"); + subwindow_list->add_item("#test"); - auto* container = new GWidget(widget); + m_subwindow_container = new GWidget(widget); + m_subwindow_container->set_layout(make(Orientation::Vertical)); + m_subwindow_container->set_fill_with_background_color(true); + m_subwindow_container->set_background_color(Color::Yellow); + m_subwindow_container->set_size_policy(SizePolicy::Fill, SizePolicy::Fill); - auto* subwindow = new IRCSubWindow("Server", container); + create_subwindow(IRCSubWindow::Server, "Server"); +} + +void IRCAppWindow::create_subwindow(IRCSubWindow::Type type, const String& name) +{ + auto* subwindow = new IRCSubWindow(m_client, type, name, m_subwindow_container); + subwindow->set_fill_with_background_color(true); + subwindow->set_background_color(Color::Magenta); + m_subwindows.append(subwindow); } diff --git a/Applications/IRCClient/IRCAppWindow.h b/Applications/IRCClient/IRCAppWindow.h index 966a0d2ea4..6bbb303047 100644 --- a/Applications/IRCClient/IRCAppWindow.h +++ b/Applications/IRCClient/IRCAppWindow.h @@ -3,6 +3,7 @@ #include #include #include "IRCClient.h" +#include "IRCSubWindow.h" class IRCAppWindow : public GWindow { public: @@ -13,5 +14,10 @@ private: void setup_client(); void setup_widgets(); + void create_subwindow(IRCSubWindow::Type, const String& name); + IRCClient m_client; + + GWidget* m_subwindow_container { nullptr }; + Vector m_subwindows; }; diff --git a/Applications/IRCClient/IRCClient.cpp b/Applications/IRCClient/IRCClient.cpp index fcff869573..bc1bfbd295 100644 --- a/Applications/IRCClient/IRCClient.cpp +++ b/Applications/IRCClient/IRCClient.cpp @@ -1,6 +1,8 @@ #include "IRCClient.h" #include "IRCChannel.h" #include "IRCQuery.h" +#include "IRCLogBuffer.h" +#include "IRCSubWindow.h" #include #include #include @@ -17,6 +19,7 @@ IRCClient::IRCClient(const String& address, int port) : m_hostname(address) , m_port(port) , m_nickname("anon") + , m_log(IRCLogBuffer::create()) { } @@ -165,7 +168,7 @@ void IRCClient::process_line() msg.arguments.append(String(current_parameter.data(), current_parameter.size())); msg.prefix = String(prefix.data(), prefix.size()); msg.command = String(command.data(), command.size()); - handle(msg); + handle(msg, String(m_line_buffer.data(), m_line_buffer.size())); } void IRCClient::send(const String& text) @@ -198,7 +201,7 @@ void IRCClient::join_channel(const String& channel_name) send(String::format("JOIN %s\r\n", channel_name.characters())); } -void IRCClient::handle(const Message& msg) +void IRCClient::handle(const Message& msg, const String& verbatim) { printf("IRCClient::execute: prefix='%s', command='%s', arguments=%d\n", msg.prefix.characters(), @@ -231,6 +234,8 @@ void IRCClient::handle(const Message& msg) if (msg.command == "PRIVMSG") return handle_privmsg(msg); + + m_log->add_message(0, "Server", verbatim); } bool IRCClient::is_nick_prefix(char ch) const @@ -297,7 +302,7 @@ void IRCClient::handle_ping(const Message& msg) { if (msg.arguments.size() < 0) return; - m_server_messages.enqueue(String::format("Ping? Pong! %s\n", msg.arguments[0].characters())); + m_log->add_message(0, "", String::format("Ping? Pong! %s\n", msg.arguments[0].characters())); send_pong(msg.arguments[0]); } @@ -339,3 +344,30 @@ void IRCClient::handle_namreply(const Message& msg) channel.dump(); } + +void IRCClient::register_subwindow(IRCSubWindow& subwindow) +{ + if (subwindow.type() == IRCSubWindow::Server) { + m_server_subwindow = &subwindow; + subwindow.set_log_buffer(*m_log); + return; + } + if (subwindow.type() == IRCSubWindow::Channel) { + auto it = m_channels.find(subwindow.name()); + ASSERT(it != m_channels.end()); + auto& channel = *(*it).value; + subwindow.set_log_buffer(channel.log()); + return; + } + if (subwindow.type() == IRCSubWindow::Query) { + subwindow.set_log_buffer(ensure_query(subwindow.name()).log()); + } +} + +void IRCClient::unregister_subwindow(IRCSubWindow& subwindow) +{ + if (subwindow.type() == IRCSubWindow::Server) { + m_server_subwindow = &subwindow; + return; + } +} diff --git a/Applications/IRCClient/IRCClient.h b/Applications/IRCClient/IRCClient.h index 12f4fbe5ac..71ea4e8dd6 100644 --- a/Applications/IRCClient/IRCClient.h +++ b/Applications/IRCClient/IRCClient.h @@ -4,9 +4,11 @@ #include #include #include +#include "IRCLogBuffer.h" class IRCChannel; class IRCQuery; +class IRCSubWindow; class GNotifier; class IRCClient { @@ -31,6 +33,9 @@ public: Function on_query_message; Function on_server_message; + void register_subwindow(IRCSubWindow&); + void unregister_subwindow(IRCSubWindow&); + private: struct Message { String prefix; @@ -48,7 +53,7 @@ private: void handle_ping(const Message&); void handle_namreply(const Message&); void handle_privmsg(const Message&); - void handle(const Message&); + void handle(const Message&, const String& verbatim); IRCQuery& ensure_query(const String& name); String m_hostname; @@ -61,5 +66,7 @@ private: HashMap> m_channels; HashMap> m_queries; - CircularQueue m_server_messages; + IRCSubWindow* m_server_subwindow { nullptr }; + + Retained m_log; }; diff --git a/Applications/IRCClient/IRCLogBuffer.cpp b/Applications/IRCClient/IRCLogBuffer.cpp index 1f62aa031a..e9450ae804 100644 --- a/Applications/IRCClient/IRCLogBuffer.cpp +++ b/Applications/IRCClient/IRCLogBuffer.cpp @@ -1,4 +1,5 @@ #include "IRCLogBuffer.h" +#include "IRCLogBufferModel.h" #include #include @@ -9,6 +10,7 @@ Retained IRCLogBuffer::create() IRCLogBuffer::IRCLogBuffer() { + m_model = new IRCLogBufferModel(*this); } IRCLogBuffer::~IRCLogBuffer() @@ -18,6 +20,7 @@ IRCLogBuffer::~IRCLogBuffer() void IRCLogBuffer::add_message(char prefix, const String& name, const String& text) { m_messages.enqueue({ time(nullptr), prefix, name, text }); + m_model->update(); } void IRCLogBuffer::dump() const diff --git a/Applications/IRCClient/IRCLogBuffer.h b/Applications/IRCClient/IRCLogBuffer.h index 3e59c7c51d..7cfb6531af 100644 --- a/Applications/IRCClient/IRCLogBuffer.h +++ b/Applications/IRCClient/IRCLogBuffer.h @@ -5,6 +5,8 @@ #include #include +class IRCLogBufferModel; + class IRCLogBuffer : public Retainable { public: static Retained create(); @@ -24,8 +26,13 @@ public: void dump() const; + const IRCLogBufferModel* model() const { return m_model; } + IRCLogBufferModel* model() { return m_model; } + private: IRCLogBuffer(); + IRCLogBufferModel* m_model { nullptr }; + CircularQueue m_messages; }; diff --git a/Applications/IRCClient/IRCLogBufferModel.cpp b/Applications/IRCClient/IRCLogBufferModel.cpp new file mode 100644 index 0000000000..e3a9e698ab --- /dev/null +++ b/Applications/IRCClient/IRCLogBufferModel.cpp @@ -0,0 +1,65 @@ +#include "IRCLogBufferModel.h" +#include "IRCLogBuffer.h" +#include + +IRCLogBufferModel::IRCLogBufferModel(Retained&& log_buffer) + : m_log_buffer(move(log_buffer)) +{ +} + +IRCLogBufferModel::~IRCLogBufferModel() +{ +} + +int IRCLogBufferModel::row_count() const +{ + return m_log_buffer->count(); +} + +int IRCLogBufferModel::column_count() const +{ + return 4; +} + +String IRCLogBufferModel::column_name(int column) const +{ + switch (column) { + case Column::Timestamp: return "Time"; + case Column::Prefix: return "@"; + case Column::Name: return "Name"; + case Column::Text: return "Text"; + } + ASSERT_NOT_REACHED(); +} + +GTableModel::ColumnMetadata IRCLogBufferModel::column_metadata(int column) const +{ + switch (column) { + case Column::Timestamp: return { 90, TextAlignment::CenterLeft }; + case Column::Prefix: return { 10, TextAlignment::CenterLeft }; + case Column::Name: return { 70, TextAlignment::CenterRight }; + case Column::Text: return { 400, TextAlignment::CenterLeft }; + } + ASSERT_NOT_REACHED(); +} + +GVariant IRCLogBufferModel::data(const GModelIndex& index, Role role) const +{ + auto& entry = m_log_buffer->at(index.row()); + switch (index.column()) { + case Column::Timestamp: return String::format("%u", entry.timestamp); + case Column::Prefix: return entry.prefix; + case Column::Name: return entry.sender; + case Column::Text: return entry.text; + } + ASSERT_NOT_REACHED(); +} + +void IRCLogBufferModel::update() +{ + did_update(); +} + +void IRCLogBufferModel::activate(const GModelIndex&) +{ +} diff --git a/Applications/IRCClient/IRCLogBufferModel.h b/Applications/IRCClient/IRCLogBufferModel.h new file mode 100644 index 0000000000..5f5fe30c89 --- /dev/null +++ b/Applications/IRCClient/IRCLogBufferModel.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class IRCLogBuffer; + +class IRCLogBufferModel final : public GTableModel { +public: + enum Column { + Timestamp = 0, + Prefix, + Name, + Text, + }; + + explicit IRCLogBufferModel(Retained&&); + virtual ~IRCLogBufferModel() override; + + virtual int row_count() const override; + virtual int column_count() const override; + virtual String column_name(int column) const override; + virtual ColumnMetadata column_metadata(int column) const override; + virtual GVariant data(const GModelIndex&, Role = Role::Display) const override; + virtual void update() override; + virtual void activate(const GModelIndex&) override; + +private: + Retained m_log_buffer; +}; diff --git a/Applications/IRCClient/IRCSubWindow.cpp b/Applications/IRCClient/IRCSubWindow.cpp index a150ae37fd..e242a19fa9 100644 --- a/Applications/IRCClient/IRCSubWindow.cpp +++ b/Applications/IRCClient/IRCSubWindow.cpp @@ -1,11 +1,30 @@ #include "IRCSubWindow.h" +#include "IRCClient.h" +#include "IRCLogBufferModel.h" +#include +#include +#include -IRCSubWindow::IRCSubWindow(const String& name, GWidget* parent) +IRCSubWindow::IRCSubWindow(IRCClient& client, Type type, const String& name, GWidget* parent) : GWidget(parent) + , m_client(client) + , m_type(type) , m_name(name) { + set_layout(make(Orientation::Vertical)); + m_table_view = new GTableView(this); + m_table_view->set_font(Font::default_fixed_width_font()); + + m_client.register_subwindow(*this); } IRCSubWindow::~IRCSubWindow() { + m_client.unregister_subwindow(*this); +} + +void IRCSubWindow::set_log_buffer(const IRCLogBuffer& log_buffer) +{ + m_log_buffer = &log_buffer; + m_table_view->set_model(OwnPtr((IRCLogBufferModel*)log_buffer.model())); } diff --git a/Applications/IRCClient/IRCSubWindow.h b/Applications/IRCClient/IRCSubWindow.h index c62afc133d..76e64db9a9 100644 --- a/Applications/IRCClient/IRCSubWindow.h +++ b/Applications/IRCClient/IRCSubWindow.h @@ -2,14 +2,32 @@ #include +class IRCClient; +class IRCLogBuffer; +class GTableView; + class IRCSubWindow : public GWidget { public: - explicit IRCSubWindow(const String& name, GWidget* parent); + enum Type { + Server, + Channel, + Query, + }; + + explicit IRCSubWindow(IRCClient&, Type, const String& name, GWidget* parent); virtual ~IRCSubWindow() override; String name() const { return m_name; } void set_name(const String& name) { m_name = name; } + Type type() const { return m_type; } + + void set_log_buffer(const IRCLogBuffer&); + private: + IRCClient& m_client; + Type m_type; String m_name; + GTableView* m_table_view { nullptr }; + RetainPtr m_log_buffer; }; diff --git a/Applications/IRCClient/Makefile b/Applications/IRCClient/Makefile index bcf149a6ca..e7eb5ffef8 100644 --- a/Applications/IRCClient/Makefile +++ b/Applications/IRCClient/Makefile @@ -3,6 +3,7 @@ OBJS = \ IRCChannel.o \ IRCQuery.o \ IRCLogBuffer.o \ + IRCLogBufferModel.o \ IRCAppWindow.o \ IRCSubWindow.o \ main.o