mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 15:40:19 +00:00
LibGUI: Move table view headers into their own widget
This patch introduces the HeaderView class, which is a widget that implements the column headers of TableView and TreeView. This greatly simplifies event management in the view implementations and also makes it much easier to eventually implement row headers.
This commit is contained in:
parent
eca6ff353e
commit
44e371635e
Notes:
sideshowbarker
2024-07-19 03:09:24 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/44e371635ea
16 changed files with 555 additions and 351 deletions
|
@ -330,7 +330,7 @@ void IRCAppWindow::setup_widgets()
|
|||
auto& horizontal_container = outer_container.add<GUI::HorizontalSplitter>();
|
||||
|
||||
m_window_list = horizontal_container.add<GUI::TableView>();
|
||||
m_window_list->set_headers_visible(false);
|
||||
m_window_list->set_column_headers_visible(false);
|
||||
m_window_list->set_alternating_row_colors(false);
|
||||
m_window_list->set_model(m_client->client_window_list_model());
|
||||
m_window_list->set_activates_on_selection(true);
|
||||
|
|
|
@ -56,7 +56,7 @@ IRCWindow::IRCWindow(IRCClient& client, void* owner, Type type, const String& na
|
|||
|
||||
if (m_type == Channel) {
|
||||
auto& member_view = container.add<GUI::TableView>();
|
||||
member_view.set_headers_visible(false);
|
||||
member_view.set_column_headers_visible(false);
|
||||
member_view.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
|
||||
member_view.set_preferred_size(100, 0);
|
||||
member_view.set_alternating_row_colors(false);
|
||||
|
|
|
@ -123,7 +123,6 @@ void SpreadsheetWidget::setup_tabs()
|
|||
m_current_cell_label->set_enabled(false);
|
||||
m_current_cell_label->set_text("");
|
||||
};
|
||||
m_selected_view->set_focus(true);
|
||||
};
|
||||
|
||||
if (first_tab_widget)
|
||||
|
|
|
@ -180,7 +180,7 @@ int main(int argc, char** argv)
|
|||
process_table_container.layout()->set_spacing(0);
|
||||
|
||||
auto& process_table_view = process_table_container.add<GUI::TableView>();
|
||||
process_table_view.set_headers_visible(true);
|
||||
process_table_view.set_column_headers_visible(true);
|
||||
process_table_view.set_model(GUI::SortingProxyModel::create(ProcessModel::create()));
|
||||
process_table_view.set_key_column_and_sort_order(ProcessModel::Column::CPU, GUI::SortOrder::Descending);
|
||||
process_table_view.model()->update();
|
||||
|
|
|
@ -130,7 +130,7 @@ Locator::Locator()
|
|||
m_popup_window->set_rect(0, 0, 500, 200);
|
||||
|
||||
m_suggestion_view = m_popup_window->set_main_widget<GUI::TableView>();
|
||||
m_suggestion_view->set_headers_visible(false);
|
||||
m_suggestion_view->set_column_headers_visible(false);
|
||||
|
||||
m_suggestion_view->on_activation = [this](auto& index) {
|
||||
open_suggestion(index);
|
||||
|
|
|
@ -92,7 +92,7 @@ int main(int argc, char** argv)
|
|||
auto& bottom_splitter = main_widget.add<GUI::VerticalSplitter>();
|
||||
|
||||
auto& tree_view = bottom_splitter.add<GUI::TreeView>();
|
||||
tree_view.set_headers_visible(true);
|
||||
tree_view.set_column_headers_visible(true);
|
||||
tree_view.set_model(profile->model());
|
||||
|
||||
auto& disassembly_view = bottom_splitter.add<GUI::TableView>();
|
||||
|
|
|
@ -87,7 +87,7 @@ VBPropertiesWindow::VBPropertiesWindow()
|
|||
widget.layout()->set_margins({ 2, 2, 2, 2 });
|
||||
|
||||
m_table_view = widget.add<GUI::TableView>();
|
||||
m_table_view->set_headers_visible(false);
|
||||
m_table_view->set_column_headers_visible(false);
|
||||
m_table_view->set_editable(true);
|
||||
|
||||
m_table_view->aid_create_editing_delegate = [this](auto& index) -> OwnPtr<GUI::ModelEditingDelegate> {
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <AK/Vector.h>
|
||||
#include <LibGUI/AbstractTableView.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/HeaderView.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
|
@ -37,10 +38,10 @@
|
|||
|
||||
namespace GUI {
|
||||
|
||||
static const int minimum_column_width = 2;
|
||||
|
||||
AbstractTableView::AbstractTableView()
|
||||
{
|
||||
m_column_header = add<HeaderView>(*this, Gfx::Orientation::Horizontal);
|
||||
m_column_header->move_to_back();
|
||||
set_should_hide_unnecessary_scrollbars(true);
|
||||
}
|
||||
|
||||
|
@ -67,9 +68,9 @@ void AbstractTableView::update_column_sizes()
|
|||
int row_count = model.row_count();
|
||||
|
||||
for (int column = 0; column < column_count; ++column) {
|
||||
if (is_column_hidden(column))
|
||||
if (!column_header().is_section_visible(column))
|
||||
continue;
|
||||
int header_width = header_font().width(model.column_name(column));
|
||||
int header_width = m_column_header->font().width(model.column_name(column));
|
||||
if (column == m_key_column && model.is_column_sortable(column))
|
||||
header_width += font().width(" \xE2\xAC\x86"); // UPWARDS BLACK ARROW
|
||||
int column_width = header_width;
|
||||
|
@ -85,9 +86,7 @@ void AbstractTableView::update_column_sizes()
|
|||
}
|
||||
column_width = max(column_width, cell_width);
|
||||
}
|
||||
auto& column_data = this->column_data(column);
|
||||
column_data.width = max(column_data.width, column_width);
|
||||
column_data.has_initialized_width = true;
|
||||
column_header().set_section_size(column, max(m_column_header->section_size(column), column_width));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,264 +97,54 @@ void AbstractTableView::update_content_size()
|
|||
|
||||
int content_width = 0;
|
||||
int column_count = model()->column_count();
|
||||
|
||||
for (int i = 0; i < column_count; ++i) {
|
||||
if (!is_column_hidden(i))
|
||||
if (column_header().is_section_visible(i))
|
||||
content_width += column_width(i) + horizontal_padding() * 2;
|
||||
}
|
||||
int content_height = item_count() * item_height();
|
||||
|
||||
set_content_size({ content_width, content_height });
|
||||
set_size_occupied_by_fixed_elements({ 0, header_height() });
|
||||
column_header().set_width(content_width);
|
||||
set_size_occupied_by_fixed_elements({ 0, m_column_header->height() });
|
||||
}
|
||||
|
||||
Gfx::IntRect AbstractTableView::header_rect(int column_index) const
|
||||
TableCellPaintingDelegate* AbstractTableView::column_painting_delegate(int column) const
|
||||
{
|
||||
if (!model())
|
||||
return {};
|
||||
if (is_column_hidden(column_index))
|
||||
return {};
|
||||
int x_offset = 0;
|
||||
for (int i = 0; i < column_index; ++i) {
|
||||
if (is_column_hidden(i))
|
||||
continue;
|
||||
x_offset += column_width(i) + horizontal_padding() * 2;
|
||||
}
|
||||
return { x_offset, 0, column_width(column_index) + horizontal_padding() * 2, header_height() };
|
||||
// FIXME: This should return a const pointer I think..
|
||||
return const_cast<TableCellPaintingDelegate*>(m_column_painting_delegate.get(column).value_or(nullptr));
|
||||
}
|
||||
|
||||
void AbstractTableView::set_hovered_header_index(int index)
|
||||
void AbstractTableView::set_cell_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate> delegate)
|
||||
{
|
||||
if (m_hovered_column_header_index == index)
|
||||
return;
|
||||
m_hovered_column_header_index = index;
|
||||
update_headers();
|
||||
}
|
||||
|
||||
void AbstractTableView::paint_headers(Painter& painter)
|
||||
{
|
||||
if (!headers_visible())
|
||||
return;
|
||||
int exposed_width = max(content_size().width(), width());
|
||||
painter.fill_rect({ 0, 0, exposed_width, header_height() }, palette().button());
|
||||
painter.draw_line({ 0, 0 }, { exposed_width - 1, 0 }, palette().threed_highlight());
|
||||
painter.draw_line({ 0, header_height() - 1 }, { exposed_width - 1, header_height() - 1 }, palette().threed_shadow1());
|
||||
int x_offset = 0;
|
||||
int column_count = model()->column_count();
|
||||
for (int column_index = 0; column_index < column_count; ++column_index) {
|
||||
if (is_column_hidden(column_index))
|
||||
continue;
|
||||
int column_width = this->column_width(column_index);
|
||||
bool is_key_column = m_key_column == column_index;
|
||||
Gfx::IntRect cell_rect(x_offset, 0, column_width + horizontal_padding() * 2, header_height());
|
||||
bool pressed = column_index == m_pressed_column_header_index && m_pressed_column_header_is_pressed;
|
||||
bool hovered = column_index == m_hovered_column_header_index && model()->is_column_sortable(column_index);
|
||||
Gfx::StylePainter::paint_button(painter, cell_rect, palette(), Gfx::ButtonStyle::Normal, pressed, hovered);
|
||||
String text;
|
||||
if (is_key_column) {
|
||||
StringBuilder builder;
|
||||
builder.append(model()->column_name(column_index));
|
||||
if (m_sort_order == SortOrder::Ascending)
|
||||
builder.append(" \xE2\xAC\x86"); // UPWARDS BLACK ARROW
|
||||
else if (m_sort_order == SortOrder::Descending)
|
||||
builder.append(" \xE2\xAC\x87"); // DOWNWARDS BLACK ARROW
|
||||
text = builder.to_string();
|
||||
} else {
|
||||
text = model()->column_name(column_index);
|
||||
}
|
||||
auto text_rect = cell_rect.shrunken(horizontal_padding() * 2, 0);
|
||||
if (pressed)
|
||||
text_rect.move_by(1, 1);
|
||||
painter.draw_text(text_rect, text, header_font(), column_header_alignment(column_index), palette().button_text());
|
||||
x_offset += column_width + horizontal_padding() * 2;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractTableView::is_column_hidden(int column) const
|
||||
{
|
||||
return !column_data(column).visibility;
|
||||
}
|
||||
|
||||
void AbstractTableView::set_column_hidden(int column, bool hidden)
|
||||
{
|
||||
auto& column_data = this->column_data(column);
|
||||
if (column_data.visibility == !hidden)
|
||||
return;
|
||||
column_data.visibility = !hidden;
|
||||
if (column_data.visibility_action) {
|
||||
column_data.visibility_action->set_checked(!hidden);
|
||||
}
|
||||
update_content_size();
|
||||
update();
|
||||
}
|
||||
|
||||
Menu& AbstractTableView::ensure_header_context_menu()
|
||||
{
|
||||
// FIXME: This menu needs to be rebuilt if the model is swapped out,
|
||||
// or if the column count/names change.
|
||||
if (!m_header_context_menu) {
|
||||
ASSERT(model());
|
||||
m_header_context_menu = Menu::construct();
|
||||
|
||||
for (int column = 0; column < model()->column_count(); ++column) {
|
||||
auto& column_data = this->column_data(column);
|
||||
auto name = model()->column_name(column);
|
||||
column_data.visibility_action = Action::create_checkable(name, [this, column](auto& action) {
|
||||
set_column_hidden(column, !action.is_checked());
|
||||
});
|
||||
column_data.visibility_action->set_checked(column_data.visibility);
|
||||
|
||||
m_header_context_menu->add_action(*column_data.visibility_action);
|
||||
}
|
||||
}
|
||||
return *m_header_context_menu;
|
||||
}
|
||||
|
||||
const Gfx::Font& AbstractTableView::header_font()
|
||||
{
|
||||
return Gfx::Font::default_bold_font();
|
||||
}
|
||||
|
||||
void AbstractTableView::set_cell_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate>&& delegate)
|
||||
{
|
||||
column_data(column).cell_painting_delegate = move(delegate);
|
||||
}
|
||||
|
||||
void AbstractTableView::update_headers()
|
||||
{
|
||||
Gfx::IntRect rect { 0, 0, frame_inner_rect().width(), header_height() };
|
||||
rect.move_by(frame_thickness(), frame_thickness());
|
||||
update(rect);
|
||||
}
|
||||
|
||||
AbstractTableView::ColumnData& AbstractTableView::column_data(int column) const
|
||||
{
|
||||
if (static_cast<size_t>(column) >= m_column_data.size())
|
||||
m_column_data.resize(column + 1);
|
||||
return m_column_data.at(column);
|
||||
}
|
||||
|
||||
Gfx::IntRect AbstractTableView::column_resize_grabbable_rect(int column) const
|
||||
{
|
||||
if (!model())
|
||||
return {};
|
||||
auto header_rect = this->header_rect(column);
|
||||
return { header_rect.right() - 1, header_rect.top(), 4, header_rect.height() };
|
||||
if (!delegate)
|
||||
m_column_painting_delegate.remove(column);
|
||||
else
|
||||
m_column_painting_delegate.set(column, move(delegate));
|
||||
}
|
||||
|
||||
int AbstractTableView::column_width(int column_index) const
|
||||
{
|
||||
if (!model())
|
||||
return 0;
|
||||
return column_data(column_index).width;
|
||||
return m_column_header->section_size(column_index);
|
||||
}
|
||||
|
||||
void AbstractTableView::set_column_width(int column, int width)
|
||||
{
|
||||
column_data(column).width = width;
|
||||
column_header().set_section_size(column, width);
|
||||
}
|
||||
|
||||
Gfx::TextAlignment AbstractTableView::column_header_alignment(int column_index) const
|
||||
{
|
||||
if (!model())
|
||||
return Gfx::TextAlignment::CenterLeft;
|
||||
return column_data(column_index).header_alignment;
|
||||
return m_column_header->section_alignment(column_index);
|
||||
}
|
||||
|
||||
void AbstractTableView::set_column_header_alignment(int column, Gfx::TextAlignment alignment)
|
||||
{
|
||||
column_data(column).header_alignment = alignment;
|
||||
}
|
||||
|
||||
void AbstractTableView::mousemove_event(MouseEvent& event)
|
||||
{
|
||||
if (!model())
|
||||
return AbstractView::mousemove_event(event);
|
||||
|
||||
auto adjusted_position = this->adjusted_position(event.position());
|
||||
Gfx::IntPoint horizontally_adjusted_position(adjusted_position.x(), event.position().y());
|
||||
|
||||
if (m_in_column_resize) {
|
||||
auto delta = adjusted_position - m_column_resize_origin;
|
||||
int new_width = m_column_resize_original_width + delta.x();
|
||||
if (new_width <= minimum_column_width)
|
||||
new_width = minimum_column_width;
|
||||
ASSERT(m_resizing_column >= 0 && m_resizing_column < model()->column_count());
|
||||
auto& column_data = this->column_data(m_resizing_column);
|
||||
if (column_data.width != new_width) {
|
||||
column_data.width = new_width;
|
||||
update_content_size();
|
||||
update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pressed_column_header_index != -1) {
|
||||
auto header_rect = this->header_rect(m_pressed_column_header_index);
|
||||
if (header_rect.contains(horizontally_adjusted_position)) {
|
||||
set_hovered_header_index(m_pressed_column_header_index);
|
||||
if (!m_pressed_column_header_is_pressed)
|
||||
update_headers();
|
||||
m_pressed_column_header_is_pressed = true;
|
||||
} else {
|
||||
set_hovered_header_index(-1);
|
||||
if (m_pressed_column_header_is_pressed)
|
||||
update_headers();
|
||||
m_pressed_column_header_is_pressed = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.buttons() == 0) {
|
||||
int column_count = model()->column_count();
|
||||
bool found_hovered_header = false;
|
||||
for (int i = 0; i < column_count; ++i) {
|
||||
if (column_resize_grabbable_rect(i).contains(horizontally_adjusted_position)) {
|
||||
window()->set_override_cursor(StandardCursor::ResizeColumn);
|
||||
set_hovered_header_index(-1);
|
||||
return;
|
||||
}
|
||||
if (header_rect(i).contains(horizontally_adjusted_position)) {
|
||||
set_hovered_header_index(i);
|
||||
found_hovered_header = true;
|
||||
}
|
||||
}
|
||||
if (!found_hovered_header)
|
||||
set_hovered_header_index(-1);
|
||||
}
|
||||
window()->set_override_cursor(StandardCursor::None);
|
||||
|
||||
AbstractView::mousemove_event(event);
|
||||
}
|
||||
|
||||
void AbstractTableView::mouseup_event(MouseEvent& event)
|
||||
{
|
||||
auto adjusted_position = this->adjusted_position(event.position());
|
||||
Gfx::IntPoint horizontally_adjusted_position(adjusted_position.x(), event.position().y());
|
||||
if (event.button() == MouseButton::Left) {
|
||||
if (m_in_column_resize) {
|
||||
if (!column_resize_grabbable_rect(m_resizing_column).contains(horizontally_adjusted_position))
|
||||
window()->set_override_cursor(StandardCursor::None);
|
||||
m_in_column_resize = false;
|
||||
return;
|
||||
}
|
||||
if (m_pressed_column_header_index != -1) {
|
||||
auto header_rect = this->header_rect(m_pressed_column_header_index);
|
||||
if (header_rect.contains(horizontally_adjusted_position)) {
|
||||
auto new_sort_order = SortOrder::Ascending;
|
||||
if (m_key_column == m_pressed_column_header_index)
|
||||
new_sort_order = m_sort_order == SortOrder::Ascending
|
||||
? SortOrder::Descending
|
||||
: SortOrder::Ascending;
|
||||
set_key_column_and_sort_order(m_pressed_column_header_index, new_sort_order);
|
||||
}
|
||||
m_pressed_column_header_index = -1;
|
||||
m_pressed_column_header_is_pressed = false;
|
||||
update_headers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AbstractView::mouseup_event(event);
|
||||
column_header().set_section_alignment(column, alignment);
|
||||
}
|
||||
|
||||
void AbstractTableView::mousedown_event(MouseEvent& event)
|
||||
|
@ -369,27 +158,6 @@ void AbstractTableView::mousedown_event(MouseEvent& event)
|
|||
auto adjusted_position = this->adjusted_position(event.position());
|
||||
Gfx::IntPoint horizontally_adjusted_position(adjusted_position.x(), event.position().y());
|
||||
|
||||
if (event.y() < header_height()) {
|
||||
int column_count = model()->column_count();
|
||||
for (int i = 0; i < column_count; ++i) {
|
||||
if (column_resize_grabbable_rect(i).contains(horizontally_adjusted_position)) {
|
||||
m_resizing_column = i;
|
||||
m_in_column_resize = true;
|
||||
m_column_resize_original_width = column_width(i);
|
||||
m_column_resize_origin = adjusted_position;
|
||||
return;
|
||||
}
|
||||
auto header_rect = this->header_rect(i);
|
||||
if (header_rect.contains(horizontally_adjusted_position) && model()->is_column_sortable(i)) {
|
||||
m_pressed_column_header_index = i;
|
||||
m_pressed_column_header_is_pressed = true;
|
||||
update_headers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_toggle;
|
||||
auto index = index_at_event_position(event.position(), is_toggle);
|
||||
|
||||
|
@ -456,13 +224,13 @@ void AbstractTableView::move_selection(int vertical_steps, int horizontal_steps)
|
|||
|
||||
void AbstractTableView::scroll_into_view(const ModelIndex& index, Orientation orientation)
|
||||
{
|
||||
auto rect = row_rect(index.row()).translated(0, -header_height());
|
||||
auto rect = row_rect(index.row()).translated(0, -m_column_header->height());
|
||||
ScrollableWidget::scroll_into_view(rect, orientation);
|
||||
}
|
||||
|
||||
void AbstractTableView::scroll_into_view(const ModelIndex& index, bool scroll_horizontally, bool scroll_vertically)
|
||||
{
|
||||
auto rect = row_rect(index.row()).translated(0, -header_height());
|
||||
auto rect = row_rect(index.row()).translated(0, -m_column_header->height());
|
||||
ScrollableWidget::scroll_into_view(rect, scroll_horizontally, scroll_vertically);
|
||||
}
|
||||
|
||||
|
@ -471,11 +239,8 @@ void AbstractTableView::doubleclick_event(MouseEvent& event)
|
|||
if (!model())
|
||||
return;
|
||||
if (event.button() == MouseButton::Left) {
|
||||
if (event.y() < header_height())
|
||||
return;
|
||||
if (!selection().is_empty()) {
|
||||
if (!selection().is_empty())
|
||||
activate_or_edit_selected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,10 +248,6 @@ void AbstractTableView::context_menu_event(ContextMenuEvent& event)
|
|||
{
|
||||
if (!model())
|
||||
return;
|
||||
if (event.position().y() < header_height()) {
|
||||
ensure_header_context_menu().popup(event.screen_position());
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_toggle;
|
||||
auto index = index_at_event_position(event.position(), is_toggle);
|
||||
|
@ -500,13 +261,6 @@ void AbstractTableView::context_menu_event(ContextMenuEvent& event)
|
|||
on_context_menu_request(index, event);
|
||||
}
|
||||
|
||||
void AbstractTableView::leave_event(Core::Event& event)
|
||||
{
|
||||
AbstractView::leave_event(event);
|
||||
window()->set_override_cursor(StandardCursor::None);
|
||||
set_hovered_header_index(-1);
|
||||
}
|
||||
|
||||
Gfx::IntRect AbstractTableView::content_rect(int row, int column) const
|
||||
{
|
||||
auto row_rect = this->row_rect(row);
|
||||
|
@ -524,7 +278,7 @@ Gfx::IntRect AbstractTableView::content_rect(const ModelIndex& index) const
|
|||
|
||||
Gfx::IntRect AbstractTableView::row_rect(int item_index) const
|
||||
{
|
||||
return { 0, header_height() + (item_index * item_height()), max(content_size().width(), width()), item_height() };
|
||||
return { 0, m_column_header->height() + (item_index * item_height()), max(content_size().width(), width()), item_height() };
|
||||
}
|
||||
|
||||
Gfx::IntPoint AbstractTableView::adjusted_position(const Gfx::IntPoint& position) const
|
||||
|
@ -540,4 +294,40 @@ void AbstractTableView::did_update_model(unsigned flags)
|
|||
update();
|
||||
}
|
||||
|
||||
void AbstractTableView::resize_event(ResizeEvent& event)
|
||||
{
|
||||
AbstractView::resize_event(event);
|
||||
|
||||
if (column_header().is_visible())
|
||||
column_header().set_relative_rect(frame_thickness(), frame_thickness(), content_width(), column_header().preferred_size().height());
|
||||
}
|
||||
|
||||
void AbstractTableView::header_did_change_section_size(Badge<HeaderView>, Gfx::Orientation, int, int)
|
||||
{
|
||||
update_content_size();
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractTableView::header_did_change_section_visibility(Badge<HeaderView>, Gfx::Orientation, int, bool)
|
||||
{
|
||||
update_content_size();
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractTableView::set_column_hidden(int column, bool hidden)
|
||||
{
|
||||
column_header().set_section_visible(column, !hidden);
|
||||
}
|
||||
|
||||
void AbstractTableView::set_column_headers_visible(bool visible)
|
||||
{
|
||||
column_header().set_visible(visible);
|
||||
}
|
||||
|
||||
void AbstractTableView::did_scroll()
|
||||
{
|
||||
AbstractView::did_scroll();
|
||||
column_header().set_x(frame_thickness() + -horizontal_scrollbar().value());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,12 +46,9 @@ public:
|
|||
bool highlight_selected_rows() const { return m_highlight_selected_rows; }
|
||||
void set_highlight_selected_rows(bool b) { m_highlight_selected_rows = b; }
|
||||
|
||||
int header_height() const { return m_headers_visible ? 16 : 0; }
|
||||
bool column_headers_visible() const;
|
||||
void set_column_headers_visible(bool);
|
||||
|
||||
bool headers_visible() const { return m_headers_visible; }
|
||||
void set_headers_visible(bool headers_visible) { m_headers_visible = headers_visible; }
|
||||
|
||||
bool is_column_hidden(int) const;
|
||||
void set_column_hidden(int, bool);
|
||||
|
||||
int column_width(int column) const;
|
||||
|
@ -60,7 +57,7 @@ public:
|
|||
Gfx::TextAlignment column_header_alignment(int column) const;
|
||||
void set_column_header_alignment(int column, Gfx::TextAlignment);
|
||||
|
||||
void set_cell_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate>&&);
|
||||
void set_cell_painting_delegate(int column, OwnPtr<TableCellPaintingDelegate>);
|
||||
|
||||
int horizontal_padding() const { return m_horizontal_padding; }
|
||||
|
||||
|
@ -80,59 +77,40 @@ public:
|
|||
|
||||
void move_selection(int vertical_steps, int horizontal_steps);
|
||||
|
||||
void header_did_change_section_visibility(Badge<HeaderView>, Gfx::Orientation, int section, bool visible);
|
||||
void header_did_change_section_size(Badge<HeaderView>, Gfx::Orientation, int section, int size);
|
||||
|
||||
virtual void did_scroll() override;
|
||||
|
||||
protected:
|
||||
virtual ~AbstractTableView() override;
|
||||
AbstractTableView();
|
||||
|
||||
virtual void did_update_model(unsigned flags) override;
|
||||
virtual void mouseup_event(MouseEvent&) override;
|
||||
virtual void mousedown_event(MouseEvent&) override;
|
||||
virtual void mousemove_event(MouseEvent&) override;
|
||||
virtual void doubleclick_event(MouseEvent&) override;
|
||||
virtual void leave_event(Core::Event&) override;
|
||||
virtual void context_menu_event(ContextMenuEvent&) override;
|
||||
virtual void resize_event(ResizeEvent&) override;
|
||||
|
||||
virtual void did_update_model(unsigned flags) override;
|
||||
virtual void toggle_index(const ModelIndex&) { }
|
||||
|
||||
void paint_headers(Painter&);
|
||||
Gfx::IntRect header_rect(int column) const;
|
||||
|
||||
static const Gfx::Font& header_font();
|
||||
void update_headers();
|
||||
void set_hovered_header_index(int);
|
||||
|
||||
struct ColumnData {
|
||||
int width { 0 };
|
||||
bool has_initialized_width { false };
|
||||
bool visibility { true };
|
||||
RefPtr<Action> visibility_action;
|
||||
Gfx::TextAlignment header_alignment { Gfx::TextAlignment::CenterLeft };
|
||||
OwnPtr<TableCellPaintingDelegate> cell_painting_delegate;
|
||||
};
|
||||
ColumnData& column_data(int column) const;
|
||||
|
||||
mutable Vector<ColumnData> m_column_data;
|
||||
|
||||
Menu& ensure_header_context_menu();
|
||||
RefPtr<Menu> m_header_context_menu;
|
||||
|
||||
Gfx::IntRect column_resize_grabbable_rect(int) const;
|
||||
void update_content_size();
|
||||
virtual void update_column_sizes();
|
||||
virtual int item_count() const;
|
||||
|
||||
TableCellPaintingDelegate* column_painting_delegate(int column) const;
|
||||
|
||||
HeaderView& column_header() { return *m_column_header; }
|
||||
const HeaderView& column_header() const { return *m_column_header; }
|
||||
|
||||
private:
|
||||
bool m_headers_visible { true };
|
||||
bool m_in_column_resize { false };
|
||||
RefPtr<HeaderView> m_column_header;
|
||||
|
||||
HashMap<int, OwnPtr<TableCellPaintingDelegate>> m_column_painting_delegate;
|
||||
|
||||
bool m_alternating_row_colors { true };
|
||||
bool m_highlight_selected_rows { true };
|
||||
int m_horizontal_padding { 5 };
|
||||
Gfx::IntPoint m_column_resize_origin;
|
||||
int m_column_resize_original_width { 0 };
|
||||
int m_resizing_column { -1 };
|
||||
int m_pressed_column_header_index { -1 };
|
||||
bool m_pressed_column_header_is_pressed { false };
|
||||
int m_hovered_column_header_index { -1 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ public:
|
|||
|
||||
ModelSelection& selection() { return m_selection; }
|
||||
const ModelSelection& selection() const { return m_selection; }
|
||||
virtual void select_all() = 0;
|
||||
virtual void select_all() { }
|
||||
|
||||
bool is_editable() const { return m_editable; }
|
||||
void set_editable(bool editable) { m_editable = editable; }
|
||||
|
@ -55,7 +55,7 @@ public:
|
|||
virtual void did_update_selection();
|
||||
|
||||
virtual Gfx::IntRect content_rect(const ModelIndex&) const { return {}; }
|
||||
virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const = 0;
|
||||
virtual ModelIndex index_at_event_position(const Gfx::IntPoint&) const { return {}; }
|
||||
void begin_editing(const ModelIndex&);
|
||||
void stop_editing();
|
||||
|
||||
|
@ -78,6 +78,9 @@ public:
|
|||
|
||||
void set_key_column_and_sort_order(int column, SortOrder);
|
||||
|
||||
int key_column() const { return m_key_column; }
|
||||
SortOrder sort_order() const { return m_sort_order; }
|
||||
|
||||
protected:
|
||||
AbstractView();
|
||||
virtual ~AbstractView() override;
|
||||
|
|
|
@ -32,6 +32,7 @@ set(SOURCES
|
|||
FontDatabase.cpp
|
||||
Frame.cpp
|
||||
GroupBox.cpp
|
||||
HeaderView.cpp
|
||||
INILexer.cpp
|
||||
INISyntaxHighlighter.cpp
|
||||
Icon.cpp
|
||||
|
|
|
@ -43,6 +43,7 @@ class DropEvent;
|
|||
class FileSystemModel;
|
||||
class Frame;
|
||||
class GroupBox;
|
||||
class HeaderView;
|
||||
class HorizontalBoxLayout;
|
||||
class HorizontalSlider;
|
||||
class Icon;
|
||||
|
|
342
Libraries/LibGUI/HeaderView.cpp
Normal file
342
Libraries/LibGUI/HeaderView.cpp
Normal file
|
@ -0,0 +1,342 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGUI/AbstractTableView.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/HeaderView.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibGfx/StylePainter.h>
|
||||
|
||||
namespace GUI {
|
||||
|
||||
static constexpr int minimum_column_size = 2;
|
||||
|
||||
HeaderView::HeaderView(AbstractTableView& table_view, Gfx::Orientation orientation)
|
||||
: m_table_view(table_view)
|
||||
, m_orientation(orientation)
|
||||
{
|
||||
set_font(Gfx::Font::default_bold_font());
|
||||
|
||||
if (m_orientation == Gfx::Orientation::Horizontal) {
|
||||
set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
|
||||
set_preferred_size(0, 16);
|
||||
} else {
|
||||
set_size_policy(SizePolicy::Fixed, SizePolicy::Fill);
|
||||
set_preferred_size(40, 0);
|
||||
}
|
||||
}
|
||||
|
||||
HeaderView::~HeaderView()
|
||||
{
|
||||
}
|
||||
|
||||
void HeaderView::set_section_size(int section, int size)
|
||||
{
|
||||
auto& data = section_data(section);
|
||||
data.size = size;
|
||||
data.has_initialized_size = true;
|
||||
}
|
||||
|
||||
int HeaderView::section_size(int section) const
|
||||
{
|
||||
return section_data(section).size;
|
||||
}
|
||||
|
||||
HeaderView::SectionData& HeaderView::section_data(int section) const
|
||||
{
|
||||
if (static_cast<size_t>(section) >= m_section_data.size())
|
||||
m_section_data.resize(section + 1);
|
||||
return m_section_data.at(section);
|
||||
}
|
||||
|
||||
Gfx::IntRect HeaderView::section_rect(int section) const
|
||||
{
|
||||
if (!model())
|
||||
return {};
|
||||
auto& data = section_data(section);
|
||||
if (!data.visibility)
|
||||
return {};
|
||||
int offset = 0;
|
||||
for (int i = 0; i < section; ++i) {
|
||||
if (!is_section_visible(i))
|
||||
continue;
|
||||
offset += section_data(i).size + horizontal_padding() * 2;
|
||||
}
|
||||
if (orientation() == Gfx::Orientation::Horizontal)
|
||||
return { offset, 0, section_size(section) + horizontal_padding() * 2, height() };
|
||||
return { 0, offset, width(), section_size(section) };
|
||||
}
|
||||
|
||||
Gfx::IntRect HeaderView::section_resize_grabbable_rect(int section) const
|
||||
{
|
||||
if (!model())
|
||||
return {};
|
||||
auto rect = section_rect(section);
|
||||
return { rect.right() - 1, rect.top(), 4, rect.height() };
|
||||
}
|
||||
|
||||
int HeaderView::section_count() const
|
||||
{
|
||||
if (!model())
|
||||
return 0;
|
||||
return m_orientation == Gfx::Orientation::Horizontal ? model()->column_count() : model()->row_count();
|
||||
}
|
||||
|
||||
void HeaderView::mousedown_event(MouseEvent& event)
|
||||
{
|
||||
if (!model())
|
||||
return;
|
||||
|
||||
auto& model = *this->model();
|
||||
int section_count = this->section_count();
|
||||
|
||||
for (int i = 0; i < section_count; ++i) {
|
||||
if (section_resize_grabbable_rect(i).contains(event.position())) {
|
||||
m_resizing_section = i;
|
||||
m_in_section_resize = true;
|
||||
m_section_resize_original_width = section_size(i);
|
||||
m_section_resize_origin = event.position();
|
||||
return;
|
||||
}
|
||||
auto rect = this->section_rect(i);
|
||||
if (rect.contains(event.position()) && model.is_column_sortable(i)) {
|
||||
m_pressed_section = i;
|
||||
m_pressed_section_is_pressed = true;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HeaderView::mousemove_event(MouseEvent& event)
|
||||
{
|
||||
if (!model())
|
||||
return;
|
||||
|
||||
if (m_in_section_resize) {
|
||||
auto delta = event.position() - m_section_resize_origin;
|
||||
int new_size = m_section_resize_original_width + delta.primary_offset_for_orientation(m_orientation);
|
||||
if (new_size <= minimum_column_size)
|
||||
new_size = minimum_column_size;
|
||||
ASSERT(m_resizing_section >= 0 && m_resizing_section < model()->column_count());
|
||||
auto& data = this->section_data(m_resizing_section);
|
||||
if (data.size != new_size) {
|
||||
data.size = new_size;
|
||||
m_table_view.header_did_change_section_size({}, m_orientation, m_resizing_section, new_size);
|
||||
update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pressed_section != -1) {
|
||||
auto header_rect = this->section_rect(m_pressed_section);
|
||||
if (header_rect.contains(event.position())) {
|
||||
set_hovered_section(m_pressed_section);
|
||||
if (!m_pressed_section_is_pressed)
|
||||
update();
|
||||
m_pressed_section_is_pressed = true;
|
||||
} else {
|
||||
set_hovered_section(-1);
|
||||
if (m_pressed_section_is_pressed)
|
||||
update();
|
||||
m_pressed_section_is_pressed = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.buttons() == 0) {
|
||||
int section_count = this->section_count();
|
||||
bool found_hovered_header = false;
|
||||
for (int i = 0; i < section_count; ++i) {
|
||||
if (section_resize_grabbable_rect(i).contains(event.position())) {
|
||||
window()->set_override_cursor(StandardCursor::ResizeColumn);
|
||||
set_hovered_section(-1);
|
||||
return;
|
||||
}
|
||||
if (section_rect(i).contains(event.position())) {
|
||||
set_hovered_section(i);
|
||||
found_hovered_header = true;
|
||||
}
|
||||
}
|
||||
if (!found_hovered_header)
|
||||
set_hovered_section(-1);
|
||||
}
|
||||
window()->set_override_cursor(StandardCursor::None);
|
||||
}
|
||||
|
||||
void HeaderView::mouseup_event(MouseEvent& event)
|
||||
{
|
||||
Gfx::IntPoint horizontally_adjusted_position(event.x(), event.y());
|
||||
if (event.button() == MouseButton::Left) {
|
||||
if (m_in_section_resize) {
|
||||
if (!section_resize_grabbable_rect(m_resizing_section).contains(horizontally_adjusted_position))
|
||||
window()->set_override_cursor(StandardCursor::None);
|
||||
m_in_section_resize = false;
|
||||
return;
|
||||
}
|
||||
if (m_pressed_section != -1) {
|
||||
auto header_rect = this->section_rect(m_pressed_section);
|
||||
if (header_rect.contains(horizontally_adjusted_position)) {
|
||||
auto new_sort_order = SortOrder::Ascending;
|
||||
if (m_table_view.key_column() == m_pressed_section)
|
||||
new_sort_order = m_table_view.sort_order() == SortOrder::Ascending
|
||||
? SortOrder::Descending
|
||||
: SortOrder::Ascending;
|
||||
m_table_view.set_key_column_and_sort_order(m_pressed_section, new_sort_order);
|
||||
}
|
||||
m_pressed_section = -1;
|
||||
m_pressed_section_is_pressed = false;
|
||||
update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HeaderView::paint_event(PaintEvent& event)
|
||||
{
|
||||
Painter painter(*this);
|
||||
painter.add_clip_rect(event.rect());
|
||||
painter.fill_rect(rect(), palette().button());
|
||||
painter.draw_line({ 0, 0 }, { rect().right(), 0 }, palette().threed_highlight());
|
||||
painter.draw_line({ 0, rect().bottom() }, { rect().right(), rect().bottom() }, palette().threed_shadow1());
|
||||
int x_offset = 0;
|
||||
int section_count = this->section_count();
|
||||
for (int section = 0; section < section_count; ++section) {
|
||||
if (!is_section_visible(section))
|
||||
continue;
|
||||
int section_width = section_size(section);
|
||||
bool is_key_column = m_table_view.key_column() == section;
|
||||
Gfx::IntRect cell_rect(x_offset, 0, section_width + horizontal_padding() * 2, height());
|
||||
bool pressed = section == m_pressed_section && m_pressed_section_is_pressed;
|
||||
bool hovered = section == m_hovered_section && model()->is_column_sortable(section);
|
||||
Gfx::StylePainter::paint_button(painter, cell_rect, palette(), Gfx::ButtonStyle::Normal, pressed, hovered);
|
||||
String text;
|
||||
if (is_key_column) {
|
||||
StringBuilder builder;
|
||||
builder.append(model()->column_name(section));
|
||||
if (m_table_view.sort_order() == SortOrder::Ascending)
|
||||
builder.append(" \xE2\xAC\x86"); // UPWARDS BLACK ARROW
|
||||
else if (m_table_view.sort_order() == SortOrder::Descending)
|
||||
builder.append(" \xE2\xAC\x87"); // DOWNWARDS BLACK ARROW
|
||||
text = builder.to_string();
|
||||
} else {
|
||||
text = model()->column_name(section);
|
||||
}
|
||||
auto text_rect = cell_rect.shrunken(horizontal_padding() * 2, 0);
|
||||
if (pressed)
|
||||
text_rect.move_by(1, 1);
|
||||
painter.draw_text(text_rect, text, font(), section_alignment(section), palette().button_text());
|
||||
x_offset += section_width + horizontal_padding() * 2;
|
||||
}
|
||||
}
|
||||
|
||||
void HeaderView::set_section_visible(int section, bool visible)
|
||||
{
|
||||
auto& data = section_data(section);
|
||||
if (data.visibility == visible)
|
||||
return;
|
||||
data.visibility = visible;
|
||||
if (data.visibility_action) {
|
||||
data.visibility_action->set_checked(visible);
|
||||
}
|
||||
m_table_view.header_did_change_section_visibility({}, m_orientation, section, visible);
|
||||
update();
|
||||
}
|
||||
|
||||
Menu& HeaderView::ensure_context_menu()
|
||||
{
|
||||
// FIXME: This menu needs to be rebuilt if the model is swapped out,
|
||||
// or if the column count/names change.
|
||||
if (!m_context_menu) {
|
||||
ASSERT(model());
|
||||
m_context_menu = Menu::construct();
|
||||
|
||||
int section_count = this->section_count();
|
||||
for (int section = 0; section < section_count; ++section) {
|
||||
auto& column_data = this->section_data(section);
|
||||
// FIXME: Vertical support
|
||||
ASSERT(m_orientation == Gfx::Orientation::Horizontal);
|
||||
auto name = model()->column_name(section);
|
||||
column_data.visibility_action = Action::create_checkable(name, [this, section](auto& action) {
|
||||
set_section_visible(section, action.is_checked());
|
||||
});
|
||||
column_data.visibility_action->set_checked(column_data.visibility);
|
||||
|
||||
m_context_menu->add_action(*column_data.visibility_action);
|
||||
}
|
||||
}
|
||||
return *m_context_menu;
|
||||
}
|
||||
|
||||
void HeaderView::context_menu_event(ContextMenuEvent& event)
|
||||
{
|
||||
ensure_context_menu().popup(event.screen_position());
|
||||
}
|
||||
|
||||
void HeaderView::leave_event(Core::Event& event)
|
||||
{
|
||||
Widget::leave_event(event);
|
||||
window()->set_override_cursor(StandardCursor::None);
|
||||
}
|
||||
|
||||
Gfx::TextAlignment HeaderView::section_alignment(int section) const
|
||||
{
|
||||
return section_data(section).alignment;
|
||||
}
|
||||
|
||||
void HeaderView::set_section_alignment(int section, Gfx::TextAlignment alignment)
|
||||
{
|
||||
section_data(section).alignment = alignment;
|
||||
}
|
||||
|
||||
bool HeaderView::is_section_visible(int section) const
|
||||
{
|
||||
return section_data(section).visibility;
|
||||
}
|
||||
|
||||
void HeaderView::set_hovered_section(int section)
|
||||
{
|
||||
if (m_hovered_section == section)
|
||||
return;
|
||||
m_hovered_section = section;
|
||||
update();
|
||||
}
|
||||
|
||||
Model* HeaderView::model()
|
||||
{
|
||||
return m_table_view.model();
|
||||
}
|
||||
|
||||
const Model* HeaderView::model() const
|
||||
{
|
||||
return m_table_view.model();
|
||||
}
|
||||
|
||||
}
|
100
Libraries/LibGUI/HeaderView.h
Normal file
100
Libraries/LibGUI/HeaderView.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <LibGfx/Orientation.h>
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class HeaderView final : public Widget {
|
||||
C_OBJECT(HeaderView);
|
||||
|
||||
public:
|
||||
virtual ~HeaderView() override;
|
||||
|
||||
Gfx::Orientation orientation() const { return m_orientation; }
|
||||
|
||||
Model* model();
|
||||
const Model* model() const;
|
||||
|
||||
void set_section_size(int section, int size);
|
||||
int section_size(int section) const;
|
||||
|
||||
Gfx::TextAlignment section_alignment(int section) const;
|
||||
void set_section_alignment(int section, Gfx::TextAlignment);
|
||||
|
||||
bool is_section_visible(int section) const;
|
||||
void set_section_visible(int section, bool);
|
||||
|
||||
int section_count() const;
|
||||
Gfx::IntRect section_rect(int section) const;
|
||||
|
||||
private:
|
||||
HeaderView(AbstractTableView&, Gfx::Orientation);
|
||||
|
||||
virtual void paint_event(PaintEvent&) override;
|
||||
virtual void mousedown_event(MouseEvent&) override;
|
||||
virtual void mousemove_event(MouseEvent&) override;
|
||||
virtual void mouseup_event(MouseEvent&) override;
|
||||
virtual void context_menu_event(ContextMenuEvent&) override;
|
||||
virtual void leave_event(Core::Event&) override;
|
||||
|
||||
int horizontal_padding() const { return 5; }
|
||||
|
||||
Gfx::IntRect section_resize_grabbable_rect(int) const;
|
||||
|
||||
Menu& ensure_context_menu();
|
||||
RefPtr<Menu> m_context_menu;
|
||||
|
||||
AbstractTableView& m_table_view;
|
||||
|
||||
Gfx::Orientation m_orientation { Gfx::Orientation::Horizontal };
|
||||
|
||||
struct SectionData {
|
||||
int size { 0 };
|
||||
bool has_initialized_size { false };
|
||||
bool visibility { true };
|
||||
RefPtr<Action> visibility_action;
|
||||
Gfx::TextAlignment alignment { Gfx::TextAlignment::CenterLeft };
|
||||
};
|
||||
SectionData& section_data(int section) const;
|
||||
|
||||
void set_hovered_section(int);
|
||||
|
||||
mutable Vector<SectionData> m_section_data;
|
||||
|
||||
bool m_in_section_resize { false };
|
||||
Gfx::IntPoint m_section_resize_origin;
|
||||
int m_section_resize_original_width { 0 };
|
||||
int m_resizing_section { -1 };
|
||||
int m_pressed_section { -1 };
|
||||
bool m_pressed_section_is_pressed { false };
|
||||
int m_hovered_section { -1 };
|
||||
};
|
||||
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/HeaderView.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
|
@ -65,7 +66,7 @@ void TableView::paint_event(PaintEvent& event)
|
|||
return;
|
||||
|
||||
int exposed_width = max(content_size().width(), width());
|
||||
int y_offset = header_height();
|
||||
int y_offset = column_header().height();
|
||||
|
||||
bool dummy;
|
||||
int first_visible_row = index_at_event_position(frame_inner_rect().top_left(), dummy).row();
|
||||
|
@ -100,7 +101,7 @@ void TableView::paint_event(PaintEvent& event)
|
|||
|
||||
int x_offset = 0;
|
||||
for (int column_index = 0; column_index < model()->column_count(); ++column_index) {
|
||||
if (is_column_hidden(column_index))
|
||||
if (!column_header().is_section_visible(column_index))
|
||||
continue;
|
||||
int column_width = this->column_width(column_index);
|
||||
bool is_key_column = m_key_column == column_index;
|
||||
|
@ -110,7 +111,7 @@ void TableView::paint_event(PaintEvent& event)
|
|||
painter.fill_rect(cell_rect_for_fill, key_column_background_color);
|
||||
auto cell_index = model()->index(row_index, column_index);
|
||||
|
||||
if (auto* delegate = column_data(column_index).cell_painting_delegate.ptr()) {
|
||||
if (auto* delegate = column_painting_delegate(column_index)) {
|
||||
delegate->paint(painter, cell_rect, palette(), cell_index);
|
||||
} else {
|
||||
auto data = cell_index.data();
|
||||
|
@ -143,14 +144,9 @@ void TableView::paint_event(PaintEvent& event)
|
|||
++painted_item_index;
|
||||
};
|
||||
|
||||
Gfx::IntRect unpainted_rect(0, header_height() + painted_item_index * item_height(), exposed_width, height());
|
||||
Gfx::IntRect unpainted_rect(0, column_header().height() + painted_item_index * item_height(), exposed_width, height());
|
||||
if (fill_with_background_color())
|
||||
painter.fill_rect(unpainted_rect, widget_background_color);
|
||||
|
||||
// Untranslate the painter vertically and do the column headers.
|
||||
painter.translate(0, vertical_scrollbar().value());
|
||||
if (headers_visible())
|
||||
paint_headers(painter);
|
||||
}
|
||||
|
||||
void TableView::keydown_event(KeyEvent& event)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <LibGUI/HeaderView.h>
|
||||
#include <LibGUI/Model.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ScrollBar.h>
|
||||
|
@ -56,7 +57,7 @@ TreeView::TreeView()
|
|||
set_fill_with_background_color(true);
|
||||
set_background_role(ColorRole::Base);
|
||||
set_foreground_role(ColorRole::BaseText);
|
||||
set_headers_visible(false);
|
||||
set_column_headers_visible(false);
|
||||
m_expand_bitmap = Gfx::Bitmap::load_from_file("/res/icons/treeview-expand.png");
|
||||
m_collapse_bitmap = Gfx::Bitmap::load_from_file("/res/icons/treeview-collapse.png");
|
||||
}
|
||||
|
@ -67,7 +68,7 @@ TreeView::~TreeView()
|
|||
|
||||
ModelIndex TreeView::index_at_event_position(const Gfx::IntPoint& a_position, bool& is_toggle) const
|
||||
{
|
||||
auto position = a_position.translated(0, -header_height()).translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
|
||||
auto position = a_position.translated(0, -column_header().height()).translated(horizontal_scrollbar().value() - frame_thickness(), vertical_scrollbar().value() - frame_thickness());
|
||||
is_toggle = false;
|
||||
if (!model())
|
||||
return {};
|
||||
|
@ -229,7 +230,7 @@ void TreeView::paint_event(PaintEvent& event)
|
|||
int tree_column = model.tree_column();
|
||||
int tree_column_x_offset = this->tree_column_x_offset();
|
||||
|
||||
int y_offset = header_height();
|
||||
int y_offset = column_header().height();
|
||||
|
||||
int painted_row_index = 0;
|
||||
|
||||
|
@ -267,7 +268,7 @@ void TreeView::paint_event(PaintEvent& event)
|
|||
|
||||
int row_width = 0;
|
||||
for (int column_index = 0; column_index < model.column_count(); ++column_index) {
|
||||
if (is_column_hidden(column_index))
|
||||
if (!column_header().is_section_visible(column_index))
|
||||
continue;
|
||||
row_width += this->column_width(column_index) + horizontal_padding() * 2;
|
||||
}
|
||||
|
@ -280,7 +281,7 @@ void TreeView::paint_event(PaintEvent& event)
|
|||
|
||||
int x_offset = 0;
|
||||
for (int column_index = 0; column_index < model.column_count(); ++column_index) {
|
||||
if (is_column_hidden(column_index))
|
||||
if (!column_header().is_section_visible(column_index))
|
||||
continue;
|
||||
int column_width = this->column_width(column_index);
|
||||
|
||||
|
@ -290,7 +291,7 @@ void TreeView::paint_event(PaintEvent& event)
|
|||
Gfx::IntRect cell_rect(horizontal_padding() + x_offset, rect.y(), column_width, item_height());
|
||||
auto cell_index = model.index(index.row(), column_index, index.parent());
|
||||
|
||||
if (auto* delegate = column_data(column_index).cell_painting_delegate.ptr()) {
|
||||
if (auto* delegate = column_painting_delegate(column_index)) {
|
||||
delegate->paint(painter, cell_rect, palette(), cell_index);
|
||||
} else {
|
||||
auto data = cell_index.data();
|
||||
|
@ -357,10 +358,6 @@ void TreeView::paint_event(PaintEvent& event)
|
|||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// Untranslate the painter vertically and do the column headers.
|
||||
painter.translate(0, vertical_scrollbar().value());
|
||||
paint_headers(painter);
|
||||
}
|
||||
|
||||
void TreeView::scroll_into_view(const ModelIndex& a_index, Orientation orientation)
|
||||
|
@ -538,9 +535,9 @@ void TreeView::update_column_sizes()
|
|||
for (int column = 0; column < column_count; ++column) {
|
||||
if (column == tree_column)
|
||||
continue;
|
||||
if (is_column_hidden(column))
|
||||
if (!column_header().is_section_visible(column))
|
||||
continue;
|
||||
int header_width = header_font().width(model.column_name(column));
|
||||
int header_width = column_header().font().width(model.column_name(column));
|
||||
int column_width = header_width;
|
||||
|
||||
for (int row = 0; row < row_count; ++row) {
|
||||
|
@ -553,24 +550,21 @@ void TreeView::update_column_sizes()
|
|||
}
|
||||
column_width = max(column_width, cell_width);
|
||||
}
|
||||
auto& column_data = this->column_data(column);
|
||||
column_data.width = max(column_data.width, column_width);
|
||||
column_data.has_initialized_width = true;
|
||||
|
||||
set_column_width(column, max(this->column_width(column), column_width));
|
||||
|
||||
if (column < tree_column)
|
||||
tree_column_x_offset += column_width;
|
||||
}
|
||||
|
||||
int tree_column_header_width = header_font().width(model.column_name(tree_column));
|
||||
int tree_column_header_width = column_header().font().width(model.column_name(tree_column));
|
||||
int tree_column_width = tree_column_header_width;
|
||||
traverse_in_paint_order([&](const ModelIndex&, const Gfx::IntRect& rect, const Gfx::IntRect&, int) {
|
||||
tree_column_width = max(rect.right() - tree_column_x_offset, tree_column_width);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
auto& column_data = this->column_data(tree_column);
|
||||
column_data.width = max(column_data.width, tree_column_width);
|
||||
column_data.has_initialized_width = true;
|
||||
set_column_width(tree_column, max(this->column_width(tree_column), tree_column_width));
|
||||
}
|
||||
|
||||
int TreeView::tree_column_x_offset() const
|
||||
|
@ -578,7 +572,7 @@ int TreeView::tree_column_x_offset() const
|
|||
int tree_column = model()->tree_column();
|
||||
int offset = 0;
|
||||
for (int i = 0; i < tree_column; ++i) {
|
||||
if (!is_column_hidden(i)) {
|
||||
if (column_header().is_section_visible(i)) {
|
||||
offset += column_width(i);
|
||||
offset += horizontal_padding();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue