Переглянути джерело

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. :^)
Andreas Kling 6 роки тому
батько
коміт
7d1142c7d9

+ 1 - 0
AK/AKString.h

@@ -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;
 

+ 11 - 0
AK/String.cpp

@@ -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();

+ 0 - 13
Applications/ProcessManager/ProcessTableModel.cpp

@@ -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();

+ 13 - 0
Applications/ProcessManager/ProcessTableModel.h

@@ -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;
 

+ 6 - 12
Applications/ProcessManager/ProcessTableView.cpp

@@ -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());
-}

+ 4 - 2
Applications/ProcessManager/ProcessTableView.h

@@ -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 };
 };
 

+ 2 - 2
Kernel/init.cpp

@@ -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

+ 31 - 10
LibC/qsort.cpp

@@ -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;
+    }
+}

+ 2 - 1
LibC/stdlib.h

@@ -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 - 0
LibGUI/GSortingProxyTableModel.cpp

@@ -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 - 0
LibGUI/GSortingProxyTableModel.h

@@ -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 };
+};

+ 2 - 0
LibGUI/GTableModel.cpp

@@ -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();
     });

+ 8 - 0
LibGUI/GTableModel.h

@@ -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();
 

+ 43 - 0
LibGUI/GVariant.cpp

@@ -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) {

+ 3 - 0
LibGUI/GVariant.h

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

+ 1 - 0
LibGUI/Makefile

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