mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
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:
parent
0680fe2383
commit
7d1142c7d9
Notes:
sideshowbarker
2024-07-19 15:06:53 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/7d1142c7d9f
16 changed files with 278 additions and 40 deletions
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
116
LibGUI/GSortingProxyTableModel.cpp
Normal file
116
LibGUI/GSortingProxyTableModel.cpp
Normal 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();
|
||||
}
|
35
LibGUI/GSortingProxyTableModel.h
Normal file
35
LibGUI/GSortingProxyTableModel.h
Normal 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 };
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -62,6 +62,9 @@ public:
|
|||
|
||||
String to_string() const;
|
||||
|
||||
bool operator==(const GVariant&) const;
|
||||
bool operator<(const GVariant&) const;
|
||||
|
||||
private:
|
||||
union {
|
||||
StringImpl* as_string;
|
||||
|
|
|
@ -34,6 +34,7 @@ LIBGUI_OBJS = \
|
|||
GShortcut.o \
|
||||
GTextEditor.o \
|
||||
GClipboard.o \
|
||||
GSortingProxyTableModel.o \
|
||||
GWindow.o
|
||||
|
||||
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)
|
||||
|
|
Loading…
Reference in a new issue