mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 06:32:44 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									370624bc37
								
							
						
					
					
						commit
						e1ed71ef9e
					
				
					 15 changed files with 85 additions and 55 deletions
				
			
		|  | @ -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); | ||||
|  |  | |||
|  | @ -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(); }); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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>( | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -92,4 +92,6 @@ class Widget; | |||
| class Window; | ||||
| class WindowServerConnection; | ||||
| 
 | ||||
| enum class SortOrder; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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&); | ||||
|  |  | |||
|  | @ -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); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -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); }; | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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 }; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling