LibGUI: Add GModelSelection to help implementing multiple-select views

Each GAbstractView now has a GModelSelection backed by a simple
HashTable<GModelIndex>. When the selection changes somehow, the view
gets notified via the notify_selection_changed() callback.

In the future it will probably make sense to move to using some kind of
ranges as the internal representation instead.
This commit is contained in:
Andreas Kling 2019-09-07 19:14:59 +02:00
parent 19b69741ed
commit 82559e211d
Notes: sideshowbarker 2024-07-19 12:13:55 +09:00
5 changed files with 95 additions and 0 deletions

View file

@ -8,6 +8,7 @@
GAbstractView::GAbstractView(GWidget* parent)
: GScrollableWidget(parent)
, m_selection(*this)
{
}
@ -96,3 +97,8 @@ void GAbstractView::activate(const GModelIndex& index)
if (on_activation)
on_activation(index);
}
void GAbstractView::notify_selection_changed(Badge<GModelSelection>)
{
update();
}

View file

@ -2,6 +2,7 @@
#include <AK/Function.h>
#include <LibGUI/GModel.h>
#include <LibGUI/GModelSelection.h>
#include <LibGUI/GScrollableWidget.h>
class GModelEditingDelegate;
@ -9,6 +10,7 @@ class GModelEditingDelegate;
class GAbstractView : public GScrollableWidget {
C_OBJECT(GAbstractView)
friend class GModel;
public:
explicit GAbstractView(GWidget* parent);
virtual ~GAbstractView() override;
@ -17,6 +19,9 @@ public:
GModel* model() { return m_model.ptr(); }
const GModel* model() const { return m_model.ptr(); }
GModelSelection& selection() { return m_selection; }
const GModelSelection& selection() const { return m_selection; }
bool is_editable() const { return m_editable; }
void set_editable(bool editable) { m_editable = editable; }
@ -37,6 +42,8 @@ public:
Function<OwnPtr<GModelEditingDelegate>(const GModelIndex&)> aid_create_editing_delegate;
void notify_selection_changed(Badge<GModelSelection>);
protected:
virtual void did_scroll() override;
void activate(const GModelIndex&);
@ -50,5 +57,6 @@ protected:
private:
RefPtr<GModel> m_model;
OwnPtr<GModelEditingDelegate> m_editing_delegate;
GModelSelection m_selection;
bool m_activates_on_selection { false };
};

View file

@ -0,0 +1,39 @@
#include <LibGUI/GAbstractView.h>
#include <LibGUI/GModelSelection.h>
void GModelSelection::set(const GModelIndex& index)
{
ASSERT(index.is_valid());
if (m_indexes.size() == 1 && m_indexes.contains(index))
return;
m_indexes.clear();
m_indexes.set(index);
m_view.notify_selection_changed({});
}
void GModelSelection::add(const GModelIndex& index)
{
ASSERT(index.is_valid());
if (m_indexes.contains(index))
return;
m_indexes.set(index);
m_view.notify_selection_changed({});
}
bool GModelSelection::remove(const GModelIndex& index)
{
ASSERT(index.is_valid());
if (!m_indexes.contains(index))
return false;
m_indexes.remove(index);
m_view.notify_selection_changed({});
return true;
}
void GModelSelection::clear()
{
if (m_indexes.is_empty())
return;
m_indexes.clear();
m_view.notify_selection_changed({});
}

View file

@ -0,0 +1,41 @@
#pragma once
#include <AK/HashTable.h>
#include <LibGUI/GModelIndex.h>
class GAbstractView;
class GModelSelection {
public:
GModelSelection(GAbstractView& view)
: m_view(view)
{
}
bool is_empty() const { return m_indexes.is_empty(); }
bool contains(const GModelIndex& index) const { return m_indexes.contains(index); }
void set(const GModelIndex&);
void add(const GModelIndex&);
bool remove(const GModelIndex&);
void clear();
template<typename Callback>
void for_each_index(Callback callback)
{
for (auto& index : m_indexes)
callback(index);
}
// FIXME: This doesn't guarantee that what you get is the lowest or "first" index selected..
GModelIndex first() const
{
if (m_indexes.is_empty())
return {};
return *m_indexes.begin();
}
private:
GAbstractView& m_view;
HashTable<GModelIndex> m_indexes;
};

View file

@ -54,6 +54,7 @@ OBJS = \
GComboBox.o \
GJsonArrayModel.o \
GAboutDialog.o \
GModelSelection.o \
GWindow.o
LIBRARY = libgui.a