Make it possible to sort a GTableModel by column+order.

This is accomplished by putting a GSortingProxyTableModel between the model
and the view. It's pretty simplistic but it works for this use case. :^)
This commit is contained in:
Andreas Kling 2019-03-09 13:33:52 +01:00
parent 0680fe2383
commit 7d1142c7d9
Notes: sideshowbarker 2024-07-19 15:06:53 +09:00
16 changed files with 278 additions and 40 deletions

View file

@ -76,6 +76,7 @@ public:
bool operator==(const String&) const;
bool operator!=(const String& other) const { return !(*this == other); }
bool operator<(const String&) const;
String isolated_copy() const;

View file

@ -19,6 +19,17 @@ bool String::operator==(const String& other) const
return !memcmp(characters(), other.characters(), length());
}
bool String::operator<(const String& other) const
{
if (!m_impl)
return other.m_impl;
if (!other.m_impl)
return false;
return strcmp(characters(), other.characters());
}
String String::empty()
{
return StringImpl::the_empty_stringimpl();

View file

@ -3,19 +3,6 @@
#include <stdio.h>
#include <pwd.h>
enum Column {
Icon = 0,
Name,
CPU,
State,
Priority,
User,
PID,
Linear,
Physical,
__Count
};
ProcessTableModel::ProcessTableModel()
{
setpwent();

View file

@ -8,6 +8,19 @@
class ProcessTableModel final : public GTableModel {
public:
enum Column {
Icon = 0,
Name,
CPU,
State,
Priority,
User,
PID,
Linear,
Physical,
__Count
};
ProcessTableModel();
virtual ~ProcessTableModel() override;

View file

@ -1,11 +1,15 @@
#include "ProcessTableView.h"
#include "ProcessTableModel.h"
#include <LibGUI/GSortingProxyTableModel.h>
#include <stdio.h>
ProcessTableView::ProcessTableView(GWidget* parent)
: GTableView(parent)
{
set_model(make<ProcessTableModel>());
auto process_model = make<ProcessTableModel>();
m_model = process_model.ptr();
set_model(make<GSortingProxyTableModel>(move(process_model)));
GTableView::model()->set_key_column_and_sort_order(ProcessTableModel::Column::CPU, GSortOrder::Descending);
start_timer(1000);
model().update();
}
@ -32,13 +36,3 @@ pid_t ProcessTableView::selected_pid() const
{
return model().selected_pid();
}
inline ProcessTableModel& ProcessTableView::model()
{
return static_cast<ProcessTableModel&>(*GTableView::model());
}
inline const ProcessTableModel& ProcessTableView::model() const
{
return static_cast<const ProcessTableModel&>(*GTableView::model());
}

View file

@ -21,7 +21,9 @@ protected:
private:
virtual void timer_event(GTimerEvent&) override;
ProcessTableModel& model();
const ProcessTableModel& model() const;
ProcessTableModel& model() { return *m_model; }
const ProcessTableModel& model() const { return *m_model; }
ProcessTableModel* m_model { nullptr };
};

View file

@ -27,8 +27,8 @@
#define SPAWN_LAUNCHER
//#define SPAWN_GUITEST2
//#define SPAWN_FILE_MANAGER
//#define SPAWN_PROCESS_MANAGER
#define SPAWN_TEXT_EDITOR
#define SPAWN_PROCESS_MANAGER
//#define SPAWN_TEXT_EDITOR
//#define SPAWN_FONTEDITOR
//#define SPAWN_MULTIPLE_SHELLS
//#define STRESS_TEST_SPAWNING

View file

@ -38,7 +38,8 @@ static char sccsid[] = "@(#)qsort.c 5.9 (Berkeley) 2/23/91";
#include <sys/types.h>
#include <stdlib.h>
static void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
static void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
static void insertion_sort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg);
void qsort(void* bot, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
{
@ -48,23 +49,23 @@ void qsort(void* bot, size_t nmemb, size_t size, int (*compar)(const void *, con
insertion_sort(bot, nmemb, size, compar);
}
void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
void qsort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg)
{
if (nmemb <= 1)
return;
insertion_sort_r(bot, nmemb, size, compar, arg);
}
void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*))
{
int cnt;
unsigned char ch;
char *s1, *s2, *t1, *t2, *top;
/*
* A simple insertion sort (see Knuth, Vol. 3, page 81, Algorithm
* S). Insertion sort has the same worst case as most simple sorts
* (O N^2). It gets used here because it is (O N) in the case of
* sorted data.
*/
top = (char*)bot + nmemb * size;
for (t1 = (char*)bot + size; t1 < top;) {
for (t2 = t1; (t2 -= size) >= bot && compar(t1, t2) < 0;);
if (t1 != (t2 += size)) {
/* Bubble bytes up through each element. */
for (cnt = size; cnt--; ++t1) {
ch = *t1;
for (s1 = s2 = t1; (s2 -= size) >= t2; s1 = s2)
@ -75,3 +76,23 @@ void insertion_sort(void* bot, size_t nmemb, size_t size, int (*compar)(const vo
t1 += size;
}
}
void insertion_sort_r(void* bot, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg)
{
int cnt;
unsigned char ch;
char *s1, *s2, *t1, *t2, *top;
top = (char*)bot + nmemb * size;
for (t1 = (char*)bot + size; t1 < top;) {
for (t2 = t1; (t2 -= size) >= bot && compar(t1, t2, arg) < 0;);
if (t1 != (t2 += size)) {
for (cnt = size; cnt--; ++t1) {
ch = *t1;
for (s1 = s2 = t1; (s2 -= size) >= t2; s1 = s2)
*s1 = *s2;
*s1 = ch;
}
} else
t1 += size;
}
}

View file

@ -21,7 +21,8 @@ long atol(const char*);
double strtod(const char*, char** endptr);
long strtol(const char*, char** endptr, int base);
unsigned long strtoul(const char*, char** endptr, int base);
void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*));
void qsort_r(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*, void*), void* arg);
int atexit(void (*function)());
__attribute__((noreturn)) void exit(int status);
__attribute__((noreturn)) void abort();

View file

@ -0,0 +1,116 @@
#include <LibGUI/GSortingProxyTableModel.h>
#include <stdlib.h>
#include <stdio.h>
GSortingProxyTableModel::GSortingProxyTableModel(OwnPtr<GTableModel>&& target)
: m_target(move(target))
, m_key_column(-1)
{
m_target->on_model_update = [this] (GTableModel&) {
resort();
};
}
GSortingProxyTableModel::~GSortingProxyTableModel()
{
}
int GSortingProxyTableModel::row_count() const
{
return target().row_count();
}
int GSortingProxyTableModel::column_count() const
{
return target().column_count();
}
GModelIndex GSortingProxyTableModel::map_to_target(const GModelIndex& index) const
{
ASSERT(!m_row_mappings.is_empty());
if (!index.is_valid()) {
ASSERT_NOT_REACHED();
return { };
}
if (index.row() >= row_count() || index.column() >= column_count()) {
ASSERT_NOT_REACHED();
return { };
}
return { m_row_mappings[index.row()], index.column() };
}
String GSortingProxyTableModel::row_name(int index) const
{
return target().row_name(index);
}
String GSortingProxyTableModel::column_name(int index) const
{
return target().column_name(index);
}
GTableModel::ColumnMetadata GSortingProxyTableModel::column_metadata(int index) const
{
return target().column_metadata(index);
}
GVariant GSortingProxyTableModel::data(const GModelIndex& index) const
{
return target().data(map_to_target(index));
}
void GSortingProxyTableModel::activate(const GModelIndex& index)
{
target().activate(map_to_target(index));
}
void GSortingProxyTableModel::update()
{
target().update();
}
void GSortingProxyTableModel::set_key_column_and_sort_order(int column, GSortOrder 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;
resort();
}
void GSortingProxyTableModel::resort()
{
int row_count = target().row_count();
m_row_mappings.resize(row_count);
for (int i = 0; i < row_count; ++i)
m_row_mappings[i] = i;
if (m_key_column == -1)
return;
struct Context {
GTableModel* target;
int key_column;
GSortOrder sort_order;
};
Context context { m_target.ptr(), m_key_column, m_sort_order };
qsort_r(m_row_mappings.data(), m_row_mappings.size(), sizeof(int), [] (const void* a, const void* b, void* ctx) -> int {
int row1 = *(const int*)(a);
int row2 = *(const int*)(b);
auto& context = *(Context*)(ctx);
GModelIndex index1 { row1, context.key_column };
GModelIndex index2 { row2, context.key_column };
auto data1 = context.target->data(index1);
auto data2 = context.target->data(index2);
if (data1 == data2)
return 0;
bool is_less_than = data1 < data2;
if (context.sort_order == GSortOrder::Ascending)
return is_less_than ? -1 : 1;
return is_less_than ? 1 : -1;
}, &context);
did_update();
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <LibGUI/GTableModel.h>
class GSortingProxyTableModel final : public GTableModel {
public:
explicit GSortingProxyTableModel(OwnPtr<GTableModel>&&);
virtual ~GSortingProxyTableModel() override;
virtual int row_count() const override;
virtual int column_count() const override;
virtual String row_name(int) const override;
virtual String column_name(int) const override;
virtual ColumnMetadata column_metadata(int) const override;
virtual GVariant data(const GModelIndex&) const override;
virtual void update() override;
virtual void activate(const GModelIndex&) override;
virtual int key_column() const override { return m_key_column; }
virtual GSortOrder sort_order() const override { return m_sort_order; }
virtual void set_key_column_and_sort_order(int, GSortOrder) override;
GModelIndex map_to_target(const GModelIndex&) const;
private:
GTableModel& target() { return *m_target; }
const GTableModel& target() const { return *m_target; }
void resort();
OwnPtr<GTableModel> m_target;
Vector<int> m_row_mappings;
int m_key_column { -1 };
GSortOrder m_sort_order { GSortOrder::Ascending };
};

View file

@ -27,6 +27,8 @@ void GTableModel::for_each_view(Function<void(GTableView&)> callback)
void GTableModel::did_update()
{
if (on_model_update)
on_model_update(*this);
for_each_view([] (GTableView& view) {
view.did_update_model();
});

View file

@ -10,6 +10,8 @@
class GTableView;
enum class GSortOrder { None, Ascending, Descending };
class GModelNotification {
public:
enum Type {
@ -57,9 +59,15 @@ public:
void set_selected_index(const GModelIndex& index) { m_selected_index = index; }
GModelIndex selected_index() const { return m_selected_index; }
virtual int key_column() const { return -1; }
virtual GSortOrder sort_order() const { return GSortOrder::None; }
virtual void set_key_column_and_sort_order(int, GSortOrder) { }
void register_view(Badge<GTableView>, GTableView&);
void unregister_view(Badge<GTableView>, GTableView&);
Function<void(GTableModel&)> on_model_update;
protected:
GTableModel();

View file

@ -48,6 +48,49 @@ GVariant::GVariant(const GraphicsBitmap& value)
AK::retain_if_not_null(m_value.as_bitmap);
}
bool GVariant::operator==(const GVariant& other) const
{
if (m_type != other.m_type)
return to_string() == other.to_string();
switch (m_type) {
case Type::Bool:
return as_bool() == other.as_bool();
case Type::Int:
return as_int() == other.as_int();
case Type::Float:
return as_float() == other.as_float();
case Type::String:
return as_string() == other.as_string();
case Type::Bitmap:
return m_value.as_bitmap == other.m_value.as_bitmap;
case Type::Invalid:
break;
}
ASSERT_NOT_REACHED();
}
bool GVariant::operator<(const GVariant& other) const
{
if (m_type != other.m_type)
return to_string() < other.to_string();
switch (m_type) {
case Type::Bool:
return as_bool() < other.as_bool();
case Type::Int:
return as_int() < other.as_int();
case Type::Float:
return as_float() < other.as_float();
case Type::String:
return as_string() < other.as_string();
case Type::Bitmap:
// FIXME: Maybe compare bitmaps somehow differently?
return m_value.as_bitmap < other.m_value.as_bitmap;
case Type::Invalid:
break;
}
ASSERT_NOT_REACHED();
}
String GVariant::to_string() const
{
switch (m_type) {

View file

@ -62,6 +62,9 @@ public:
String to_string() const;
bool operator==(const GVariant&) const;
bool operator<(const GVariant&) const;
private:
union {
StringImpl* as_string;

View file

@ -34,6 +34,7 @@ LIBGUI_OBJS = \
GShortcut.o \
GTextEditor.o \
GClipboard.o \
GSortingProxyTableModel.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)