mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 17:57:35 +00:00
Terminal: Add basic mouse selection with copy and paste.
Left mouse button selects (and copies the selection on mouse up). The right mouse button then pastes whatever's on the clipboard. I always liked this behavior in PuTTY, so now we have it here as well :^)
This commit is contained in:
parent
2cebf78fad
commit
08c04f0a41
2 changed files with 156 additions and 4 deletions
|
@ -5,6 +5,7 @@
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <Kernel/KeyCode.h>
|
#include <Kernel/KeyCode.h>
|
||||||
#include <LibGUI/GApplication.h>
|
#include <LibGUI/GApplication.h>
|
||||||
|
#include <LibGUI/GClipboard.h>
|
||||||
#include <LibGUI/GPainter.h>
|
#include <LibGUI/GPainter.h>
|
||||||
#include <LibGUI/GWindow.h>
|
#include <LibGUI/GWindow.h>
|
||||||
#include <SharedGraphics/Font.h>
|
#include <SharedGraphics/Font.h>
|
||||||
|
@ -1068,16 +1069,17 @@ void Terminal::paint_event(GPaintEvent& event)
|
||||||
painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color).with_alpha(255 * m_opacity));
|
painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color).with_alpha(255 * m_opacity));
|
||||||
for (word column = 0; column < m_columns; ++column) {
|
for (word column = 0; column < m_columns; ++column) {
|
||||||
char ch = line.characters[column];
|
char ch = line.characters[column];
|
||||||
bool should_reverse_fill_for_cursor = m_cursor_blink_state && m_in_active_window && row == m_cursor_row && column == m_cursor_column;
|
bool should_reverse_fill_for_cursor_or_selection = (m_cursor_blink_state && m_in_active_window && row == m_cursor_row && column == m_cursor_column)
|
||||||
|
|| selection_contains({ row, column });
|
||||||
auto& attribute = line.attributes[column];
|
auto& attribute = line.attributes[column];
|
||||||
auto character_rect = glyph_rect(row, column);
|
auto character_rect = glyph_rect(row, column);
|
||||||
if (!has_only_one_background_color || should_reverse_fill_for_cursor) {
|
if (!has_only_one_background_color || should_reverse_fill_for_cursor_or_selection) {
|
||||||
auto cell_rect = character_rect.inflated(0, m_line_spacing);
|
auto cell_rect = character_rect.inflated(0, m_line_spacing);
|
||||||
painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor ? attribute.foreground_color : attribute.background_color).with_alpha(255 * m_opacity));
|
painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.foreground_color : attribute.background_color).with_alpha(255 * m_opacity));
|
||||||
}
|
}
|
||||||
if (ch == ' ')
|
if (ch == ' ')
|
||||||
continue;
|
continue;
|
||||||
painter.draw_glyph(character_rect.location(), ch, lookup_color(should_reverse_fill_for_cursor ? attribute.background_color : attribute.foreground_color));
|
painter.draw_glyph(character_rect.location(), ch, lookup_color(should_reverse_fill_for_cursor_or_selection ? attribute.background_color : attribute.foreground_color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1150,3 +1152,92 @@ void Terminal::set_opacity(float opacity)
|
||||||
m_opacity = opacity;
|
m_opacity = opacity;
|
||||||
force_repaint();
|
force_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BufferPosition Terminal::normalized_selection_start() const
|
||||||
|
{
|
||||||
|
if (m_selection_start < m_selection_end)
|
||||||
|
return m_selection_start;
|
||||||
|
return m_selection_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferPosition Terminal::normalized_selection_end() const
|
||||||
|
{
|
||||||
|
if (m_selection_start < m_selection_end)
|
||||||
|
return m_selection_end;
|
||||||
|
return m_selection_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Terminal::has_selection() const
|
||||||
|
{
|
||||||
|
return m_selection_start.is_valid() && m_selection_end.is_valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Terminal::selection_contains(const BufferPosition& position) const
|
||||||
|
{
|
||||||
|
if (!has_selection())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return position >= normalized_selection_start() && position <= normalized_selection_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferPosition Terminal::buffer_position_at(const Point& position) const
|
||||||
|
{
|
||||||
|
auto adjusted_position = position.translated(-(frame_thickness() + m_inset), -(frame_thickness() + m_inset));
|
||||||
|
int row = adjusted_position.y() / m_line_height;
|
||||||
|
int column = adjusted_position.x() / font().glyph_width('x');
|
||||||
|
return { row, column };
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::mousedown_event(GMouseEvent& event)
|
||||||
|
{
|
||||||
|
if (event.button() == GMouseButton::Left) {
|
||||||
|
m_selection_start = buffer_position_at(event.position());
|
||||||
|
m_selection_end = {};
|
||||||
|
update();
|
||||||
|
} else if (event.button() == GMouseButton::Right) {
|
||||||
|
auto text = GClipboard::the().data();
|
||||||
|
if (text.is_empty())
|
||||||
|
return;
|
||||||
|
int nwritten = write(m_ptm_fd, text.characters(), text.length());
|
||||||
|
if (nwritten < 0) {
|
||||||
|
perror("write");
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::mousemove_event(GMouseEvent& event)
|
||||||
|
{
|
||||||
|
if (!(event.buttons() & GMouseButton::Left))
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto old_selection_end = m_selection_end;
|
||||||
|
m_selection_end = buffer_position_at(event.position());
|
||||||
|
if (old_selection_end != m_selection_end)
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::mouseup_event(GMouseEvent& event)
|
||||||
|
{
|
||||||
|
if (event.button() != GMouseButton::Left)
|
||||||
|
return;
|
||||||
|
if (!has_selection())
|
||||||
|
return;
|
||||||
|
GClipboard::the().set_data(selected_text());
|
||||||
|
}
|
||||||
|
|
||||||
|
String Terminal::selected_text() const
|
||||||
|
{
|
||||||
|
StringBuilder builder;
|
||||||
|
auto start = normalized_selection_start();
|
||||||
|
auto end = normalized_selection_end();
|
||||||
|
|
||||||
|
for (int row = start.row(); row <= end.row(); ++row) {
|
||||||
|
int first_column = row == start.row() ? start.column() : 0;
|
||||||
|
int last_column = row == end.row() ? end.column() : m_columns - 1;
|
||||||
|
for (int column = first_column; column <= last_column; ++column)
|
||||||
|
builder.append(line(row).characters[column]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.to_string();
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,49 @@
|
||||||
|
|
||||||
class Font;
|
class Font;
|
||||||
|
|
||||||
|
class BufferPosition {
|
||||||
|
public:
|
||||||
|
BufferPosition() {}
|
||||||
|
BufferPosition(int row, int column)
|
||||||
|
: m_row(row)
|
||||||
|
, m_column(column)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_valid() const { return m_row >= 0 && m_column >= 0; }
|
||||||
|
int row() const { return m_row; }
|
||||||
|
int column() const { return m_column; }
|
||||||
|
|
||||||
|
bool operator<(const BufferPosition& other) const
|
||||||
|
{
|
||||||
|
return m_row < other.m_row || (m_row == other.m_row && m_column < other.m_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<=(const BufferPosition& other) const
|
||||||
|
{
|
||||||
|
return *this < other || *this == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator>=(const BufferPosition& other) const
|
||||||
|
{
|
||||||
|
return !(*this < other);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const BufferPosition& other) const
|
||||||
|
{
|
||||||
|
return m_row == other.m_row && m_column == other.m_column;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const BufferPosition& other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_row { -1 };
|
||||||
|
int m_column { -1 };
|
||||||
|
};
|
||||||
|
|
||||||
class Terminal final : public GFrame {
|
class Terminal final : public GFrame {
|
||||||
public:
|
public:
|
||||||
explicit Terminal(int ptm_fd, RefPtr<CConfigFile> config);
|
explicit Terminal(int ptm_fd, RefPtr<CConfigFile> config);
|
||||||
|
@ -32,6 +75,13 @@ public:
|
||||||
|
|
||||||
RefPtr<CConfigFile> config() const { return m_config; }
|
RefPtr<CConfigFile> config() const { return m_config; }
|
||||||
|
|
||||||
|
bool has_selection() const;
|
||||||
|
bool selection_contains(const BufferPosition&) const;
|
||||||
|
String selected_text() const;
|
||||||
|
BufferPosition buffer_position_at(const Point&) const;
|
||||||
|
BufferPosition normalized_selection_start() const;
|
||||||
|
BufferPosition normalized_selection_end() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
typedef Vector<unsigned, 4> ParamVector;
|
typedef Vector<unsigned, 4> ParamVector;
|
||||||
|
|
||||||
|
@ -39,6 +89,9 @@ private:
|
||||||
virtual void paint_event(GPaintEvent&) override;
|
virtual void paint_event(GPaintEvent&) override;
|
||||||
virtual void resize_event(GResizeEvent&) override;
|
virtual void resize_event(GResizeEvent&) override;
|
||||||
virtual void keydown_event(GKeyEvent&) override;
|
virtual void keydown_event(GKeyEvent&) override;
|
||||||
|
virtual void mousedown_event(GMouseEvent&) override;
|
||||||
|
virtual void mousemove_event(GMouseEvent&) override;
|
||||||
|
virtual void mouseup_event(GMouseEvent&) override;
|
||||||
virtual const char* class_name() const override { return "Terminal"; }
|
virtual const char* class_name() const override { return "Terminal"; }
|
||||||
|
|
||||||
void scroll_up();
|
void scroll_up();
|
||||||
|
@ -139,9 +192,17 @@ private:
|
||||||
ASSERT(index < m_rows);
|
ASSERT(index < m_rows);
|
||||||
return *m_lines[index];
|
return *m_lines[index];
|
||||||
}
|
}
|
||||||
|
const Line& line(size_t index) const
|
||||||
|
{
|
||||||
|
ASSERT(index < m_rows);
|
||||||
|
return *m_lines[index];
|
||||||
|
}
|
||||||
|
|
||||||
Vector<OwnPtr<Line>> m_lines;
|
Vector<OwnPtr<Line>> m_lines;
|
||||||
|
|
||||||
|
BufferPosition m_selection_start;
|
||||||
|
BufferPosition m_selection_end;
|
||||||
|
|
||||||
int m_scroll_region_top { 0 };
|
int m_scroll_region_top { 0 };
|
||||||
int m_scroll_region_bottom { 0 };
|
int m_scroll_region_bottom { 0 };
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue