Преглед изворни кода

IRCClient+LibGUI: Add an input box so we can send messages to channels.

Implement this using a GTextEditor with a special single-line mode.
This new mode needs some polishing, but it's already very useful.
Andreas Kling пре 6 година
родитељ
комит
1fc283ed7d

+ 7 - 2
Applications/IRCClient/IRCChannel.cpp

@@ -40,8 +40,13 @@ void IRCChannel::add_message(char prefix, const String& name, const String& text
 void IRCChannel::dump() const
 {
     printf("IRCChannel{%p}: %s\n", this, m_name.characters());
-    for (auto& member : m_members) {
+    for (auto& member : m_members)
         printf("   (%c)%s\n", member.prefix ? member.prefix : ' ', member.name.characters());
-    }
     log().dump();
 }
+
+void IRCChannel::say(const String& text)
+{
+    m_client.send_privmsg(m_name, text);
+    add_message(' ', m_client.nickname(), text);
+}

+ 2 - 0
Applications/IRCClient/IRCChannel.h

@@ -26,6 +26,8 @@ public:
 
     void dump() const;
 
+    void say(const String&);
+
     const IRCLogBuffer& log() const { return *m_log; }
     IRCLogBuffer& log() { return *m_log; }
 

+ 36 - 4
Applications/IRCClient/IRCClient.cpp

@@ -237,6 +237,30 @@ void IRCClient::handle(const Message& msg, const String&)
         m_log->add_message(0, "Server", String::format("[%s] %s", msg.command.characters(), msg.arguments[1].characters()));
 }
 
+void IRCClient::send_privmsg(const String& target, const String& text)
+{
+    send(String::format("PRIVMSG %s :%s\r\n", target.characters(), text.characters()));
+}
+
+void IRCClient::handle_user_input_in_channel(const String& channel_name, const String& input)
+{
+    if (input.is_empty())
+        return;
+    ensure_channel(channel_name).say(input);
+}
+
+void IRCClient::handle_user_input_in_query(const String& query_name, const String& input)
+{
+    if (input.is_empty())
+        return;
+}
+
+void IRCClient::handle_user_input_in_server(const String& input)
+{
+    if (input.is_empty())
+        return;
+}
+
 bool IRCClient::is_nick_prefix(char ch) const
 {
     switch (ch) {
@@ -297,6 +321,17 @@ IRCQuery& IRCClient::ensure_query(const String& name)
     return query_reference;
 }
 
+IRCChannel& IRCClient::ensure_channel(const String& name)
+{
+    auto it = m_channels.find(name);
+    if (it != m_channels.end())
+        return *(*it).value;
+    auto channel = IRCChannel::create(*this, name);
+    auto& channel_reference = *channel;
+    m_channels.set(name, channel.copy_ref());
+    return channel_reference;
+}
+
 void IRCClient::handle_ping(const Message& msg)
 {
     if (msg.arguments.size() < 0)
@@ -310,10 +345,7 @@ void IRCClient::handle_join(const Message& msg)
     if (msg.arguments.size() != 1)
         return;
     auto& channel_name = msg.arguments[0];
-    auto it = m_channels.find(channel_name);
-    ASSERT(it == m_channels.end());
-    auto channel = IRCChannel::create(*this, channel_name);
-    m_channels.set(channel_name, move(channel));
+    ensure_channel(channel_name);
     if (on_join)
         on_join(channel_name);
 }

+ 7 - 0
Applications/IRCClient/IRCClient.h

@@ -13,6 +13,7 @@ class IRCClientWindowListModel;
 class GNotifier;
 
 class IRCClient {
+    friend class IRCChannel;
 public:
     IRCClient(const String& address, int port = 6667);
     ~IRCClient();
@@ -45,6 +46,10 @@ public:
     const IRCClientWindow& window_at(int index) const { return *m_windows.at(index); }
     IRCClientWindow& window_at(int index) { return *m_windows.at(index); }
 
+    void handle_user_input_in_channel(const String& channel_name, const String&);
+    void handle_user_input_in_query(const String& query_name, const String&);
+    void handle_user_input_in_server(const String&);
+
 private:
     struct Message {
         String prefix;
@@ -57,6 +62,7 @@ private:
     void send_user();
     void send_nick();
     void send_pong(const String& server);
+    void send_privmsg(const String& target, const String&);
     void process_line();
     void handle_join(const Message&);
     void handle_ping(const Message&);
@@ -64,6 +70,7 @@ private:
     void handle_privmsg(const Message&);
     void handle(const Message&, const String& verbatim);
     IRCQuery& ensure_query(const String& name);
+    IRCChannel& ensure_channel(const String& name);
 
     String m_hostname;
     int m_port { 0 };

+ 14 - 0
Applications/IRCClient/IRCClientWindow.cpp

@@ -3,6 +3,7 @@
 #include "IRCLogBufferModel.h"
 #include <LibGUI/GBoxLayout.h>
 #include <LibGUI/GTableView.h>
+#include <LibGUI/GTextEditor.h>
 #include <LibGUI/GTextBox.h>
 
 IRCClientWindow::IRCClientWindow(IRCClient& client, Type type, const String& name, GWidget* parent)
@@ -16,6 +17,19 @@ IRCClientWindow::IRCClientWindow(IRCClient& client, Type type, const String& nam
     m_table_view->set_headers_visible(false);
     m_table_view->set_font(Font::default_fixed_width_font());
 
+    m_text_editor = new GTextEditor(GTextEditor::SingleLine, this);
+    m_text_editor->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
+    m_text_editor->set_preferred_size({ 0, 18 });
+    m_text_editor->on_return_pressed = [this] (GTextEditor& editor) {
+        if (m_type == Channel)
+            m_client.handle_user_input_in_channel(m_name, editor.text());
+        else if (m_type == Query)
+            m_client.handle_user_input_in_query(m_name, editor.text());
+        else if (m_type == Server)
+            m_client.handle_user_input_in_server(editor.text());
+        m_text_editor->clear();
+    };
+
     m_client.register_subwindow(*this);
 }
 

+ 2 - 0
Applications/IRCClient/IRCClientWindow.h

@@ -5,6 +5,7 @@
 class IRCClient;
 class IRCLogBuffer;
 class GTableView;
+class GTextEditor;
 
 class IRCClientWindow : public GWidget {
 public:
@@ -29,5 +30,6 @@ private:
     Type m_type;
     String m_name;
     GTableView* m_table_view { nullptr };
+    GTextEditor* m_text_editor { nullptr };
     RetainPtr<IRCLogBuffer> m_log_buffer;
 };

+ 4 - 10
Applications/IRCClient/IRCLogBufferModel.cpp

@@ -2,6 +2,7 @@
 #include "IRCLogBuffer.h"
 #include <stdio.h>
 #include <time.h>
+#include <SharedGraphics/Font.h>
 
 IRCLogBufferModel::IRCLogBufferModel(Retained<IRCLogBuffer>&& log_buffer)
     : m_log_buffer(move(log_buffer))
@@ -19,14 +20,13 @@ int IRCLogBufferModel::row_count() const
 
 int IRCLogBufferModel::column_count() const
 {
-    return 4;
+    return Column::__Count;
 }
 
 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";
     }
@@ -37,8 +37,7 @@ GTableModel::ColumnMetadata IRCLogBufferModel::column_metadata(int column) const
 {
     switch (column) {
     case Column::Timestamp: return { 60, TextAlignment::CenterLeft };
-    case Column::Prefix: return { 10, TextAlignment::CenterLeft };
-    case Column::Name: return { 70, TextAlignment::CenterRight };
+    case Column::Name: return { 70, TextAlignment::CenterRight, &Font::default_bold_font() };
     case Column::Text: return { 800, TextAlignment::CenterLeft };
     }
     ASSERT_NOT_REACHED();
@@ -52,12 +51,7 @@ GVariant IRCLogBufferModel::data(const GModelIndex& index, Role) const
         auto* tm = localtime(&entry.timestamp);
         return String::format("%02u:%02u:%02u", tm->tm_hour, tm->tm_min, tm->tm_sec);
     }
-    case Column::Prefix: {
-        if (!entry.prefix)
-            return String("");
-        return String(&entry.prefix, 1);
-    }
-    case Column::Name: return entry.sender;
+    case Column::Name: return String::format("<%c%s>", entry.prefix ? entry.prefix : ' ', entry.sender.characters());
     case Column::Text: return entry.text;
     }
     ASSERT_NOT_REACHED();

+ 1 - 1
Applications/IRCClient/IRCLogBufferModel.h

@@ -8,9 +8,9 @@ class IRCLogBufferModel final : public GTableModel {
 public:
     enum Column {
         Timestamp = 0,
-        Prefix,
         Name,
         Text,
+        __Count,
     };
 
     explicit IRCLogBufferModel(Retained<IRCLogBuffer>&&);

+ 2 - 0
LibGUI/GTableModel.h

@@ -8,6 +8,7 @@
 #include <LibGUI/GVariant.h>
 #include <SharedGraphics/TextAlignment.h>
 
+class Font;
 class GTableView;
 
 enum class GSortOrder { None, Ascending, Descending };
@@ -38,6 +39,7 @@ public:
     struct ColumnMetadata {
         int preferred_width { 0 };
         TextAlignment text_alignment { TextAlignment::CenterLeft };
+        const Font* font { nullptr };
     };
 
     enum class Role { Display, Sort, Custom };

+ 2 - 1
LibGUI/GTableView.cpp

@@ -167,6 +167,7 @@ void GTableView::paint_event(GPaintEvent& event)
         for (int column_index = 0; column_index < m_model->column_count(); ++column_index) {
             auto column_metadata = m_model->column_metadata(column_index);
             int column_width = column_metadata.preferred_width;
+            const Font& font = column_metadata.font ? *column_metadata.font : this->font();
             bool is_key_column = m_model->key_column() == column_index;
             Rect cell_rect(horizontal_padding() + x_offset, y, column_width, item_height());
             if (is_key_column) {
@@ -177,7 +178,7 @@ void GTableView::paint_event(GPaintEvent& event)
             if (data.is_bitmap())
                 painter.blit(cell_rect.location(), data.as_bitmap(), data.as_bitmap().rect());
             else
-                painter.draw_text(cell_rect, data.to_string(), column_metadata.text_alignment, text_color);
+                painter.draw_text(cell_rect, data.to_string(), font, column_metadata.text_alignment, text_color);
             x_offset += column_width + horizontal_padding() * 2;
         }
         ++painted_item_index;

+ 36 - 4
LibGUI/GTextEditor.cpp

@@ -9,13 +9,15 @@
 #include <fcntl.h>
 #include <stdio.h>
 
-GTextEditor::GTextEditor(GWidget* parent)
+GTextEditor::GTextEditor(Type type, GWidget* parent)
     : GWidget(parent)
+    , m_type(type)
 {
     set_font(GFontDatabase::the().get_by_name("Csilla Thin"));
 
     m_vertical_scrollbar = new GScrollBar(Orientation::Vertical, this);
     m_vertical_scrollbar->set_step(4);
+    m_vertical_scrollbar->set_visible(is_multi_line());
     m_vertical_scrollbar->on_change = [this] (int) {
         update();
     };
@@ -23,12 +25,14 @@ GTextEditor::GTextEditor(GWidget* parent)
     m_horizontal_scrollbar = new GScrollBar(Orientation::Horizontal, this);
     m_horizontal_scrollbar->set_step(4);
     m_horizontal_scrollbar->set_big_step(30);
+    m_horizontal_scrollbar->set_visible(is_multi_line());
     m_horizontal_scrollbar->on_change = [this] (int) {
         update();
     };
 
     m_corner_widget = new GWidget(this);
     m_corner_widget->set_fill_with_background_color(true);
+    m_corner_widget->set_visible(is_multi_line());
 
     m_lines.append(make<Line>());
 }
@@ -63,9 +67,11 @@ void GTextEditor::set_text(const String& text)
 void GTextEditor::resize_event(GResizeEvent& event)
 {
     update_scrollbar_ranges();
-    m_vertical_scrollbar->set_relative_rect(event.size().width() - m_vertical_scrollbar->preferred_size().width(), 0, m_vertical_scrollbar->preferred_size().width(), event.size().height() - m_horizontal_scrollbar->preferred_size().height());
-    m_horizontal_scrollbar->set_relative_rect(0, event.size().height() - m_horizontal_scrollbar->preferred_size().height(), event.size().width() - m_vertical_scrollbar->preferred_size().width(), m_horizontal_scrollbar->preferred_size().height());
-    m_corner_widget->set_relative_rect(m_horizontal_scrollbar->rect().right() + 1, m_vertical_scrollbar->rect().bottom() + 1, m_horizontal_scrollbar->height(), m_vertical_scrollbar->width());
+    if (is_multi_line()) {
+        m_vertical_scrollbar->set_relative_rect(event.size().width() - m_vertical_scrollbar->preferred_size().width(), 0, m_vertical_scrollbar->preferred_size().width(), event.size().height() - m_horizontal_scrollbar->preferred_size().height());
+        m_horizontal_scrollbar->set_relative_rect(0, event.size().height() - m_horizontal_scrollbar->preferred_size().height(), event.size().width() - m_vertical_scrollbar->preferred_size().width(), m_horizontal_scrollbar->preferred_size().height());
+        m_corner_widget->set_relative_rect(m_horizontal_scrollbar->rect().right() + 1, m_vertical_scrollbar->rect().bottom() + 1, m_horizontal_scrollbar->height(), m_vertical_scrollbar->width());
+    }
 }
 
 void GTextEditor::update_scrollbar_ranges()
@@ -441,6 +447,11 @@ void GTextEditor::insert_at_cursor(char ch)
     bool at_head = m_cursor.column() == 0;
     bool at_tail = m_cursor.column() == current_line().length();
     if (ch == '\n') {
+        if (is_single_line()) {
+            if (on_return_pressed)
+                on_return_pressed(*this);
+            return;
+        }
         if (at_tail || at_head) {
             m_lines.insert(m_cursor.line() + (at_tail ? 1 : 0), make<Line>());
             update_scrollbar_ranges();
@@ -689,6 +700,27 @@ bool GTextEditor::write_to_file(const String& path)
     return true;
 }
 
+String GTextEditor::text() const
+{
+    StringBuilder builder;
+    for (int i = 0; i < line_count(); ++i) {
+        auto& line = *m_lines[i];
+        builder.append(line.characters(), line.length());
+        if (i != line_count() - 1)
+            builder.append('\n');
+    }
+    return builder.to_string();
+}
+
+void GTextEditor::clear()
+{
+    m_lines.clear();
+    m_lines.append(make<Line>());
+    m_selection.clear();
+    set_cursor(0, 0);
+    update();
+}
+
 String GTextEditor::selected_text() const
 {
     if (!has_selection())

+ 13 - 1
LibGUI/GTextEditor.h

@@ -62,9 +62,14 @@ private:
 
 class GTextEditor : public GWidget {
 public:
-    explicit GTextEditor(GWidget* parent);
+    enum Type { MultiLine, SingleLine };
+    GTextEditor(Type, GWidget* parent);
     virtual ~GTextEditor() override;
 
+    Type type() const { return m_type; }
+    bool is_single_line() const { return m_type == SingleLine; }
+    bool is_multi_line() const { return m_type == MultiLine; }
+
     Function<void(GTextEditor&)> on_cursor_change;
 
     void set_text(const String&);
@@ -84,11 +89,16 @@ public:
 
     bool has_selection() const { return m_selection.is_valid(); }
     String selected_text() const;
+    String text() const;
+
+    void clear();
 
     void cut();
     void copy();
     void paste();
 
+    Function<void(GTextEditor&)> on_return_pressed;
+
 private:
     virtual void paint_event(GPaintEvent&) override;
     virtual void resize_event(GResizeEvent&) override;
@@ -141,6 +151,8 @@ private:
     void insert_at_cursor_or_replace_selection(const String&);
     void delete_selection();
 
+    Type m_type { MultiLine };
+
     GScrollBar* m_vertical_scrollbar { nullptr };
     GScrollBar* m_horizontal_scrollbar { nullptr };
     GWidget* m_corner_widget { nullptr };