LibGUI: Make model sorting imperative and move order to AbstractView

Instead of SortingProxyModel having a column+order, we move that state
to AbstractView. When you click on a column header, the view tells the
model to resort the relevant column with the new order.

This is implemented in SortingProxyModel by simply walking all the
reified source/proxy mappings and resorting their row indexes.
This commit is contained in:
Andreas Kling 2020-08-16 10:44:10 +02:00
parent 370624bc37
commit e1ed71ef9e
15 changed files with 85 additions and 55 deletions

View file

@ -127,7 +127,7 @@ DirectoryView::DirectoryView()
m_table_view = add<GUI::TableView>();
m_table_view->set_model(m_sorting_model);
m_table_view->model()->set_key_column_and_sort_order(GUI::FileSystemModel::Column::Name, GUI::SortOrder::Ascending);
m_table_view->set_key_column_and_sort_order(GUI::FileSystemModel::Column::Name, GUI::SortOrder::Ascending);
m_icon_view->set_model_column(GUI::FileSystemModel::Column::Name);
m_columns_view->set_model_column(GUI::FileSystemModel::Column::Name);

View file

@ -120,7 +120,7 @@ ProcessMemoryMapWidget::ProcessMemoryMapWidget()
m_table_view->set_cell_painting_delegate(7, make<PagemapPaintingDelegate>());
m_table_view->model()->set_key_column_and_sort_order(0, GUI::SortOrder::Ascending);
m_table_view->set_key_column_and_sort_order(0, GUI::SortOrder::Ascending);
m_timer = add<Core::Timer>(1000, [this] { refresh(); });
}

View file

@ -179,7 +179,7 @@ int main(int argc, char** argv)
auto& process_table_view = process_table_container.add<GUI::TableView>();
process_table_view.set_headers_visible(true);
process_table_view.set_model(GUI::SortingProxyModel::create(ProcessModel::create()));
process_table_view.model()->set_key_column_and_sort_order(ProcessModel::Column::CPU, GUI::SortOrder::Descending);
process_table_view.set_key_column_and_sort_order(ProcessModel::Column::CPU, GUI::SortOrder::Descending);
process_table_view.model()->update();
auto& refresh_timer = window->add<Core::Timer>(

View file

@ -65,13 +65,12 @@ void AbstractTableView::update_column_sizes()
auto& model = *this->model();
int column_count = model.column_count();
int row_count = model.row_count();
int key_column = model.key_column();
for (int column = 0; column < column_count; ++column) {
if (is_column_hidden(column))
continue;
int header_width = header_font().width(model.column_name(column));
if (column == key_column && model.is_column_sortable(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;
for (int row = 0; row < row_count; ++row) {
@ -146,7 +145,7 @@ void AbstractTableView::paint_headers(Painter& painter)
if (is_column_hidden(column_index))
continue;
int column_width = this->column_width(column_index);
bool is_key_column = model()->key_column() == 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);
@ -155,10 +154,9 @@ void AbstractTableView::paint_headers(Painter& painter)
if (is_key_column) {
StringBuilder builder;
builder.append(model()->column_name(column_index));
auto sort_order = model()->sort_order();
if (sort_order == SortOrder::Ascending)
if (m_sort_order == SortOrder::Ascending)
builder.append(" \xE2\xAC\x86"); // UPWARDS BLACK ARROW
else if (sort_order == SortOrder::Descending)
else if (m_sort_order == SortOrder::Descending)
builder.append(" \xE2\xAC\x87"); // DOWNWARDS BLACK ARROW
text = builder.to_string();
} else {
@ -327,11 +325,11 @@ void AbstractTableView::mouseup_event(MouseEvent& event)
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 (model()->key_column() == m_pressed_column_header_index)
new_sort_order = model()->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;
model()->set_key_column_and_sort_order(m_pressed_column_header_index, new_sort_order);
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;

View file

@ -37,7 +37,8 @@
namespace GUI {
AbstractView::AbstractView()
: m_selection(*this)
: m_sort_order(SortOrder::Ascending)
, m_selection(*this)
{
}
@ -400,4 +401,13 @@ void AbstractView::set_multi_select(bool multi_select)
}
}
void AbstractView::set_key_column_and_sort_order(int column, SortOrder sort_order)
{
m_key_column = column;
m_sort_order = sort_order;
if (model())
model()->sort(column, sort_order);
}
}

View file

@ -76,6 +76,8 @@ public:
void set_last_valid_hovered_index(const ModelIndex&);
void set_key_column_and_sort_order(int column, SortOrder);
protected:
AbstractView();
virtual ~AbstractView() override;
@ -111,6 +113,9 @@ protected:
ModelIndex m_hovered_index;
ModelIndex m_last_valid_hovered_index;
int m_key_column { 0 };
SortOrder m_sort_order;
private:
RefPtr<Model> m_model;
OwnPtr<ModelEditingDelegate> m_editing_delegate;

View file

@ -127,7 +127,7 @@ FilePicker::FilePicker(Window* parent_window, Mode mode, Options options, const
m_view->set_multi_select(m_mode == Mode::OpenMultiple);
m_view->set_model(SortingProxyModel::create(*m_model));
m_view->set_model_column(FileSystemModel::Column::Name);
m_view->model()->set_key_column_and_sort_order(GUI::FileSystemModel::Column::Name, GUI::SortOrder::Ascending);
m_view->set_key_column_and_sort_order(GUI::FileSystemModel::Column::Name, GUI::SortOrder::Ascending);
m_view->set_column_hidden(FileSystemModel::Column::Owner, true);
m_view->set_column_hidden(FileSystemModel::Column::Group, true);
m_view->set_column_hidden(FileSystemModel::Column::Permissions, true);

View file

@ -92,4 +92,6 @@ class Widget;
class Window;
class WindowServerConnection;
enum class SortOrder;
}

View file

@ -87,6 +87,7 @@ public:
virtual bool accepts_drag(const ModelIndex&, const StringView& data_type);
virtual bool is_column_sortable([[maybe_unused]] int column_index) const { return true; }
virtual void sort([[maybe_unused]] int column, SortOrder) { }
bool is_valid(const ModelIndex& index) const
{
@ -94,10 +95,6 @@ public:
return index.row() >= 0 && index.row() < row_count(parent_index) && index.column() >= 0 && index.column() < column_count(parent_index);
}
virtual int key_column() const { return -1; }
virtual SortOrder sort_order() const { return SortOrder::None; }
virtual void set_key_column_and_sort_order(int, SortOrder) { }
virtual StringView drag_data_type() const { return {}; }
void register_view(Badge<AbstractView>, AbstractView&);

View file

@ -212,4 +212,11 @@ void MultiView::set_multi_select(bool multi_select)
apply_multi_select();
}
void MultiView::set_key_column_and_sort_order(int column, SortOrder sort_order)
{
for_each_view_implementation([&](auto& view) {
view.set_key_column_and_sort_order(column, sort_order);
});
}
}

View file

@ -63,6 +63,8 @@ public:
void set_column_hidden(int column_index, bool hidden);
void set_key_column_and_sort_order(int column, SortOrder);
GUI::AbstractView& current_view()
{
switch (m_view_mode) {

View file

@ -58,7 +58,7 @@ ProcessChooser::ProcessChooser(const StringView& window_title, const StringView&
m_table_view = widget.add<GUI::TableView>();
auto sorting_model = GUI::SortingProxyModel::create(RunningProcessesModel::create());
sorting_model->set_sort_role(GUI::Model::Role::Display);
sorting_model->set_key_column_and_sort_order(RunningProcessesModel::Column::PID, GUI::SortOrder::Descending);
m_table_view->set_key_column_and_sort_order(RunningProcessesModel::Column::PID, GUI::SortOrder::Descending);
m_table_view->set_model(sorting_model);
m_table_view->on_activation = [this](const ModelIndex& index) { set_pid_from_index_and_close(index); };

View file

@ -32,7 +32,6 @@ namespace GUI {
SortingProxyModel::SortingProxyModel(NonnullRefPtr<Model> source)
: m_source(move(source))
, m_key_column(-1)
{
m_source->register_client(*this);
invalidate();
@ -126,17 +125,6 @@ StringView SortingProxyModel::drag_data_type() const
return source().drag_data_type();
}
void SortingProxyModel::set_key_column_and_sort_order(int column, SortOrder sort_order)
{
if (column == m_key_column && sort_order == m_sort_order)
return;
ASSERT(column >= 0 && column < column_count());
m_key_column = column;
m_sort_order = sort_order;
invalidate();
}
bool SortingProxyModel::less_than(const ModelIndex& index1, const ModelIndex& index2) const
{
auto data1 = index1.model() ? index1.model()->data(index1, m_sort_role) : Variant();
@ -177,6 +165,43 @@ ModelIndex SortingProxyModel::parent_index(const ModelIndex& proxy_index) const
return map_to_proxy(it->value->source_parent);
}
void SortingProxyModel::sort_mapping(Mapping& mapping, int column, SortOrder sort_order)
{
if (column == -1) {
int row_count = source().row_count(mapping.source_parent);
for (int i = 0; i < row_count; ++i) {
mapping.source_rows[i] = i;
mapping.proxy_rows[i] = i;
}
return;
}
int row_count = source().row_count(mapping.source_parent);
for (int i = 0; i < row_count; ++i)
mapping.source_rows[i] = i;
quick_sort(mapping.source_rows, [&](auto row1, auto row2) -> bool {
bool is_less_than = less_than(source().index(row1, column, mapping.source_parent), source().index(row2, column, mapping.source_parent));
return sort_order == SortOrder::Ascending ? is_less_than : !is_less_than;
});
for (int i = 0; i < row_count; ++i)
mapping.proxy_rows[mapping.source_rows[i]] = i;
}
void SortingProxyModel::sort(int column, SortOrder sort_order)
{
for (auto& it : m_mappings) {
auto& mapping = *it.value;
sort_mapping(mapping, column, sort_order);
}
m_last_key_column = column;
m_last_sort_order = sort_order;
did_update();
}
SortingProxyModel::InternalMapIterator SortingProxyModel::build_mapping(const ModelIndex& source_parent)
{
auto it = m_mappings.find(source_parent);
@ -191,24 +216,7 @@ SortingProxyModel::InternalMapIterator SortingProxyModel::build_mapping(const Mo
mapping->source_rows.resize(row_count);
mapping->proxy_rows.resize(row_count);
for (int i = 0; i < row_count; ++i) {
mapping->source_rows[i] = i;
}
// If we don't have a key column, we're not sorting.
if (m_key_column == -1) {
m_mappings.set(source_parent, move(mapping));
return m_mappings.find(source_parent);
}
quick_sort(mapping->source_rows, [&](auto row1, auto row2) -> bool {
bool is_less_than = less_than(source().index(row1, m_key_column, source_parent), source().index(row2, m_key_column, source_parent));
return m_sort_order == SortOrder::Ascending ? is_less_than : !is_less_than;
});
for (int i = 0; i < row_count; ++i) {
mapping->proxy_rows[mapping->source_rows[i]] = i;
}
sort_mapping(*mapping, m_last_key_column, m_last_sort_order);
if (source_parent.is_valid()) {
auto source_grand_parent = source_parent.parent();

View file

@ -46,9 +46,6 @@ public:
virtual ModelIndex parent_index(const ModelIndex&) const override;
virtual ModelIndex index(int row, int column, const ModelIndex& parent) const override;
virtual int key_column() const override { return m_key_column; }
virtual SortOrder sort_order() const override { return m_sort_order; }
virtual void set_key_column_and_sort_order(int, SortOrder) override;
virtual bool is_column_sortable(int column_index) const override;
virtual bool less_than(const ModelIndex&, const ModelIndex&) const;
@ -59,6 +56,8 @@ public:
Role sort_role() const { return m_sort_role; }
void set_sort_role(Role role) { m_sort_role = role; }
virtual void sort(int column, SortOrder) override;
private:
explicit SortingProxyModel(NonnullRefPtr<Model> source);
@ -71,6 +70,8 @@ private:
using InternalMapIterator = HashMap<ModelIndex, NonnullOwnPtr<Mapping>>::IteratorType;
void sort_mapping(Mapping&, int column, SortOrder);
// ^ModelClient
virtual void model_did_update(unsigned) override;
@ -83,9 +84,9 @@ private:
NonnullRefPtr<Model> m_source;
HashMap<ModelIndex, NonnullOwnPtr<Mapping>> m_mappings;
int m_key_column { -1 };
SortOrder m_sort_order { SortOrder::Ascending };
Role m_sort_role { Role::Sort };
int m_last_key_column { -1 };
SortOrder m_last_sort_order { SortOrder::Ascending };
};
}

View file

@ -103,7 +103,7 @@ void TableView::paint_event(PaintEvent& event)
if (is_column_hidden(column_index))
continue;
int column_width = this->column_width(column_index);
bool is_key_column = model()->key_column() == column_index;
bool is_key_column = m_key_column == column_index;
Gfx::IntRect cell_rect(horizontal_padding() + x_offset, y, column_width, item_height());
auto cell_rect_for_fill = cell_rect.inflated(horizontal_padding() * 2, 0);
if (is_key_column)