diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-05-27 20:19:05 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-05-27 21:44:39 +0200 |
commit | ca4d65ba59be5ddae246a68dcd2cd525a1a220e6 (patch) | |
tree | 589e665a2a14f70df54983727a614b3cdba11576 | |
parent | 77185c1541337b551bfbc24f510bac9abeffcf42 (diff) | |
download | serenity-ca4d65ba59be5ddae246a68dcd2cd525a1a220e6.zip |
LibGUI: Add a GListView widget.
GListView displays a single column of data from a GModel.
-rw-r--r-- | LibGUI/GListView.cpp | 227 | ||||
-rw-r--r-- | LibGUI/GListView.h | 46 | ||||
-rw-r--r-- | LibGUI/Makefile | 1 |
3 files changed, 274 insertions, 0 deletions
diff --git a/LibGUI/GListView.cpp b/LibGUI/GListView.cpp new file mode 100644 index 0000000000..b3b28a1ce7 --- /dev/null +++ b/LibGUI/GListView.cpp @@ -0,0 +1,227 @@ +#include <LibGUI/GListView.h> +#include <LibGUI/GScrollBar.h> +#include <LibGUI/GPainter.h> +#include <Kernel/KeyCode.h> + +GListView::GListView(GWidget* parent) + : GAbstractView(parent) +{ + set_frame_shape(FrameShape::Container); + set_frame_shadow(FrameShadow::Sunken); + set_frame_thickness(2); +} + +GListView::~GListView() +{ +} + +void GListView::update_content_size() +{ + if (!model()) + return set_content_size({ }); + + int content_width = 0; + for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) { + auto text = model()->data(model()->index(row, m_model_column), GModel::Role::Display); + content_width = max(content_width, font().width(text.to_string())); + } + + content_width = max(content_width, widget_inner_rect().width()); + + int content_height = item_count() * item_height(); + set_content_size({ content_width, content_height }); +} + +void GListView::resize_event(GResizeEvent& event) +{ + update_content_size(); + GAbstractView::resize_event(event); +} + +void GListView::did_update_model() +{ + GAbstractView::did_update_model(); + update_content_size(); + update(); +} + +Rect GListView::content_rect(int row) const +{ + return { 0, row * item_height(), content_width(), item_height() }; +} + +Rect GListView::content_rect(const GModelIndex& index) const +{ + return content_rect(index.row()); +} + +Point GListView::adjusted_position(const Point& position) +{ + return position.translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness()); +} + +void GListView::mousedown_event(GMouseEvent& event) +{ + if (!model()) + return; + + if (event.button() != GMouseButton::Left) + return; + + auto adjusted_position = this->adjusted_position(event.position()); + for (int row = 0, row_count = model()->row_count(); row < row_count; ++row) { + if (!content_rect(row).contains(adjusted_position)) + continue; + model()->set_selected_index(model()->index(row, m_model_column)); + update(); + return; + } + model()->set_selected_index({ }); + update(); +} + +void GListView::paint_event(GPaintEvent& event) +{ + GFrame::paint_event(event); + + if (!model()) + return; + + GPainter painter(*this); + painter.add_clip_rect(frame_inner_rect()); + painter.add_clip_rect(event.rect()); + painter.translate(frame_thickness(), frame_thickness()); + painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); + + int exposed_width = max(content_size().width(), width()); + int painted_item_index = 0; + + for (int row_index = 0; row_index < model()->row_count(); ++row_index) { + bool is_selected_row = row_index == model()->selected_index().row(); + int y = painted_item_index * item_height(); + + Color background_color; + if (is_selected_row) { + background_color = is_focused() ? Color::from_rgb(0x84351a) : Color::from_rgb(0x606060); + } else { + if (alternating_row_colors() && (painted_item_index % 2)) + background_color = Color(210, 210, 210); + else + background_color = Color::White; + } + + auto column_metadata = model()->column_metadata(m_model_column); + const Font& font = column_metadata.font ? *column_metadata.font : this->font(); + Rect row_rect(0, y, content_width(), item_height()); + painter.fill_rect(row_rect, background_color); + auto index = model()->index(row_index, m_model_column); + auto data = model()->data(index); + if (data.is_bitmap()) { + painter.blit(row_rect.location(), data.as_bitmap(), data.as_bitmap().rect()); + } else if (data.is_icon()) { + if (auto bitmap = data.as_icon().bitmap_for_size(16)) + painter.blit(row_rect.location(), *bitmap, bitmap->rect()); + } else { + Color text_color; + if (is_selected_row) + text_color = Color::White; + else + text_color = model()->data(index, GModel::Role::ForegroundColor).to_color(Color::Black); + auto text_rect = row_rect; + text_rect.move_by(horizontal_padding(), 0); + text_rect.set_width(text_rect.width() - horizontal_padding() * 2); + painter.draw_text(text_rect, data.to_string(), font, column_metadata.text_alignment, text_color); + } + + ++painted_item_index; + }; + + Rect unpainted_rect(0, painted_item_index * item_height(), exposed_width, height()); + painter.fill_rect(unpainted_rect, Color::White); +} + +int GListView::item_count() const +{ + if (!model()) + return 0; + return model()->row_count(); +} + +void GListView::keydown_event(GKeyEvent& event) +{ + if (!model()) + return; + auto& model = *this->model(); + if (event.key() == KeyCode::Key_Return) { + activate(model.selected_index()); + return; + } + if (event.key() == KeyCode::Key_Up) { + GModelIndex new_index; + if (model.selected_index().is_valid()) + new_index = model.index(model.selected_index().row() - 1, model.selected_index().column()); + else + new_index = model.index(0, 0); + if (model.is_valid(new_index)) { + model.set_selected_index(new_index); + scroll_into_view(new_index, Orientation::Vertical); + update(); + } + return; + } + if (event.key() == KeyCode::Key_Down) { + GModelIndex new_index; + if (model.selected_index().is_valid()) + new_index = model.index(model.selected_index().row() + 1, model.selected_index().column()); + else + new_index = model.index(0, 0); + if (model.is_valid(new_index)) { + model.set_selected_index(new_index); + scroll_into_view(new_index, Orientation::Vertical); + update(); + } + return; + } + if (event.key() == KeyCode::Key_PageUp) { + int items_per_page = visible_content_rect().height() / item_height(); + auto new_index = model.index(max(0, model.selected_index().row() - items_per_page), model.selected_index().column()); + if (model.is_valid(new_index)) { + model.set_selected_index(new_index); + scroll_into_view(new_index, Orientation::Vertical); + update(); + } + return; + } + if (event.key() == KeyCode::Key_PageDown) { + int items_per_page = visible_content_rect().height() / item_height(); + auto new_index = model.index(min(model.row_count() - 1, model.selected_index().row() + items_per_page), model.selected_index().column()); + if (model.is_valid(new_index)) { + model.set_selected_index(new_index); + scroll_into_view(new_index, Orientation::Vertical); + update(); + } + return; + } + return GWidget::keydown_event(event); +} + +void GListView::scroll_into_view(const GModelIndex& index, Orientation orientation) +{ + auto rect = content_rect(index.row()); + GScrollableWidget::scroll_into_view(rect, orientation); +} + +void GListView::doubleclick_event(GMouseEvent& event) +{ + if (!model()) + return; + auto& model = *this->model(); + if (event.button() == GMouseButton::Left) { + if (model.selected_index().is_valid()) { + if (is_editable()) + begin_editing(model.selected_index()); + else + activate(model.selected_index()); + } + } +} diff --git a/LibGUI/GListView.h b/LibGUI/GListView.h new file mode 100644 index 0000000000..8cf1c4cb0f --- /dev/null +++ b/LibGUI/GListView.h @@ -0,0 +1,46 @@ +#pragma once + +#include <LibGUI/GModel.h> +#include <LibGUI/GAbstractView.h> +#include <AK/Function.h> +#include <AK/HashMap.h> + +class GScrollBar; +class Painter; + +class GListView : public GAbstractView { +public: + explicit GListView(GWidget* parent); + virtual ~GListView() override; + + int item_height() const { return 16; } + + bool alternating_row_colors() const { return m_alternating_row_colors; } + void set_alternating_row_colors(bool b) { m_alternating_row_colors = b; } + + int horizontal_padding() const { return m_horizontal_padding; } + + void scroll_into_view(const GModelIndex&, Orientation); + + Point adjusted_position(const Point&); + + virtual Rect content_rect(const GModelIndex&) const override; + + virtual const char* class_name() const override { return "GListView"; } + +private: + virtual void did_update_model() override; + virtual void paint_event(GPaintEvent&) override; + virtual void mousedown_event(GMouseEvent&) override; + virtual void doubleclick_event(GMouseEvent&) override; + virtual void keydown_event(GKeyEvent&) override; + virtual void resize_event(GResizeEvent&) override; + + Rect content_rect(int row) const; + int item_count() const; + void update_content_size(); + + int m_horizontal_padding { 2 }; + int m_model_column { 0 }; + bool m_alternating_row_colors { true }; +}; diff --git a/LibGUI/Makefile b/LibGUI/Makefile index ad9dc3cb28..fc4ce53df1 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -59,6 +59,7 @@ LIBGUI_OBJS = \ GTabWidget.o \ GRadioButton.o \ GAbstractButton.o \ + GListView.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS) |