Compare commits

...

49 Commits

Author SHA1 Message Date
Benjamin Otte
55acfae96a xxx 2020-07-22 03:22:27 +02:00
Benjamin Otte
34f0270ee5 gtk-demo: Add a progress bar when the colors demo resorts 2020-07-22 03:21:41 +02:00
Benjamin Otte
102d80f234 sortlistmodel: Add progress estimation 2020-07-22 03:21:41 +02:00
Benjamin Otte
115d14e8d1 timsort: Add progress estimation 2020-07-22 03:21:41 +02:00
Benjamin Otte
1336a6e434 sortlistmodel: Make key generation part of the step function
SSave the missing keys as a bitset and iterate over that bitset in the
step function.

Solves the problem with a large UI block at the beginning of a sort
operation when all the keys were generated, in particular when key
generation was slow.

Benchmarks for maximum time taken by a single main loop callback:

     initial sort with complex GFileInfo keys
                       old      new
      32,000 items   137ms      3ms
     128,000 items   520ms     31ms

     initial sort with string keys
                       old      new
      32,000 items   187ms      1ms
     128,000 items   804ms      3ms
2020-07-22 03:21:41 +02:00
Benjamin Otte
c5b675659a gtk-demo: Make colors demo do incremental sorting 2020-07-22 03:21:41 +02:00
Benjamin Otte
3cd9908b43 sortlistmodel: Properly compute runs
When updating a (partially) sorted model, take the known runs for the
existing sort and apply them to the new sort. That way, we don't have to
check the whole model again.

Benchmarks:

      appending half the items to a model of strings
                        old      new
      512,000 items   437ms    389ms
    1,024,000 items  1006ms    914ms

      appending 10% of the items to a model of strings
                        old      new
      512,000 items   206ms    132ms
    1,024,000 items   438ms    301ms

      appending 1 item to a model of strings
                        old      new
       64,000 items   1.8ms   0.00ms
      512,000 items     ---   0.01ms
2020-07-22 03:21:41 +02:00
Benjamin Otte
7d95bc4dc6 testsuite: Support different models
In particular, add a GtkTreeModel of GtkDirectoryList that enumerates
the full tree of everything below G_TEST_SRCDIR.

You can set it to ~ or / to get lots of files.

Use this to test numeric sorting, string sorting, the treelistrow
sorter and the multisorter.
And while doing all of that, try to use something that is still
realistic.
2020-07-22 03:21:41 +02:00
Benjamin Otte
cf0f0956a9 testsuite: Support incremental and non-incremental sorting 2020-07-22 03:21:41 +02:00
Benjamin Otte
ceaa0e3962 testsuite: Collect more statistics on models
* "max time"
   Reports the max time taken for all main loop iterations + initial
   set.
   This is useful to judge the performance of incremental search models.
 * "changes"
   Reports the sum of all changes during items-changed signal emissions.
   Uses MAX(added, removed) as the number to not double-count items that
   were moved.
   This is useful to judge the accuracy of the models by comparing the
   number among them.
2020-07-22 03:21:41 +02:00
Benjamin Otte
7dcde5620f testsuite: Add more sorting tests 2020-07-22 03:21:41 +02:00
Benjamin Otte
693a459b43 testsuite: Add some sorting performance tests 2020-07-22 03:21:41 +02:00
Matthias Clasen
33656fe588 sor3listmodel: Add an incremental property
This lets us easily compare incremental and non-incremental
sorting.
2020-07-22 03:21:41 +02:00
Matthias Clasen
8ae3529bb3 sor3listmodel: Do time-based batching
Stop a sorting step if it has run for more than 1.5ms.
This is an attempt to improve interactivity during
incremental sorting.
2020-07-22 03:21:41 +02:00
Matthias Clasen
3bdd9fe50f sor3listmodel: Count the number of steps 2020-07-22 03:21:41 +02:00
Matthias Clasen
c6e6d682dc gtksor3listmodel: Track changed items precisely
Keep track of what items we swap, and emit
minimal items-changed signals based on that.
2020-07-22 03:21:41 +02:00
Matthias Clasen
a82f6d06ba sor3listmodel: Add profiler marks 2020-07-22 03:21:41 +02:00
Matthias Clasen
863c7e1a0a sor3listmodel: Add a :sorting boolean
This is convenient for quitting a test run
when the sorting is done.
2020-07-22 03:21:41 +02:00
Matthias Clasen
99a6c230f3 Add a custom model using quicksort 2020-07-22 03:21:41 +02:00
Matthias Clasen
e65dc063a8 Add another incremental sort model
This one uses a very simple iterative mergesort.
2020-07-22 03:21:41 +02:00
Benjamin Otte
5a058bf375 REMOVE: Copy current sort model to Tim4SortModel
This allows judging the impact of sort keys.
2020-07-22 03:21:41 +02:00
Benjamin Otte
6458305723 sortlistmodel: Make sort stable again
Previously, the sort was not stable when items were added/removed while
sorting or the sort algorithm was changed.

Now the sort looks at the item position (via the key's location in the
keys array) to make sure each comparison stays stable with respect to
this position.
2020-07-22 03:21:41 +02:00
Benjamin Otte
3326976669 multisorter: Implement GtkSortKeys 2020-07-22 03:21:41 +02:00
Benjamin Otte
26e7a0d050 treelistrowsorter: Implement GtkSortKeys 2020-07-22 03:21:41 +02:00
Benjamin Otte
20a6c2a2bd numericsorter: Implement GtkSortKeys 2020-07-22 03:21:41 +02:00
Benjamin Otte
61cdbebf6a stringsorter: Implement GtkSortKeys 2020-07-22 03:21:41 +02:00
Benjamin Otte
4aa811cde7 sortkeys: Add an equal sort keys
Compares every element as equal.
This is useful when sorters are in an invalid configuration.
2020-07-22 03:21:41 +02:00
Benjamin Otte
1544c90813 sortlistmodel: Use GtkSortKeys
This massively speeds up sorting with expensive sort functions that it's
the most worthwhile optimization of this whole branch.
It's slower for simple sort functions though.

It's also quite a lot slower when the model doesn't support sort keys
(like GtkCustomSorter), but all the other sorters do support keys.

Of course, this depends on the number of items in the model - the number
of comparisons scales O(N * log N) while the overhead for key handling
scales O(N).
So as the log N part grows, generating keys gets more and more
beneficial.

Benchmarks:

       initial sort of a GFileInfo model with display-name keys
                       items     keys
         8,000 items   715ms     50ms
        64,000 items     ---    554ms

       initial sort of a GFileInfo model with complex keys
                       items     keys
        64,000 items   340ms    295ms
       128,000 items   641ms    605ms

       removing half a GFileInfo model with display-name keys
       (no comparisons, just key freeing overhead of a complex sorter)
                       items     keys
       512,000 items    14ms     21ms
     2,048,000 items    40ms     62ms

       removing half a GFileInfo model with complex keys
       (no comparisons, just key freeing overhead of a complex sorter)
                       items     keys
       512,000 items    90ms    237ms
     2,048,000 items   247ms    601ms
2020-07-22 03:21:41 +02:00
Benjamin Otte
0941d39126 sorter: Introduce GtkSortKeys
GtkSortKeys is an immutable struct that can be used to manage "sort
keys" for items.

Sort keys are memory that is created specifically for sorting. Because
sorting involves lots of comparisons, it's a good idea to prepare the
data relevant for sorting in advance and sort on that data.

In measurements with a PropertyExpression on a string sorter, it's about
??? faster
2020-07-22 03:21:41 +02:00
Benjamin Otte
2da5a3600a REMOVE: Copy current sort model to Tim3SortModel
This is to compare with Tim2SortModel, which uses the different array
structure.
2020-07-22 03:21:41 +02:00
Benjamin Otte
5b406b07c6 sortlistmodel: Split the SortItem into 2 arrays
Instead of one item keeping the item + its position and sorting that
list, keep the items in 1 array and put the positions into a 2nd array.

This is generally slower while sorting, but allows multiple improvements:

1. We can replace items with keys
   This allows avoiding multiple slow lookups when using complex
   comparisons

2. We can keep multiple position arrays
   This allows doing a sorting in the background without actually
   emitting items-changed() until the array is completely sorted.

3. The main list tracks the items in the original model
   So only a single memmove() is necessary there, while the old version
   had to upgrade the position in every item.
Benchmarks:

        sorting a model of simple strings
                          old      new
        256,000 items   256ms    268ms
        512,000 items   569ms    638ms

        sorting a model of file trees, directories first, by size
                          old      new
         64,000 items   350ms    364ms
        128,000 items   667ms    691ms

        removing half the model
                          old      new
        512,000 items    24ms     15ms
      1,024,000 items    49ms     25ms
2020-07-22 03:21:41 +02:00
Benjamin Otte
76df9adfdc REMOVE: Copy current sort model to Tim2SortModel
This is the state of the model with incremental sorting support, before
doing some surgery that might slow down operations.
2020-07-22 03:21:41 +02:00
Benjamin Otte
da35f00405 sortlistmodel: Add an incremental property
Also refactor a large part of the sortmodel to make this convenient.

A large amount of time has been spent on getting items-changed regions
minimized.
2020-07-22 03:21:41 +02:00
Benjamin Otte
1edd8a5d49 testsuite: Add exhaustive sortlistmodel test
This is basically a copy/paste from the filterlistmodel test, but
adapted for sorting.
2020-07-21 16:16:34 +02:00
Benjamin Otte
22295e1585 sortlistmodel: Make the sort callback useful
1. Run step() for a while to avoid very short steps
   This way, we batch items-changed() emissions.

2. Track the change region accurately
   This way, we can avoid invalidating the whole list if our step just
   touched a small part of a huge list.
   As this is a merge sort, this is a common occurence when we're buys
   merging chunks: The rest of the model outside those chunks isn't
   changed.

Note that the tracking is accurate: It determines the minimum change
region in the model.

This will be important, because the testsuite is going to test this.
2020-07-21 16:16:34 +02:00
Benjamin Otte
18545870e2 timsort: Add change tracking to gtk_tim_sort_step() 2020-07-21 16:16:34 +02:00
Benjamin Otte
ee6a5c9c02 timsort: Add gtk_tim_sort_set_max_merge_size()
Makes the SOrtListModel responsive when incrementally sorting.

By making it configurable we can avoid losting performance in the
non-incremental case.
2020-07-21 16:16:34 +02:00
Benjamin Otte
5d35fb7f17 timsort: Make sure merges don't take too long
Limit the size of the merged areas and thereby chunk larger merges into
smaller ones.
2020-07-21 16:16:34 +02:00
Benjamin Otte
cc02085952 sortlistmodel: Make sorting incremental
This is just an experiment so far to see how long it takes to sort.
2020-07-21 16:16:34 +02:00
Benjamin Otte
25b8222db7 timsort: Add gtk_tim_sort_set_runs()
... and use it in the SortListModel

Setting runs allows declaring already sorted regions so the sort does
not attempt to sort them again.

This massively speeds up partial inserts where we can reuse the sorted
model as a run and only resort the newly inserted parts.

Benchmarks:

    appending half the model
                    qsort  timsort
    128,000 items    94ms     69ms
    256,000 items   202ms    143ms
    512,000 items   488ms    328ms

    appending 1 item
                    qsort  timsort
      8,000 items   1.5ms    0.0ms
     16,000 items   3.1ms    0.0ms
              ...
    512,000 items     ---    1.8ms
2020-07-21 16:16:34 +02:00
Benjamin Otte
adc8728de0 REMOVE: Copy initial timsort model to Tim1SortModel
This allows a good comparison with Sor4ListModel, because all that
changes is the sort function.
2020-07-21 16:16:34 +02:00
Benjamin Otte
153c671d8a sortlistmodel: Use timsort
Simply replace the old qsort() call with a timsort() call.

This is ultimately relevant because timsort is a LOT faster in merging
to already sorted lists (think items-chaged adding some items) or
reversing an existing list (think columnview sort order changes).

Benchmarks:

    initially sorting the model
                    qsort  timsort
    128,000 items   124ms    111ms
    256,000 items   264ms    250ms
2020-07-21 16:16:34 +02:00
Benjamin Otte
8657930c64 Add a timsort() implementation 2020-07-21 16:16:34 +02:00
Benjamin Otte
30953269ee REMOVE: Copy current sort model to Sor4ListModel
More benchmarking.
2020-07-20 22:28:01 +02:00
Benjamin Otte
7007655d49 sortlistmodel: Track item positions
The model now tracks the original positions on top of just the items so that
it can remove items in an items-changed emission.

It now takes twice as much memory but removes items much faster.

Benchmarks:

Removing 50% of a model:
                   before    after
   250,000 items    135ms     10ms
   500,000 items    300ms     25ms

Removing 1 item:
     4,000 items    2.2ms      0ms
     8,000 items    4.6ms      0ms
   500,000 items      ---   0.01ms
2020-07-20 22:28:01 +02:00
Benjamin Otte
de69a3ffe9 REMOVE: Copy dumb sort model to Sor2ListModel
More benchmarking.
2020-07-20 22:28:01 +02:00
Benjamin Otte
35daf93ac0 sortlistmodel: Replace with an array-based model
This is the dumbest possible sortmodel using an array:
Just grab all the items, put them in the array, qsort() the array.

Some benchmarks (setting a new model):

  125,000 items - old: 549ms
                  new: 115ms
  250,000 items - new: 250ms

This performance can not be kept for simple additions and removals
though.
2020-07-20 22:28:01 +02:00
Benjamin Otte
2b987d03a3 REMOVE: Copy the old sortmodel to GtkGSeqSortModel
This allows benchmarking against it.
2020-07-20 22:28:01 +02:00
Benjamin Otte
9e525e06da demo: Add faster sorters
This is just the existing sorters, but without the overhead of GObject
properties.
2020-07-20 22:28:01 +02:00
41 changed files with 11502 additions and 269 deletions

View File

@@ -662,7 +662,6 @@ create_color_grid (void)
{
GtkWidget *gridview;
GtkListItemFactory *factory;
GListModel *model, *selection;
gridview = gtk_grid_view_new ();
gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
@@ -676,13 +675,6 @@ create_color_grid (void)
gtk_grid_view_set_max_columns (GTK_GRID_VIEW (gridview), 24);
gtk_grid_view_set_enable_rubberband (GTK_GRID_VIEW (gridview), TRUE);
model = G_LIST_MODEL (gtk_sort_list_model_new (gtk_color_list_new (0), NULL));
selection = G_LIST_MODEL (gtk_multi_selection_new (model));
gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), selection);
g_object_unref (selection);
g_object_unref (model);
return gridview;
}
@@ -835,6 +827,70 @@ update_selection_average (GListModel *model,
g_object_unref (color);
}
static void
update_progress_cb (GtkSortListModel *model,
GParamSpec *pspec,
GtkProgressBar *progress)
{
guint total;
guint pending;
total = g_list_model_get_n_items (G_LIST_MODEL (model));
total = MAX (total, 1); /* avoid div by 0 below */
pending = gtk_sort_list_model_get_pending (model);
gtk_widget_set_visible (GTK_WIDGET (progress), pending != 0);
gtk_progress_bar_set_fraction (progress, (total - pending) / (double) total);
}
static int
compare_red (gconstpointer a,
gconstpointer b,
gpointer unused)
{
GtkColor *colora = (GtkColor *) a;
GtkColor *colorb = (GtkColor *) b;
if (colora->color.red < colorb->color.red)
return GTK_ORDERING_LARGER;
else if (colora->color.red > colorb->color.red)
return GTK_ORDERING_SMALLER;
else
return GTK_ORDERING_EQUAL;
}
static int
compare_green (gconstpointer a,
gconstpointer b,
gpointer unused)
{
GtkColor *colora = (GtkColor *) a;
GtkColor *colorb = (GtkColor *) b;
if (colora->color.green < colorb->color.green)
return GTK_ORDERING_LARGER;
else if (colora->color.green > colorb->color.green)
return GTK_ORDERING_SMALLER;
else
return GTK_ORDERING_EQUAL;
}
static int
compare_blue (gconstpointer a,
gconstpointer b,
gpointer unused)
{
GtkColor *colora = (GtkColor *) a;
GtkColor *colorb = (GtkColor *) b;
if (colora->color.blue < colorb->color.blue)
return GTK_ORDERING_LARGER;
else if (colora->color.blue > colorb->color.blue)
return GTK_ORDERING_SMALLER;
else
return GTK_ORDERING_EQUAL;
}
static GtkWidget *window = NULL;
GtkWidget *
@@ -842,10 +898,11 @@ do_listview_colors (GtkWidget *do_widget)
{
if (window == NULL)
{
GtkWidget *header, *gridview, *sw, *box, *dropdown;
GtkMultiSelection *selection;
GtkSortListModel *sort_model;
GtkWidget *header, *overlay, *gridview, *sw, *box, *dropdown;
GtkListItemFactory *factory;
GListStore *factories;
GListModel *model;
GtkSorter *sorter;
GtkSorter *multi_sorter;
GListStore *sorters;
@@ -863,6 +920,7 @@ do_listview_colors (GtkWidget *do_widget)
GtkWidget *selection_average_picture;
GtkWidget *selection_info_toggle;
GtkWidget *selection_info_revealer;
GtkWidget *progress;
GtkCssProvider *provider;
provider = gtk_css_provider_new ();
@@ -872,6 +930,10 @@ do_listview_colors (GtkWidget *do_widget)
800);
g_object_unref (provider);
sort_model = gtk_sort_list_model_new (gtk_color_list_new (0), NULL);
gtk_sort_list_model_set_incremental (sort_model, TRUE);
selection = GTK_MULTI_SELECTION (gtk_multi_selection_new (G_LIST_MODEL (sort_model)));
window = gtk_window_new ();
gtk_window_set_title (GTK_WINDOW (window), "Colors");
header = gtk_header_bar_new ();
@@ -882,8 +944,17 @@ do_listview_colors (GtkWidget *do_widget)
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer*)&window);
overlay = gtk_overlay_new ();
gtk_window_set_child (GTK_WINDOW (window), overlay);
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_window_set_child (GTK_WINDOW (window), box);
gtk_overlay_set_child (GTK_OVERLAY (overlay), box);
progress = gtk_progress_bar_new ();
gtk_widget_set_hexpand (progress, TRUE);
gtk_widget_set_valign (progress, GTK_ALIGN_START);
g_signal_connect (sort_model, "notify::pending", G_CALLBACK (update_progress_cb), progress);
gtk_overlay_add_overlay (GTK_OVERLAY (overlay), progress);
selection_info_revealer = gtk_revealer_new ();
gtk_box_append (GTK_BOX (box), selection_info_revealer);
@@ -936,12 +1007,12 @@ do_listview_colors (GtkWidget *do_widget)
gtk_box_append (GTK_BOX (box), sw);
gridview = create_color_grid ();
gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), G_LIST_MODEL (selection));
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gridview);
gtk_widget_set_hexpand (sw, TRUE);
gtk_widget_set_vexpand (sw, TRUE);
model = gtk_grid_view_get_model (GTK_GRID_VIEW (gridview));
selection_filter = G_LIST_MODEL (gtk_selection_filter_model_new (GTK_SELECTION_MODEL (model)));
selection_filter = G_LIST_MODEL (gtk_selection_filter_model_new (GTK_SELECTION_MODEL (selection)));
g_signal_connect (selection_filter, "items-changed", G_CALLBACK (update_selection_count), selection_size_label);
g_signal_connect (selection_filter, "items-changed", G_CALLBACK (update_selection_average), selection_average_picture);
@@ -950,9 +1021,6 @@ do_listview_colors (GtkWidget *do_widget)
g_object_unref (selection_filter);
g_object_unref (no_selection);
model = gtk_multi_selection_get_model (GTK_MULTI_SELECTION (model));
g_object_ref (model);
selection_info_toggle = gtk_toggle_button_new ();
gtk_button_set_icon_name (GTK_BUTTON (selection_info_toggle), "emblem-important-symbolic");
gtk_widget_set_tooltip_text (selection_info_toggle, "Show selection info");
@@ -965,7 +1033,7 @@ do_listview_colors (GtkWidget *do_widget)
button = gtk_button_new_with_mnemonic ("_Refill");
g_signal_connect (button, "clicked",
G_CALLBACK (refill),
gtk_sort_list_model_get_model (GTK_SORT_LIST_MODEL (model)));
gtk_sort_list_model_get_model (sort_model));
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button);
@@ -980,15 +1048,14 @@ do_listview_colors (GtkWidget *do_widget)
gtk_label_set_width_chars (GTK_LABEL (label), len + 2);
gtk_label_set_xalign (GTK_LABEL (label), 1);
g_signal_connect (gtk_grid_view_get_model (GTK_GRID_VIEW (gridview)),
"items-changed", G_CALLBACK (items_changed_cb), label);
g_signal_connect (selection, "items-changed", G_CALLBACK (items_changed_cb), label);
gtk_header_bar_pack_start (GTK_HEADER_BAR (header), label);
dropdown = gtk_drop_down_new ();
gtk_drop_down_set_from_strings (GTK_DROP_DOWN (dropdown), (const char *[]) { "8", "64", "512", "4096", "32768", "262144", "2097152", "16777216", NULL });
g_signal_connect (dropdown, "notify::selected",
G_CALLBACK (limit_changed_cb),
gtk_sort_list_model_get_model (GTK_SORT_LIST_MODEL (model)));
gtk_sort_list_model_get_model (sort_model));
g_signal_connect (dropdown, "notify::selected",
G_CALLBACK (limit_changed_cb2),
label);
@@ -1023,18 +1090,30 @@ do_listview_colors (GtkWidget *do_widget)
g_list_store_append (sorters, sorter);
gtk_multi_sorter_append (GTK_MULTI_SORTER (multi_sorter), sorter);
sorter = gtk_custom_sorter_new (compare_red, NULL, NULL);
set_title (sorter, "Red (fast)");
g_list_store_append (sorters, sorter);
sorter = gtk_numeric_sorter_new (gtk_property_expression_new (GTK_TYPE_COLOR, NULL, "green"));
gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING);
set_title (sorter, "Green");
g_list_store_append (sorters, sorter);
gtk_multi_sorter_append (GTK_MULTI_SORTER (multi_sorter), sorter);
sorter = gtk_custom_sorter_new (compare_green, NULL, NULL);
set_title (sorter, "Green (fast)");
g_list_store_append (sorters, sorter);
sorter = gtk_numeric_sorter_new (gtk_property_expression_new (GTK_TYPE_COLOR, NULL, "blue"));
gtk_numeric_sorter_set_sort_order (GTK_NUMERIC_SORTER (sorter), GTK_SORT_DESCENDING);
set_title (sorter, "Blue");
g_list_store_append (sorters, sorter);
gtk_multi_sorter_append (GTK_MULTI_SORTER (multi_sorter), sorter);
sorter = gtk_custom_sorter_new (compare_blue, NULL, NULL);
set_title (sorter, "Blue (fast)");
g_list_store_append (sorters, sorter);
set_title (multi_sorter, "RGB");
g_list_store_append (sorters, multi_sorter);
g_object_unref (multi_sorter);
@@ -1080,7 +1159,7 @@ do_listview_colors (GtkWidget *do_widget)
gtk_drop_down_set_model (GTK_DROP_DOWN (dropdown), G_LIST_MODEL (sorters));
g_object_unref (sorters);
g_object_bind_property (dropdown, "selected-item", model, "sorter", G_BINDING_SYNC_CREATE);
g_object_bind_property (dropdown, "selected-item", sort_model, "sorter", G_BINDING_SYNC_CREATE);
factories = g_list_store_new (GTK_TYPE_LIST_ITEM_FACTORY);
@@ -1112,7 +1191,6 @@ do_listview_colors (GtkWidget *do_widget)
g_object_unref (factories);
g_object_bind_property (dropdown, "selected-item", gridview, "factory", G_BINDING_SYNC_CREATE);
g_object_unref (model);
}
if (!gtk_widget_get_visible (window))

View File

@@ -2832,6 +2832,9 @@ gtk_sort_list_model_set_sorter
gtk_sort_list_model_get_sorter
gtk_sort_list_model_set_model
gtk_sort_list_model_get_model
gtk_sort_list_model_set_incremental
gtk_sort_list_model_get_incremental
gtk_sort_list_model_get_peanding
<SUBSECTION Standard>
GTK_SORT_LIST_MODEL
GTK_IS_SORT_LIST_MODEL

View File

@@ -147,6 +147,7 @@
#include <gtk/gtkgrid.h>
#include <gtk/gtkgridlayout.h>
#include <gtk/gtkgridview.h>
#include <gtk/gtkgseqsortmodel.h>
#include <gtk/gtkheaderbar.h>
#include <gtk/gtkicontheme.h>
#include <gtk/gtkiconview.h>
@@ -233,6 +234,10 @@
#include <gtk/gtkslicelistmodel.h>
#include <gtk/gtksnapshot.h>
#include <gtk/gtksorter.h>
#include <gtk/gtksor2listmodel.h>
#include <gtk/gtksor3listmodel.h>
#include <gtk/gtksor4listmodel.h>
#include <gtk/gtksor5listmodel.h>
#include <gtk/gtksortlistmodel.h>
#include <gtk/gtkstacksidebar.h>
#include <gtk/gtksizegroup.h>
@@ -256,6 +261,10 @@
#include <gtk/gtktexttag.h>
#include <gtk/gtktexttagtable.h>
#include <gtk/gtktextview.h>
#include <gtk/gtktim1sortmodel.h>
#include <gtk/gtktim2sortmodel.h>
#include <gtk/gtktim3sortmodel.h>
#include <gtk/gtktim4sortmodel.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtktooltip.h>
#include <gtk/gtktestutils.h>

564
gtk/gtkgseqsortmodel.c Normal file
View File

@@ -0,0 +1,564 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkgseqsortmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
/**
* SECTION:gtksortlistmodel
* @title: GtkGSeqSortModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkGSeqSortModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkGSeqSortModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkGSeqSortModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
typedef struct _GtkGSeqSortEntry GtkGSeqSortEntry;
struct _GtkGSeqSortModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
GSequence *sorted; /* NULL if known unsorted */
GSequence *unsorted; /* NULL if known unsorted */
};
struct _GtkGSeqSortModelClass
{
GObjectClass parent_class;
};
struct _GtkGSeqSortEntry
{
GSequenceIter *sorted_iter;
GSequenceIter *unsorted_iter;
gpointer item; /* holds ref */
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static void
gtk_gseq_sort_entry_free (gpointer data)
{
GtkGSeqSortEntry *entry = data;
g_object_unref (entry->item);
g_slice_free (GtkGSeqSortEntry, entry);
}
static GType
gtk_gseq_sort_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_gseq_sort_model_get_n_items (GListModel *list)
{
GtkGSeqSortModel *self = GTK_GSEQ_SORT_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_gseq_sort_model_get_item (GListModel *list,
guint position)
{
GtkGSeqSortModel *self = GTK_GSEQ_SORT_MODEL (list);
GSequenceIter *iter;
GtkGSeqSortEntry *entry;
if (self->model == NULL)
return NULL;
if (self->unsorted == NULL)
return g_list_model_get_item (self->model, position);
iter = g_sequence_get_iter_at_pos (self->sorted, position);
if (g_sequence_iter_is_end (iter))
return NULL;
entry = g_sequence_get (iter);
return g_object_ref (entry->item);
}
static void
gtk_gseq_sort_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_gseq_sort_model_get_item_type;
iface->get_n_items = gtk_gseq_sort_model_get_n_items;
iface->get_item = gtk_gseq_sort_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkGSeqSortModel, gtk_gseq_sort_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_gseq_sort_model_model_init))
static void
gtk_gseq_sort_model_remove_items (GtkGSeqSortModel *self,
guint position,
guint n_items,
guint *unmodified_start,
guint *unmodified_end)
{
GSequenceIter *unsorted_iter;
guint i, pos, start, end, length_before;
start = end = length_before = g_sequence_get_length (self->sorted);
unsorted_iter = g_sequence_get_iter_at_pos (self->unsorted, position);
for (i = 0; i < n_items ; i++)
{
GtkGSeqSortEntry *entry;
GSequenceIter *next;
next = g_sequence_iter_next (unsorted_iter);
entry = g_sequence_get (unsorted_iter);
pos = g_sequence_iter_get_position (entry->sorted_iter);
start = MIN (start, pos);
end = MIN (end, length_before - i - 1 - pos);
g_sequence_remove (entry->unsorted_iter);
g_sequence_remove (entry->sorted_iter);
unsorted_iter = next;
}
*unmodified_start = start;
*unmodified_end = end;
}
static int
_sort_func (gconstpointer item1,
gconstpointer item2,
gpointer data)
{
GtkGSeqSortEntry *entry1 = (GtkGSeqSortEntry *) item1;
GtkGSeqSortEntry *entry2 = (GtkGSeqSortEntry *) item2;
GtkOrdering result;
result = gtk_sorter_compare (GTK_SORTER (data), entry1->item, entry2->item);
if (result == GTK_ORDERING_EQUAL)
result = g_sequence_iter_compare (entry1->unsorted_iter, entry2->unsorted_iter);
return result;
}
static void
gtk_gseq_sort_model_add_items (GtkGSeqSortModel *self,
guint position,
guint n_items,
guint *unmodified_start,
guint *unmodified_end)
{
GSequenceIter *unsorted_end;
guint i, pos, start, end, length_before;
unsorted_end = g_sequence_get_iter_at_pos (self->unsorted, position);
start = end = length_before = g_sequence_get_length (self->sorted);
for (i = 0; i < n_items; i++)
{
GtkGSeqSortEntry *entry = g_slice_new0 (GtkGSeqSortEntry);
entry->item = g_list_model_get_item (self->model, position + i);
entry->unsorted_iter = g_sequence_insert_before (unsorted_end, entry);
entry->sorted_iter = g_sequence_insert_sorted (self->sorted, entry, _sort_func, self->sorter);
if (unmodified_start != NULL || unmodified_end != NULL)
{
pos = g_sequence_iter_get_position (entry->sorted_iter);
start = MIN (start, pos);
end = MIN (end, length_before + i - pos);
}
}
if (unmodified_start)
*unmodified_start = start;
if (unmodified_end)
*unmodified_end = end;
}
static void
gtk_gseq_sort_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkGSeqSortModel *self)
{
guint n_items, start, end, start2, end2;
if (removed == 0 && added == 0)
return;
if (self->sorted == NULL)
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
return;
}
gtk_gseq_sort_model_remove_items (self, position, removed, &start, &end);
gtk_gseq_sort_model_add_items (self, position, added, &start2, &end2);
start = MIN (start, start2);
end = MIN (end, end2);
n_items = g_sequence_get_length (self->sorted) - start - end;
g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
}
static void
gtk_gseq_sort_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkGSeqSortModel *self = GTK_GSEQ_SORT_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_gseq_sort_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_gseq_sort_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_gseq_sort_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkGSeqSortModel *self = GTK_GSEQ_SORT_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_gseq_sort_model_clear_sequences (GtkGSeqSortModel *self)
{
g_clear_pointer (&self->unsorted, g_sequence_free);
g_clear_pointer (&self->sorted, g_sequence_free);
}
static void
gtk_gseq_sort_model_create_sequences (GtkGSeqSortModel *self)
{
if (self->sorted)
return;
if (self->sorter == NULL ||
self->model == NULL ||
gtk_sorter_get_order (self->sorter) == GTK_SORTER_ORDER_NONE)
return;
self->sorted = g_sequence_new (gtk_gseq_sort_entry_free);
self->unsorted = g_sequence_new (NULL);
gtk_gseq_sort_model_add_items (self, 0, g_list_model_get_n_items (self->model), NULL, NULL);
}
static void gtk_gseq_sort_model_resort (GtkGSeqSortModel *self);
static void
gtk_gseq_sort_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkGSeqSortModel *self)
{
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_gseq_sort_model_clear_sequences (self);
else if (self->sorted == NULL)
{
guint n_items = g_list_model_get_n_items (self->model);
gtk_gseq_sort_model_create_sequences (self);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
else
gtk_gseq_sort_model_resort (self);
}
static void
gtk_gseq_sort_model_clear_model (GtkGSeqSortModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_gseq_sort_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_gseq_sort_model_clear_sequences (self);
}
static void
gtk_gseq_sort_model_clear_sorter (GtkGSeqSortModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_gseq_sort_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
gtk_gseq_sort_model_clear_sequences (self);
}
static void
gtk_gseq_sort_model_dispose (GObject *object)
{
GtkGSeqSortModel *self = GTK_GSEQ_SORT_MODEL (object);
gtk_gseq_sort_model_clear_model (self);
gtk_gseq_sort_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_gseq_sort_model_parent_class)->dispose (object);
};
static void
gtk_gseq_sort_model_class_init (GtkGSeqSortModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_gseq_sort_model_set_property;
gobject_class->get_property = gtk_gseq_sort_model_get_property;
gobject_class->dispose = gtk_gseq_sort_model_dispose;
/**
* GtkGSeqSortModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkGSeqSortModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_gseq_sort_model_init (GtkGSeqSortModel *self)
{
}
/**
* gtk_gseq_sort_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkGSeqSortModel
**/
GtkGSeqSortModel *
gtk_gseq_sort_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkGSeqSortModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_GSEQ_SORT_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_gseq_sort_model_set_model:
* @self: a #GtkGSeqSortModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_gseq_sort_model_set_model (GtkGSeqSortModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_GSEQ_SORT_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_gseq_sort_model_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_gseq_sort_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_gseq_sort_model_create_sequences (self);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_gseq_sort_model_get_model:
* @self: a #GtkGSeqSortModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_gseq_sort_model_get_model (GtkGSeqSortModel *self)
{
g_return_val_if_fail (GTK_IS_GSEQ_SORT_MODEL (self), NULL);
return self->model;
}
static void
gtk_gseq_sort_model_resort (GtkGSeqSortModel *self)
{
guint n_items;
g_return_if_fail (GTK_IS_GSEQ_SORT_MODEL (self));
if (self->sorted == NULL)
return;
n_items = g_list_model_get_n_items (self->model);
if (n_items <= 1)
return;
g_sequence_sort (self->sorted, _sort_func, self->sorter);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
/**
* gtk_gseq_sort_model_set_sorter:
* @self: a #GtkGSeqSortModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_gseq_sort_model_set_sorter (GtkGSeqSortModel *self,
GtkSorter *sorter)
{
guint n_items;
g_return_if_fail (GTK_IS_GSEQ_SORT_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_gseq_sort_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_gseq_sort_model_sorter_changed_cb), self);
}
gtk_gseq_sort_model_create_sequences (self);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_gseq_sort_model_get_sorter:
* @self: a #GtkSortLisTModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_gseq_sort_model_get_sorter (GtkGSeqSortModel *self)
{
g_return_val_if_fail (GTK_IS_GSEQ_SORT_MODEL (self), NULL);
return self->sorter;
}

57
gtk/gtkgseqsortmodel.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_GSEQ_SORT_MODEL_H__
#define __GTK_GSEQ_SORT_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_GSEQ_SORT_MODEL (gtk_gseq_sort_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkGSeqSortModel, gtk_gseq_sort_model, GTK, GSEQ_SORT_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkGSeqSortModel * gtk_gseq_sort_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_gseq_sort_model_set_sorter (GtkGSeqSortModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_gseq_sort_model_get_sorter (GtkGSeqSortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_gseq_sort_model_set_model (GtkGSeqSortModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_gseq_sort_model_get_model (GtkGSeqSortModel *self);
G_END_DECLS
#endif /* __GTK_GSEQ_SORT_MODEL_H__ */

View File

@@ -23,6 +23,7 @@
#include "gtkbuildable.h"
#include "gtkintl.h"
#include "gtksorterprivate.h"
#include "gtktypebuiltins.h"
#define GDK_ARRAY_TYPE_NAME GtkSorters
@@ -48,6 +49,141 @@ struct _GtkMultiSorter
GtkSorters sorters;
};
typedef struct _GtkMultiSortKey GtkMultiSortKey;
typedef struct _GtkMultiSortKeys GtkMultiSortKeys;
struct _GtkMultiSortKey
{
gsize offset;
GtkSortKeys *keys;
};
struct _GtkMultiSortKeys
{
GtkSortKeys parent_keys;
guint n_keys;
GtkMultiSortKey keys[];
};
static void
gtk_multi_sort_keys_free (GtkSortKeys *keys)
{
GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
gsize i;
for (i = 0; i < self->n_keys; i++)
gtk_sort_keys_unref (self->keys[i].keys);
g_slice_free1 (sizeof (GtkMultiSortKeys) + self->n_keys * sizeof (GtkMultiSortKey), self);
}
static int
gtk_multi_sort_keys_compare (gconstpointer a,
gconstpointer b,
gpointer data)
{
GtkMultiSortKeys *self = (GtkMultiSortKeys *) data;
gsize i;
for (i = 0; i < self->n_keys; i++)
{
GtkOrdering result = gtk_sort_keys_compare (self->keys[i].keys,
((const char *) a) + self->keys[i].offset,
((const char *) b) + self->keys[i].offset);
if (result != GTK_ORDERING_EQUAL)
return result;
}
return GTK_ORDERING_EQUAL;
}
static gboolean
gtk_multi_sort_keys_is_compatible (GtkSortKeys *keys,
GtkSortKeys *other)
{
GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
GtkMultiSortKeys *compare = (GtkMultiSortKeys *) other;
gsize i;
if (keys->klass != other->klass)
return FALSE;
if (self->n_keys != compare->n_keys)
return FALSE;
for (i = 0; i < self->n_keys; i++)
{
if (!gtk_sort_keys_is_compatible (self->keys[i].keys, compare->keys[i].keys))
return FALSE;
}
return TRUE;
}
static void
gtk_multi_sort_keys_init_key (GtkSortKeys *keys,
gpointer item,
gpointer key_memory)
{
GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
char *key = (char *) key_memory;
gsize i;
for (i = 0; i < self->n_keys; i++)
gtk_sort_keys_init_key (self->keys[i].keys, item, key + self->keys[i].offset);
}
static void
gtk_multi_sort_keys_clear_key (GtkSortKeys *keys,
gpointer key_memory)
{
GtkMultiSortKeys *self = (GtkMultiSortKeys *) keys;
char *key = (char *) key_memory;
gsize i;
for (i = 0; i < self->n_keys; i++)
gtk_sort_keys_clear_key (self->keys[i].keys, key + self->keys[i].offset);
}
static const GtkSortKeysClass GTK_MULTI_SORT_KEYS_CLASS =
{
gtk_multi_sort_keys_free,
gtk_multi_sort_keys_compare,
gtk_multi_sort_keys_is_compatible,
gtk_multi_sort_keys_init_key,
gtk_multi_sort_keys_clear_key,
};
static GtkSortKeys *
gtk_multi_sort_keys_new (GtkMultiSorter *self)
{
GtkMultiSortKeys *result;
GtkSortKeys *keys;
gsize i;
if (gtk_sorters_get_size (&self->sorters) == 0)
return gtk_sort_keys_new_equal ();
else if (gtk_sorters_get_size (&self->sorters) == 1)
return gtk_sorter_get_keys (gtk_sorters_get (&self->sorters, 0));
keys = gtk_sort_keys_alloc (&GTK_MULTI_SORT_KEYS_CLASS,
sizeof (GtkMultiSortKeys) + gtk_sorters_get_size (&self->sorters) * sizeof (GtkMultiSortKey),
0, 1);
result = (GtkMultiSortKeys *) keys;
result->n_keys = gtk_sorters_get_size (&self->sorters);
for (i = 0; i < result->n_keys; i++)
{
result->keys[i].keys = gtk_sorter_get_keys (gtk_sorters_get (&self->sorters, i));
result->keys[i].offset = GTK_SORT_KEYS_ALIGN (keys->key_size, gtk_sort_keys_get_key_align (result->keys[i].keys));
keys->key_size = result->keys[i].offset + gtk_sort_keys_get_key_size (result->keys[i].keys);
keys->key_align = MAX (keys->key_align, gtk_sort_keys_get_key_align (result->keys[i].keys));
}
return keys;
}
static GType
gtk_multi_sorter_get_item_type (GListModel *list)
{
@@ -186,7 +322,9 @@ gtk_multi_sorter_changed_cb (GtkSorter *sorter,
g_assert_not_reached ();
change = GTK_SORTER_CHANGE_DIFFERENT;
}
gtk_sorter_changed (GTK_SORTER (self), change);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
change,
gtk_multi_sort_keys_new (self));
}
static void
@@ -221,6 +359,10 @@ static void
gtk_multi_sorter_init (GtkMultiSorter *self)
{
gtk_sorters_init (&self->sorters);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_multi_sort_keys_new (self));
}
/**
@@ -260,7 +402,9 @@ gtk_multi_sorter_append (GtkMultiSorter *self,
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_multi_sorter_changed_cb), self);
gtk_sorters_append (&self->sorters, sorter);
gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_MORE_STRICT);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_MORE_STRICT,
gtk_multi_sort_keys_new (self));
}
/**
@@ -290,6 +434,8 @@ gtk_multi_sorter_remove (GtkMultiSorter *self,
g_signal_handlers_disconnect_by_func (sorter, gtk_multi_sorter_changed_cb, self);
gtk_sorters_splice (&self->sorters, position, 1, NULL, 0);
gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_LESS_STRICT);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_LESS_STRICT,
gtk_multi_sort_keys_new (self));
}

View File

@@ -22,6 +22,7 @@
#include "gtknumericsorter.h"
#include "gtkintl.h"
#include "gtksorterprivate.h"
#include "gtktypebuiltins.h"
#include <math.h>
@@ -69,6 +70,265 @@ static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
_result = GTK_ORDERING_EQUAL; \
}G_STMT_END
typedef struct _GtkNumericSortKeys GtkNumericSortKeys;
struct _GtkNumericSortKeys
{
GtkSortKeys keys;
GtkExpression *expression;
};
static void
gtk_numeric_sort_keys_free (GtkSortKeys *keys)
{
GtkNumericSortKeys *self = (GtkNumericSortKeys *) keys;
gtk_expression_unref (self->expression);
g_slice_free (GtkNumericSortKeys, self);
}
#define COMPARE_FUNC(type, name, _a, _b) \
static int \
gtk_ ## type ## _sort_keys_compare_ ## name (gconstpointer a, \
gconstpointer b, \
gpointer unused) \
{ \
type num1 = *(type *) _a; \
type num2 = *(type *) _b; \
\
if (num1 < num2) \
return GTK_ORDERING_SMALLER; \
else if (num1 > num2) \
return GTK_ORDERING_LARGER; \
else \
return GTK_ORDERING_EQUAL; \
}
#define COMPARE_FUNCS(type) \
COMPARE_FUNC(type, ascending, a, b) \
COMPARE_FUNC(type, descending, b, a)
#define FLOAT_COMPARE_FUNC(type, name, _a, _b) \
static int \
gtk_ ## type ## _sort_keys_compare_ ## name (gconstpointer a, \
gconstpointer b, \
gpointer unused) \
{ \
type num1 = *(type *) _a; \
type num2 = *(type *) _b; \
\
if (isnan (num1) && isnan (num2)) \
return GTK_ORDERING_EQUAL; \
else if (isnan (num1)) \
return GTK_ORDERING_LARGER; \
else if (isnan (num2)) \
return GTK_ORDERING_SMALLER; \
else if (num1 < num2) \
return GTK_ORDERING_SMALLER; \
else if (num1 > num2) \
return GTK_ORDERING_LARGER; \
else \
return GTK_ORDERING_EQUAL; \
}
#define FLOAT_COMPARE_FUNCS(type) \
FLOAT_COMPARE_FUNC(type, ascending, a, b) \
FLOAT_COMPARE_FUNC(type, descending, b, a)
COMPARE_FUNCS(char)
COMPARE_FUNCS(guchar)
COMPARE_FUNCS(int)
COMPARE_FUNCS(guint)
FLOAT_COMPARE_FUNCS(float)
FLOAT_COMPARE_FUNCS(double)
COMPARE_FUNCS(long)
COMPARE_FUNCS(gulong)
COMPARE_FUNCS(gint64)
COMPARE_FUNCS(guint64)
#define NUMERIC_SORT_KEYS(TYPE, key_type, type, default_value) \
static void \
gtk_ ## type ## _sort_keys_init_key (GtkSortKeys *keys, \
gpointer item, \
gpointer key_memory) \
{ \
GtkNumericSortKeys *self = (GtkNumericSortKeys *) keys; \
key_type *key = (key_type *) key_memory; \
GValue value = G_VALUE_INIT; \
\
if (gtk_expression_evaluate (self->expression, item, &value)) \
*key = g_value_get_ ## type (&value); \
else \
*key = default_value; \
\
g_value_unset (&value); \
} \
\
static gboolean \
gtk_ ## type ## _sort_keys_is_compatible (GtkSortKeys *keys, \
GtkSortKeys *other); \
\
static const GtkSortKeysClass GTK_ASCENDING_ ## TYPE ## _SORT_KEYS_CLASS = \
{ \
gtk_numeric_sort_keys_free, \
gtk_ ## key_type ## _sort_keys_compare_ascending, \
gtk_ ## type ## _sort_keys_is_compatible, \
gtk_ ## type ## _sort_keys_init_key, \
NULL \
}; \
\
static const GtkSortKeysClass GTK_DESCENDING_ ## TYPE ## _SORT_KEYS_CLASS = \
{ \
gtk_numeric_sort_keys_free, \
gtk_ ## key_type ## _sort_keys_compare_descending, \
gtk_ ## type ## _sort_keys_is_compatible, \
gtk_ ## type ## _sort_keys_init_key, \
NULL \
}; \
\
static gboolean \
gtk_ ## type ## _sort_keys_is_compatible (GtkSortKeys *keys, \
GtkSortKeys *other) \
{ \
GtkNumericSorter *self = (GtkNumericSorter *) keys; \
GtkNumericSorter *compare = (GtkNumericSorter *) other; \
\
if (other->klass != &GTK_ASCENDING_ ## TYPE ## _SORT_KEYS_CLASS && \
other->klass != &GTK_DESCENDING_ ## TYPE ## _SORT_KEYS_CLASS) \
return FALSE; \
\
return self->expression == compare->expression; \
}
NUMERIC_SORT_KEYS(BOOLEAN, char, boolean, FALSE)
NUMERIC_SORT_KEYS(CHAR, char, char, G_MININT8)
NUMERIC_SORT_KEYS(UCHAR, guchar, uchar, G_MAXUINT8)
NUMERIC_SORT_KEYS(INT, int, int, G_MININT)
NUMERIC_SORT_KEYS(UINT, guint, uint, G_MAXUINT)
NUMERIC_SORT_KEYS(FLOAT, float, float, NAN)
NUMERIC_SORT_KEYS(DOUBLE, double, double, NAN)
NUMERIC_SORT_KEYS(LONG, long, long, G_MINLONG)
NUMERIC_SORT_KEYS(ULONG, gulong, ulong, G_MAXLONG)
NUMERIC_SORT_KEYS(INT64, gint64, int64, G_MININT64)
NUMERIC_SORT_KEYS(UINT64, guint64, uint64, G_MAXUINT64)
static GtkSortKeys *
gtk_numeric_sort_keys_new (GtkNumericSorter *self)
{
GtkNumericSortKeys *result;
if (self->expression == NULL)
return gtk_sort_keys_new_equal ();
switch (gtk_expression_get_value_type (self->expression))
{
case G_TYPE_BOOLEAN:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_BOOLEAN_SORT_KEYS_CLASS
: &GTK_DESCENDING_BOOLEAN_SORT_KEYS_CLASS,
sizeof (char),
sizeof (char));
break;
case G_TYPE_CHAR:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_CHAR_SORT_KEYS_CLASS
: &GTK_DESCENDING_CHAR_SORT_KEYS_CLASS,
sizeof (char),
sizeof (char));
break;
case G_TYPE_UCHAR:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_UCHAR_SORT_KEYS_CLASS
: &GTK_DESCENDING_UCHAR_SORT_KEYS_CLASS,
sizeof (guchar),
sizeof (guchar));
break;
case G_TYPE_INT:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_INT_SORT_KEYS_CLASS
: &GTK_DESCENDING_INT_SORT_KEYS_CLASS,
sizeof (int),
sizeof (int));
break;
case G_TYPE_UINT:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_UINT_SORT_KEYS_CLASS
: &GTK_DESCENDING_UINT_SORT_KEYS_CLASS,
sizeof (guint),
sizeof (guint));
break;
case G_TYPE_FLOAT:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_FLOAT_SORT_KEYS_CLASS
: &GTK_DESCENDING_FLOAT_SORT_KEYS_CLASS,
sizeof (float),
sizeof (float));
break;
case G_TYPE_DOUBLE:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_DOUBLE_SORT_KEYS_CLASS
: &GTK_DESCENDING_DOUBLE_SORT_KEYS_CLASS,
sizeof (double),
sizeof (double));
break;
case G_TYPE_LONG:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_LONG_SORT_KEYS_CLASS
: &GTK_DESCENDING_LONG_SORT_KEYS_CLASS,
sizeof (long),
sizeof (long));
break;
case G_TYPE_ULONG:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_ULONG_SORT_KEYS_CLASS
: &GTK_DESCENDING_ULONG_SORT_KEYS_CLASS,
sizeof (gulong),
sizeof (gulong));
break;
case G_TYPE_INT64:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_INT64_SORT_KEYS_CLASS
: &GTK_DESCENDING_INT64_SORT_KEYS_CLASS,
sizeof (gint64),
sizeof (gint64));
break;
case G_TYPE_UINT64:
result = gtk_sort_keys_new (GtkNumericSortKeys,
self->sort_order == GTK_SORT_ASCENDING
? &GTK_ASCENDING_UINT64_SORT_KEYS_CLASS
: &GTK_DESCENDING_UINT64_SORT_KEYS_CLASS,
sizeof (guint64),
sizeof (guint64));
break;
default:
g_critical ("Invalid value type %s for expression\n", g_type_name (gtk_expression_get_value_type (self->expression)));
return gtk_sort_keys_new_equal ();
}
result->expression = gtk_expression_ref (self->expression);
return (GtkSortKeys *) result;
}
static GtkOrdering
gtk_numeric_sorter_compare (GtkSorter *sorter,
gpointer item1,
@@ -304,6 +564,10 @@ static void
gtk_numeric_sorter_init (GtkNumericSorter *self)
{
self->sort_order = GTK_SORT_ASCENDING;
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_numeric_sort_keys_new (self));
}
/**
@@ -373,7 +637,9 @@ gtk_numeric_sorter_set_expression (GtkNumericSorter *self,
if (expression)
self->expression = gtk_expression_ref (expression);
gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_numeric_sort_keys_new (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]);
}
@@ -396,7 +662,9 @@ gtk_numeric_sorter_set_sort_order (GtkNumericSorter *self,
self->sort_order = sort_order;
gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_INVERTED);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_INVERTED,
gtk_numeric_sort_keys_new (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORT_ORDER]);
}

450
gtk/gtksor2listmodel.c Normal file
View File

@@ -0,0 +1,450 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksor2listmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#define GDK_ARRAY_ELEMENT_TYPE GObject *
#define GDK_ARRAY_TYPE_NAME SortArray
#define GDK_ARRAY_NAME sort_array
#define GDK_ARRAY_FREE_FUNC g_object_unref
#include "gdk/gdkarrayimpl.c"
/**
* SECTION:gtksor2listmodel
* @title: GtkSor2ListModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkSor2ListModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkSor2ListModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkSor2ListModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkSor2ListModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
SortArray items; /* empty if known unsorted */
};
struct _GtkSor2ListModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_sor2_list_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_sor2_list_model_get_n_items (GListModel *list)
{
GtkSor2ListModel *self = GTK_SOR2_LIST_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_sor2_list_model_get_item (GListModel *list,
guint position)
{
GtkSor2ListModel *self = GTK_SOR2_LIST_MODEL (list);
if (self->model == NULL)
return NULL;
if (sort_array_is_empty (&self->items))
return g_list_model_get_item (self->model, position);
if (position >= sort_array_get_size (&self->items))
return NULL;
return g_object_ref (sort_array_get (&self->items, position));
}
static void
gtk_sor2_list_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_sor2_list_model_get_item_type;
iface->get_n_items = gtk_sor2_list_model_get_n_items;
iface->get_item = gtk_sor2_list_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkSor2ListModel, gtk_sor2_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sor2_list_model_model_init))
static void
gtk_sor2_list_model_clear_items (GtkSor2ListModel *self)
{
sort_array_clear (&self->items);
}
static void
gtk_sor2_list_model_create_items (GtkSor2ListModel *self)
{
guint i, n_items;
if (self->sorter == NULL ||
self->model == NULL ||
gtk_sorter_get_order (self->sorter) == GTK_SORTER_ORDER_NONE)
return;
n_items = g_list_model_get_n_items (self->model);
sort_array_reserve (&self->items, n_items);
for (i = 0; i < n_items; i++)
{
sort_array_append (&self->items, g_list_model_get_item (self->model, i));
}
}
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer data)
{
return gtk_sorter_compare (data, *(GObject **) a, *(GObject **) b);
}
static void
gtk_sor2_list_model_resort (GtkSor2ListModel *self)
{
g_qsort_with_data (sort_array_get_data (&self->items),
sort_array_get_size (&self->items),
sizeof (GObject *),
sort_func,
self->sorter);
}
static void
gtk_sor2_list_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkSor2ListModel *self)
{
guint n_items;
/* doesn't free() the array */
sort_array_set_size (&self->items, 0);
gtk_sor2_list_model_create_items (self);
gtk_sor2_list_model_resort (self);
n_items = g_list_model_get_n_items (model);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items - added + removed, n_items);
}
static void
gtk_sor2_list_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSor2ListModel *self = GTK_SOR2_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_sor2_list_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_sor2_list_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor2_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSor2ListModel *self = GTK_SOR2_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor2_list_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkSor2ListModel *self)
{
guint n_items;
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_sor2_list_model_clear_items (self);
else if (sort_array_is_empty (&self->items))
gtk_sor2_list_model_create_items (self);
gtk_sor2_list_model_resort (self);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
static void
gtk_sor2_list_model_clear_model (GtkSor2ListModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_sor2_list_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_sor2_list_model_clear_items (self);
}
static void
gtk_sor2_list_model_clear_sorter (GtkSor2ListModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_sor2_list_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
gtk_sor2_list_model_clear_items (self);
}
static void
gtk_sor2_list_model_dispose (GObject *object)
{
GtkSor2ListModel *self = GTK_SOR2_LIST_MODEL (object);
gtk_sor2_list_model_clear_model (self);
gtk_sor2_list_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_sor2_list_model_parent_class)->dispose (object);
};
static void
gtk_sor2_list_model_class_init (GtkSor2ListModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_sor2_list_model_set_property;
gobject_class->get_property = gtk_sor2_list_model_get_property;
gobject_class->dispose = gtk_sor2_list_model_dispose;
/**
* GtkSor2ListModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSor2ListModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_sor2_list_model_init (GtkSor2ListModel *self)
{
}
/**
* gtk_sor2_list_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkSor2ListModel
**/
GtkSor2ListModel *
gtk_sor2_list_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkSor2ListModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_SOR2_LIST_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_sor2_list_model_set_model:
* @self: a #GtkSor2ListModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_sor2_list_model_set_model (GtkSor2ListModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_SOR2_LIST_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_sor2_list_model_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_sor2_list_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_sor2_list_model_create_items (self);
gtk_sor2_list_model_resort (self);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_sor2_list_model_get_model:
* @self: a #GtkSor2ListModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_sor2_list_model_get_model (GtkSor2ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR2_LIST_MODEL (self), NULL);
return self->model;
}
/**
* gtk_sor2_list_model_set_sorter:
* @self: a #GtkSor2ListModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_sor2_list_model_set_sorter (GtkSor2ListModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_SOR2_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_sor2_list_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sor2_list_model_sorter_changed_cb), self);
gtk_sor2_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
else
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_sor2_list_model_get_sorter:
* @self: a #GtkSor2LisTModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_sor2_list_model_get_sorter (GtkSor2ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR2_LIST_MODEL (self), NULL);
return self->sorter;
}

57
gtk/gtksor2listmodel.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_SOR2_LIST_MODEL_H__
#define __GTK_SOR2_LIST_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_SOR2_LIST_MODEL (gtk_sor2_list_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkSor2ListModel, gtk_sor2_list_model, GTK, SOR2_LIST_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkSor2ListModel * gtk_sor2_list_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_sor2_list_model_set_sorter (GtkSor2ListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_sor2_list_model_get_sorter (GtkSor2ListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sor2_list_model_set_model (GtkSor2ListModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_sor2_list_model_get_model (GtkSor2ListModel *self);
G_END_DECLS
#endif /* __GTK_SOR2_LIST_MODEL_H__ */

727
gtk/gtksor3listmodel.c Normal file
View File

@@ -0,0 +1,727 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksor3listmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gdk/gdkprofilerprivate.h"
#define GDK_ARRAY_ELEMENT_TYPE GObject *
#define GDK_ARRAY_TYPE_NAME SortArray
#define GDK_ARRAY_NAME sort_array
#define GDK_ARRAY_FREE_FUNC g_object_unref
#define GDK_ARRAY_PREALLOC 16
#include "gdk/gdkarrayimpl.c"
#define GDK_ARRAY_ELEMENT_TYPE guint
#define GDK_ARRAY_TYPE_NAME PivotStack
#define GDK_ARRAY_NAME pivot_stack
#define GDK_ARRAY_BY_VALUE
#define GDK_ARRAY_PREALLOC 16
#include "gdk/gdkarrayimpl.c"
static inline void
pivot_stack_push (PivotStack *stack,
guint pos)
{
gsize size = pivot_stack_get_size (stack);
pivot_stack_set_size (stack, size + 1);
*pivot_stack_get (stack, size) = pos;
}
static inline guint
pivot_stack_top (PivotStack *stack)
{
return *pivot_stack_get (stack, pivot_stack_get_size (stack) - 1);
}
static inline guint
pivot_stack_pop (PivotStack *stack)
{
gsize size = pivot_stack_get_size (stack);
guint top = *pivot_stack_get (stack, size - 1);
pivot_stack_set_size (stack, size - 1);
return top;
}
/**
* SECTION:gtksor3listmodel
* @title: GtkSor3ListModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkSor3ListModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkSor3ListModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkSor3ListModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_MODEL,
PROP_SORTER,
PROP_SORTING,
PROP_INCREMENTAL,
NUM_PROPERTIES
};
struct _GtkSor3ListModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
SortArray items; /* empty if known unsorted */
guint sorting_cb;
guint sorted_to;
PivotStack stack;
gint64 start_time;
guint steps;
gboolean incremental;
};
struct _GtkSor3ListModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_sor3_list_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_sor3_list_model_get_n_items (GListModel *list)
{
GtkSor3ListModel *self = GTK_SOR3_LIST_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_sor3_list_model_get_item (GListModel *list,
guint position)
{
GtkSor3ListModel *self = GTK_SOR3_LIST_MODEL (list);
if (self->model == NULL)
return NULL;
if (sort_array_is_empty (&self->items))
return g_list_model_get_item (self->model, position);
if (position >= sort_array_get_size (&self->items))
return NULL;
return g_object_ref (sort_array_get (&self->items, position));
}
static void
gtk_sor3_list_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_sor3_list_model_get_item_type;
iface->get_n_items = gtk_sor3_list_model_get_n_items;
iface->get_item = gtk_sor3_list_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkSor3ListModel, gtk_sor3_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sor3_list_model_model_init))
static void
gtk_sor3_list_model_clear_items (GtkSor3ListModel *self)
{
sort_array_clear (&self->items);
}
static void
gtk_sor3_list_model_create_items (GtkSor3ListModel *self)
{
guint i, n_items;
if (self->sorter == NULL ||
self->model == NULL ||
gtk_sorter_get_order (self->sorter) == GTK_SORTER_ORDER_NONE)
return;
n_items = g_list_model_get_n_items (self->model);
sort_array_reserve (&self->items, n_items);
for (i = 0; i < n_items; i++)
sort_array_append (&self->items, g_list_model_get_item (self->model, i));
}
static void
gtk_sor3_list_model_stop_sorting (GtkSor3ListModel *self)
{
g_clear_handle_id (&self->sorting_cb, g_source_remove);
pivot_stack_set_size (&self->stack, 0);
if (GDK_PROFILER_IS_RUNNING)
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (self->start_time != 0)
gdk_profiler_add_markf (self->start_time, g_get_monotonic_time () - self->start_time, "quicksort", "sorting %u items, %u steps", n_items, self->steps);
self->start_time = 0;
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTING]);
}
static inline int
compare (GtkSorter *sorter, GObject *a, GObject *b)
{
return gtk_sorter_compare (sorter, a, b);
}
static inline void
swap (SortArray *items, guint i, guint j, guint *changed_start, guint *changed_end)
{
GObject *tmp = sort_array_get (items, i);
g_assert (i < j);
*sort_array_index (items, i) = sort_array_get (items, j);
*sort_array_index (items, j) = tmp;
if (i < *changed_start)
*changed_start = i;
if (*changed_end < j)
*changed_end = j;
}
/* This is pretty much the incremental quicksort that is described
* in the wikipedia article about it. Calling iqs repeatedly for
* each position from 0..k gives you that k smallest elements, in
* order. Which is what we do. As a side-effect, the array ends up
* getting sorted. The nice thing is that we simply remember how
* far we've enumerated (in sorted_to), and interrupt the sorting,
* which is the incremental part.
*/
static guint
partition (SortArray *items, guint first, guint end, GtkSorter *sorter, guint *changed_start, guint *changed_end)
{
guint mid;
guint i, j;
GObject *pivot;
mid = first + (end - first) / 2;
if (compare (sorter, sort_array_get (items, mid),
sort_array_get (items, first)) < 0)
swap (items, first, mid, changed_start, changed_end);
if (compare (sorter, sort_array_get (items, end),
sort_array_get (items, first)) < 0)
swap (items, first, end, changed_start, changed_end);
if (compare (sorter, sort_array_get (items, mid),
sort_array_get (items, end)) < 0)
swap (items, mid, end, changed_start, changed_end);
pivot = sort_array_get (items, end);
i = first;
j = end;
while (1)
{
while (compare (sorter, sort_array_get (items, i), pivot) < 0) i++;
while (j > i && compare (sorter, sort_array_get (items, j), pivot) >= 0) j--;
if (i >= j) return j;
swap (items, i, j, changed_start, changed_end);
}
return j;
}
static gpointer
iqs (SortArray *items, guint pos, PivotStack *stack, GtkSorter *sorter, guint *changed_start, guint *changed_end)
{
guint top;
guint pivot;
top = pivot_stack_top (stack);
if (top == pos)
{
pivot_stack_pop (stack);
return sort_array_get (items, pos);
}
pivot = partition (items, pos, top, sorter, changed_start, changed_end);
pivot_stack_push (stack, pivot);
return iqs (items, pos, stack, sorter, changed_start, changed_end);
}
static gboolean
gtk_sor3_list_model_sort_cb (gpointer data)
{
GtkSor3ListModel *self = GTK_SOR3_LIST_MODEL (data);
guint start;
guint end;
guint n_items;
guint i;
gint64 begin = g_get_monotonic_time ();
guint changed_start;
guint changed_end;
guint changed_items = 0;
self->steps++;
start = self->sorted_to;
n_items = sort_array_get_size (&self->items);
end = n_items - start;
changed_start = G_MAXUINT;
changed_end = 0;
for (i = 0; i < end; i++)
{
iqs (&self->items, self->sorted_to, &self->stack, self->sorter, &changed_start, &changed_end);
self->sorted_to++;
if (g_get_monotonic_time () - begin > 1500)
break;
}
if (self->sorted_to >= n_items)
gtk_sor3_list_model_stop_sorting (self);
if (changed_start != GTK_INVALID_LIST_POSITION)
{
changed_items = changed_end - changed_start + 1;
g_list_model_items_changed (G_LIST_MODEL (self), changed_start, changed_items, changed_items);
}
if (GDK_PROFILER_IS_RUNNING)
gdk_profiler_add_markf (begin, g_get_monotonic_time () - begin, "quicksort", "sort step (%u:%u)", changed_start, changed_items);
return G_SOURCE_CONTINUE;
}
static void
gtk_sor3_list_model_start_sorting (GtkSor3ListModel *self)
{
if (sort_array_get_size (&self->items) == 0)
return;
g_assert (pivot_stack_get_size (&self->stack) == 0);
g_assert (self->sorting_cb == 0);
self->start_time = g_get_monotonic_time ();
self->steps = 0;
pivot_stack_push (&self->stack, (guint)sort_array_get_size (&self->items) - 1);
self->sorted_to = 0;
self->sorting_cb = g_idle_add (gtk_sor3_list_model_sort_cb, self);
g_source_set_name_by_id (self->sorting_cb, "[gtk] gtk_sor3_list_model_sort_cb");
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTING]);
}
static void
gtk_sor3_list_model_sort_fully (GtkSor3ListModel *self)
{
guint n_items;
guint i;
gint64 begin = g_get_monotonic_time ();
guint changed_start;
guint changed_end;
guint changed_items = 0;
pivot_stack_set_size (&self->stack, 0);
pivot_stack_push (&self->stack, (guint)sort_array_get_size (&self->items) - 1);
self->sorted_to = 0;
n_items = sort_array_get_size (&self->items);
changed_start = G_MAXUINT;
changed_end = 0;
for (i = 0; i < n_items; i++)
{
iqs (&self->items, self->sorted_to, &self->stack, self->sorter, &changed_start, &changed_end);
self->sorted_to++;
}
if (changed_start != GTK_INVALID_LIST_POSITION)
{
changed_items = changed_end - changed_start + 1;
g_list_model_items_changed (G_LIST_MODEL (self), changed_start, changed_items, changed_items);
}
if (GDK_PROFILER_IS_RUNNING)
gdk_profiler_add_markf (begin, g_get_monotonic_time () - begin, "quicksort", "sort fully (%u:%u)", changed_start, changed_items);
}
static void
gtk_sor3_list_model_resort (GtkSor3ListModel *self)
{
guint64 begin = g_get_monotonic_time ();
gtk_sor3_list_model_stop_sorting (self);
if (self->incremental)
gtk_sor3_list_model_start_sorting (self);
else
gtk_sor3_list_model_sort_fully (self);
if (GDK_PROFILER_IS_RUNNING)
gdk_profiler_add_mark (begin, g_get_monotonic_time () - begin, "resort", NULL);
}
static void
gtk_sor3_list_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkSor3ListModel *self)
{
guint n_items;
/* doesn't free() the array */
sort_array_set_size (&self->items, 0);
gtk_sor3_list_model_create_items (self);
gtk_sor3_list_model_resort (self);
n_items = g_list_model_get_n_items (model);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items - added + removed, n_items);
}
static void gtk_sor3_list_model_set_incremental (GtkSor3ListModel *self,
gboolean incremental);
static void
gtk_sor3_list_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSor3ListModel *self = GTK_SOR3_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_sor3_list_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_sor3_list_model_set_sorter (self, g_value_get_object (value));
break;
case PROP_INCREMENTAL:
gtk_sor3_list_model_set_incremental (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor3_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSor3ListModel *self = GTK_SOR3_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
case PROP_SORTING:
g_value_set_boolean (value, self->sorting_cb != 0);
break;
case PROP_INCREMENTAL:
g_value_set_boolean (value, self->incremental);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor3_list_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkSor3ListModel *self)
{
guint n_items;
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_sor3_list_model_clear_items (self);
else if (sort_array_is_empty (&self->items))
gtk_sor3_list_model_create_items (self);
gtk_sor3_list_model_resort (self);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
static void
gtk_sor3_list_model_clear_model (GtkSor3ListModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_sor3_list_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_sor3_list_model_stop_sorting (self);
gtk_sor3_list_model_clear_items (self);
}
static void
gtk_sor3_list_model_clear_sorter (GtkSor3ListModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_sor3_list_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
gtk_sor3_list_model_stop_sorting (self);
gtk_sor3_list_model_clear_items (self);
}
static void
gtk_sor3_list_model_dispose (GObject *object)
{
GtkSor3ListModel *self = GTK_SOR3_LIST_MODEL (object);
gtk_sor3_list_model_stop_sorting (self);
gtk_sor3_list_model_clear_model (self);
gtk_sor3_list_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_sor3_list_model_parent_class)->dispose (object);
};
static void
gtk_sor3_list_model_class_init (GtkSor3ListModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_sor3_list_model_set_property;
gobject_class->get_property = gtk_sor3_list_model_get_property;
gobject_class->dispose = gtk_sor3_list_model_dispose;
/**
* GtkSor3ListModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSor3ListModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_SORTING] =
g_param_spec_boolean ("sorting",
P_("Sorting"),
P_("Whether sorting is currently underway"),
FALSE,
GTK_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_INCREMENTAL] =
g_param_spec_boolean ("incremental",
P_("Incremental"),
P_("Whether to sort incrementally"),
FALSE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_sor3_list_model_init (GtkSor3ListModel *self)
{
}
/**
* gtk_sor3_list_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkSor3ListModel
**/
GtkSor3ListModel *
gtk_sor3_list_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkSor3ListModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_SOR3_LIST_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_sor3_list_model_set_model:
* @self: a #GtkSor3ListModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_sor3_list_model_set_model (GtkSor3ListModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_SOR3_LIST_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_sor3_list_model_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_sor3_list_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_sor3_list_model_create_items (self);
gtk_sor3_list_model_resort (self);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_sor3_list_model_get_model:
* @self: a #GtkSor3ListModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_sor3_list_model_get_model (GtkSor3ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR3_LIST_MODEL (self), NULL);
return self->model;
}
/**
* gtk_sor3_list_model_set_sorter:
* @self: a #GtkSor3ListModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_sor3_list_model_set_sorter (GtkSor3ListModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_SOR3_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_sor3_list_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sor3_list_model_sorter_changed_cb), self);
gtk_sor3_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_sor3_list_model_get_sorter:
* @self: a #GtkSor3LisTModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_sor3_list_model_get_sorter (GtkSor3ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR3_LIST_MODEL (self), NULL);
return self->sorter;
}
static void
gtk_sor3_list_model_set_incremental (GtkSor3ListModel *self,
gboolean incremental)
{
g_return_if_fail (GTK_IS_SOR3_LIST_MODEL (self));
if (self->incremental == incremental)
return;
self->incremental = incremental;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCREMENTAL]);
}

57
gtk/gtksor3listmodel.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_SOR3_LIST_MODEL_H__
#define __GTK_SOR3_LIST_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_SOR3_LIST_MODEL (gtk_sor3_list_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkSor3ListModel, gtk_sor3_list_model, GTK, SOR3_LIST_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkSor3ListModel * gtk_sor3_list_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_sor3_list_model_set_sorter (GtkSor3ListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_sor3_list_model_get_sorter (GtkSor3ListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sor3_list_model_set_model (GtkSor3ListModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_sor3_list_model_get_model (GtkSor3ListModel *self);
G_END_DECLS
#endif /* __GTK_SOR3_LIST_MODEL_H__ */

551
gtk/gtksor4listmodel.c Normal file
View File

@@ -0,0 +1,551 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksor4listmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
typedef struct _SortItem SortItem;
struct _SortItem
{
GObject *item;
guint position;
};
static void
sort_item_clear (gpointer data)
{
SortItem *si = data;
g_clear_object (&si->item);
}
#define GDK_ARRAY_ELEMENT_TYPE SortItem
#define GDK_ARRAY_TYPE_NAME SortArray
#define GDK_ARRAY_NAME sort_array
#define GDK_ARRAY_FREE_FUNC sort_item_clear
#define GDK_ARRAY_BY_VALUE 1
#include "gdk/gdkarrayimpl.c"
/**
* SECTION:gtksor4listmodel
* @title: GtkSor4ListModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkSor4ListModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkSor4ListModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkSor4ListModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkSor4ListModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
SortArray items; /* empty if known unsorted */
};
struct _GtkSor4ListModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_sor4_list_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_sor4_list_model_get_n_items (GListModel *list)
{
GtkSor4ListModel *self = GTK_SOR4_LIST_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_sor4_list_model_get_item (GListModel *list,
guint position)
{
GtkSor4ListModel *self = GTK_SOR4_LIST_MODEL (list);
if (self->model == NULL)
return NULL;
if (sort_array_is_empty (&self->items))
return g_list_model_get_item (self->model, position);
if (position >= sort_array_get_size (&self->items))
return NULL;
return g_object_ref (sort_array_get (&self->items, position)->item);
}
static void
gtk_sor4_list_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_sor4_list_model_get_item_type;
iface->get_n_items = gtk_sor4_list_model_get_n_items;
iface->get_item = gtk_sor4_list_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkSor4ListModel, gtk_sor4_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sor4_list_model_model_init))
static void
gtk_sor4_list_model_clear_items (GtkSor4ListModel *self)
{
sort_array_clear (&self->items);
}
static gboolean
gtk_sor4_list_model_should_sort (GtkSor4ListModel *self)
{
return self->sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
}
static void
gtk_sor4_list_model_create_items (GtkSor4ListModel *self)
{
guint i, n_items;
if (!gtk_sor4_list_model_should_sort (self))
return;
n_items = g_list_model_get_n_items (self->model);
sort_array_reserve (&self->items, n_items);
for (i = 0; i < n_items; i++)
{
sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
}
}
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer data)
{
SortItem *sa = (SortItem *) a;
SortItem *sb = (SortItem *) b;
return gtk_sorter_compare (data, sa->item, sb->item);
}
static void
gtk_sor4_list_model_resort (GtkSor4ListModel *self)
{
g_qsort_with_data (sort_array_get_data (&self->items),
sort_array_get_size (&self->items),
sizeof (SortItem),
sort_func,
self->sorter);
}
static void
gtk_sor4_list_model_remove_items (GtkSor4ListModel *self,
guint position,
guint removed,
guint added,
guint *unmodified_start,
guint *unmodified_end)
{
guint i, n_items, valid;
guint start, end;
n_items = sort_array_get_size (&self->items);
start = n_items;
end = n_items;
valid = 0;
for (i = 0; i < n_items; i++)
{
SortItem *si = sort_array_index (&self->items, i);
if (si->position >= position + removed)
si->position = si->position - removed + added;
else if (si->position >= position)
{
start = MIN (start, valid);
end = n_items - i - 1;
sort_item_clear (si);
continue;
}
if (valid < i)
*sort_array_index (&self->items, valid) = *sort_array_index (&self->items, i);
valid++;
}
g_assert (valid == n_items - removed);
memset (sort_array_index (&self->items, valid), 0, sizeof (SortItem) * removed);
sort_array_set_size (&self->items, valid);
*unmodified_start = start;
*unmodified_end = end;
}
static void
gtk_sor4_list_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkSor4ListModel *self)
{
guint i, n_items, start, end;
if (removed == 0 && added == 0)
return;
if (!gtk_sor4_list_model_should_sort (self))
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
return;
}
gtk_sor4_list_model_remove_items (self, position, removed, added, &start, &end);
if (added > 0)
{
sort_array_reserve (&self->items, sort_array_get_size (&self->items) + added);
for (i = position; i < position + added; i++)
{
sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
}
gtk_sor4_list_model_resort (self);
for (i = 0; i < start; i++)
{
SortItem *si = sort_array_get (&self->items, i);
if (si->position >= position && si->position < position + added)
{
start = i;
break;
}
}
n_items = sort_array_get_size (&self->items);
for (i = 0; i < end; i++)
{
SortItem *si = sort_array_get (&self->items, n_items - i - 1);
if (si->position >= position && si->position < position + added)
{
end = i;
break;
}
}
}
n_items = sort_array_get_size (&self->items) - start - end;
g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
}
static void
gtk_sor4_list_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSor4ListModel *self = GTK_SOR4_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_sor4_list_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_sor4_list_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor4_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSor4ListModel *self = GTK_SOR4_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor4_list_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkSor4ListModel *self)
{
guint n_items;
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_sor4_list_model_clear_items (self);
else if (sort_array_is_empty (&self->items))
gtk_sor4_list_model_create_items (self);
gtk_sor4_list_model_resort (self);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
static void
gtk_sor4_list_model_clear_model (GtkSor4ListModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_sor4_list_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_sor4_list_model_clear_items (self);
}
static void
gtk_sor4_list_model_clear_sorter (GtkSor4ListModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_sor4_list_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
gtk_sor4_list_model_clear_items (self);
}
static void
gtk_sor4_list_model_dispose (GObject *object)
{
GtkSor4ListModel *self = GTK_SOR4_LIST_MODEL (object);
gtk_sor4_list_model_clear_model (self);
gtk_sor4_list_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_sor4_list_model_parent_class)->dispose (object);
};
static void
gtk_sor4_list_model_class_init (GtkSor4ListModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_sor4_list_model_set_property;
gobject_class->get_property = gtk_sor4_list_model_get_property;
gobject_class->dispose = gtk_sor4_list_model_dispose;
/**
* GtkSor4ListModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSor4ListModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_sor4_list_model_init (GtkSor4ListModel *self)
{
}
/**
* gtk_sor4_list_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkSor4ListModel
**/
GtkSor4ListModel *
gtk_sor4_list_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkSor4ListModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_SOR4_LIST_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_sor4_list_model_set_model:
* @self: a #GtkSor4ListModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_sor4_list_model_set_model (GtkSor4ListModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_SOR4_LIST_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_sor4_list_model_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_sor4_list_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_sor4_list_model_create_items (self);
gtk_sor4_list_model_resort (self);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_sor4_list_model_get_model:
* @self: a #GtkSor4ListModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_sor4_list_model_get_model (GtkSor4ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR4_LIST_MODEL (self), NULL);
return self->model;
}
/**
* gtk_sor4_list_model_set_sorter:
* @self: a #GtkSor4ListModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_sor4_list_model_set_sorter (GtkSor4ListModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_SOR4_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_sor4_list_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sor4_list_model_sorter_changed_cb), self);
gtk_sor4_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
else
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_sor4_list_model_get_sorter:
* @self: a #GtkSor4LisTModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_sor4_list_model_get_sorter (GtkSor4ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR4_LIST_MODEL (self), NULL);
return self->sorter;
}

57
gtk/gtksor4listmodel.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_SOR4_LIST_MODEL_H__
#define __GTK_SOR4_LIST_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_SOR4_LIST_MODEL (gtk_sor4_list_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkSor4ListModel, gtk_sor4_list_model, GTK, SOR4_LIST_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkSor4ListModel * gtk_sor4_list_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_sor4_list_model_set_sorter (GtkSor4ListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_sor4_list_model_get_sorter (GtkSor4ListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sor4_list_model_set_model (GtkSor4ListModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_sor4_list_model_get_model (GtkSor4ListModel *self);
G_END_DECLS
#endif /* __GTK_SOR4_LIST_MODEL_H__ */

549
gtk/gtksor5listmodel.c Normal file
View File

@@ -0,0 +1,549 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtksor5listmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#define GDK_ARRAY_ELEMENT_TYPE GObject *
#define GDK_ARRAY_TYPE_NAME SortArray
#define GDK_ARRAY_NAME sort_array
#define GDK_ARRAY_FREE_FUNC g_object_unref
#define GDK_ARRAY_PREALLOC 16
#include "gdk/gdkarrayimpl.c"
/**
* SECTION:gtksor5listmodel
* @title: GtkSor5ListModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkSor5ListModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkSor5ListModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkSor5ListModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkSor5ListModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
SortArray items; /* empty if known unsorted */
guint sorting_cb;
guint size;
guint start;
};
struct _GtkSor5ListModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_sor5_list_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_sor5_list_model_get_n_items (GListModel *list)
{
GtkSor5ListModel *self = GTK_SOR5_LIST_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_sor5_list_model_get_item (GListModel *list,
guint position)
{
GtkSor5ListModel *self = GTK_SOR5_LIST_MODEL (list);
if (self->model == NULL)
return NULL;
if (sort_array_is_empty (&self->items))
return g_list_model_get_item (self->model, position);
if (position >= sort_array_get_size (&self->items))
return NULL;
return g_object_ref (sort_array_get (&self->items, position));
}
static void
gtk_sor5_list_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_sor5_list_model_get_item_type;
iface->get_n_items = gtk_sor5_list_model_get_n_items;
iface->get_item = gtk_sor5_list_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkSor5ListModel, gtk_sor5_list_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_sor5_list_model_model_init))
static void
gtk_sor5_list_model_clear_items (GtkSor5ListModel *self)
{
sort_array_clear (&self->items);
}
static void
gtk_sor5_list_model_create_items (GtkSor5ListModel *self)
{
guint i, n_items;
if (self->sorter == NULL ||
self->model == NULL ||
gtk_sorter_get_order (self->sorter) == GTK_SORTER_ORDER_NONE)
return;
n_items = g_list_model_get_n_items (self->model);
sort_array_reserve (&self->items, n_items);
for (i = 0; i < n_items; i++)
sort_array_append (&self->items, g_list_model_get_item (self->model, i));
}
static void
gtk_sor5_list_model_stop_sorting (GtkSor5ListModel *self)
{
g_clear_handle_id (&self->sorting_cb, g_source_remove);
}
static guint
merge (SortArray *items,
guint start,
guint mid,
guint end,
GtkSorter *sorter)
{
int start2 = mid + 1;
int c = 0;
if (mid == end)
return 0;
if (gtk_sorter_compare (sorter,
sort_array_get (items, mid),
sort_array_get (items, start2)) <= 0)
return 1;
while (start <= mid && start2 <= end)
{
c++;
if (gtk_sorter_compare (sorter,
sort_array_get (items, start),
sort_array_get (items, start2)) <= 0)
start++;
else
{
GObject *value = sort_array_get (items, start2);
int index = start2;
while (index != start)
{
c++;
*sort_array_index (items, index) = sort_array_get (items, index - 1);
index--;
}
c++;
*sort_array_index (items, start) = value;
start++;
mid++;
start2++;
}
}
return c;
}
static gboolean
gtk_sor5_list_model_sort_cb (gpointer data)
{
GtkSor5ListModel *self = GTK_SOR5_LIST_MODEL (data);
guint n_items = sort_array_get_size (&self->items);
guint i;
guint s, e;
s = self->start;
e = self->start;
i = 0;
while (i < 10000)
{
guint mid = MIN (self->start + self->size - 1, n_items - 1);
guint end = MIN (self->start + 2 * self->size - 1, n_items - 1);
i += merge (&self->items, self->start, mid, end, self->sorter);
s = MIN (s, self->start);
e = MAX (e, end);
self->start += 2 * self->size;
if (self->start >= n_items - 1)
{
self->start = 0;
self->size *= 2;
if (self->size >= n_items)
{
gtk_sor5_list_model_stop_sorting (self);
break;
}
}
}
//g_print ("items changed %u:%u\n", s, e - s + 1);
g_list_model_items_changed (G_LIST_MODEL (self), s, e - s + 1, e - s + 1);
return G_SOURCE_CONTINUE;
}
static void
gtk_sor5_list_model_start_sorting (GtkSor5ListModel *self)
{
if (sort_array_get_size (&self->items) == 0)
return;
g_assert (self->sorting_cb == 0);
self->size = 1;
self->start = 0;
self->sorting_cb = g_idle_add (gtk_sor5_list_model_sort_cb, self);
g_source_set_name_by_id (self->sorting_cb, "[gtk] gtk_sor5_list_model_sort_cb");
}
static void
gtk_sor5_list_model_resort (GtkSor5ListModel *self)
{
gtk_sor5_list_model_stop_sorting (self);
gtk_sor5_list_model_start_sorting (self);
}
static void
gtk_sor5_list_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkSor5ListModel *self)
{
guint n_items;
/* doesn't free() the array */
sort_array_set_size (&self->items, 0);
gtk_sor5_list_model_create_items (self);
gtk_sor5_list_model_resort (self);
n_items = g_list_model_get_n_items (model);
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items - added + removed, n_items);
}
static void
gtk_sor5_list_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkSor5ListModel *self = GTK_SOR5_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_sor5_list_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_sor5_list_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor5_list_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkSor5ListModel *self = GTK_SOR5_LIST_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_sor5_list_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkSor5ListModel *self)
{
guint n_items;
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_sor5_list_model_clear_items (self);
else if (sort_array_is_empty (&self->items))
gtk_sor5_list_model_create_items (self);
gtk_sor5_list_model_resort (self);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
static void
gtk_sor5_list_model_clear_model (GtkSor5ListModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_sor5_list_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_sor5_list_model_stop_sorting (self);
gtk_sor5_list_model_clear_items (self);
}
static void
gtk_sor5_list_model_clear_sorter (GtkSor5ListModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_sor5_list_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
gtk_sor5_list_model_stop_sorting (self);
gtk_sor5_list_model_clear_items (self);
}
static void
gtk_sor5_list_model_dispose (GObject *object)
{
GtkSor5ListModel *self = GTK_SOR5_LIST_MODEL (object);
gtk_sor5_list_model_stop_sorting (self);
gtk_sor5_list_model_clear_model (self);
gtk_sor5_list_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_sor5_list_model_parent_class)->dispose (object);
};
static void
gtk_sor5_list_model_class_init (GtkSor5ListModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_sor5_list_model_set_property;
gobject_class->get_property = gtk_sor5_list_model_get_property;
gobject_class->dispose = gtk_sor5_list_model_dispose;
/**
* GtkSor5ListModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkSor5ListModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_sor5_list_model_init (GtkSor5ListModel *self)
{
}
/**
* gtk_sor5_list_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkSor5ListModel
**/
GtkSor5ListModel *
gtk_sor5_list_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkSor5ListModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_SOR5_LIST_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_sor5_list_model_set_model:
* @self: a #GtkSor5ListModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_sor5_list_model_set_model (GtkSor5ListModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_SOR5_LIST_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_sor5_list_model_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_sor5_list_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_sor5_list_model_create_items (self);
gtk_sor5_list_model_resort (self);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_sor5_list_model_get_model:
* @self: a #GtkSor5ListModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_sor5_list_model_get_model (GtkSor5ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR5_LIST_MODEL (self), NULL);
return self->model;
}
/**
* gtk_sor5_list_model_set_sorter:
* @self: a #GtkSor5ListModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_sor5_list_model_set_sorter (GtkSor5ListModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_SOR5_LIST_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_sor5_list_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_sor5_list_model_sorter_changed_cb), self);
gtk_sor5_list_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_sor5_list_model_get_sorter:
* @self: a #GtkSor5LisTModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_sor5_list_model_get_sorter (GtkSor5ListModel *self)
{
g_return_val_if_fail (GTK_IS_SOR5_LIST_MODEL (self), NULL);
return self->sorter;
}

57
gtk/gtksor5listmodel.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_SOR5_LIST_MODEL_H__
#define __GTK_SOR5_LIST_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_SOR5_LIST_MODEL (gtk_sor5_list_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkSor5ListModel, gtk_sor5_list_model, GTK, SOR5_LIST_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkSor5ListModel * gtk_sor5_list_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_sor5_list_model_set_sorter (GtkSor5ListModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_sor5_list_model_get_sorter (GtkSor5ListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sor5_list_model_set_model (GtkSor5ListModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_sor5_list_model_get_model (GtkSor5ListModel *self);
G_END_DECLS
#endif /* __GTK_SOR5_LIST_MODEL_H__ */

View File

@@ -19,7 +19,7 @@
#include "config.h"
#include "gtksorter.h"
#include "gtksorterprivate.h"
#include "gtkintl.h"
#include "gtktypebuiltins.h"
@@ -48,12 +48,26 @@
* and provide one's own sorter.
*/
typedef struct _GtkSorterPrivate GtkSorterPrivate;
typedef struct _GtkDefaultSortKeys GtkDefaultSortKeys;
struct _GtkSorterPrivate
{
GtkSortKeys *keys;
};
struct _GtkDefaultSortKeys
{
GtkSortKeys keys;
GtkSorter *sorter;
};
enum {
CHANGED,
LAST_SIGNAL
};
G_DEFINE_TYPE (GtkSorter, gtk_sorter, G_TYPE_OBJECT)
G_DEFINE_TYPE_WITH_PRIVATE (GtkSorter, gtk_sorter, G_TYPE_OBJECT)
static guint signals[LAST_SIGNAL] = { 0 };
@@ -73,9 +87,24 @@ gtk_sorter_default_get_order (GtkSorter *self)
return GTK_SORTER_ORDER_PARTIAL;
}
static void
gtk_sorter_dispose (GObject *object)
{
GtkSorter *self = GTK_SORTER (object);
GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
g_clear_pointer (&priv->keys, gtk_sort_keys_unref);
G_OBJECT_CLASS (gtk_sorter_parent_class)->dispose (object);
}
static void
gtk_sorter_class_init (GtkSorterClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = gtk_sorter_dispose;
class->compare = gtk_sorter_default_compare;
class->get_order = gtk_sorter_default_get_order;
@@ -181,6 +210,98 @@ gtk_sorter_get_order (GtkSorter *self)
return GTK_SORTER_GET_CLASS (self)->get_order (self);
}
static int
gtk_default_sort_keys_compare (gconstpointer a,
gconstpointer b,
gpointer data)
{
GtkDefaultSortKeys *self = data;
gpointer *key_a = (gpointer *) a;
gpointer *key_b = (gpointer *) b;
return gtk_sorter_compare (self->sorter, *key_a, *key_b);
}
static void
gtk_default_sort_keys_free (GtkSortKeys *keys)
{
GtkDefaultSortKeys *self = (GtkDefaultSortKeys *) keys;
g_object_unref (self->sorter);
g_slice_free (GtkDefaultSortKeys, self);
}
static gboolean
gtk_default_sort_keys_is_compatible (GtkSortKeys *keys,
GtkSortKeys *other)
{
if (keys->klass != other->klass)
return FALSE;
return TRUE;
}
static void
gtk_default_sort_keys_init_key (GtkSortKeys *self,
gpointer item,
gpointer key_memory)
{
gpointer *key = (gpointer *) key_memory;
*key = g_object_ref (item);
}
static void
gtk_default_sort_keys_clear_key (GtkSortKeys *self,
gpointer key_memory)
{
gpointer *key = (gpointer *) key_memory;
g_object_unref (*key);
}
static const GtkSortKeysClass GTK_DEFAULT_SORT_KEYS_CLASS =
{
gtk_default_sort_keys_free,
gtk_default_sort_keys_compare,
gtk_default_sort_keys_is_compatible,
gtk_default_sort_keys_init_key,
gtk_default_sort_keys_clear_key,
};
/*<private>
* gtk_sorter_get_keys:
* @self: a #GtkSorter
*
* Gets a #GtkSortKeys that can be used as an alternative to
* @self for faster sorting.
*
* The sort keys can change every time #GtkSorter::changed is emitted.
* When the keys change, you should redo all comparisons with the new
* keys.
* When gtk_sort_keys_is_compatible() for the old and new keys returns
* %TRUE, you can reuse keys you generated previously.
*
* Returns: (transfer full): the sort keys to sort with
**/
GtkSortKeys *
gtk_sorter_get_keys (GtkSorter *self)
{
GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
GtkDefaultSortKeys *fallback;
g_return_val_if_fail (GTK_IS_SORTER (self), NULL);
if (priv->keys)
return gtk_sort_keys_ref (priv->keys);
fallback = gtk_sort_keys_new (GtkDefaultSortKeys, &GTK_DEFAULT_SORT_KEYS_CLASS, sizeof (gpointer), sizeof (gpointer));
fallback->sorter = g_object_ref (self);
return (GtkSortKeys *) fallback;
}
/**
* gtk_sorter_changed:
* @self: a #GtkSorter
@@ -205,3 +326,31 @@ gtk_sorter_changed (GtkSorter *self,
g_signal_emit (self, signals[CHANGED], 0, change);
}
/*<private>
* gtk_sorter_changed_with_keys
* @self: a #GtkSorter
* @change: How the sorter changed
* @keys: (not nullable) (transfer full): New keys to use
*
* Updates the sorter's keys to @keys and then calls gtk_sorter_changed().
* If you do not want to update the keys, call that function instead.
*
* This function should also be called in your_sorter_init() to initialize
* the keys to use with your sorter.
*/
void
gtk_sorter_changed_with_keys (GtkSorter *self,
GtkSorterChange change,
GtkSortKeys *keys)
{
GtkSorterPrivate *priv = gtk_sorter_get_instance_private (self);
g_return_if_fail (GTK_IS_SORTER (self));
g_return_if_fail (keys != NULL);
g_clear_pointer (&priv->keys, gtk_sort_keys_unref);
priv->keys = keys;
gtk_sorter_changed (self, change);
}

View File

@@ -115,6 +115,7 @@ GDK_AVAILABLE_IN_ALL
void gtk_sorter_changed (GtkSorter *self,
GtkSorterChange change);
G_END_DECLS
#endif /* __GTK_SORTER_H__ */

33
gtk/gtksorterprivate.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_SORTER_PRIVATE_H__
#define __GTK_SORTER_PRIVATE_H__
#include <gtk/gtksorter.h>
#include "gtk/gtksortkeysprivate.h"
GtkSortKeys * gtk_sorter_get_keys (GtkSorter *self);
void gtk_sorter_changed_with_keys (GtkSorter *self,
GtkSorterChange change,
GtkSortKeys *keys);
#endif /* __GTK_SORTER_PRIVATE_H__ */

150
gtk/gtksortkeys.c Normal file
View File

@@ -0,0 +1,150 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtksortkeysprivate.h"
#include "gtkcssstyleprivate.h"
#include "gtkstyleproviderprivate.h"
GtkSortKeys *
gtk_sort_keys_alloc (const GtkSortKeysClass *klass,
gsize size,
gsize key_size,
gsize key_align)
{
GtkSortKeys *self;
g_return_val_if_fail (key_align > 0, NULL);
self = g_slice_alloc0 (size);
self->klass = klass;
self->ref_count = 1;
self->key_size = key_size;
self->key_align = key_align;
return self;
}
GtkSortKeys *
gtk_sort_keys_ref (GtkSortKeys *self)
{
self->ref_count += 1;
return self;
}
void
gtk_sort_keys_unref (GtkSortKeys *self)
{
self->ref_count -= 1;
if (self->ref_count > 0)
return;
self->klass->free (self);
}
gsize
gtk_sort_keys_get_key_size (GtkSortKeys *self)
{
return self->key_size;
}
gsize
gtk_sort_keys_get_key_align (GtkSortKeys *self)
{
return self->key_align;
}
GCompareDataFunc
gtk_sort_keys_get_key_compare_func (GtkSortKeys *self)
{
return self->klass->key_compare;
}
gboolean
gtk_sort_keys_is_compatible (GtkSortKeys *self,
GtkSortKeys *other)
{
if (self == other)
return TRUE;
return self->klass->is_compatible (self, other);
}
gboolean
gtk_sort_keys_needs_clear_key (GtkSortKeys *self)
{
return self->klass->clear_key != NULL;
}
static void
gtk_equal_sort_keys_free (GtkSortKeys *keys)
{
g_slice_free (GtkSortKeys, keys);
}
static int
gtk_equal_sort_keys_compare (gconstpointer a,
gconstpointer b,
gpointer unused)
{
return GTK_ORDERING_EQUAL;
}
static gboolean
gtk_equal_sort_keys_is_compatible (GtkSortKeys *keys,
GtkSortKeys *other)
{
return keys->klass == other->klass;
}
static void
gtk_equal_sort_keys_init_key (GtkSortKeys *keys,
gpointer item,
gpointer key_memory)
{
}
static const GtkSortKeysClass GTK_EQUAL_SORT_KEYS_CLASS =
{
gtk_equal_sort_keys_free,
gtk_equal_sort_keys_compare,
gtk_equal_sort_keys_is_compatible,
gtk_equal_sort_keys_init_key,
NULL
};
/*<private>
* gtk_sort_keys_new_equal:
*
* Creates a new #GtkSortKeys that compares every element as equal.
* This is useful when sorters are in an invalid configuration.
*
* Returns: a new #GtkSortKeys
**/
GtkSortKeys *
gtk_sort_keys_new_equal (void)
{
return gtk_sort_keys_new (GtkSortKeys,
&GTK_EQUAL_SORT_KEYS_CLASS,
0, 1);
}

97
gtk/gtksortkeysprivate.h Normal file
View File

@@ -0,0 +1,97 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_SORT_KEYS_PRIVATE_H__
#define __GTK_SORT_KEYS_PRIVATE_H__
#include <gdk/gdk.h>
#include <gtk/gtkenums.h>
#include <gtk/gtksorter.h>
typedef struct _GtkSortKeys GtkSortKeys;
typedef struct _GtkSortKeysClass GtkSortKeysClass;
struct _GtkSortKeys
{
const GtkSortKeysClass *klass;
gint ref_count;
gsize key_size;
gsize key_align; /* must be power of 2 */
};
struct _GtkSortKeysClass
{
void (* free) (GtkSortKeys *self);
GCompareDataFunc key_compare;
gboolean (* is_compatible) (GtkSortKeys *self,
GtkSortKeys *other);
void (* init_key) (GtkSortKeys *self,
gpointer item,
gpointer key_memory);
void (* clear_key) (GtkSortKeys *self,
gpointer key_memory);
};
GtkSortKeys * gtk_sort_keys_alloc (const GtkSortKeysClass *klass,
gsize size,
gsize key_size,
gsize key_align);
#define gtk_sort_keys_new(_name, _klass, _key_size, _key_align) \
((_name *) gtk_sort_keys_alloc ((_klass), sizeof (_name), (_key_size), (_key_align)))
GtkSortKeys * gtk_sort_keys_ref (GtkSortKeys *self);
void gtk_sort_keys_unref (GtkSortKeys *self);
GtkSortKeys * gtk_sort_keys_new_equal (void);
gsize gtk_sort_keys_get_key_size (GtkSortKeys *self);
gsize gtk_sort_keys_get_key_align (GtkSortKeys *self);
GCompareDataFunc gtk_sort_keys_get_key_compare_func (GtkSortKeys *self);
gboolean gtk_sort_keys_is_compatible (GtkSortKeys *self,
GtkSortKeys *other);
gboolean gtk_sort_keys_needs_clear_key (GtkSortKeys *self);
#define GTK_SORT_KEYS_ALIGN(_size,_align) (((_size) + (_align) - 1) & ~((_align) - 1))
static inline int
gtk_sort_keys_compare (GtkSortKeys *self,
gconstpointer a,
gconstpointer b)
{
return self->klass->key_compare (a, b, self);
}
static inline void
gtk_sort_keys_init_key (GtkSortKeys *self,
gpointer item,
gpointer key_memory)
{
self->klass->init_key (self, item, key_memory);
}
static inline void
gtk_sort_keys_clear_key (GtkSortKeys *self,
gpointer key_memory)
{
if (self->klass->clear_key)
self->klass->clear_key (self, key_memory);
}
#endif /* __GTK_SORT_KEYS_PRIVATE_H__ */

File diff suppressed because it is too large Load Diff

View File

@@ -52,6 +52,15 @@ void gtk_sort_list_model_set_model (GtkSortListMode
GDK_AVAILABLE_IN_ALL
GListModel * gtk_sort_list_model_get_model (GtkSortListModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_sort_list_model_set_incremental (GtkSortListModel *self,
gboolean incremental);
GDK_AVAILABLE_IN_ALL
gboolean gtk_sort_list_model_get_incremental (GtkSortListModel *self);
GDK_AVAILABLE_IN_ALL
guint gtk_sort_list_model_get_pending (GtkSortListModel *self);
G_END_DECLS
#endif /* __GTK_SORT_LIST_MODEL_H__ */

View File

@@ -22,6 +22,7 @@
#include "gtkstringsorter.h"
#include "gtkintl.h"
#include "gtksorterprivate.h"
#include "gtktypebuiltins.h"
/**
@@ -58,70 +59,58 @@ G_DEFINE_TYPE (GtkStringSorter, gtk_string_sorter, GTK_TYPE_SORTER)
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static char *
gtk_string_sorter_get_key (GtkExpression *expression,
gboolean ignore_case,
gpointer item1)
{
GValue value = G_VALUE_INIT;
char *s;
if (expression == NULL)
return NULL;
if (!gtk_expression_evaluate (expression, item1, &value))
return NULL;
/* If strings are NULL, order them before "". */
if (ignore_case)
{
char *t;
t = g_utf8_casefold (g_value_get_string (&value), -1);
s = g_utf8_collate_key (t, -1);
g_free (t);
}
else
{
s = g_utf8_collate_key (g_value_get_string (&value), -1);
}
g_value_unset (&value);
return s;
}
static GtkOrdering
gtk_string_sorter_compare (GtkSorter *sorter,
gpointer item1,
gpointer item2)
{
GtkStringSorter *self = GTK_STRING_SORTER (sorter);
GValue value1 = G_VALUE_INIT;
GValue value2 = G_VALUE_INIT;
const char *s1, *s2;
gboolean res1, res2;
char *s1, *s2;
GtkOrdering result;
if (self->expression == NULL)
return GTK_ORDERING_EQUAL;
res1 = gtk_expression_evaluate (self->expression, item1, &value1);
res2 = gtk_expression_evaluate (self->expression, item2, &value2);
s1 = gtk_string_sorter_get_key (self->expression, self->ignore_case, item1);
s2 = gtk_string_sorter_get_key (self->expression, self->ignore_case, item2);
/* If items don't evaluate, order them at the end, so they aren't
* in the way. */
if (!res1)
{
result = res2 ? GTK_ORDERING_LARGER : GTK_ORDERING_EQUAL;
goto out;
}
else if (!res2)
{
result = GTK_ORDERING_SMALLER;
goto out;
}
result = gtk_ordering_from_cmpfunc (g_strcmp0 (s1, s2));
s1 = g_value_get_string (&value1);
s2 = g_value_get_string (&value2);
/* If strings are NULL, order them before "". */
if (s1 == NULL)
{
result = s2 == NULL ? GTK_ORDERING_EQUAL : GTK_ORDERING_SMALLER;
goto out;
}
else if (s2 == NULL)
{
result = GTK_ORDERING_LARGER;
goto out;
}
if (self->ignore_case)
{
char *t1, *t2;
t1 = g_utf8_casefold (s1, -1);
t2 = g_utf8_casefold (s2, -1);
result = gtk_ordering_from_cmpfunc (g_utf8_collate (t1, t2));
g_free (t1);
g_free (t2);
}
else
result = gtk_ordering_from_cmpfunc (g_utf8_collate (s1, s2));
out:
g_value_unset (&value1);
g_value_unset (&value2);
g_free (s1);
g_free (s2);
return result;
}
@@ -137,6 +126,95 @@ gtk_string_sorter_get_order (GtkSorter *sorter)
return GTK_SORTER_ORDER_PARTIAL;
}
typedef struct _GtkStringSortKeys GtkStringSortKeys;
struct _GtkStringSortKeys
{
GtkSortKeys keys;
GtkExpression *expression;
gboolean ignore_case;
};
static void
gtk_string_sort_keys_free (GtkSortKeys *keys)
{
GtkStringSortKeys *self = (GtkStringSortKeys *) keys;
gtk_expression_unref (self->expression);
g_slice_free (GtkStringSortKeys, self);
}
static int
gtk_string_sort_keys_compare (gconstpointer a,
gconstpointer b,
gpointer unused)
{
const char *sa = *(const char **) a;
const char *sb = *(const char **) b;
if (sa == NULL)
return sb == NULL ? GTK_ORDERING_EQUAL : GTK_ORDERING_LARGER;
else if (sb == NULL)
return GTK_ORDERING_SMALLER;
return gtk_ordering_from_cmpfunc (strcmp (sa, sb));
}
static gboolean
gtk_string_sort_keys_is_compatible (GtkSortKeys *keys,
GtkSortKeys *other)
{
return FALSE;
}
static void
gtk_string_sort_keys_init_key (GtkSortKeys *keys,
gpointer item,
gpointer key_memory)
{
GtkStringSortKeys *self = (GtkStringSortKeys *) keys;
char **key = (char **) key_memory;
*key = gtk_string_sorter_get_key (self->expression, self->ignore_case, item);
}
static void
gtk_string_sort_keys_clear_key (GtkSortKeys *keys,
gpointer key_memory)
{
char **key = (char **) key_memory;
g_free (*key);
}
static const GtkSortKeysClass GTK_STRING_SORT_KEYS_CLASS =
{
gtk_string_sort_keys_free,
gtk_string_sort_keys_compare,
gtk_string_sort_keys_is_compatible,
gtk_string_sort_keys_init_key,
gtk_string_sort_keys_clear_key,
};
static GtkSortKeys *
gtk_string_sort_keys_new (GtkStringSorter *self)
{
GtkStringSortKeys *result;
if (self->expression == NULL)
return gtk_sort_keys_new_equal ();
result = gtk_sort_keys_new (GtkStringSortKeys,
&GTK_STRING_SORT_KEYS_CLASS,
sizeof (char *),
sizeof (char *));
result->expression = gtk_expression_ref (self->expression);
result->ignore_case = self->ignore_case;
return (GtkSortKeys *) result;
}
static void
gtk_string_sorter_set_property (GObject *object,
guint prop_id,
@@ -239,6 +317,10 @@ static void
gtk_string_sorter_init (GtkStringSorter *self)
{
self->ignore_case = TRUE;
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_string_sort_keys_new (self));
}
/**
@@ -306,7 +388,9 @@ gtk_string_sorter_set_expression (GtkStringSorter *self,
if (expression)
self->expression = gtk_expression_ref (expression);
gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_string_sort_keys_new (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]);
}
@@ -345,7 +429,9 @@ gtk_string_sorter_set_ignore_case (GtkStringSorter *self,
self->ignore_case = ignore_case;
gtk_sorter_changed (GTK_SORTER (self), ignore_case ? GTK_SORTER_CHANGE_LESS_STRICT : GTK_SORTER_CHANGE_MORE_STRICT);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
ignore_case ? GTK_SORTER_CHANGE_LESS_STRICT : GTK_SORTER_CHANGE_MORE_STRICT,
gtk_string_sort_keys_new (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IGNORE_CASE]);
}

561
gtk/gtktim1sortmodel.c Normal file
View File

@@ -0,0 +1,561 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtktim1sortmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktimsortprivate.h"
typedef struct _SortItem SortItem;
struct _SortItem
{
GObject *item;
guint position;
};
static void
sort_item_clear (gpointer data)
{
SortItem *si = data;
g_clear_object (&si->item);
}
#define GDK_ARRAY_ELEMENT_TYPE SortItem
#define GDK_ARRAY_TYPE_NAME SortArray
#define GDK_ARRAY_NAME sort_array
#define GDK_ARRAY_FREE_FUNC sort_item_clear
#define GDK_ARRAY_BY_VALUE 1
#include "gdk/gdkarrayimpl.c"
/**
* SECTION:gtksor4listmodel
* @title: GtkTim1SortModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkTim1SortModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkTim1SortModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkTim1SortModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkTim1SortModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
SortArray items; /* empty if known unsorted */
};
struct _GtkTim1SortModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_tim1_sort_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_tim1_sort_model_get_n_items (GListModel *list)
{
GtkTim1SortModel *self = GTK_TIM1_SORT_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_tim1_sort_model_get_item (GListModel *list,
guint position)
{
GtkTim1SortModel *self = GTK_TIM1_SORT_MODEL (list);
if (self->model == NULL)
return NULL;
if (sort_array_is_empty (&self->items))
return g_list_model_get_item (self->model, position);
if (position >= sort_array_get_size (&self->items))
return NULL;
return g_object_ref (sort_array_get (&self->items, position)->item);
}
static void
gtk_tim1_sort_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_tim1_sort_model_get_item_type;
iface->get_n_items = gtk_tim1_sort_model_get_n_items;
iface->get_item = gtk_tim1_sort_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkTim1SortModel, gtk_tim1_sort_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tim1_sort_model_model_init))
static void
gtk_tim1_sort_model_clear_items (GtkTim1SortModel *self)
{
sort_array_clear (&self->items);
}
static gboolean
gtk_tim1_sort_model_should_sort (GtkTim1SortModel *self)
{
return self->sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
}
static void
gtk_tim1_sort_model_create_items (GtkTim1SortModel *self)
{
guint i, n_items;
if (!gtk_tim1_sort_model_should_sort (self))
return;
n_items = g_list_model_get_n_items (self->model);
sort_array_reserve (&self->items, n_items);
for (i = 0; i < n_items; i++)
{
sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
}
}
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer data)
{
SortItem *sa = (SortItem *) a;
SortItem *sb = (SortItem *) b;
return gtk_sorter_compare (data, sa->item, sb->item);
}
static void
gtk_tim1_sort_model_resort (GtkTim1SortModel *self,
guint already_sorted)
{
GtkTimSort sort;
gtk_tim_sort_init (&sort,
sort_array_get_data (&self->items),
sort_array_get_size (&self->items),
sizeof (SortItem),
sort_func,
self->sorter);
gtk_tim_sort_set_runs (&sort, (gsize[2]) { already_sorted, 0});
while (gtk_tim_sort_step (&sort, NULL));
gtk_tim_sort_finish (&sort);
}
static void
gtk_tim1_sort_model_remove_items (GtkTim1SortModel *self,
guint position,
guint removed,
guint added,
guint *unmodified_start,
guint *unmodified_end)
{
guint i, n_items, valid;
guint start, end;
n_items = sort_array_get_size (&self->items);
start = n_items;
end = n_items;
valid = 0;
for (i = 0; i < n_items; i++)
{
SortItem *si = sort_array_index (&self->items, i);
if (si->position >= position + removed)
si->position = si->position - removed + added;
else if (si->position >= position)
{
start = MIN (start, valid);
end = n_items - i - 1;
sort_item_clear (si);
continue;
}
if (valid < i)
*sort_array_index (&self->items, valid) = *sort_array_index (&self->items, i);
valid++;
}
g_assert (valid == n_items - removed);
memset (sort_array_index (&self->items, valid), 0, sizeof (SortItem) * removed);
sort_array_set_size (&self->items, valid);
*unmodified_start = start;
*unmodified_end = end;
}
static void
gtk_tim1_sort_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkTim1SortModel *self)
{
guint i, n_items, start, end;
if (removed == 0 && added == 0)
return;
if (!gtk_tim1_sort_model_should_sort (self))
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
return;
}
gtk_tim1_sort_model_remove_items (self, position, removed, added, &start, &end);
if (added > 0)
{
sort_array_reserve (&self->items, sort_array_get_size (&self->items) + added);
for (i = position; i < position + added; i++)
{
sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
}
gtk_tim1_sort_model_resort (self, sort_array_get_size (&self->items) - added);
for (i = 0; i < start; i++)
{
SortItem *si = sort_array_get (&self->items, i);
if (si->position >= position && si->position < position + added)
{
start = i;
break;
}
}
n_items = sort_array_get_size (&self->items);
for (i = 0; i < end; i++)
{
SortItem *si = sort_array_get (&self->items, n_items - i - 1);
if (si->position >= position && si->position < position + added)
{
end = i;
break;
}
}
}
n_items = sort_array_get_size (&self->items) - start - end;
g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
}
static void
gtk_tim1_sort_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTim1SortModel *self = GTK_TIM1_SORT_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
gtk_tim1_sort_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_tim1_sort_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim1_sort_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkTim1SortModel *self = GTK_TIM1_SORT_MODEL (object);
switch (prop_id)
{
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim1_sort_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkTim1SortModel *self)
{
guint n_items;
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_tim1_sort_model_clear_items (self);
else if (sort_array_is_empty (&self->items))
gtk_tim1_sort_model_create_items (self);
gtk_tim1_sort_model_resort (self, 0);
n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
static void
gtk_tim1_sort_model_clear_model (GtkTim1SortModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_tim1_sort_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_tim1_sort_model_clear_items (self);
}
static void
gtk_tim1_sort_model_clear_sorter (GtkTim1SortModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_tim1_sort_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
gtk_tim1_sort_model_clear_items (self);
}
static void
gtk_tim1_sort_model_dispose (GObject *object)
{
GtkTim1SortModel *self = GTK_TIM1_SORT_MODEL (object);
gtk_tim1_sort_model_clear_model (self);
gtk_tim1_sort_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_tim1_sort_model_parent_class)->dispose (object);
};
static void
gtk_tim1_sort_model_class_init (GtkTim1SortModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_tim1_sort_model_set_property;
gobject_class->get_property = gtk_tim1_sort_model_get_property;
gobject_class->dispose = gtk_tim1_sort_model_dispose;
/**
* GtkTim1SortModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim1SortModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_tim1_sort_model_init (GtkTim1SortModel *self)
{
}
/**
* gtk_tim1_sort_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkTim1SortModel
**/
GtkTim1SortModel *
gtk_tim1_sort_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkTim1SortModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_TIM1_SORT_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_tim1_sort_model_set_model:
* @self: a #GtkTim1SortModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_tim1_sort_model_set_model (GtkTim1SortModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_TIM1_SORT_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_tim1_sort_model_clear_model (self);
if (model)
{
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_tim1_sort_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_tim1_sort_model_create_items (self);
gtk_tim1_sort_model_resort (self, 0);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_tim1_sort_model_get_model:
* @self: a #GtkTim1SortModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_tim1_sort_model_get_model (GtkTim1SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM1_SORT_MODEL (self), NULL);
return self->model;
}
/**
* gtk_tim1_sort_model_set_sorter:
* @self: a #GtkTim1SortModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_tim1_sort_model_set_sorter (GtkTim1SortModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_TIM1_SORT_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_tim1_sort_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_tim1_sort_model_sorter_changed_cb), self);
gtk_tim1_sort_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
else
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_tim1_sort_model_get_sorter:
* @self: a #GtkSor4LisTModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_tim1_sort_model_get_sorter (GtkTim1SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM1_SORT_MODEL (self), NULL);
return self->sorter;
}

57
gtk/gtktim1sortmodel.h Normal file
View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_TIM1_SORT_MODEL_H__
#define __GTK_TIM1_SORT_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_TIM1_SORT_MODEL (gtk_tim1_sort_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkTim1SortModel, gtk_tim1_sort_model, GTK, TIM1_SORT_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkTim1SortModel * gtk_tim1_sort_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_tim1_sort_model_set_sorter (GtkTim1SortModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_tim1_sort_model_get_sorter (GtkTim1SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim1_sort_model_set_model (GtkTim1SortModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_tim1_sort_model_get_model (GtkTim1SortModel *self);
G_END_DECLS
#endif /* __GTK_TIM1_SORT_MODEL_H__ */

789
gtk/gtktim2sortmodel.c Normal file
View File

@@ -0,0 +1,789 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtktim2sortmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktimsortprivate.h"
typedef struct _SortItem SortItem;
struct _SortItem
{
GObject *item;
guint position;
};
static void
sort_item_clear (gpointer data)
{
SortItem *si = data;
g_clear_object (&si->item);
}
#define GDK_ARRAY_ELEMENT_TYPE SortItem
#define GDK_ARRAY_TYPE_NAME SortArray
#define GDK_ARRAY_NAME sort_array
#define GDK_ARRAY_FREE_FUNC sort_item_clear
#define GDK_ARRAY_BY_VALUE 1
#include "gdk/gdkarrayimpl.c"
/**
* SECTION:gtksor4listmodel
* @title: GtkTim2SortModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkTim2SortModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkTim2SortModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkTim2SortModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_INCREMENTAL,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkTim2SortModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
gboolean incremental;
GtkTimSort sort; /* ongoing sort operation */
guint sort_cb; /* 0 or current ongoing sort callback */
SortArray items; /* empty if known unsorted */
};
struct _GtkTim2SortModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GType
gtk_tim2_sort_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_tim2_sort_model_get_n_items (GListModel *list)
{
GtkTim2SortModel *self = GTK_TIM2_SORT_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_tim2_sort_model_get_item (GListModel *list,
guint position)
{
GtkTim2SortModel *self = GTK_TIM2_SORT_MODEL (list);
if (self->model == NULL)
return NULL;
if (sort_array_is_empty (&self->items))
return g_list_model_get_item (self->model, position);
if (position >= sort_array_get_size (&self->items))
return NULL;
return g_object_ref (sort_array_get (&self->items, position)->item);
}
static void
gtk_tim2_sort_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_tim2_sort_model_get_item_type;
iface->get_n_items = gtk_tim2_sort_model_get_n_items;
iface->get_item = gtk_tim2_sort_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkTim2SortModel, gtk_tim2_sort_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tim2_sort_model_model_init))
static gboolean
gtk_tim2_sort_model_is_sorting (GtkTim2SortModel *self)
{
return self->sort_cb != 0;
}
static void
gtk_tim2_sort_model_stop_sorting (GtkTim2SortModel *self,
gsize *runs)
{
if (self->sort_cb == 0)
{
if (runs)
{
runs[0] = sort_array_get_size (&self->items);
runs[1] = 0;
}
return;
}
if (runs)
gtk_tim_sort_get_runs (&self->sort, runs);
gtk_tim_sort_finish (&self->sort);
g_clear_handle_id (&self->sort_cb, g_source_remove);
}
static gboolean
gtk_tim2_sort_model_sort_step (GtkTim2SortModel *self,
gboolean finish,
guint *out_position,
guint *out_n_items)
{
gint64 end_time = g_get_monotonic_time ();
gboolean result = FALSE;
GtkTimSortRun change;
SortItem *start_change, *end_change;
/* 1 millisecond */
end_time += 1000;
end_change = sort_array_get_data (&self->items);
start_change = end_change + sort_array_get_size (&self->items);
while (gtk_tim_sort_step (&self->sort, &change))
{
result = TRUE;
if (change.len)
{
start_change = MIN (start_change, (SortItem *) change.base);
end_change = MAX (end_change, ((SortItem *) change.base) + change.len);
}
if (g_get_monotonic_time () >= end_time && !finish)
break;
}
if (start_change < end_change)
{
*out_position = start_change - sort_array_get_data (&self->items);
*out_n_items = end_change - start_change;
}
else
{
*out_position = 0;
*out_n_items = 0;
}
return result;
}
static gboolean
gtk_tim2_sort_model_sort_cb (gpointer data)
{
GtkTim2SortModel *self = data;
guint pos, n_items;
if (gtk_tim2_sort_model_sort_step (self, FALSE, &pos, &n_items))
{
if (n_items)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
return G_SOURCE_CONTINUE;
}
gtk_tim2_sort_model_stop_sorting (self, NULL);
return G_SOURCE_REMOVE;
}
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer data)
{
SortItem *sa = (SortItem *) a;
SortItem *sb = (SortItem *) b;
return gtk_sorter_compare (data, sa->item, sb->item);
}
static gboolean
gtk_tim2_sort_model_start_sorting (GtkTim2SortModel *self,
gsize *runs)
{
g_assert (self->sort_cb == 0);
gtk_tim_sort_init (&self->sort,
sort_array_get_data (&self->items),
sort_array_get_size (&self->items),
sizeof (SortItem),
sort_func,
self->sorter);
if (runs)
gtk_tim_sort_set_runs (&self->sort, runs);
if (self->incremental)
gtk_tim_sort_set_max_merge_size (&self->sort, 1024);
if (!self->incremental)
return FALSE;
self->sort_cb = g_idle_add (gtk_tim2_sort_model_sort_cb, self);
return TRUE;
}
static void
gtk_tim2_sort_model_finish_sorting (GtkTim2SortModel *self,
guint *pos,
guint *n_items)
{
gtk_tim_sort_set_max_merge_size (&self->sort, 0);
gtk_tim2_sort_model_sort_step (self, TRUE, pos, n_items);
gtk_tim_sort_finish (&self->sort);
gtk_tim2_sort_model_stop_sorting (self, NULL);
}
static void
gtk_tim2_sort_model_clear_items (GtkTim2SortModel *self,
guint *pos,
guint *n_items)
{
gtk_tim2_sort_model_stop_sorting (self, NULL);
if (pos || n_items)
{
guint start, end;
for (start = 0; start < sort_array_get_size (&self->items); start++)
{
if (sort_array_index (&self->items, start)->position != start)
break;
}
for (end = sort_array_get_size (&self->items); end > start; end--)
{
if (sort_array_index (&self->items, end - 1)->position != end - 1)
break;
}
*n_items = end - start;
if (*n_items == 0)
*pos = 0;
else
*pos = start;
}
sort_array_clear (&self->items);
}
static gboolean
gtk_tim2_sort_model_should_sort (GtkTim2SortModel *self)
{
return self->sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
}
static void
gtk_tim2_sort_model_create_items (GtkTim2SortModel *self)
{
guint i, n_items;
if (!gtk_tim2_sort_model_should_sort (self))
return;
n_items = g_list_model_get_n_items (self->model);
sort_array_reserve (&self->items, n_items);
for (i = 0; i < n_items; i++)
{
sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
}
}
static void
gtk_tim2_sort_model_update_items (GtkTim2SortModel *self,
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1],
guint position,
guint removed,
guint added,
guint *unmodified_start,
guint *unmodified_end)
{
guint i, n_items, valid;
guint start, end;
n_items = sort_array_get_size (&self->items);
start = n_items;
end = n_items;
valid = 0;
for (i = 0; i < n_items; i++)
{
SortItem *si = sort_array_index (&self->items, i);
if (si->position >= position + removed)
si->position = si->position - removed + added;
else if (si->position >= position)
{
start = MIN (start, valid);
end = n_items - i - 1;
sort_item_clear (si);
continue;
}
if (valid < i)
*sort_array_index (&self->items, valid) = *sort_array_index (&self->items, i);
valid++;
}
/* FIXME */
runs[0] = 0;
g_assert (valid == n_items - removed);
memset (sort_array_index (&self->items, valid), 0, sizeof (SortItem) * removed);
sort_array_set_size (&self->items, valid);
*unmodified_start = start;
*unmodified_end = end;
}
static void
gtk_tim2_sort_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkTim2SortModel *self)
{
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1];
guint i, n_items, start, end;
gboolean was_sorting;
if (removed == 0 && added == 0)
return;
if (!gtk_tim2_sort_model_should_sort (self))
{
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
return;
}
was_sorting = gtk_tim2_sort_model_is_sorting (self);
gtk_tim2_sort_model_stop_sorting (self, runs);
gtk_tim2_sort_model_update_items (self, runs, position, removed, added, &start, &end);
if (added > 0)
{
gboolean success;
sort_array_reserve (&self->items, sort_array_get_size (&self->items) + added);
for (i = position; i < position + added; i++)
{
sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
}
end = 0;
success = gtk_tim2_sort_model_start_sorting (self, runs);
if (!success)
{
guint pos, n;
gtk_tim2_sort_model_finish_sorting (self, &pos, &n);
start = MIN (start, pos);
end = MIN (end, sort_array_get_size (&self->items) - pos - n);
}
}
else
{
if (was_sorting)
gtk_tim2_sort_model_start_sorting (self, runs);
}
n_items = sort_array_get_size (&self->items) - start - end;
g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
}
static void
gtk_tim2_sort_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTim2SortModel *self = GTK_TIM2_SORT_MODEL (object);
switch (prop_id)
{
case PROP_INCREMENTAL:
gtk_tim2_sort_model_set_incremental (self, g_value_get_boolean (value));
break;
case PROP_MODEL:
gtk_tim2_sort_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_tim2_sort_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim2_sort_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkTim2SortModel *self = GTK_TIM2_SORT_MODEL (object);
switch (prop_id)
{
case PROP_INCREMENTAL:
g_value_set_boolean (value, self->incremental);
break;
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim2_sort_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkTim2SortModel *self)
{
guint pos, n_items;
if (gtk_sorter_get_order (sorter) == GTK_SORTER_ORDER_NONE)
gtk_tim2_sort_model_clear_items (self, &pos, &n_items);
else
{
if (sort_array_is_empty (&self->items))
gtk_tim2_sort_model_create_items (self);
gtk_tim2_sort_model_stop_sorting (self, NULL);
if (gtk_tim2_sort_model_start_sorting (self, NULL))
pos = n_items = 0;
else
gtk_tim2_sort_model_finish_sorting (self, &pos, &n_items);
}
if (n_items > 0)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
}
static void
gtk_tim2_sort_model_clear_model (GtkTim2SortModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_tim2_sort_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_tim2_sort_model_clear_items (self, NULL, NULL);
}
static void
gtk_tim2_sort_model_clear_sorter (GtkTim2SortModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_tim2_sort_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
}
static void
gtk_tim2_sort_model_dispose (GObject *object)
{
GtkTim2SortModel *self = GTK_TIM2_SORT_MODEL (object);
gtk_tim2_sort_model_clear_model (self);
gtk_tim2_sort_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_tim2_sort_model_parent_class)->dispose (object);
};
static void
gtk_tim2_sort_model_class_init (GtkTim2SortModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_tim2_sort_model_set_property;
gobject_class->get_property = gtk_tim2_sort_model_get_property;
gobject_class->dispose = gtk_tim2_sort_model_dispose;
/**
* GtkTim2SortModel:incremental:
*
* If the model should sort items incrementally
*/
properties[PROP_INCREMENTAL] =
g_param_spec_boolean ("incremental",
P_("Incremental"),
P_("Sort items incrementally"),
FALSE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim2SortModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim2SortModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_tim2_sort_model_init (GtkTim2SortModel *self)
{
}
/**
* gtk_tim2_sort_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkTim2SortModel
**/
GtkTim2SortModel *
gtk_tim2_sort_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkTim2SortModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_TIM2_SORT_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_tim2_sort_model_set_model:
* @self: a #GtkTim2SortModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_tim2_sort_model_set_model (GtkTim2SortModel *self,
GListModel *model)
{
guint removed, added;
g_return_if_fail (GTK_IS_TIM2_SORT_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_tim2_sort_model_clear_model (self);
if (model)
{
guint ignore1, ignore2;
self->model = g_object_ref (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_tim2_sort_model_items_changed_cb), self);
added = g_list_model_get_n_items (model);
gtk_tim2_sort_model_create_items (self);
if (!gtk_tim2_sort_model_start_sorting (self, NULL))
gtk_tim2_sort_model_finish_sorting (self, &ignore1, &ignore2);
}
else
added = 0;
if (removed > 0 || added > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, added);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_tim2_sort_model_get_model:
* @self: a #GtkTim2SortModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_tim2_sort_model_get_model (GtkTim2SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM2_SORT_MODEL (self), NULL);
return self->model;
}
/**
* gtk_tim2_sort_model_set_sorter:
* @self: a #GtkTim2SortModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_tim2_sort_model_set_sorter (GtkTim2SortModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_TIM2_SORT_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_tim2_sort_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_tim2_sort_model_sorter_changed_cb), self);
gtk_tim2_sort_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
}
else
{
guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
if (n_items > 1)
g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_tim2_sort_model_get_sorter:
* @self: a #GtkTim2SortModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_tim2_sort_model_get_sorter (GtkTim2SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM2_SORT_MODEL (self), NULL);
return self->sorter;
}
/**
* gtk_tim2_sort_model_set_incremental:
* @self: a #GtkTim2SortModel
* @incremental: %TRUE to sort incrementally
*
* Sets the sort model to do an incremental sort.
*
* When incremental sorting is enabled, the sortlistmodel will not do
* a complete sort immediately, but will instead queue an idle handler that
* incrementally sorts the items towards their correct position. This of
* course means that items do not instantly appear in the right place. It
* also means that the total sorting time is a lot slower.
*
* When your filter blocks the UI while sorting, you might consider
* turning this on. Depending on your model and sorters, this may become
* interesting around 10,000 to 100,000 items.
*
* By default, incremental sortinging is disabled.
*/
void
gtk_tim2_sort_model_set_incremental (GtkTim2SortModel *self,
gboolean incremental)
{
g_return_if_fail (GTK_IS_TIM2_SORT_MODEL (self));
if (self->incremental == incremental)
return;
self->incremental = incremental;
if (!incremental && gtk_tim2_sort_model_is_sorting (self))
{
guint pos, n_items;
gtk_tim2_sort_model_finish_sorting (self, &pos, &n_items);
if (n_items)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCREMENTAL]);
}
/**
* gtk_tim2_sort_model_get_incremental:
* @self: a #GtkModel
*
* Returns whether incremental sorting was enabled via
* gtk_sort_list_model_set_incremental().
*
* Returns: %TRUE if incremental sorting is enabled
*/
gboolean
gtk_tim2_sort_model_get_incremental (GtkTim2SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM2_SORT_MODEL (self), FALSE);
return self->incremental;
}

63
gtk/gtktim2sortmodel.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_TIM2_SORT_MODEL_H__
#define __GTK_TIM2_SORT_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_TIM2_SORT_MODEL (gtk_tim2_sort_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkTim2SortModel, gtk_tim2_sort_model, GTK, TIM2_SORT_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkTim2SortModel * gtk_tim2_sort_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_tim2_sort_model_set_sorter (GtkTim2SortModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_tim2_sort_model_get_sorter (GtkTim2SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim2_sort_model_set_model (GtkTim2SortModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_tim2_sort_model_get_model (GtkTim2SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim2_sort_model_set_incremental (GtkTim2SortModel *self,
gboolean incremental);
GDK_AVAILABLE_IN_ALL
gboolean gtk_tim2_sort_model_get_incremental (GtkTim2SortModel *self);
G_END_DECLS
#endif /* __GTK_TIM2_SORT_MODEL_H__ */

830
gtk/gtktim3sortmodel.c Normal file
View File

@@ -0,0 +1,830 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtktim3sortmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtktimsortprivate.h"
/**
* SECTION:gtkstim3listmodel
* @title: GtkTim3SortModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkTim3SortModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkTim3SortModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkTim3SortModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_INCREMENTAL,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkTim3SortModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
gboolean incremental;
GtkTimSort sort; /* ongoing sort operation */
guint sort_cb; /* 0 or current ongoing sort callback */
guint n_items;
gpointer *keys;
gpointer *positions;
};
struct _GtkTim3SortModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static guint
pos_from_key (GtkTim3SortModel *self,
gpointer key)
{
guint pos = (gpointer *) key - self->keys;
g_assert (pos < self->n_items);
return pos;
}
static gpointer
key_from_pos (GtkTim3SortModel *self,
guint pos)
{
return self->keys + pos;
}
static GType
gtk_tim3_sort_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_tim3_sort_model_get_n_items (GListModel *list)
{
GtkTim3SortModel *self = GTK_TIM3_SORT_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_tim3_sort_model_get_item (GListModel *list,
guint position)
{
GtkTim3SortModel *self = GTK_TIM3_SORT_MODEL (list);
if (self->model == NULL)
return NULL;
if (self->positions)
position = pos_from_key (self, self->positions[position]);
return g_list_model_get_item (self->model, position);
}
static void
gtk_tim3_sort_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_tim3_sort_model_get_item_type;
iface->get_n_items = gtk_tim3_sort_model_get_n_items;
iface->get_item = gtk_tim3_sort_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkTim3SortModel, gtk_tim3_sort_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tim3_sort_model_model_init))
static gboolean
gtk_tim3_sort_model_is_sorting (GtkTim3SortModel *self)
{
return self->sort_cb != 0;
}
static void
gtk_tim3_sort_model_stop_sorting (GtkTim3SortModel *self,
gsize *runs)
{
if (self->sort_cb == 0)
{
if (runs)
{
runs[0] = self->n_items;
runs[1] = 0;
}
return;
}
if (runs)
gtk_tim_sort_get_runs (&self->sort, runs);
gtk_tim_sort_finish (&self->sort);
g_clear_handle_id (&self->sort_cb, g_source_remove);
}
static gboolean
gtk_tim3_sort_model_sort_step (GtkTim3SortModel *self,
gboolean finish,
guint *out_position,
guint *out_n_items)
{
gint64 end_time = g_get_monotonic_time ();
gboolean result = FALSE;
GtkTimSortRun change;
gpointer *start_change, *end_change;
/* 1 millisecond */
end_time += 1000;
end_change = self->positions;
start_change = self->positions + self->n_items;
while (gtk_tim_sort_step (&self->sort, &change))
{
result = TRUE;
if (change.len)
{
start_change = MIN (start_change, (gpointer *) change.base);
end_change = MAX (end_change, ((gpointer *) change.base) + change.len);
}
if (g_get_monotonic_time () >= end_time && !finish)
break;
}
if (start_change < end_change)
{
*out_position = start_change - self->positions;
*out_n_items = end_change - start_change;
}
else
{
*out_position = 0;
*out_n_items = 0;
}
return result;
}
static gboolean
gtk_tim3_sort_model_sort_cb (gpointer data)
{
GtkTim3SortModel *self = data;
guint pos, n_items;
if (gtk_tim3_sort_model_sort_step (self, FALSE, &pos, &n_items))
{
if (n_items)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
return G_SOURCE_CONTINUE;
}
gtk_tim3_sort_model_stop_sorting (self, NULL);
return G_SOURCE_REMOVE;
}
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer data)
{
gpointer **sa = (gpointer **) a;
gpointer **sb = (gpointer **) b;
return gtk_sorter_compare (data, **sa, **sb);
}
static gboolean
gtk_tim3_sort_model_start_sorting (GtkTim3SortModel *self,
gsize *runs)
{
g_assert (self->sort_cb == 0);
gtk_tim_sort_init (&self->sort,
self->positions,
self->n_items,
sizeof (gpointer),
sort_func,
self->sorter);
if (runs)
gtk_tim_sort_set_runs (&self->sort, runs);
if (self->incremental)
gtk_tim_sort_set_max_merge_size (&self->sort, 1024);
if (!self->incremental)
return FALSE;
self->sort_cb = g_idle_add (gtk_tim3_sort_model_sort_cb, self);
return TRUE;
}
static void
gtk_tim3_sort_model_finish_sorting (GtkTim3SortModel *self,
guint *pos,
guint *n_items)
{
gtk_tim_sort_set_max_merge_size (&self->sort, 0);
gtk_tim3_sort_model_sort_step (self, TRUE, pos, n_items);
gtk_tim_sort_finish (&self->sort);
gtk_tim3_sort_model_stop_sorting (self, NULL);
}
static void
gtk_tim3_sort_model_clear_items (GtkTim3SortModel *self,
guint *pos,
guint *n_items)
{
guint i;
gtk_tim3_sort_model_stop_sorting (self, NULL);
if (self->positions == NULL)
{
if (pos || n_items)
*pos = *n_items = 0;
return;
}
if (pos || n_items)
{
guint start, end;
for (start = 0; start < self->n_items; start++)
{
if (pos_from_key (self, self->positions[start]) != + start)
break;
}
for (end = self->n_items; end > start; end--)
{
if (pos_from_key (self, self->positions[end - 1]) != end - 1)
break;
}
*n_items = end - start;
if (*n_items == 0)
*pos = 0;
else
*pos = start;
}
g_clear_pointer (&self->positions, g_free);
for (i = 0; i < self->n_items; i++)
g_object_unref (self->keys[i]);
g_clear_pointer (&self->keys, g_free);
}
static gboolean
gtk_tim3_sort_model_should_sort (GtkTim3SortModel *self)
{
return self->sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
}
static void
gtk_tim3_sort_model_create_items (GtkTim3SortModel *self)
{
guint i;
if (!gtk_tim3_sort_model_should_sort (self))
return;
self->positions = g_new (gpointer, self->n_items);
self->keys = g_new (gpointer, self->n_items);
for (i = 0; i < self->n_items; i++)
{
self->keys[i] = g_list_model_get_item (self->model, i);
self->positions[i] = key_from_pos (self, i);
}
}
/* This realloc()s the arrays but does not set the added values. */
static void
gtk_tim3_sort_model_update_items (GtkTim3SortModel *self,
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1],
guint position,
guint removed,
guint added,
guint *unmodified_start,
guint *unmodified_end)
{
guint i, n_items, valid;
guint start, end;
gpointer *old_keys;
n_items = self->n_items;
start = n_items;
end = n_items;
/* first, move the keys over */
old_keys = self->keys;
for (i = position; i < position + removed; i++)
g_object_unref (self->keys[i]);
if (removed > added)
{
memmove (self->keys + position + removed,
self->keys + position + added,
sizeof (gpointer) * (n_items - position - removed));
self->keys = g_renew (gpointer, self->keys, n_items - removed + added);
}
else if (removed < added)
{
self->keys = g_renew (gpointer, self->keys, n_items - removed + added);
memmove (self->keys + position + removed,
self->keys + position + added,
sizeof (gpointer) * (n_items - position - removed));
}
/* then, update the positions */
valid = 0;
for (i = 0; i < n_items; i++)
{
guint pos = (gpointer *) self->positions[i] - old_keys;
if (pos >= position + removed)
pos = pos - removed + added;
else if (pos >= position)
{
start = MIN (start, valid);
end = n_items - i - 1;
continue;
}
self->positions[valid] = key_from_pos (self, pos);
valid++;
}
self->positions = g_renew (gpointer, self->positions, n_items - removed + added);
/* FIXME */
runs[0] = 0;
g_assert (valid == n_items - removed);
self->n_items = n_items - removed + added;
for (i = 0; i < added; i++)
{
self->keys[position + i] = g_list_model_get_item (self->model, position + i);
self->positions[self->n_items - added + i] = key_from_pos (self, position + i);
}
*unmodified_start = start;
*unmodified_end = end;
}
static void
gtk_tim3_sort_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkTim3SortModel *self)
{
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1];
guint i, n_items, start, end;
gboolean was_sorting;
if (removed == 0 && added == 0)
return;
if (!gtk_tim3_sort_model_should_sort (self))
{
self->n_items = self->n_items - removed + added;
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
return;
}
was_sorting = gtk_tim3_sort_model_is_sorting (self);
gtk_tim3_sort_model_stop_sorting (self, runs);
gtk_tim3_sort_model_update_items (self, runs, position, removed, added, &start, &end);
if (added > 0)
{
if (gtk_tim3_sort_model_start_sorting (self, runs))
{
end = 0;
}
else
{
guint pos, n;
gtk_tim3_sort_model_finish_sorting (self, &pos, &n);
if (n)
start = MIN (start, pos);
/* find first item that was added */
for (i = 0; i < end; i++)
{
pos = pos_from_key (self, self->positions[self->n_items - i - 1]);
if (pos >= position && pos < position + added)
{
end = i;
break;
}
}
}
}
else
{
if (was_sorting)
gtk_tim3_sort_model_start_sorting (self, runs);
}
n_items = self->n_items - start - end;
g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
}
static void
gtk_tim3_sort_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTim3SortModel *self = GTK_TIM3_SORT_MODEL (object);
switch (prop_id)
{
case PROP_INCREMENTAL:
gtk_tim3_sort_model_set_incremental (self, g_value_get_boolean (value));
break;
case PROP_MODEL:
gtk_tim3_sort_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_tim3_sort_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim3_sort_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkTim3SortModel *self = GTK_TIM3_SORT_MODEL (object);
switch (prop_id)
{
case PROP_INCREMENTAL:
g_value_set_boolean (value, self->incremental);
break;
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim3_sort_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkTim3SortModel *self)
{
guint pos, n_items;
if (gtk_tim3_sort_model_should_sort (self))
{
if (self->positions == NULL)
gtk_tim3_sort_model_create_items (self);
gtk_tim3_sort_model_stop_sorting (self, NULL);
if (gtk_tim3_sort_model_start_sorting (self, NULL))
pos = n_items = 0;
else
gtk_tim3_sort_model_finish_sorting (self, &pos, &n_items);
}
else
{
gtk_tim3_sort_model_clear_items (self, &pos, &n_items);
}
if (n_items > 0)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
}
static void
gtk_tim3_sort_model_clear_model (GtkTim3SortModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_tim3_sort_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_tim3_sort_model_clear_items (self, NULL, NULL);
self->n_items = 0;
}
static void
gtk_tim3_sort_model_clear_sorter (GtkTim3SortModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_tim3_sort_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
}
static void
gtk_tim3_sort_model_dispose (GObject *object)
{
GtkTim3SortModel *self = GTK_TIM3_SORT_MODEL (object);
gtk_tim3_sort_model_clear_model (self);
gtk_tim3_sort_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_tim3_sort_model_parent_class)->dispose (object);
};
static void
gtk_tim3_sort_model_class_init (GtkTim3SortModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_tim3_sort_model_set_property;
gobject_class->get_property = gtk_tim3_sort_model_get_property;
gobject_class->dispose = gtk_tim3_sort_model_dispose;
/**
* GtkTim3SortModel:incremental:
*
* If the model should sort items incrementally
*/
properties[PROP_INCREMENTAL] =
g_param_spec_boolean ("incremental",
P_("Incremental"),
P_("Sort items incrementally"),
FALSE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim3SortModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim3SortModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_tim3_sort_model_init (GtkTim3SortModel *self)
{
}
/**
* gtk_tim3_sort_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkTim3SortModel
**/
GtkTim3SortModel *
gtk_tim3_sort_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkTim3SortModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_TIM3_SORT_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_tim3_sort_model_set_model:
* @self: a #GtkTim3SortModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_tim3_sort_model_set_model (GtkTim3SortModel *self,
GListModel *model)
{
guint removed;
g_return_if_fail (GTK_IS_TIM3_SORT_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_tim3_sort_model_clear_model (self);
if (model)
{
guint ignore1, ignore2;
self->model = g_object_ref (model);
self->n_items = g_list_model_get_n_items (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_tim3_sort_model_items_changed_cb), self);
if (gtk_tim3_sort_model_should_sort (self))
{
gtk_tim3_sort_model_create_items (self);
if (!gtk_tim3_sort_model_start_sorting (self, NULL))
gtk_tim3_sort_model_finish_sorting (self, &ignore1, &ignore2);
}
}
if (removed > 0 || self->n_items > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, self->n_items);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_tim3_sort_model_get_model:
* @self: a #GtkTim3SortModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_tim3_sort_model_get_model (GtkTim3SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM3_SORT_MODEL (self), NULL);
return self->model;
}
/**
* gtk_tim3_sort_model_set_sorter:
* @self: a #GtkTim3SortModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_tim3_sort_model_set_sorter (GtkTim3SortModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_TIM3_SORT_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_tim3_sort_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_tim3_sort_model_sorter_changed_cb), self);
}
gtk_tim3_sort_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_tim3_sort_model_get_sorter:
* @self: a #GtkTim3SortModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_tim3_sort_model_get_sorter (GtkTim3SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM3_SORT_MODEL (self), NULL);
return self->sorter;
}
/**
* gtk_tim3_sort_model_set_incremental:
* @self: a #GtkTim3SortModel
* @incremental: %TRUE to sort incrementally
*
* Sets the sort model to do an incremental sort.
*
* When incremental sorting is enabled, the sortlistmodel will not do
* a complete sort immediately, but will instead queue an idle handler that
* incrementally sorts the items towards their correct position. This of
* course means that items do not instantly appear in the right place. It
* also means that the total sorting time is a lot slower.
*
* When your filter blocks the UI while sorting, you might consider
* turning this on. Depending on your model and sorters, this may become
* interesting around 10,000 to 100,000 items.
*
* By default, incremental sortinging is disabled.
*/
void
gtk_tim3_sort_model_set_incremental (GtkTim3SortModel *self,
gboolean incremental)
{
g_return_if_fail (GTK_IS_TIM3_SORT_MODEL (self));
if (self->incremental == incremental)
return;
self->incremental = incremental;
if (!incremental && gtk_tim3_sort_model_is_sorting (self))
{
guint pos, n_items;
gtk_tim3_sort_model_finish_sorting (self, &pos, &n_items);
if (n_items)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCREMENTAL]);
}
/**
* gtk_tim3_sort_model_get_incremental:
* @self: a #GtkModel
*
* Returns whether incremental sorting was enabled via
* gtk_sort_list_model_set_incremental().
*
* Returns: %TRUE if incremental sorting is enabled
*/
gboolean
gtk_tim3_sort_model_get_incremental (GtkTim3SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM3_SORT_MODEL (self), FALSE);
return self->incremental;
}

63
gtk/gtktim3sortmodel.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_TIM3_SORT_MODEL_H__
#define __GTK_TIM3_SORT_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_TIM3_SORT_MODEL (gtk_tim3_sort_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkTim3SortModel, gtk_tim3_sort_model, GTK, TIM3_SORT_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkTim3SortModel * gtk_tim3_sort_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_tim3_sort_model_set_sorter (GtkTim3SortModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_tim3_sort_model_get_sorter (GtkTim3SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim3_sort_model_set_model (GtkTim3SortModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_tim3_sort_model_get_model (GtkTim3SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim3_sort_model_set_incremental (GtkTim3SortModel *self,
gboolean incremental);
GDK_AVAILABLE_IN_ALL
gboolean gtk_tim3_sort_model_get_incremental (GtkTim3SortModel *self);
G_END_DECLS
#endif /* __GTK_TIM3_SORT_MODEL_H__ */

904
gtk/gtktim4sortmodel.c Normal file
View File

@@ -0,0 +1,904 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtktim4sortmodel.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtksorterprivate.h"
#include "gtktimsortprivate.h"
/**
* SECTION:gtkstim4listmodel
* @title: GtkTim4SortModel
* @short_description: A list model that sorts its items
* @see_also: #GListModel, #GtkSorter
*
* #GtkTim4SortModel is a list model that takes a list model and
* sorts its elements according to a #GtkSorter.
*
* #GtkTim4SortModel is a generic model and because of that it
* cannot take advantage of any external knowledge when sorting.
* If you run into performance issues with #GtkTim4SortModel, it
* is strongly recommended that you write your own sorting list
* model.
*/
enum {
PROP_0,
PROP_INCREMENTAL,
PROP_MODEL,
PROP_SORTER,
NUM_PROPERTIES
};
struct _GtkTim4SortModel
{
GObject parent_instance;
GListModel *model;
GtkSorter *sorter;
gboolean incremental;
GtkTimSort sort; /* ongoing sort operation */
guint sort_cb; /* 0 or current ongoing sort callback */
guint n_items;
GtkSortKeys *sort_keys;
gsize key_size;
gpointer keys;
gpointer *positions;
};
struct _GtkTim4SortModelClass
{
GObjectClass parent_class;
};
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static guint
pos_from_key (GtkTim4SortModel *self,
gpointer key)
{
guint pos = ((char *) key - (char *) self->keys) / self->key_size;
g_assert (pos < self->n_items);
return pos;
}
static gpointer
key_from_pos (GtkTim4SortModel *self,
guint pos)
{
return (char *) self->keys + self->key_size * pos;
}
static GType
gtk_tim4_sort_model_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_tim4_sort_model_get_n_items (GListModel *list)
{
GtkTim4SortModel *self = GTK_TIM4_SORT_MODEL (list);
if (self->model == NULL)
return 0;
return g_list_model_get_n_items (self->model);
}
static gpointer
gtk_tim4_sort_model_get_item (GListModel *list,
guint position)
{
GtkTim4SortModel *self = GTK_TIM4_SORT_MODEL (list);
if (self->model == NULL)
return NULL;
if (self->positions)
position = pos_from_key (self, self->positions[position]);
return g_list_model_get_item (self->model, position);
}
static void
gtk_tim4_sort_model_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_tim4_sort_model_get_item_type;
iface->get_n_items = gtk_tim4_sort_model_get_n_items;
iface->get_item = gtk_tim4_sort_model_get_item;
}
G_DEFINE_TYPE_WITH_CODE (GtkTim4SortModel, gtk_tim4_sort_model, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_tim4_sort_model_model_init))
static gboolean
gtk_tim4_sort_model_is_sorting (GtkTim4SortModel *self)
{
return self->sort_cb != 0;
}
static void
gtk_tim4_sort_model_stop_sorting (GtkTim4SortModel *self,
gsize *runs)
{
if (self->sort_cb == 0)
{
if (runs)
{
runs[0] = self->n_items;
runs[1] = 0;
}
return;
}
if (runs)
gtk_tim_sort_get_runs (&self->sort, runs);
gtk_tim_sort_finish (&self->sort);
g_clear_handle_id (&self->sort_cb, g_source_remove);
}
static gboolean
gtk_tim4_sort_model_sort_step (GtkTim4SortModel *self,
gboolean finish,
guint *out_position,
guint *out_n_items)
{
gint64 end_time = g_get_monotonic_time ();
gboolean result = FALSE;
GtkTimSortRun change;
gpointer *start_change, *end_change;
/* 1 millisecond */
end_time += 1000;
end_change = self->positions;
start_change = self->positions + self->n_items;
while (gtk_tim_sort_step (&self->sort, &change))
{
result = TRUE;
if (change.len)
{
start_change = MIN (start_change, (gpointer *) change.base);
end_change = MAX (end_change, ((gpointer *) change.base) + change.len);
}
if (g_get_monotonic_time () >= end_time && !finish)
break;
}
if (start_change < end_change)
{
*out_position = start_change - self->positions;
*out_n_items = end_change - start_change;
}
else
{
*out_position = 0;
*out_n_items = 0;
}
return result;
}
static gboolean
gtk_tim4_sort_model_sort_cb (gpointer data)
{
GtkTim4SortModel *self = data;
guint pos, n_items;
if (gtk_tim4_sort_model_sort_step (self, FALSE, &pos, &n_items))
{
if (n_items)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
return G_SOURCE_CONTINUE;
}
gtk_tim4_sort_model_stop_sorting (self, NULL);
return G_SOURCE_REMOVE;
}
static int
sort_func (gconstpointer a,
gconstpointer b,
gpointer data)
{
gpointer *sa = (gpointer *) a;
gpointer *sb = (gpointer *) b;
int result;
result = gtk_sort_keys_compare (data, *sa, *sb);
if (result)
return result;
return *sa < *sb ? -1 : 1;
}
static gboolean
gtk_tim4_sort_model_start_sorting (GtkTim4SortModel *self,
gsize *runs)
{
g_assert (self->sort_cb == 0);
gtk_tim_sort_init (&self->sort,
self->positions,
self->n_items,
sizeof (gpointer),
sort_func,
self->sort_keys);
if (runs)
gtk_tim_sort_set_runs (&self->sort, runs);
if (self->incremental)
gtk_tim_sort_set_max_merge_size (&self->sort, 1024);
if (!self->incremental)
return FALSE;
self->sort_cb = g_idle_add (gtk_tim4_sort_model_sort_cb, self);
return TRUE;
}
static void
gtk_tim4_sort_model_finish_sorting (GtkTim4SortModel *self,
guint *pos,
guint *n_items)
{
gtk_tim_sort_set_max_merge_size (&self->sort, 0);
gtk_tim4_sort_model_sort_step (self, TRUE, pos, n_items);
gtk_tim_sort_finish (&self->sort);
gtk_tim4_sort_model_stop_sorting (self, NULL);
}
static void
gtk_tim4_sort_model_clear_keys (GtkTim4SortModel *self)
{
if (gtk_sort_keys_needs_clear_key (self->sort_keys))
{
guint i;
for (i = 0; i < self->n_items; i++)
gtk_sort_keys_clear_key (self->sort_keys, key_from_pos (self, i));
}
g_clear_pointer (&self->keys, g_free);
g_clear_pointer (&self->sort_keys, gtk_sort_keys_unref);
self->key_size = 0;
}
static void
gtk_tim4_sort_model_clear_items (GtkTim4SortModel *self,
guint *pos,
guint *n_items)
{
gtk_tim4_sort_model_stop_sorting (self, NULL);
if (self->sort_keys == NULL)
{
if (pos || n_items)
*pos = *n_items = 0;
return;
}
if (pos || n_items)
{
guint start, end;
for (start = 0; start < self->n_items; start++)
{
if (pos_from_key (self, self->positions[start]) != + start)
break;
}
for (end = self->n_items; end > start; end--)
{
if (pos_from_key (self, self->positions[end - 1]) != end - 1)
break;
}
*n_items = end - start;
if (*n_items == 0)
*pos = 0;
else
*pos = start;
}
g_clear_pointer (&self->positions, g_free);
gtk_tim4_sort_model_clear_keys (self);
}
static gboolean
gtk_tim4_sort_model_should_sort (GtkTim4SortModel *self)
{
return self->sorter != NULL &&
self->model != NULL &&
gtk_sorter_get_order (self->sorter) != GTK_SORTER_ORDER_NONE;
}
static void
gtk_tim4_sort_model_create_keys (GtkTim4SortModel *self)
{
guint i;
g_assert (self->keys == NULL);
g_assert (self->sort_keys == NULL);
g_assert (self->key_size == 0);
self->sort_keys = gtk_sorter_get_keys (self->sorter);
self->key_size = self->sort_keys->key_size;
self->keys = g_malloc_n (self->n_items, self->key_size);
for (i = 0; i < self->n_items; i++)
{
gpointer item = g_list_model_get_item (self->model, i);
gtk_sort_keys_init_key (self->sort_keys, item, key_from_pos (self, i));
g_object_unref (item);
}
}
static void
gtk_tim4_sort_model_create_items (GtkTim4SortModel *self)
{
guint i;
if (!gtk_tim4_sort_model_should_sort (self))
return;
g_assert (self->sort_keys == NULL);
self->positions = g_new (gpointer, self->n_items);
gtk_tim4_sort_model_create_keys (self);
for (i = 0; i < self->n_items; i++)
self->positions[i] = key_from_pos (self, i);
}
/* This realloc()s the arrays but does not set the added values. */
static void
gtk_tim4_sort_model_update_items (GtkTim4SortModel *self,
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1],
guint position,
guint removed,
guint added,
guint *unmodified_start,
guint *unmodified_end)
{
guint i, n_items, valid;
guint start, end;
gpointer *old_keys;
n_items = self->n_items;
start = n_items;
end = n_items;
/* first, move the keys over */
old_keys = self->keys;
for (i = position; i < position + removed; i++)
{
gtk_sort_keys_clear_key (self->sort_keys, key_from_pos (self, i));
}
if (removed > added)
{
memmove (key_from_pos (self, position + added),
key_from_pos (self, position + removed),
self->key_size * (n_items - position - removed));
self->keys = g_realloc_n (self->keys, n_items - removed + added, self->key_size);
}
else if (removed < added)
{
self->keys = g_realloc_n (self->keys, n_items - removed + added, self->key_size);
memmove (key_from_pos (self, position + added),
key_from_pos (self, position + removed),
self->key_size * (n_items - position - removed));
}
/* then, update the positions */
valid = 0;
for (i = 0; i < n_items; i++)
{
guint pos = ((char *) self->positions[i] - (char *) old_keys) / self->key_size;
if (pos >= position + removed)
pos = pos - removed + added;
else if (pos >= position)
{
start = MIN (start, valid);
end = n_items - i - 1;
continue;
}
self->positions[valid] = key_from_pos (self, pos);
valid++;
}
self->positions = g_renew (gpointer, self->positions, n_items - removed + added);
/* FIXME */
runs[0] = 0;
g_assert (valid == n_items - removed);
self->n_items = n_items - removed + added;
for (i = 0; i < added; i++)
{
gpointer item = g_list_model_get_item (self->model, position + i);
gtk_sort_keys_init_key (self->sort_keys, item, key_from_pos (self, position + i));
g_object_unref (item);
self->positions[self->n_items - added + i] = key_from_pos (self, position + i);
}
*unmodified_start = start;
*unmodified_end = end;
}
static void
gtk_tim4_sort_model_items_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
GtkTim4SortModel *self)
{
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1];
guint i, n_items, start, end;
gboolean was_sorting;
if (removed == 0 && added == 0)
return;
if (!gtk_tim4_sort_model_should_sort (self))
{
self->n_items = self->n_items - removed + added;
g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
return;
}
was_sorting = gtk_tim4_sort_model_is_sorting (self);
gtk_tim4_sort_model_stop_sorting (self, runs);
gtk_tim4_sort_model_update_items (self, runs, position, removed, added, &start, &end);
if (added > 0)
{
if (gtk_tim4_sort_model_start_sorting (self, runs))
{
end = 0;
}
else
{
guint pos, n;
gtk_tim4_sort_model_finish_sorting (self, &pos, &n);
if (n)
start = MIN (start, pos);
/* find first item that was added */
for (i = 0; i < end; i++)
{
pos = pos_from_key (self, self->positions[self->n_items - i - 1]);
if (pos >= position && pos < position + added)
{
end = i;
break;
}
}
}
}
else
{
if (was_sorting)
gtk_tim4_sort_model_start_sorting (self, runs);
}
n_items = self->n_items - start - end;
g_list_model_items_changed (G_LIST_MODEL (self), start, n_items - added + removed, n_items);
}
static void
gtk_tim4_sort_model_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkTim4SortModel *self = GTK_TIM4_SORT_MODEL (object);
switch (prop_id)
{
case PROP_INCREMENTAL:
gtk_tim4_sort_model_set_incremental (self, g_value_get_boolean (value));
break;
case PROP_MODEL:
gtk_tim4_sort_model_set_model (self, g_value_get_object (value));
break;
case PROP_SORTER:
gtk_tim4_sort_model_set_sorter (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim4_sort_model_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkTim4SortModel *self = GTK_TIM4_SORT_MODEL (object);
switch (prop_id)
{
case PROP_INCREMENTAL:
g_value_set_boolean (value, self->incremental);
break;
case PROP_MODEL:
g_value_set_object (value, self->model);
break;
case PROP_SORTER:
g_value_set_object (value, self->sorter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_tim4_sort_model_sorter_changed_cb (GtkSorter *sorter,
int change,
GtkTim4SortModel *self)
{
guint pos, n_items;
if (gtk_tim4_sort_model_should_sort (self))
{
gtk_tim4_sort_model_stop_sorting (self, NULL);
if (self->sort_keys == NULL)
{
gtk_tim4_sort_model_create_items (self);
}
else
{
GtkSortKeys *new_keys = gtk_sorter_get_keys (sorter);
if (!gtk_sort_keys_is_compatible (new_keys, self->sort_keys))
{
char *old_keys = self->keys;
gsize old_key_size = self->key_size;
guint i;
gtk_tim4_sort_model_clear_keys (self);
gtk_tim4_sort_model_create_keys (self);
for (i = 0; i < self->n_items; i++)
self->positions[i] = key_from_pos (self, ((char *) self->positions[i] - old_keys) / old_key_size);
gtk_sort_keys_unref (new_keys);
}
else
{
gtk_sort_keys_unref (self->sort_keys);
self->sort_keys = new_keys;
}
}
if (gtk_tim4_sort_model_start_sorting (self, NULL))
pos = n_items = 0;
else
gtk_tim4_sort_model_finish_sorting (self, &pos, &n_items);
}
else
{
gtk_tim4_sort_model_clear_items (self, &pos, &n_items);
}
if (n_items > 0)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
}
static void
gtk_tim4_sort_model_clear_model (GtkTim4SortModel *self)
{
if (self->model == NULL)
return;
g_signal_handlers_disconnect_by_func (self->model, gtk_tim4_sort_model_items_changed_cb, self);
g_clear_object (&self->model);
gtk_tim4_sort_model_clear_items (self, NULL, NULL);
self->n_items = 0;
}
static void
gtk_tim4_sort_model_clear_sorter (GtkTim4SortModel *self)
{
if (self->sorter == NULL)
return;
g_signal_handlers_disconnect_by_func (self->sorter, gtk_tim4_sort_model_sorter_changed_cb, self);
g_clear_object (&self->sorter);
}
static void
gtk_tim4_sort_model_dispose (GObject *object)
{
GtkTim4SortModel *self = GTK_TIM4_SORT_MODEL (object);
gtk_tim4_sort_model_clear_model (self);
gtk_tim4_sort_model_clear_sorter (self);
G_OBJECT_CLASS (gtk_tim4_sort_model_parent_class)->dispose (object);
};
static void
gtk_tim4_sort_model_class_init (GtkTim4SortModelClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->set_property = gtk_tim4_sort_model_set_property;
gobject_class->get_property = gtk_tim4_sort_model_get_property;
gobject_class->dispose = gtk_tim4_sort_model_dispose;
/**
* GtkTim4SortModel:incremental:
*
* If the model should sort items incrementally
*/
properties[PROP_INCREMENTAL] =
g_param_spec_boolean ("incremental",
P_("Incremental"),
P_("Sort items incrementally"),
FALSE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim4SortModel:model:
*
* The model being sorted
*/
properties[PROP_MODEL] =
g_param_spec_object ("model",
P_("Model"),
P_("The model being sorted"),
G_TYPE_LIST_MODEL,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkTim4SortModel:sorter:
*
* The sorter for this model
*/
properties[PROP_SORTER] =
g_param_spec_object ("sorter",
P_("Sorter"),
P_("The sorter for this model"),
GTK_TYPE_SORTER,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, properties);
}
static void
gtk_tim4_sort_model_init (GtkTim4SortModel *self)
{
}
/**
* gtk_tim4_sort_model_new:
* @model: (allow-none): the model to sort
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Creates a new sort list model that uses the @sorter to sort @model.
*
* Returns: a new #GtkTim4SortModel
**/
GtkTim4SortModel *
gtk_tim4_sort_model_new (GListModel *model,
GtkSorter *sorter)
{
GtkTim4SortModel *result;
g_return_val_if_fail (model == NULL || G_IS_LIST_MODEL (model), NULL);
g_return_val_if_fail (sorter == NULL || GTK_IS_SORTER (sorter), NULL);
result = g_object_new (GTK_TYPE_TIM4_SORT_MODEL,
"model", model,
"sorter", sorter,
NULL);
return result;
}
/**
* gtk_tim4_sort_model_set_model:
* @self: a #GtkTim4SortModel
* @model: (allow-none): The model to be sorted
*
* Sets the model to be sorted. The @model's item type must conform to
* the item type of @self.
**/
void
gtk_tim4_sort_model_set_model (GtkTim4SortModel *self,
GListModel *model)
{
guint removed;
g_return_if_fail (GTK_IS_TIM4_SORT_MODEL (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
return;
removed = g_list_model_get_n_items (G_LIST_MODEL (self));
gtk_tim4_sort_model_clear_model (self);
if (model)
{
guint ignore1, ignore2;
self->model = g_object_ref (model);
self->n_items = g_list_model_get_n_items (model);
g_signal_connect (model, "items-changed", G_CALLBACK (gtk_tim4_sort_model_items_changed_cb), self);
if (gtk_tim4_sort_model_should_sort (self))
{
gtk_tim4_sort_model_create_items (self);
if (!gtk_tim4_sort_model_start_sorting (self, NULL))
gtk_tim4_sort_model_finish_sorting (self, &ignore1, &ignore2);
}
}
if (removed > 0 || self->n_items > 0)
g_list_model_items_changed (G_LIST_MODEL (self), 0, removed, self->n_items);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
/**
* gtk_tim4_sort_model_get_model:
* @self: a #GtkTim4SortModel
*
* Gets the model currently sorted or %NULL if none.
*
* Returns: (nullable) (transfer none): The model that gets sorted
**/
GListModel *
gtk_tim4_sort_model_get_model (GtkTim4SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM4_SORT_MODEL (self), NULL);
return self->model;
}
/**
* gtk_tim4_sort_model_set_sorter:
* @self: a #GtkTim4SortModel
* @sorter: (allow-none): the #GtkSorter to sort @model with
*
* Sets a new sorter on @self.
*/
void
gtk_tim4_sort_model_set_sorter (GtkTim4SortModel *self,
GtkSorter *sorter)
{
g_return_if_fail (GTK_IS_TIM4_SORT_MODEL (self));
g_return_if_fail (sorter == NULL || GTK_IS_SORTER (sorter));
gtk_tim4_sort_model_clear_sorter (self);
if (sorter)
{
self->sorter = g_object_ref (sorter);
g_signal_connect (sorter, "changed", G_CALLBACK (gtk_tim4_sort_model_sorter_changed_cb), self);
}
gtk_tim4_sort_model_sorter_changed_cb (sorter, GTK_SORTER_CHANGE_DIFFERENT, self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}
/**
* gtk_tim4_sort_model_get_sorter:
* @self: a #GtkTim4SortModel
*
* Gets the sorter that is used to sort @self.
*
* Returns: (nullable) (transfer none): the sorter of #self
*/
GtkSorter *
gtk_tim4_sort_model_get_sorter (GtkTim4SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM4_SORT_MODEL (self), NULL);
return self->sorter;
}
/**
* gtk_tim4_sort_model_set_incremental:
* @self: a #GtkTim4SortModel
* @incremental: %TRUE to sort incrementally
*
* Sets the sort model to do an incremental sort.
*
* When incremental sorting is enabled, the sortlistmodel will not do
* a complete sort immediately, but will instead queue an idle handler that
* incrementally sorts the items towards their correct position. This of
* course means that items do not instantly appear in the right place. It
* also means that the total sorting time is a lot slower.
*
* When your filter blocks the UI while sorting, you might consider
* turning this on. Depending on your model and sorters, this may become
* interesting around 10,000 to 100,000 items.
*
* By default, incremental sortinging is disabled.
*/
void
gtk_tim4_sort_model_set_incremental (GtkTim4SortModel *self,
gboolean incremental)
{
g_return_if_fail (GTK_IS_TIM4_SORT_MODEL (self));
if (self->incremental == incremental)
return;
self->incremental = incremental;
if (!incremental && gtk_tim4_sort_model_is_sorting (self))
{
guint pos, n_items;
gtk_tim4_sort_model_finish_sorting (self, &pos, &n_items);
if (n_items)
g_list_model_items_changed (G_LIST_MODEL (self), pos, n_items, n_items);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCREMENTAL]);
}
/**
* gtk_tim4_sort_model_get_incremental:
* @self: a #GtkModel
*
* Returns whether incremental sorting was enabled via
* gtk_sort_list_model_set_incremental().
*
* Returns: %TRUE if incremental sorting is enabled
*/
gboolean
gtk_tim4_sort_model_get_incremental (GtkTim4SortModel *self)
{
g_return_val_if_fail (GTK_IS_TIM4_SORT_MODEL (self), FALSE);
return self->incremental;
}

63
gtk/gtktim4sortmodel.h Normal file
View File

@@ -0,0 +1,63 @@
/*
* Copyright © 2018 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_TIM4_SORT_MODEL_H__
#define __GTK_TIM4_SORT_MODEL_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gio/gio.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtksorter.h>
G_BEGIN_DECLS
#define GTK_TYPE_TIM4_SORT_MODEL (gtk_tim4_sort_model_get_type ())
GDK_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (GtkTim4SortModel, gtk_tim4_sort_model, GTK, TIM4_SORT_MODEL, GObject)
GDK_AVAILABLE_IN_ALL
GtkTim4SortModel * gtk_tim4_sort_model_new (GListModel *model,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
void gtk_tim4_sort_model_set_sorter (GtkTim4SortModel *self,
GtkSorter *sorter);
GDK_AVAILABLE_IN_ALL
GtkSorter * gtk_tim4_sort_model_get_sorter (GtkTim4SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim4_sort_model_set_model (GtkTim4SortModel *self,
GListModel *model);
GDK_AVAILABLE_IN_ALL
GListModel * gtk_tim4_sort_model_get_model (GtkTim4SortModel *self);
GDK_AVAILABLE_IN_ALL
void gtk_tim4_sort_model_set_incremental (GtkTim4SortModel *self,
gboolean incremental);
GDK_AVAILABLE_IN_ALL
gboolean gtk_tim4_sort_model_get_incremental (GtkTim4SortModel *self);
G_END_DECLS
#endif /* __GTK_TIM4_SORT_MODEL_H__ */

943
gtk/gtktimsort-impl.c Normal file
View File

@@ -0,0 +1,943 @@
/*
* Copyright (C) 2020 Benjamin Otte
* Copyright (C) 2011 Patrick O. Perry
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NAME
#define NAME WIDTH
#endif
#define DEFINE_TEMP(temp) gpointer temp = g_alloca (WIDTH)
#define ASSIGN(x, y) memcpy (x, y, WIDTH)
#define INCPTR(x) ((gpointer) ((char *) (x) + WIDTH))
#define DECPTR(x) ((gpointer) ((char *) (x) - WIDTH))
#define ELEM(a, i) ((char *) (a) + (i) * WIDTH)
#define LEN(n) ((n) * WIDTH)
#define CONCAT(x, y) gtk_tim_sort_ ## x ## _ ## y
#define MAKE_STR(x, y) CONCAT (x, y)
#define gtk_tim_sort(x) MAKE_STR (x, NAME)
/*
* Reverse the specified range of the specified array.
*
* @param a the array in which a range is to be reversed
* @param hi the index after the last element in the range to be reversed
*/
static void gtk_tim_sort(reverse_range) (GtkTimSort *self,
gpointer a,
gsize hi)
{
DEFINE_TEMP (t);
char *front = a;
char *back = ELEM (a, hi - 1);
g_assert (hi > 0);
while (front < back)
{
ASSIGN (t, front);
ASSIGN (front, back);
ASSIGN (back, t);
front = INCPTR (front);
back = DECPTR (back);
}
}
/*
* Returns the length of the run beginning at the specified position in
* the specified array and reverses the run if it is descending (ensuring
* that the run will always be ascending when the method returns).
*
* A run is the longest ascending sequence with:
*
* a[0] <= a[1] <= a[2] <= ...
*
* or the longest descending sequence with:
*
* a[0] > a[1] > a[2] > ...
*
* For its intended use in a stable mergesort, the strictness of the
* definition of "descending" is needed so that the call can safely
* reverse a descending sequence without violating stability.
*
* @param a the array in which a run is to be counted and possibly reversed
* @param hi index after the last element that may be contained in the run.
* It is required that {@code 0 < hi}.
* @param compare the comparator to used for the sort
* @return the length of the run beginning at the specified position in
* the specified array
*/
static gsize
gtk_tim_sort(prepare_run) (GtkTimSort *self,
GtkTimSortRun *out_change)
{
gsize run_hi = 1;
char *cur;
char *next;
if (self->size <= run_hi)
{
gtk_tim_sort_set_change (out_change, NULL, 0);
return self->size;
}
cur = INCPTR (self->base);
next = INCPTR (cur);
run_hi++;
/* Find end of run, and reverse range if descending */
if (gtk_tim_sort_compare (self, cur, self->base) < 0) /* Descending */
{
while (run_hi < self->size && gtk_tim_sort_compare (self, next, cur) < 0)
{
run_hi++;
cur = next;
next = INCPTR (next);
}
gtk_tim_sort(reverse_range) (self, self->base, run_hi);
gtk_tim_sort_set_change (out_change, self->base, run_hi);
}
else /* Ascending */
{
while (run_hi < self->size && gtk_tim_sort_compare (self, next, cur) >= 0)
{
run_hi++;
cur = next;
next = INCPTR (next);
}
gtk_tim_sort_set_change (out_change, NULL, 0);
}
return run_hi;
}
/*
* Sorts the specified portion of the specified array using a binary
* insertion sort. This is the best method for sorting small numbers
* of elements. It requires O(n log n) compares, but O(n^2) data
* movement (worst case).
*
* If the initial part of the specified range is already sorted,
* this method can take advantage of it: the method assumes that the
* elements from index {@code lo}, inclusive, to {@code start},
* exclusive are already sorted.
*
* @param a the array in which a range is to be sorted
* @param hi the index after the last element in the range to be sorted
* @param start the index of the first element in the range that is
* not already known to be sorted ({@code lo <= start <= hi})
*/
static void gtk_tim_sort(binary_sort) (GtkTimSort *self,
gpointer a,
gsize hi,
gsize start,
GtkTimSortRun *inout_change)
{
DEFINE_TEMP (pivot);
char *startp;
char *change_min = ELEM (a, hi);
char *change_max = a;
g_assert (start <= hi);
if (start == 0)
start++;
startp = ELEM (a, start);
for (; start < hi; start++, startp = INCPTR (startp))
{
/* Set left (and right) to the index where a[start] (pivot) belongs */
char *leftp = a;
gsize right = start;
gsize n;
/*
* Invariants:
* pivot >= all in [0, left).
* pivot < all in [right, start).
*/
while (0 < right)
{
gsize mid = right >> 1;
gpointer midp = ELEM (leftp, mid);
if (gtk_tim_sort_compare (self, startp, midp) < 0)
{
right = mid;
}
else
{
leftp = INCPTR (midp);
right -= (mid + 1);
}
}
g_assert (0 == right);
/*
* The invariants still hold: pivot >= all in [lo, left) and
* pivot < all in [left, start), so pivot belongs at left. Note
* that if there are elements equal to pivot, left points to the
* first slot after them -- that's why this sort is stable.
* Slide elements over to make room to make room for pivot.
*/
n = startp - leftp; /* The number of bytes to move */
if (n == 0)
continue;
ASSIGN (pivot, startp);
memmove (INCPTR (leftp), leftp, n); /* POP: overlaps */
/* a[left] = pivot; */
ASSIGN (leftp, pivot);
change_min = MIN (change_min, leftp);
change_max = MAX (change_max, ELEM (startp, 1));
}
if (change_max > (char *) a)
{
g_assert (change_min < ELEM (a, hi));
if (inout_change && inout_change->len)
{
change_max = MAX (change_max, ELEM (inout_change->base, inout_change->len));
change_min = MIN (change_min, (char *) inout_change->base);
}
gtk_tim_sort_set_change (inout_change, change_min, (change_max - change_min) / WIDTH);
}
}
static gboolean
gtk_tim_sort(merge_append) (GtkTimSort *self,
GtkTimSortRun *out_change)
{
/* Identify next run */
gsize run_len;
run_len = gtk_tim_sort(prepare_run) (self, out_change);
if (run_len == 0)
return FALSE;
/* If run is short, extend to min(self->min_run, self->size) */
if (run_len < self->min_run)
{
gsize force = MIN (self->size, self->min_run);
gtk_tim_sort(binary_sort) (self, self->base, force, run_len, out_change);
run_len = force;
}
/* Push run onto pending-run stack, and maybe merge */
gtk_tim_sort_push_run (self, self->base, run_len);
return TRUE;
}
/*
* Locates the position at which to insert the specified key into the
* specified sorted range; if the range contains an element equal to key,
* returns the index of the leftmost equal element.
*
* @param key the key whose insertion point to search for
* @param base the array in which to search
* @param len the length of the range; must be > 0
* @param hint the index at which to begin the search, 0 <= hint < n.
* The closer hint is to the result, the faster this method will run.
* @param c the comparator used to order the range, and to search
* @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k],
* pretending that a[b - 1] is minus infinity and a[b + n] is infinity.
* In other words, key belongs at index b + k; or in other words,
* the first k elements of a should precede key, and the last n - k
* should follow it.
*/
static gsize
gtk_tim_sort(gallop_left) (GtkTimSort *self,
gpointer key,
gpointer base,
gsize len,
gsize hint)
{
char *hintp = ELEM (base, hint);
gsize last_ofs = 0;
gsize ofs = 1;
g_assert (len > 0 && hint < len);
if (gtk_tim_sort_compare (self, key, hintp) > 0)
{
/* Gallop right until a[hint+last_ofs] < key <= a[hint+ofs] */
gsize max_ofs = len - hint;
while (ofs < max_ofs
&& gtk_tim_sort_compare (self, key, ELEM (hintp, ofs)) > 0)
{
last_ofs = ofs;
ofs = (ofs << 1) + 1; /* eventually this becomes SIZE_MAX */
}
if (ofs > max_ofs)
ofs = max_ofs;
/* Make offsets relative to base */
last_ofs += hint + 1; /* POP: we add 1 here so last_ofs stays non-negative */
ofs += hint;
}
else /* key <= a[hint] */
/* Gallop left until a[hint-ofs] < key <= a[hint-last_ofs] */
{
const gsize max_ofs = hint + 1;
gsize tmp;
while (ofs < max_ofs
&& gtk_tim_sort_compare (self, key, ELEM (hintp, -ofs)) <= 0)
{
last_ofs = ofs;
ofs = (ofs << 1) + 1; /* no need to check for overflow */
}
if (ofs > max_ofs)
ofs = max_ofs;
/* Make offsets relative to base */
tmp = last_ofs;
last_ofs = hint + 1 - ofs; /* POP: we add 1 here so last_ofs stays non-negative */
ofs = hint - tmp;
}
g_assert (last_ofs <= ofs && ofs <= len);
/*
* Now a[last_ofs-1] < key <= a[ofs], so key belongs somewhere
* to the right of last_ofs but no farther right than ofs. Do a binary
* search, with invariant a[last_ofs - 1] < key <= a[ofs].
*/
/* last_ofs++; POP: we added 1 above to keep last_ofs non-negative */
while (last_ofs < ofs)
{
/*gsize m = last_ofs + ((ofs - last_ofs) >> 1); */
/* http://stackoverflow.com/questions/4844165/safe-integer-middle-value-formula */
gsize m = (last_ofs & ofs) + ((last_ofs ^ ofs) >> 1);
if (gtk_tim_sort_compare (self, key, ELEM (base, m)) > 0)
last_ofs = m + 1; /* a[m] < key */
else
ofs = m; /* key <= a[m] */
}
g_assert (last_ofs == ofs); /* so a[ofs - 1] < key <= a[ofs] */
return ofs;
}
/*
* Like gallop_left, except that if the range contains an element equal to
* key, gallop_right returns the index after the rightmost equal element.
*
* @param key the key whose insertion point to search for
* @param base the array in which to search
* @param len the length of the range; must be > 0
* @param hint the index at which to begin the search, 0 <= hint < n.
* The closer hint is to the result, the faster this method will run.
* @param c the comparator used to order the range, and to search
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k]
*/
static gsize
gtk_tim_sort(gallop_right) (GtkTimSort *self,
gpointer key,
gpointer base,
gsize len,
gsize hint)
{
char *hintp = ELEM (base, hint);
gsize ofs = 1;
gsize last_ofs = 0;
g_assert (len > 0 && hint < len);
if (gtk_tim_sort_compare (self, key, hintp) < 0)
{
/* Gallop left until a[hint - ofs] <= key < a[hint - last_ofs] */
gsize max_ofs = hint + 1;
gsize tmp;
while (ofs < max_ofs
&& gtk_tim_sort_compare (self, key, ELEM (hintp, -ofs)) < 0)
{
last_ofs = ofs;
ofs = (ofs << 1) + 1; /* no need to check for overflow */
}
if (ofs > max_ofs)
ofs = max_ofs;
/* Make offsets relative to base */
tmp = last_ofs;
last_ofs = hint + 1 - ofs;
ofs = hint - tmp;
}
else /* a[hint] <= key */
/* Gallop right until a[hint + last_ofs] <= key < a[hint + ofs] */
{
gsize max_ofs = len - hint;
while (ofs < max_ofs
&& gtk_tim_sort_compare (self, key, ELEM (hintp, ofs)) >= 0)
{
last_ofs = ofs;
ofs = (ofs << 1) + 1; /* no need to check for overflow */
}
if (ofs > max_ofs)
ofs = max_ofs;
/* Make offsets relative to base */
last_ofs += hint + 1;
ofs += hint;
}
g_assert (last_ofs <= ofs && ofs <= len);
/*
* Now a[last_ofs - 1] <= key < a[ofs], so key belongs somewhere to
* the right of last_ofs but no farther right than ofs. Do a binary
* search, with invariant a[last_ofs - 1] <= key < a[ofs].
*/
while (last_ofs < ofs)
{
/* gsize m = last_ofs + ((ofs - last_ofs) >> 1); */
gsize m = (last_ofs & ofs) + ((last_ofs ^ ofs) >> 1);
if (gtk_tim_sort_compare (self, key, ELEM (base, m)) < 0)
ofs = m; /* key < a[m] */
else
last_ofs = m + 1; /* a[m] <= key */
}
g_assert (last_ofs == ofs); /* so a[ofs - 1] <= key < a[ofs] */
return ofs;
}
/*
* Merges two adjacent runs in place, in a stable fashion. The first
* element of the first run must be greater than the first element of the
* second run (a[base1] > a[base2]), and the last element of the first run
* (a[base1 + len1-1]) must be greater than all elements of the second run.
*
* For performance, this method should be called only when len1 <= len2;
* its twin, merge_hi should be called if len1 >= len2. (Either method
* may be called if len1 == len2.)
*
* @param base1 first element in first run to be merged
* @param len1 length of first run to be merged (must be > 0)
* @param base2 first element in second run to be merged
* (must be aBase + aLen)
* @param len2 length of second run to be merged (must be > 0)
*/
static void
gtk_tim_sort(merge_lo) (GtkTimSort *self,
gpointer base1,
gsize len1,
gpointer base2,
gsize len2)
{
/* Copy first run into temp array */
gpointer tmp = gtk_tim_sort_ensure_capacity (self, len1);
char *cursor1;
char *cursor2;
char *dest;
gsize min_gallop;
g_assert (len1 > 0 && len2 > 0 && ELEM (base1, len1) == base2);
/* System.arraycopy(a, base1, tmp, 0, len1); */
memcpy (tmp, base1, LEN (len1)); /* POP: can't overlap */
cursor1 = tmp; /* Indexes into tmp array */
cursor2 = base2; /* Indexes int a */
dest = base1; /* Indexes int a */
/* Move first element of second run and deal with degenerate cases */
/* a[dest++] = a[cursor2++]; */
ASSIGN (dest, cursor2);
dest = INCPTR (dest);
cursor2 = INCPTR (cursor2);
if (--len2 == 0)
{
memcpy (dest, cursor1, LEN (len1)); /* POP: can't overlap */
return;
}
if (len1 == 1)
{
memmove (dest, cursor2, LEN (len2)); /* POP: overlaps */
/* a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge */
ASSIGN (ELEM (dest, len2), cursor1);
return;
}
/* Use local variable for performance */
min_gallop = self->min_gallop;
while (TRUE)
{
gsize count1 = 0; /* Number of times in a row that first run won */
gsize count2 = 0; /* Number of times in a row that second run won */
/*
* Do the straightforward thing until (if ever) one run starts
* winning consistently.
*/
do
{
g_assert (len1 > 1 && len2 > 0);
if (gtk_tim_sort_compare (self, cursor2, cursor1) < 0)
{
ASSIGN (dest, cursor2);
dest = INCPTR (dest);
cursor2 = INCPTR (cursor2);
count2++;
count1 = 0;
if (--len2 == 0)
goto outer;
if (count2 >= min_gallop)
break;
}
else
{
ASSIGN (dest, cursor1);
dest = INCPTR (dest);
cursor1 = INCPTR (cursor1);
count1++;
count2 = 0;
if (--len1 == 1)
goto outer;
if (count1 >= min_gallop)
break;
}
}
while (TRUE); /* (count1 | count2) < min_gallop); */
/*
* One run is winning so consistently that galloping may be a
* huge win. So try that, and continue galloping until (if ever)
* neither run appears to be winning consistently anymore.
*/
do
{
g_assert (len1 > 1 && len2 > 0);
count1 = gtk_tim_sort(gallop_right) (self, cursor2, cursor1, len1, 0);
if (count1 != 0)
{
memcpy (dest, cursor1, LEN (count1)); /* POP: can't overlap */
dest = ELEM (dest, count1);
cursor1 = ELEM (cursor1, count1);
len1 -= count1;
if (len1 <= 1) /* len1 == 1 || len1 == 0 */
goto outer;
}
ASSIGN (dest, cursor2);
dest = INCPTR (dest);
cursor2 = INCPTR (cursor2);
if (--len2 == 0)
goto outer;
count2 = gtk_tim_sort(gallop_left) (self, cursor1, cursor2, len2, 0);
if (count2 != 0)
{
memmove (dest, cursor2, LEN (count2)); /* POP: might overlap */
dest = ELEM (dest, count2);
cursor2 = ELEM (cursor2, count2);
len2 -= count2;
if (len2 == 0)
goto outer;
}
ASSIGN (dest, cursor1);
dest = INCPTR (dest);
cursor1 = INCPTR (cursor1);
if (--len1 == 1)
goto outer;
if (min_gallop > 0)
min_gallop--;
}
while (count1 >= MIN_GALLOP || count2 >= MIN_GALLOP);
min_gallop += 2; /* Penalize for leaving gallop mode */
} /* End of "outer" loop */
outer:
self->min_gallop = min_gallop < 1 ? 1 : min_gallop; /* Write back to field */
if (len1 == 1)
{
g_assert (len2 > 0);
memmove (dest, cursor2, LEN (len2)); /* POP: might overlap */
ASSIGN (ELEM (dest, len2), cursor1); /* Last elt of run 1 to end of merge */
}
else if (len1 == 0)
{
g_critical ("Comparison method violates its general contract");
return;
}
else
{
g_assert (len2 == 0);
g_assert (len1 > 1);
memcpy (dest, cursor1, LEN (len1)); /* POP: can't overlap */
}
}
/*
* Like merge_lo, except that this method should be called only if
* len1 >= len2; merge_lo should be called if len1 <= len2. (Either method
* may be called if len1 == len2.)
*
* @param base1 first element in first run to be merged
* @param len1 length of first run to be merged (must be > 0)
* @param base2 first element in second run to be merged
* (must be aBase + aLen)
* @param len2 length of second run to be merged (must be > 0)
*/
static void
gtk_tim_sort(merge_hi) (GtkTimSort *self,
gpointer base1,
gsize len1,
gpointer base2,
gsize len2)
{
/* Copy second run into temp array */
gpointer tmp = gtk_tim_sort_ensure_capacity (self, len2);
char *cursor1; /* Indexes into a */
char *cursor2; /* Indexes into tmp array */
char *dest; /* Indexes into a */
gsize min_gallop;
g_assert (len1 > 0 && len2 > 0 && ELEM (base1, len1) == base2);
memcpy (tmp, base2, LEN (len2)); /* POP: can't overlap */
cursor1 = ELEM (base1, len1 - 1); /* Indexes into a */
cursor2 = ELEM (tmp, len2 - 1); /* Indexes into tmp array */
dest = ELEM (base2, len2 - 1); /* Indexes into a */
/* Move last element of first run and deal with degenerate cases */
/* a[dest--] = a[cursor1--]; */
ASSIGN (dest, cursor1);
dest = DECPTR (dest);
cursor1 = DECPTR (cursor1);
if (--len1 == 0)
{
memcpy (ELEM (dest, -(len2 - 1)), tmp, LEN (len2)); /* POP: can't overlap */
return;
}
if (len2 == 1)
{
dest = ELEM (dest, -len1);
cursor1 = ELEM (cursor1, -len1);
memmove (ELEM (dest, 1), ELEM (cursor1, 1), LEN (len1)); /* POP: overlaps */
/* a[dest] = tmp[cursor2]; */
ASSIGN (dest, cursor2);
return;
}
/* Use local variable for performance */
min_gallop = self->min_gallop;
while (TRUE)
{
gsize count1 = 0; /* Number of times in a row that first run won */
gsize count2 = 0; /* Number of times in a row that second run won */
/*
* Do the straightforward thing until (if ever) one run
* appears to win consistently.
*/
do
{
g_assert (len1 > 0 && len2 > 1);
if (gtk_tim_sort_compare (self, cursor2, cursor1) < 0)
{
ASSIGN (dest, cursor1);
dest = DECPTR (dest);
cursor1 = DECPTR (cursor1);
count1++;
count2 = 0;
if (--len1 == 0)
goto outer;
}
else
{
ASSIGN (dest, cursor2);
dest = DECPTR (dest);
cursor2 = DECPTR (cursor2);
count2++;
count1 = 0;
if (--len2 == 1)
goto outer;
}
}
while ((count1 | count2) < min_gallop);
/*
* One run is winning so consistently that galloping may be a
* huge win. So try that, and continue galloping until (if ever)
* neither run appears to be winning consistently anymore.
*/
do
{
g_assert (len1 > 0 && len2 > 1);
count1 = len1 - gtk_tim_sort(gallop_right) (self, cursor2, base1, len1, len1 - 1);
if (count1 != 0)
{
dest = ELEM (dest, -count1);
cursor1 = ELEM (cursor1, -count1);
len1 -= count1;
memmove (INCPTR (dest), INCPTR (cursor1),
LEN (count1)); /* POP: might overlap */
if (len1 == 0)
goto outer;
}
ASSIGN (dest, cursor2);
dest = DECPTR (dest);
cursor2 = DECPTR (cursor2);
if (--len2 == 1)
goto outer;
count2 = len2 - gtk_tim_sort(gallop_left) (self, cursor1, tmp, len2, len2 - 1);
if (count2 != 0)
{
dest = ELEM (dest, -count2);
cursor2 = ELEM (cursor2, -count2);
len2 -= count2;
memcpy (INCPTR (dest), INCPTR (cursor2), LEN (count2)); /* POP: can't overlap */
if (len2 <= 1) /* len2 == 1 || len2 == 0 */
goto outer;
}
ASSIGN (dest, cursor1);
dest = DECPTR (dest);
cursor1 = DECPTR (cursor1);
if (--len1 == 0)
goto outer;
if (min_gallop > 0)
min_gallop--;
}
while (count1 >= MIN_GALLOP || count2 >= MIN_GALLOP);
min_gallop += 2; /* Penalize for leaving gallop mode */
} /* End of "outer" loop */
outer:
self->min_gallop = min_gallop < 1 ? 1 : min_gallop; /* Write back to field */
if (len2 == 1)
{
g_assert (len1 > 0);
dest = ELEM (dest, -len1);
cursor1 = ELEM (cursor1, -len1);
memmove (INCPTR (dest), INCPTR (cursor1), LEN (len1)); /* POP: might overlap */
/* a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge */
ASSIGN (dest, cursor2);
}
else if (len2 == 0)
{
g_critical ("Comparison method violates its general contract");
return;
}
else
{
g_assert (len1 == 0);
g_assert (len2 > 0);
memcpy (ELEM (dest, -(len2 - 1)), tmp, LEN (len2)); /* POP: can't overlap */
}
}
/*
* Merges the two runs at stack indices i and i+1. Run i must be
* the penultimate or antepenultimate run on the stack. In other words,
* i must be equal to pending_runs-2 or pending_runs-3.
*
* @param i stack index of the first of the two runs to merge
*/
static void
gtk_tim_sort(merge_at) (GtkTimSort *self,
gsize i,
GtkTimSortRun *out_change)
{
gpointer base1 = self->run[i].base;
gsize len1 = self->run[i].len;
gpointer base2 = self->run[i + 1].base;
gsize len2 = self->run[i + 1].len;
gsize k;
g_assert (self->pending_runs >= 2);
g_assert (i == self->pending_runs - 2 || i == self->pending_runs - 3);
g_assert (len1 > 0 && len2 > 0);
g_assert (ELEM (base1, len1) == base2);
/*
* Find where the first element of run2 goes in run1. Prior elements
* in run1 can be ignored (because they're already in place).
*/
k = gtk_tim_sort(gallop_right) (self, base2, base1, len1, 0);
base1 = ELEM (base1, k);
len1 -= k;
if (len1 == 0)
{
gtk_tim_sort_set_change (out_change, NULL, 0);
goto done;
}
/*
* Find where the last element of run1 goes in run2. Subsequent elements
* in run2 can be ignored (because they're already in place).
*/
len2 = gtk_tim_sort(gallop_left) (self,
ELEM (base1, len1 - 1),
base2, len2, len2 - 1);
if (len2 == 0)
{
gtk_tim_sort_set_change (out_change, NULL, 0);
goto done;
}
/* Merge remaining runs, using tmp array with min(len1, len2) elements */
if (len1 <= len2)
{
if (len1 > self->max_merge_size)
{
base1 = ELEM (self->run[i].base, self->run[i].len - self->max_merge_size);
gtk_tim_sort(merge_lo) (self, base1, self->max_merge_size, base2, len2);
gtk_tim_sort_set_change (out_change, base1, self->max_merge_size + len2);
self->run[i].len -= self->max_merge_size;
self->run[i + 1].base = ELEM (self->run[i + 1].base, - self->max_merge_size);
self->run[i + 1].len += self->max_merge_size;
g_assert (ELEM (self->run[i].base, self->run[i].len) == self->run[i + 1].base);
return;
}
else
{
gtk_tim_sort(merge_lo) (self, base1, len1, base2, len2);
gtk_tim_sort_set_change (out_change, base1, len1 + len2);
}
}
else
{
if (len2 > self->max_merge_size)
{
gtk_tim_sort(merge_hi) (self, base1, len1, base2, self->max_merge_size);
gtk_tim_sort_set_change (out_change, base1, len1 + self->max_merge_size);
self->run[i].len += self->max_merge_size;
self->run[i + 1].base = ELEM (self->run[i + 1].base, self->max_merge_size);
self->run[i + 1].len -= self->max_merge_size;
g_assert (ELEM (self->run[i].base, self->run[i].len) == self->run[i + 1].base);
return;
}
else
{
gtk_tim_sort(merge_hi) (self, base1, len1, base2, len2);
gtk_tim_sort_set_change (out_change, base1, len1 + len2);
}
}
done:
/*
* Record the length of the combined runs; if i is the 3rd-last
* run now, also slide over the last run (which isn't involved
* in this merge). The current run (i+1) goes away in any case.
*/
self->run[i].len += self->run[i + 1].len;
if (i == self->pending_runs - 3)
self->run[i + 1] = self->run[i + 2];
self->pending_runs--;
}
/*
* Examines the stack of runs waiting to be merged and merges adjacent runs
* until the stack invariants are reestablished:
*
* 1. run_len[i - 3] > run_len[i - 2] + run_len[i - 1]
* 2. run_len[i - 2] > run_len[i - 1]
*
* This method is called each time a new run is pushed onto the stack,
* so the invariants are guaranteed to hold for i < pending_runs upon
* entry to the method.
*
* POP:
* Modified according to http://envisage-project.eu/wp-content/uploads/2015/02/sorting.pdf
*
* and
*
* https://bugs.openjdk.java.net/browse/JDK-8072909 (suggestion 2)
*
*/
static gboolean
gtk_tim_sort(merge_collapse) (GtkTimSort *self,
GtkTimSortRun *out_change)
{
GtkTimSortRun *run = self->run;
gsize n;
if (self->pending_runs <= 1)
return FALSE;
n = self->pending_runs - 2;
if ((n > 0 && run[n - 1].len <= run[n].len + run[n + 1].len) ||
(n > 1 && run[n - 2].len <= run[n].len + run[n - 1].len))
{
if (run[n - 1].len < run[n + 1].len)
n--;
}
else if (run[n].len > run[n + 1].len)
{
return FALSE; /* Invariant is established */
}
gtk_tim_sort(merge_at) (self, n, out_change);
return TRUE;
}
/*
* Merges all runs on the stack until only one remains. This method is
* called once, to complete the sort.
*/
static gboolean
gtk_tim_sort(merge_force_collapse) (GtkTimSort *self,
GtkTimSortRun *out_change)
{
gsize n;
if (self->pending_runs <= 1)
return FALSE;
n = self->pending_runs - 2;
if (n > 0 && self->run[n - 1].len < self->run[n + 1].len)
n--;
gtk_tim_sort(merge_at) (self, n, out_change);
return TRUE;
}
static gboolean
gtk_tim_sort(step) (GtkTimSort *self,
GtkTimSortRun *out_change)
{
g_assert (self);
if (gtk_tim_sort(merge_collapse) (self, out_change))
return TRUE;
if (gtk_tim_sort(merge_append) (self, out_change))
return TRUE;
if (gtk_tim_sort(merge_force_collapse) (self, out_change))
return TRUE;
return FALSE;
}
#undef DEFINE_TEMP
#undef ASSIGN
#undef INCPTR
#undef DECPTR
#undef ELEM
#undef LEN
#undef CONCAT
#undef MAKE_STR
#undef gtk_tim_sort
#undef WIDTH
#undef NAME

373
gtk/gtktimsort.c Normal file
View File

@@ -0,0 +1,373 @@
/* Lots of code for an adaptive, stable, natural mergesort. There are many
* pieces to this algorithm; read listsort.txt for overviews and details.
*/
#include "config.h"
#include "gtktimsortprivate.h"
/*
* This is the minimum sized sequence that will be merged. Shorter
* sequences will be lengthened by calling binarySort. If the entire
* array is less than this length, no merges will be performed.
*
* This constant should be a power of two. It was 64 in Tim Peter's C
* implementation, but 32 was empirically determined to work better in
* [Android's Java] implementation. In the unlikely event that you set
* this constant to be a number that's not a power of two, you'll need
* to change the compute_min_run() computation.
*
* If you decrease this constant, you must change the
* GTK_TIM_SORT_MAX_PENDING value, or you risk running out of space.
* See Python's listsort.txt for a discussion of the minimum stack
* length required as a function of the length of the array being sorted and
* the minimum merge sequence length.
*/
#define MIN_MERGE 32
/*
* When we get into galloping mode, we stay there until both runs win less
* often than MIN_GALLOP consecutive times.
*/
#define MIN_GALLOP 7
/*
* Returns the minimum acceptable run length for an array of the specified
* length. Natural runs shorter than this will be extended with binary sort.
*
* Roughly speaking, the computation is:
*
* If n < MIN_MERGE, return n (it's too small to bother with fancy stuff).
* Else if n is an exact power of 2, return MIN_MERGE/2.
* Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k
* is close to, but strictly less than, an exact power of 2.
*
* For the rationale, see listsort.txt.
*
* @param n the length of the array to be sorted
* @return the length of the minimum run to be merged
*/
static gsize
compute_min_run (gsize n)
{
gsize r = 0; // Becomes 1 if any 1 bits are shifted off
while (n >= MIN_MERGE) {
r |= (n & 1);
n >>= 1;
}
return n + r;
}
void
gtk_tim_sort_init (GtkTimSort *self,
gpointer base,
gsize size,
gsize element_size,
GCompareDataFunc compare_func,
gpointer data)
{
self->element_size = element_size;
self->base = base;
self->size = size;
self->compare_func = compare_func;
self->data = data;
self->min_gallop = MIN_GALLOP;
self->max_merge_size = G_MAXSIZE;
self->min_run = compute_min_run (size);
self->tmp = NULL;
self->tmp_length = 0;
self->pending_runs = 0;
}
void
gtk_tim_sort_finish (GtkTimSort *self)
{
g_free (self->tmp);
}
void
gtk_tim_sort (gpointer base,
gsize size,
gsize element_size,
GCompareDataFunc compare_func,
gpointer user_data)
{
GtkTimSort self;
gtk_tim_sort_init (&self, base, size, element_size, compare_func, user_data);
while (gtk_tim_sort_step (&self, NULL));
gtk_tim_sort_finish (&self);
}
static inline int
gtk_tim_sort_compare (GtkTimSort *self,
gpointer a,
gpointer b)
{
return self->compare_func (a, b, self->data);
}
/**
* Pushes the specified run onto the pending-run stack.
*
* @param runBase index of the first element in the run
* @param runLen the number of elements in the run
*/
static void
gtk_tim_sort_push_run (GtkTimSort *self,
void *base,
gsize len)
{
g_assert (self->pending_runs < GTK_TIM_SORT_MAX_PENDING);
g_assert (len <= self->size);
self->run[self->pending_runs].base = base;
self->run[self->pending_runs].len = len;
self->pending_runs++;
/* Advance to find next run */
self->base = ((char *) self->base) + len * self->element_size;
self->size -= len;
}
/**
* Ensures that the external array tmp has at least the specified
* number of elements, increasing its size if necessary. The size
* increases exponentially to ensure amortized linear time complexity.
*
* @param min_capacity the minimum required capacity of the tmp array
* @return tmp, whether or not it grew
*/
static gpointer
gtk_tim_sort_ensure_capacity (GtkTimSort *self,
gsize min_capacity)
{
if (self->tmp_length < min_capacity)
{
/* Compute smallest power of 2 > min_capacity */
gsize new_size = min_capacity;
new_size |= new_size >> 1;
new_size |= new_size >> 2;
new_size |= new_size >> 4;
new_size |= new_size >> 8;
new_size |= new_size >> 16;
if (sizeof(new_size) > 4)
new_size |= new_size >> 32;
new_size++;
if (new_size == 0) /* (overflow) Not bloody likely! */
new_size = min_capacity;
g_free (self->tmp);
self->tmp_length = new_size;
self->tmp = g_malloc (self->tmp_length * self->element_size);
}
return self->tmp;
}
static void
gtk_tim_sort_set_change (GtkTimSortRun *out_change,
gpointer base,
gsize len)
{
if (out_change)
{
out_change->base = base;
out_change->len = len;
}
}
/*<private>
* gtk_tim_sort_get_runs:
* @self: a #GtkTimSort
* @runs: (out) (caller-allocates): Place to store the 0-terminated list of
* runs
*
* Stores the already presorted list of runs - ranges of items that are
* known to be sorted among themselves.
*
* This can be used with gtk_tim_sort_set_runs() when resuming a sort later.
**/
void
gtk_tim_sort_get_runs (GtkTimSort *self,
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1])
{
gsize i;
g_return_if_fail (self);
g_return_if_fail (runs);
for (i = 0; i < self->pending_runs; i++)
runs[i] = self->run[i].len;
}
/*<private>
* gtk_tim_sort_set_runs:
* @self: a freshly initialized #GtkTimSort
* @runs: (array length=zero-terminated): a 0-terminated list of runs
*
* Sets the list of runs. A run is a range of items that are already
* sorted correctly among themselves. Runs must appear at the beginning of
* the array.
*
* Runs can only be set at the beginning of the sort operation.
**/
void
gtk_tim_sort_set_runs (GtkTimSort *self,
gsize *runs)
{
gsize i;
g_return_if_fail (self);
g_return_if_fail (self->pending_runs == 0);
for (i = 0; runs[i] != 0; i++)
gtk_tim_sort_push_run (self, self->base, runs[i]);
}
/*
* gtk_tim_sort_set_max_merge_size:
* @self: a #GtkTimSort
* @max_merge_size: Maximum size of a merge step, 0 for unlimited
*
* Sets the maximum size of a merge step. Every time
* gtk_tim_sort_step() is called and a merge operation has to be
* done, the @max_merge_size will be used to limit the size of
* the merge.
*
* The benefit is that merges happen faster, and if you're using
* an incremental sorting algorithm in the main thread, this will
* limit the runtime.
*
* The disadvantage is that setting up merges is expensive and that
* various optimizations benefit from larger merges, so the total
* runtime of the sorting will increase with the number of merges.
*
* A good estimate is to set a @max_merge_size to 1024 for around
* 1ms runtimes, if your compare function is fast.
*
* By default, max_merge_size is set to unlimited.
**/
void
gtk_tim_sort_set_max_merge_size (GtkTimSort *self,
gsize max_merge_size)
{
g_return_if_fail (self != NULL);
if (max_merge_size == 0)
max_merge_size = G_MAXSIZE;
self->max_merge_size = max_merge_size;
}
/**
* gtk_tim_sort_get_progress:
* @self: a #GtkTimSort
*
* Does a progress estimate about sort progress, estimates relative
* to the number of items to sort.
*
* Note that this is entirely a progress estimate and does not have
* a relationship with items put in their correct place.
* It is also an estimate, so no guarantees are made about accuracy,
* other than that it will only report 100% completion when it is
* indeed done sorting.
*
* To get a percentage, you need to divide this number by the total
* number of elements that are being sorted.
*
* Returns: Rough guess of sort progress
**/
gsize
gtk_tim_sort_get_progress (GtkTimSort *self)
{
#define DEPTH 4
gsize i;
gsize last, progress;
g_return_val_if_fail (self != NULL, 0);
if (self->pending_runs == 0)
return 0;
last = self->run[0].len;
progress = 0;
for (i = 1; i < DEPTH + 1 && i < self->pending_runs; i++)
{
progress += (DEPTH + 1 - i) * MAX (last, self->run[i].len);
last = MIN (last, self->run[i].len);
}
if (i < DEPTH + 1)
progress += (DEPTH + 1 - i) * last;
return progress / DEPTH;
#undef DEPTH
}
#if 1
#define WIDTH 4
#include "gtktimsort-impl.c"
#define WIDTH 8
#include "gtktimsort-impl.c"
#define WIDTH 16
#include "gtktimsort-impl.c"
#endif
#define NAME default
#define WIDTH (self->element_size)
#include "gtktimsort-impl.c"
/*
* gtk_tim_sort_step:
* @self: a #GtkTimSort
* @out_change: (optional): Return location for changed
* area. If a change did not cause any changes (for example,
* if an already sorted array gets sorted), out_change
* will be set to %NULL and 0.
*
* Performs another step in the sorting process. If a
* step was performed, %TRUE is returned and @out_change is
* set to the smallest area that contains all changes while
* sorting.
*
* If the data is completely sorted, %FALSE will be
* returned.
*
* Returns: %TRUE if an action was performed
**/
gboolean
gtk_tim_sort_step (GtkTimSort *self,
GtkTimSortRun *out_change)
{
gboolean result;
g_assert (self);
switch (self->element_size)
{
case 4:
result = gtk_tim_sort_step_4 (self, out_change);
break;
case 8:
result = gtk_tim_sort_step_8 (self, out_change);
break;
case 16:
result = gtk_tim_sort_step_16 (self, out_change);
break;
default:
result = gtk_tim_sort_step_default (self, out_change);
break;
}
return result;
}

122
gtk/gtktimsortprivate.h Normal file
View File

@@ -0,0 +1,122 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_TIMSORT_PRIVATE_H__
#define __GTK_TIMSORT_PRIVATE_H__
#include <gdk/gdk.h>
/* The maximum number of entries in a GtkTimState's pending-runs stack.
* This is enough to sort arrays of size up to about
* 32 * phi ** GTK_TIM_SORT_MAX_PENDING
* where phi ~= 1.618. 85 is ridiculously large enough, good for an array
* with 2**64 elements.
*/
#define GTK_TIM_SORT_MAX_PENDING 86
typedef struct _GtkTimSort GtkTimSort;
typedef struct _GtkTimSortRun GtkTimSortRun;
struct _GtkTimSortRun
{
void *base;
gsize len;
};
struct _GtkTimSort
{
/*
* Size of elements. Used to decide on fast paths.
*/
gsize element_size;
/* The comparator for this sort.
*/
GCompareDataFunc compare_func;
gpointer data;
/*
* The array being sorted.
*/
gpointer base;
gsize size;
/*
* The maximum size of a merge. It's guaranteed >0 and user-provided.
* See the comments for gtk_tim_sort_set_max_merge_size() for details.
*/
gsize max_merge_size;
/*
* This controls when we get *into* galloping mode. It is initialized
* to MIN_GALLOP. The mergeLo and mergeHi methods nudge it higher for
* random data, and lower for highly structured data.
*/
gsize min_gallop;
/*
* The minimum run length. See compute_min_run() for details.
*/
gsize min_run;
/*
* Temp storage for merges.
*/
void *tmp;
gsize tmp_length;
/*
* A stack of pending runs yet to be merged. Run i starts at
* address base[i] and extends for len[i] elements. It's always
* true (so long as the indices are in bounds) that:
*
* runBase[i] + runLen[i] == runBase[i + 1]
*
* so we could cut the storage for this, but it's a minor amount,
* and keeping all the info explicit simplifies the code.
*/
gsize pending_runs; // Number of pending runs on stack
GtkTimSortRun run[GTK_TIM_SORT_MAX_PENDING];
};
void gtk_tim_sort_init (GtkTimSort *self,
gpointer base,
gsize size,
gsize element_size,
GCompareDataFunc compare_func,
gpointer data);
void gtk_tim_sort_finish (GtkTimSort *self);
void gtk_tim_sort_get_runs (GtkTimSort *self,
gsize runs[GTK_TIM_SORT_MAX_PENDING + 1]);
void gtk_tim_sort_set_runs (GtkTimSort *self,
gsize *runs);
void gtk_tim_sort_set_max_merge_size (GtkTimSort *self,
gsize max_merge_size);
gsize gtk_tim_sort_get_progress (GtkTimSort *self);
gboolean gtk_tim_sort_step (GtkTimSort *self,
GtkTimSortRun *out_change);
void gtk_tim_sort (gpointer base,
gsize size,
gsize element_size,
GCompareDataFunc compare_func,
gpointer user_data);
#endif /* __GTK_TIMSORT_PRIVATE_H__ */

View File

@@ -24,6 +24,7 @@
#include "gtktreelistmodel.h"
#include "gtkintl.h"
#include "gtksorterprivate.h"
#include "gtktypebuiltins.h"
/**
@@ -64,6 +65,301 @@ static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
G_DEFINE_TYPE (GtkTreeListRowSorter, gtk_tree_list_row_sorter, GTK_TYPE_SORTER)
#define MAX_KEY_DEPTH (8)
/* our key is a gpointer[MAX_KEY_DEPTH] and contains:
*
* key[0] != NULL:
* The depth of the item is <= MAX_KEY_DEPTH so we can put the keys
* inline. This is the key for the ancestor at depth 0.
*
* key[0] == NULL && key[1] != NULL:
* The depth of the item is > MAX_KEY_DEPTH so it had to be allocated.
* key[1] contains this allocated and NULL-terminated array.
*
* key[0] == NULL && key[1] == NULL:
* The item is not a TreeListRow. To break ties, we put the item in key[2] to
* allow a direct compare.
*/
typedef struct _GtkTreeListRowSortKeys GtkTreeListRowSortKeys;
typedef struct _GtkTreeListRowCacheKey GtkTreeListRowCacheKey;
struct _GtkTreeListRowSortKeys
{
GtkSortKeys keys;
GtkSortKeys *sort_keys;
GHashTable *cached_keys;
};
struct _GtkTreeListRowCacheKey
{
GtkTreeListRow *row;
guint ref_count;
};
static GtkTreeListRowCacheKey *
cache_key_from_key (GtkTreeListRowSortKeys *self,
gpointer key)
{
if (self->sort_keys == NULL)
return key;
return (GtkTreeListRowCacheKey *) ((char *) key + GTK_SORT_KEYS_ALIGN (gtk_sort_keys_get_key_size (self->sort_keys), G_ALIGNOF (GtkTreeListRowCacheKey)));
}
static void
gtk_tree_list_row_sort_keys_free (GtkSortKeys *keys)
{
GtkTreeListRowSortKeys *self = (GtkTreeListRowSortKeys *) keys;
g_assert (g_hash_table_size (self->cached_keys) == 0);
g_hash_table_unref (self->cached_keys);
if (self->sort_keys)
gtk_sort_keys_unref (self->sort_keys);
g_slice_free (GtkTreeListRowSortKeys, self);
}
static inline gboolean
unpack (gpointer *key,
gpointer **out_keys,
gsize *out_max_size)
{
if (key[0])
{
*out_keys = key;
*out_max_size = MAX_KEY_DEPTH;
return TRUE;
}
else if (key[1])
{
*out_keys = (gpointer *) key[1];
*out_max_size = G_MAXSIZE;
return TRUE;
}
else
{
return FALSE;
}
}
static int
gtk_tree_list_row_sort_keys_compare (gconstpointer a,
gconstpointer b,
gpointer data)
{
GtkTreeListRowSortKeys *self = (GtkTreeListRowSortKeys *) data;
gpointer *keysa = (gpointer *) a;
gpointer *keysb = (gpointer *) b;
gsize sizea, sizeb;
gboolean resa, resb;
gsize i;
GtkOrdering result;
resa = unpack (keysa, &keysa, &sizea);
resb = unpack (keysb, &keysb, &sizeb);
if (!resa)
return resb ? GTK_ORDERING_LARGER : (keysa[2] < keysb[2] ? GTK_ORDERING_SMALLER :
(keysb[2] > keysa[2] ? GTK_ORDERING_LARGER : GTK_ORDERING_EQUAL));
else if (!resb)
return GTK_ORDERING_SMALLER;
for (i = 0; i < MIN (sizea, sizeb); i++)
{
if (keysa[i] == keysb[i])
{
if (keysa[i] == NULL)
return GTK_ORDERING_EQUAL;
continue;
}
else if (keysa[i] == NULL)
return GTK_ORDERING_SMALLER;
else if (keysb[i] == NULL)
return GTK_ORDERING_LARGER;
if (self->sort_keys)
result = gtk_sort_keys_compare (self->sort_keys, keysa[i], keysb[i]);
else
result = GTK_ORDERING_EQUAL;
if (result == GTK_ORDERING_EQUAL)
{
/* We must break ties here because if a ever gets a child,
* it would need to go right inbetween a and b. */
GtkTreeListRowCacheKey *cachea = cache_key_from_key (self, keysa[i]);
GtkTreeListRowCacheKey *cacheb = cache_key_from_key (self, keysb[i]);
if (gtk_tree_list_row_get_position (cachea->row) < gtk_tree_list_row_get_position (cacheb->row))
result = GTK_ORDERING_SMALLER;
else
result = GTK_ORDERING_LARGER;
}
return result;
}
if (sizea < sizeb)
return GTK_ORDERING_SMALLER;
else if (sizea > sizeb)
return GTK_ORDERING_LARGER;
else
return GTK_ORDERING_EQUAL;
}
static gboolean
gtk_tree_list_row_sort_keys_is_compatible (GtkSortKeys *keys,
GtkSortKeys *other)
{
GtkTreeListRowSortKeys *self = (GtkTreeListRowSortKeys *) keys;
GtkTreeListRowSortKeys *compare;
if (keys->klass != other->klass)
return FALSE;
compare = (GtkTreeListRowSortKeys *) other;
if (self->sort_keys && compare->sort_keys)
return gtk_sort_keys_is_compatible (self->sort_keys, compare->sort_keys);
else
return self->sort_keys == compare->sort_keys;
}
static gpointer
gtk_tree_list_row_sort_keys_ref_key (GtkTreeListRowSortKeys *self,
GtkTreeListRow *row)
{
GtkTreeListRowCacheKey *cache_key;
gpointer key;
key = g_hash_table_lookup (self->cached_keys, row);
if (key)
{
cache_key = cache_key_from_key (self, key);
cache_key->ref_count++;
return key;
}
if (self->sort_keys)
key = g_malloc (GTK_SORT_KEYS_ALIGN (gtk_sort_keys_get_key_size (self->sort_keys), G_ALIGNOF (GtkTreeListRowCacheKey))
+ sizeof (GtkTreeListRowCacheKey));
else
key = g_malloc (sizeof (GtkTreeListRowCacheKey));
cache_key = cache_key_from_key (self, key);
cache_key->row = g_object_ref (row);
cache_key->ref_count = 1;
if (self->sort_keys)
{
gpointer item = gtk_tree_list_row_get_item (row);
gtk_sort_keys_init_key (self->sort_keys, item, key);
g_object_unref (item);
}
g_hash_table_insert (self->cached_keys, row, key);
return key;
}
static void
gtk_tree_list_row_sort_keys_unref_key (GtkTreeListRowSortKeys *self,
gpointer key)
{
GtkTreeListRowCacheKey *cache_key = cache_key_from_key (self, key);
cache_key->ref_count--;
if (cache_key->ref_count > 0)
return;
if (self->sort_keys)
gtk_sort_keys_clear_key (self->sort_keys, key);
g_hash_table_remove (self->cached_keys, cache_key->row);
g_object_unref (cache_key->row);
g_free (key);
}
static void
gtk_tree_list_row_sort_keys_init_key (GtkSortKeys *keys,
gpointer item,
gpointer key_memory)
{
GtkTreeListRowSortKeys *self = (GtkTreeListRowSortKeys *) keys;
gpointer *key = (gpointer *) key_memory;
GtkTreeListRow *row, *parent;
guint i, depth;
if (!GTK_IS_TREE_LIST_ROW (item))
{
key[0] = NULL;
key[1] = NULL;
key[2] = item;
return;
}
row = GTK_TREE_LIST_ROW (item);
depth = gtk_tree_list_row_get_depth (row) + 1;
if (depth > MAX_KEY_DEPTH)
{
key[0] = NULL;
key[1] = g_new (gpointer, depth + 1);
key = key[1];
key[depth] = NULL;
}
else if (depth < MAX_KEY_DEPTH)
{
key[depth] = NULL;
}
g_object_ref (row);
for (i = depth; i-- > 0; )
{
key[i] = gtk_tree_list_row_sort_keys_ref_key (self, row);
parent = gtk_tree_list_row_get_parent (row);
g_object_unref (row);
row = parent;
}
g_assert (row == NULL);
}
static void
gtk_tree_list_row_sort_keys_clear_key (GtkSortKeys *keys,
gpointer key_memory)
{
GtkTreeListRowSortKeys *self = (GtkTreeListRowSortKeys *) keys;
gpointer *key = (gpointer *) key_memory;
gsize i, max;
if (!unpack (key, &key, &max))
return;
for (i = 0; i < max && key[i] != NULL; i++)
gtk_tree_list_row_sort_keys_unref_key (self, key[i]);
if (key[0] == NULL)
g_free (key[1]);
}
static const GtkSortKeysClass GTK_TREE_LIST_ROW_SORT_KEYS_CLASS =
{
gtk_tree_list_row_sort_keys_free,
gtk_tree_list_row_sort_keys_compare,
gtk_tree_list_row_sort_keys_is_compatible,
gtk_tree_list_row_sort_keys_init_key,
gtk_tree_list_row_sort_keys_clear_key,
};
static GtkSortKeys *
gtk_tree_list_row_sort_keys_new (GtkTreeListRowSorter *self)
{
GtkTreeListRowSortKeys *result;
result = gtk_sort_keys_new (GtkTreeListRowSortKeys,
&GTK_TREE_LIST_ROW_SORT_KEYS_CLASS,
sizeof (gpointer[MAX_KEY_DEPTH]),
sizeof (gpointer[MAX_KEY_DEPTH]));
if (self->sorter)
result->sort_keys = gtk_sort_keys_ref (gtk_sorter_get_keys (self->sorter));
result->cached_keys = g_hash_table_new (NULL, NULL);
return (GtkSortKeys *) result;
}
static GtkOrdering
gtk_tree_list_row_sorter_compare (GtkSorter *sorter,
gpointer item1,
@@ -164,9 +460,13 @@ gtk_tree_list_row_sorter_get_order (GtkSorter *sorter)
}
static void
propagate_changed (GtkSorter *sorter, GtkSorterChange change, gpointer data)
propagate_changed (GtkSorter *sorter,
GtkSorterChange change,
GtkTreeListRowSorter *self)
{
gtk_sorter_changed (GTK_SORTER (data), change);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
change,
gtk_tree_list_row_sort_keys_new (self));
}
static void
@@ -252,6 +552,9 @@ gtk_tree_list_row_sorter_class_init (GtkTreeListRowSorterClass *class)
static void
gtk_tree_list_row_sorter_init (GtkTreeListRowSorter *self)
{
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_tree_list_row_sort_keys_new (self));
}
/**
@@ -308,7 +611,9 @@ gtk_tree_list_row_sorter_set_sorter (GtkTreeListRowSorter *self,
if (self->sorter)
g_signal_connect (sorter, "changed", G_CALLBACK (propagate_changed), self);
gtk_sorter_changed (GTK_SORTER (self), GTK_SORTER_CHANGE_DIFFERENT);
gtk_sorter_changed_with_keys (GTK_SORTER (self),
GTK_SORTER_CHANGE_DIFFERENT,
gtk_tree_list_row_sort_keys_new (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SORTER]);
}

View File

@@ -133,12 +133,14 @@ gtk_private_sources = files([
'gtksearchengine.c',
'gtksearchenginemodel.c',
'gtksizerequestcache.c',
'gtksortkeys.c',
'gtkstyleanimation.c',
'gtkstylecascade.c',
'gtkstyleproperty.c',
'gtktextbtree.c',
'gtktexthistory.c',
'gtktextviewchild.c',
'gtktimsort.c',
'gtktrashmonitor.c',
'gtktreedatalist.c',
])
@@ -265,6 +267,7 @@ gtk_public_sources = files([
'gtkgrid.c',
'gtkgridlayout.c',
'gtkgridview.c',
'gtkgseqsortmodel.c',
'gtkheaderbar.c',
'gtkicontheme.c',
'gtkiconview.c',
@@ -369,6 +372,10 @@ gtk_public_sources = files([
'gtkslicelistmodel.c',
'gtksnapshot.c',
'gtksorter.c',
'gtksor2listmodel.c',
'gtksor3listmodel.c',
'gtksor4listmodel.c',
'gtksor5listmodel.c',
'gtksortlistmodel.c',
'gtkspinbutton.c',
'gtkspinner.c',
@@ -398,6 +405,10 @@ gtk_public_sources = files([
'gtktexttypes.c',
'gtktextutil.c',
'gtktextview.c',
'gtktim1sortmodel.c',
'gtktim2sortmodel.c',
'gtktim3sortmodel.c',
'gtktim4sortmodel.c',
'gtktogglebutton.c',
'gtktooltip.c',
'gtktooltipwindow.c',
@@ -549,6 +560,7 @@ gtk_public_headers = files([
'gtkgrid.h',
'gtkgridlayout.h',
'gtkgridview.h',
'gtkgseqsortmodel.h',
'gtkheaderbar.h',
'gtkicontheme.h',
'gtkiconview.h',
@@ -638,6 +650,10 @@ gtk_public_headers = files([
'gtkslicelistmodel.h',
'gtksnapshot.h',
'gtksorter.h',
'gtksor2listmodel.h',
'gtksor3listmodel.h',
'gtksor4listmodel.h',
'gtksor5listmodel.h',
'gtksortlistmodel.h',
'gtkspinbutton.h',
'gtkspinner.h',
@@ -660,6 +676,10 @@ gtk_public_headers = files([
'gtktexttag.h',
'gtktexttagtable.h',
'gtktextview.h',
'gtktim1sortmodel.h',
'gtktim2sortmodel.h',
'gtktim3sortmodel.h',
'gtktim4sortmodel.h',
'gtktogglebutton.h',
'gtktooltip.h',
'gtktreednd.h',

View File

@@ -92,12 +92,17 @@ tests = [
{ 'name': 'slicelistmodel' },
{ 'name': 'sorter' },
{ 'name': 'sortlistmodel' },
{ 'name': 'sortlistmodel-exhaustive' },
{ 'name': 'spinbutton' },
{ 'name': 'stringlist' },
{ 'name': 'templates' },
{ 'name': 'textbuffer' },
{ 'name': 'textiter' },
{ 'name': 'theme-validate' },
{
'name': 'timsort',
'sources': ['timsort.c', '../../gtk/gtktimsort.c'],
},
{ 'name': 'tooltips' },
{ 'name': 'treelistmodel' },
{

View File

@@ -0,0 +1,794 @@
#include <gtk/gtk.h>
#include <math.h>
#define MAX_SIZE 1024000
#define MAX_TIME (G_USEC_PER_SEC / 2)
static inline guint
quick_random (guint prev)
{
prev ^= prev << 13;
prev ^= prev >> 17;
prev ^= prev << 5;
return prev;
}
static guint comparisons = 0;
static int
count_comparisons (gconstpointer a,
gconstpointer b,
gpointer unused)
{
comparisons++;
return GTK_ORDERING_EQUAL;
}
static int
compare_string_object (gconstpointer a,
gconstpointer b,
gpointer unused)
{
GtkStringObject *sa = (GtkStringObject *) a;
GtkStringObject *sb = (GtkStringObject *) b;
comparisons++;
return gtk_ordering_from_cmpfunc (strcmp (gtk_string_object_get_string (sa),
gtk_string_object_get_string (sb)));
}
static void
count_changed_cb (GListModel *model,
guint position,
guint removed,
guint added,
guint *counter)
{
*counter += MAX (removed, added);
}
static gint64
snapshot_time (gint64 last,
gint64 *inout_max)
{
gint64 now = g_get_monotonic_time ();
*inout_max = MAX (*inout_max, now - last);
return now;
}
static void
print_result (const char *testname,
GType type,
gboolean incremental,
gsize size,
guint total_time,
guint max_time,
guint n_comparisons,
guint n_changed)
{
g_print ("# \"%s\", \"%s%s\",%8zu,%8uus,%8uus, %8u,%9u\n",
testname,
g_type_name (type),
incremental ? "-inc" : "",
size,
total_time,
max_time,
n_comparisons,
n_changed);
}
static void
set_model (const char *testname,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
gint64 start, end, max, total;
GtkSliceListModel *slice;
GListModel *sort;
guint n_changed, size = 1000;
slice = gtk_slice_list_model_new (source, 0, size);
sort = g_object_new (type,
"sorter", sorter,
incremental ? "incremental" : NULL, TRUE,
NULL);
g_signal_connect (sort, "items-changed", G_CALLBACK (count_changed_cb), &n_changed);
while (TRUE)
{
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, TRUE);
comparisons = 0;
n_changed = 0;
max = 0;
start = end = g_get_monotonic_time ();
g_object_set (sort, "model", slice, NULL);
end = snapshot_time (end, &max);
while (g_main_context_pending (NULL))
{
g_main_context_iteration (NULL, TRUE);
end = snapshot_time (end, &max);
}
total = (end - start);
print_result (testname, type, incremental, size, total, max, comparisons, n_changed);
if (total > MAX_TIME ||
size >= g_list_model_get_n_items (source))
break;
size *= 2;
g_object_set (sort, "model", NULL, NULL);
gtk_slice_list_model_set_size (slice, size);
}
g_object_unref (sort);
g_object_unref (slice);
}
static void
append (const char *testname,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random,
guint fraction)
{
gint64 start, end, max, total;
GtkSliceListModel *slice;
GListModel *sort;
guint n_changed, size = 1000;
slice = gtk_slice_list_model_new (source, 0, (fraction - 1) * size / fraction);
sort = g_object_new (type,
"model", slice,
"sorter", sorter,
incremental ? "incremental" : NULL, TRUE,
NULL);
g_signal_connect (sort, "items-changed", G_CALLBACK (count_changed_cb), &n_changed);
while (TRUE)
{
gtk_slice_list_model_set_size (slice, (fraction - 1) * size / fraction);
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, TRUE);
comparisons = 0;
n_changed = 0;
max = 0;
start = end = g_get_monotonic_time ();
gtk_slice_list_model_set_size (slice, size);
end = snapshot_time (end, &max);
while (g_main_context_pending (NULL))
{
g_main_context_iteration (NULL, TRUE);
end = snapshot_time (end, &max);
}
total = (end - start);
print_result (testname, type, incremental, size, total, max, comparisons, n_changed);
if (total > MAX_TIME ||
size >= g_list_model_get_n_items (source))
break;
size *= 2;
}
g_object_unref (sort);
g_object_unref (slice);
}
static void
append_half (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
append (name, type, incremental, source, sorter, random, 2);
}
static void
append_10th (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
append (name, type, incremental, source, sorter, random, 10);
}
static void
append_100th (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
append (name, type, incremental, source, sorter, random, 100);
}
static void
remove_test (const char *testname,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random,
guint fraction)
{
gint64 start, end, max, total;
GtkSliceListModel *slice;
GListModel *sort;
guint n_changed, size = 1000;
slice = gtk_slice_list_model_new (source, 0, size);
sort = g_object_new (type,
"model", slice,
"sorter", sorter,
incremental ? "incremental" : NULL, TRUE,
NULL);
g_signal_connect (sort, "items-changed", G_CALLBACK (count_changed_cb), &n_changed);
while (TRUE)
{
gtk_slice_list_model_set_size (slice, size);
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, TRUE);
comparisons = 0;
n_changed = 0;
max = 0;
start = end = g_get_monotonic_time ();
gtk_slice_list_model_set_size (slice, (fraction - 1) * size / fraction);
end = snapshot_time (end, &max);
while (g_main_context_pending (NULL))
{
g_main_context_iteration (NULL, TRUE);
end = snapshot_time (end, &max);
}
total = (end - start);
print_result (testname, type, incremental, size, total, max, comparisons, n_changed);
if (total > MAX_TIME ||
size >= g_list_model_get_n_items (source))
break;
size *= 2;
}
g_object_unref (sort);
g_object_unref (slice);
}
static void
remove_half (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
remove_test (name, type, incremental, source, sorter, random, 2);
}
static void
remove_10th (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
remove_test (name, type, incremental, source, sorter, random, 10);
}
static void
remove_100th (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
remove_test (name, type, incremental, source, sorter, random, 100);
}
static void
append_n (const char *testname,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random,
guint n)
{
gint64 start, end, max, total;
GtkSliceListModel *slice;
GListModel *sort;
guint i, n_changed, size = 1000;
slice = gtk_slice_list_model_new (source, 0, size);
sort = g_object_new (type,
"model", slice,
"sorter", sorter,
incremental ? "incremental" : NULL, TRUE,
NULL);
g_signal_connect (sort, "items-changed", G_CALLBACK (count_changed_cb), &n_changed);
while (TRUE)
{
gtk_slice_list_model_set_size (slice, size - n * 100);
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, TRUE);
comparisons = 0;
n_changed = 0;
max = 0;
start = end = g_get_monotonic_time ();
for (i = 0; i < 100; i++)
{
gtk_slice_list_model_set_size (slice, size - n * (100 - i));
end = snapshot_time (end, &max);
while (g_main_context_pending (NULL))
{
g_main_context_iteration (NULL, TRUE);
end = snapshot_time (end, &max);
}
}
total = (end - start);
print_result (testname, type, incremental, size, total, max, comparisons, n_changed);
if (total > MAX_TIME ||
size >= g_list_model_get_n_items (source))
break;
size *= 2;
}
g_object_unref (sort);
g_object_unref (slice);
}
static void
append_1 (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
append_n (name, type, incremental, source, sorter, random, 1);
}
static void
append_2 (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
append_n (name, type, incremental, source, sorter, random, 2);
}
static void
append_10 (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
append_n (name, type, incremental, source, sorter, random, 10);
}
static void
remove_n (const char *testname,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random,
guint n)
{
gint64 start, end, max, total;
GtkSliceListModel *slice;
GListModel *sort;
guint i, n_changed, size = 1000;
slice = gtk_slice_list_model_new (source, 0, size);
sort = g_object_new (type,
"model", slice,
"sorter", sorter,
incremental ? "incremental" : NULL, TRUE,
NULL);
g_signal_connect (sort, "items-changed", G_CALLBACK (count_changed_cb), &n_changed);
while (TRUE)
{
gtk_slice_list_model_set_size (slice, size);
while (g_main_context_pending (NULL))
g_main_context_iteration (NULL, TRUE);
comparisons = 0;
n_changed = 0;
max = 0;
start = end = g_get_monotonic_time ();
for (i = 0; i < 100; i++)
{
gtk_slice_list_model_set_size (slice, size - n * (i + 1));
end = snapshot_time (end, &max);
while (g_main_context_pending (NULL))
{
g_main_context_iteration (NULL, TRUE);
end = snapshot_time (end, &max);
}
}
total = (end - start);
print_result (testname, type, incremental, size, total, max, comparisons, n_changed);
if (total > MAX_TIME ||
size >= g_list_model_get_n_items (source))
break;
size *= 2;
}
g_object_unref (sort);
g_object_unref (slice);
}
static void
remove_1 (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
remove_n (name, type, incremental, source, sorter, random, 1);
}
static void
remove_2 (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
remove_n (name, type, incremental, source, sorter, random, 2);
}
static void
remove_10 (const char *name,
GType type,
gboolean incremental,
GListModel *source,
GtkSorter *sorter,
guint random)
{
remove_n (name, type, incremental, source, sorter, random, 10);
}
static void
done_loading_directory (GtkDirectoryList *dir,
GParamSpec *pspec,
gpointer data)
{
/* When we get G_IO_ERROR_TOO_MANY_OPEN_FILES we enqueue directories here for reloading
* as more file descriptors get available
*/
static GSList *too_many = NULL;
const GError *error;
guint *counters = data;
/* happens when restarting the load below */
if (gtk_directory_list_is_loading (dir))
return;
error = gtk_directory_list_get_error (dir);
if (error)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_OPEN_FILES))
{
too_many = g_slist_prepend (too_many, g_object_ref (dir));
return;
}
}
counters[1]++;
if (too_many)
{
GtkDirectoryList *reload = too_many->data;
GFile *file;
too_many = g_slist_remove (too_many, reload);
file = g_object_ref (gtk_directory_list_get_file (reload));
gtk_directory_list_set_file (reload, NULL);
gtk_directory_list_set_file (reload, file);
g_object_unref (file);
}
}
static gboolean
file_info_is_directory (GFileInfo *file_info)
{
if (g_file_info_get_is_symlink (file_info))
return FALSE;
return g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY;
}
static GListModel *
create_directory_list (gpointer item,
gpointer data)
{
GFileInfo *file_info = G_FILE_INFO (item);
guint *counters = data;
GtkDirectoryList *dir;
GFile *file;
if (!file_info_is_directory (file_info))
return NULL;
file = G_FILE (g_file_info_get_attribute_object (file_info, "standard::file"));
if (file == NULL)
return NULL;
dir = gtk_directory_list_new (G_FILE_ATTRIBUTE_STANDARD_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME
"," G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
NULL);
gtk_directory_list_set_io_priority (dir, G_PRIORITY_DEFAULT + g_random_int_range (-5, 5));
gtk_directory_list_set_monitored (dir, FALSE);
gtk_directory_list_set_file (dir, file);
counters[0]++;
g_signal_connect (dir, "notify::loading", G_CALLBACK (done_loading_directory), counters);
g_assert (gtk_directory_list_is_loading (dir));
return G_LIST_MODEL (dir);
}
static GListModel *
get_file_infos (void)
{
static GtkTreeListModel *tree = NULL;
gint64 start, end, max;
GtkDirectoryList *dir;
GFile *root;
guint counters[2] = { 1, 0 };
if (tree)
return G_LIST_MODEL (g_object_ref (tree));
if (g_getenv ("G_TEST_SRCDIR"))
root = g_file_new_for_path (g_getenv ("G_TEST_SRCDIR"));
else
root = g_file_new_for_path (g_get_home_dir ());
start = end = g_get_monotonic_time ();
max = 0;
dir = gtk_directory_list_new (G_FILE_ATTRIBUTE_STANDARD_TYPE
"," G_FILE_ATTRIBUTE_STANDARD_NAME
"," G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME
"," G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK,
NULL);
gtk_directory_list_set_monitored (dir, FALSE);
gtk_directory_list_set_file (dir, root);
tree = gtk_tree_list_model_new (FALSE,
G_LIST_MODEL (dir),
TRUE,
create_directory_list,
counters, NULL);
g_signal_connect (dir, "notify::loading", G_CALLBACK (done_loading_directory), counters);
end = snapshot_time (end, &max);
while (counters[0] != counters[1])
{
g_main_context_iteration (NULL, TRUE);
end = snapshot_time (end, &max);
}
//g_print ("%u/%u\n", counters[0], counters[1]);
end = snapshot_time (end, &max);
print_result ("load-directory", GTK_TYPE_DIRECTORY_LIST, FALSE, g_list_model_get_n_items (G_LIST_MODEL (tree)), end - start, max, 0, counters[0]);
g_object_unref (dir);
g_object_unref (root);
return G_LIST_MODEL (g_object_ref (tree));
}
static void
run_test (GListModel *source,
GtkSorter *sorter,
const char * const *tests,
const char *test_name,
void (* test_func) (const char *name, GType type, gboolean incremental, GListModel *source, GtkSorter *sorter, guint random))
{
struct {
GType type;
gboolean incremental;
} types[] = {
{ GTK_TYPE_SORT_LIST_MODEL, FALSE },
{ GTK_TYPE_GSEQ_SORT_MODEL, FALSE },
{ GTK_TYPE_SOR2_LIST_MODEL, FALSE },
{ GTK_TYPE_SOR3_LIST_MODEL, FALSE },
{ GTK_TYPE_SOR4_LIST_MODEL, FALSE },
{ GTK_TYPE_SOR5_LIST_MODEL, FALSE },
{ GTK_TYPE_TIM1_SORT_MODEL, FALSE },
{ GTK_TYPE_TIM2_SORT_MODEL, FALSE },
{ GTK_TYPE_TIM3_SORT_MODEL, FALSE },
{ GTK_TYPE_TIM4_SORT_MODEL, FALSE },
{ GTK_TYPE_SORT_LIST_MODEL, TRUE },
{ GTK_TYPE_SOR3_LIST_MODEL, TRUE },
{ GTK_TYPE_TIM2_SORT_MODEL, TRUE },
{ GTK_TYPE_TIM3_SORT_MODEL, TRUE },
{ GTK_TYPE_TIM4_SORT_MODEL, TRUE },
};
guint random = g_random_int ();
guint i;
for (i = 0; i < G_N_ELEMENTS (types); i++)
{
test_func (test_name, types[i].type, types[i].incremental, source, sorter, random);
}
}
static GListModel *
get_string_list (void)
{
static GtkStringList *string_list = NULL;
if (string_list == NULL)
{
guint i, random;
random = g_test_rand_int ();
string_list = gtk_string_list_new (NULL);
for (i = 0; i < MAX_SIZE; i++)
{
gtk_string_list_take (string_list, g_strdup_printf ("%u", random));
random = quick_random (random);
}
}
return G_LIST_MODEL (g_object_ref (string_list));
}
static void
run_tests (const char * const *tests,
const char *test_name,
void (* test_func) (const char *name, GType type, gboolean incremental, GListModel *source, GtkSorter *sorter, guint random))
{
const char *suffixes[] = { "string", "tree", "filename" };
gsize i;
for (i = 0; i < G_N_ELEMENTS(suffixes); i++)
{
GListModel *source;
GtkSorter *sorter;
char *name;
name = g_strdup_printf ("%s-%s", test_name, suffixes[i]);
if (tests != NULL && !g_strv_contains (tests, name))
{
g_free (name);
continue;
}
switch (i)
{
case 0:
source = get_string_list ();
sorter = gtk_custom_sorter_new (compare_string_object, NULL, NULL);
break;
case 1:
source = get_file_infos ();
sorter = gtk_multi_sorter_new ();
gtk_multi_sorter_append (GTK_MULTI_SORTER (sorter), gtk_custom_sorter_new (count_comparisons, NULL, NULL));
gtk_multi_sorter_append (GTK_MULTI_SORTER (sorter),
gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_BOOLEAN,
NULL,
0,
NULL,
(GCallback) file_info_is_directory,
NULL, NULL)));
gtk_multi_sorter_append (GTK_MULTI_SORTER (sorter),
gtk_numeric_sorter_new (gtk_cclosure_expression_new (G_TYPE_UINT64,
NULL,
1,
(GtkExpression *[1]) {
gtk_constant_expression_new (G_TYPE_STRING, G_FILE_ATTRIBUTE_STANDARD_SIZE)
},
(GCallback) g_file_info_get_attribute_uint64,
NULL, NULL)));
sorter = gtk_tree_list_row_sorter_new (sorter);
break;
case 2:
{
GListModel *infos = get_file_infos ();
source = G_LIST_MODEL (gtk_map_list_model_new (infos,
(GtkMapListModelMapFunc) gtk_tree_list_row_get_item,
NULL, NULL));
sorter = gtk_string_sorter_new (
gtk_cclosure_expression_new (G_TYPE_STRING,
NULL,
1,
(GtkExpression *[1]) {
gtk_constant_expression_new (G_TYPE_STRING, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME)
},
(GCallback) g_file_info_get_attribute_as_string,
NULL,
NULL));
g_object_unref (infos);
}
break;
default:
g_assert_not_reached ();
return;
}
run_test (source, sorter, tests, name, test_func);
g_free (name);
g_object_unref (sorter);
g_object_unref (source);
}
}
int
main (int argc, char *argv[])
{
const char * const *tests;
gtk_test_init (&argc, &argv);
if (argc < 2)
tests = NULL;
else
tests = (const char **) argv + 1;
g_print ("# \"test\",\"model\",\"model size\",\"time\",\"max time\",\"comparisons\",\"changes\"\n");
run_tests (tests, "set-model", set_model);
run_tests (tests, "append-half", append_half);
run_tests (tests, "append-10th", append_10th);
run_tests (tests, "append-100th", append_100th);
run_tests (tests, "remove-half", remove_half);
run_tests (tests, "remove-10th", remove_10th);
run_tests (tests, "remove-100th", remove_100th);
run_tests (tests, "append-1", append_1);
run_tests (tests, "append-2", append_2);
run_tests (tests, "append-10", append_10);
run_tests (tests, "remove-1", remove_1);
run_tests (tests, "remove-2", remove_2);
run_tests (tests, "remove-10", remove_10);
run_tests (tests, "remove-10", remove_10);
return g_test_run ();
}

View File

@@ -0,0 +1,440 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
#define ensure_updated() G_STMT_START{ \
while (g_main_context_pending (NULL)) \
g_main_context_iteration (NULL, TRUE); \
}G_STMT_END
#define assert_model_equal(model1, model2) G_STMT_START{ \
guint _i, _n; \
g_assert_cmpint (g_list_model_get_n_items (model1), ==, g_list_model_get_n_items (model2)); \
_n = g_list_model_get_n_items (model1); \
for (_i = 0; _i < _n; _i++) \
{ \
gpointer o1 = g_list_model_get_item (model1, _i); \
gpointer o2 = g_list_model_get_item (model2, _i); \
\
if (o1 != o2) \
{ \
char *_s = g_strdup_printf ("Objects differ at index %u out of %u", _i, _n); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
g_free (_s); \
} \
\
g_object_unref (o1); \
g_object_unref (o2); \
} \
}G_STMT_END
G_GNUC_UNUSED static char *
model_to_string (GListModel *model)
{
GString *string;
guint i, n;
n = g_list_model_get_n_items (model);
string = g_string_new (NULL);
/* Check that all unchanged items are indeed unchanged */
for (i = 0; i < n; i++)
{
gpointer item, model_item = g_list_model_get_item (model, i);
if (GTK_IS_TREE_LIST_ROW (model_item))
item = gtk_tree_list_row_get_item (model_item);
else
item = model_item;
if (i > 0)
g_string_append (string, ", ");
if (G_IS_LIST_MODEL (item))
g_string_append (string, "*");
else
g_string_append (string, gtk_string_object_get_string (item));
g_object_unref (model_item);
}
return g_string_free (string, FALSE);
}
static void
assert_items_changed_correctly (GListModel *model,
guint position,
guint removed,
guint added,
GListModel *compare)
{
guint i, n_items;
//g_print ("%s => %u -%u +%u => %s\n", model_to_string (compare), position, removed, added, model_to_string (model));
g_assert_cmpint (g_list_model_get_n_items (model), ==, g_list_model_get_n_items (compare) - removed + added);
n_items = g_list_model_get_n_items (model);
if (position != 0 || removed != n_items)
{
/* Check that all unchanged items are indeed unchanged */
for (i = 0; i < position; i++)
{
gpointer o1 = g_list_model_get_item (model, i);
gpointer o2 = g_list_model_get_item (compare, i);
g_assert_cmphex (GPOINTER_TO_SIZE (o1), ==, GPOINTER_TO_SIZE (o2));
g_object_unref (o1);
g_object_unref (o2);
}
for (i = position + added; i < n_items; i++)
{
gpointer o1 = g_list_model_get_item (model, i);
gpointer o2 = g_list_model_get_item (compare, i - added + removed);
g_assert_cmphex (GPOINTER_TO_SIZE (o1), ==, GPOINTER_TO_SIZE (o2));
g_object_unref (o1);
g_object_unref (o2);
}
/* Check that the first and last added item are different from
* first and last removed item.
* Otherwise we could have kept them as-is
*/
if (removed > 0 && added > 0)
{
gpointer o1 = g_list_model_get_item (model, position);
gpointer o2 = g_list_model_get_item (compare, position);
g_assert_cmphex (GPOINTER_TO_SIZE (o1), !=, GPOINTER_TO_SIZE (o2));
g_object_unref (o1);
g_object_unref (o2);
o1 = g_list_model_get_item (model, position + added - 1);
o2 = g_list_model_get_item (compare, position + removed - 1);
g_assert_cmphex (GPOINTER_TO_SIZE (o1), !=, GPOINTER_TO_SIZE (o2));
g_object_unref (o1);
g_object_unref (o2);
}
}
/* Finally, perform the same change as the signal indicates */
g_list_store_splice (G_LIST_STORE (compare), position, removed, NULL, 0);
for (i = position; i < position + added; i++)
{
gpointer item = g_list_model_get_item (G_LIST_MODEL (model), i);
g_list_store_insert (G_LIST_STORE (compare), i, item);
g_object_unref (item);
}
}
static GtkSortListModel *
sort_list_model_new (GListModel *source,
GtkSorter *sorter)
{
GtkSortListModel *model;
GListStore *check;
guint i;
model = gtk_sort_list_model_new (source, sorter);
check = g_list_store_new (G_TYPE_OBJECT);
for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++)
{
gpointer item = g_list_model_get_item (G_LIST_MODEL (model), i);
g_list_store_append (check, item);
g_object_unref (item);
}
g_signal_connect_data (model,
"items-changed",
G_CALLBACK (assert_items_changed_correctly),
check,
(GClosureNotify) g_object_unref,
0);
return model;
}
#define N_MODELS 8
static GtkSortListModel *
create_sort_list_model (gconstpointer model_id,
gboolean track_changes,
GListModel *source,
GtkSorter *sorter)
{
GtkSortListModel *model;
guint id = GPOINTER_TO_UINT (model_id);
if (track_changes)
model = sort_list_model_new (id & 1 ? NULL : source, id & 2 ? NULL : sorter);
else
model = gtk_sort_list_model_new (id & 1 ? NULL : source, id & 2 ? NULL : sorter);
switch (id >> 2)
{
case 0:
break;
case 1:
//gtk_sort_list_model_set_incremental (model, TRUE);
break;
default:
g_assert_not_reached ();
break;
}
if (id & 1)
gtk_sort_list_model_set_model (model, source);
if (id & 2)
gtk_sort_list_model_set_sorter (model, sorter);
return model;
}
static GListModel *
create_source_model (guint min_size, guint max_size)
{
const char *strings[] = { "A", "a", "B", "b" };
GtkStringList *list;
guint i, size;
size = g_test_rand_int_range (min_size, max_size + 1);
list = gtk_string_list_new (NULL);
for (i = 0; i < size; i++)
gtk_string_list_append (list, strings[g_test_rand_int_range (0, G_N_ELEMENTS (strings))]);
return G_LIST_MODEL (list);
}
#define N_SORTERS 3
static GtkSorter *
create_sorter (gsize id)
{
GtkSorter *sorter;
switch (id)
{
case 0:
return gtk_string_sorter_new (NULL);
case 1:
case 2:
/* match all As, Bs and nothing */
sorter = gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string"));
if (id == 1)
gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), TRUE);
return sorter;
default:
g_assert_not_reached ();
return NULL;
}
}
static GtkSorter *
create_random_sorter (gboolean allow_null)
{
guint n;
if (allow_null)
n = g_test_rand_int_range (0, N_SORTERS + 1);
else
n = g_test_rand_int_range (0, N_SORTERS);
if (n >= N_SORTERS)
return NULL;
return create_sorter (n);
}
/* Compare this:
* source => sorter1 => sorter2
* with:
* source => multisorter(sorter1, sorter2)
* and randomly change the source and sorters and see if the
* two continue agreeing.
*/
static void
test_two_sorters (gconstpointer model_id)
{
GtkSortListModel *compare;
GtkSortListModel *model1, *model2;
GListModel *source;
GtkSorter *every, *sorter;
guint i, j, k;
source = create_source_model (10, 10);
model2 = create_sort_list_model (model_id, TRUE, source, NULL);
/* can't track changes from a sortmodel, where the same items get reordered */
model1 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (model2), NULL);
every = gtk_multi_sorter_new ();
compare = create_sort_list_model (model_id, TRUE, source, every);
g_object_unref (every);
g_object_unref (source);
for (i = 0; i < N_SORTERS; i++)
{
sorter = create_sorter (i);
gtk_sort_list_model_set_sorter (model1, sorter);
gtk_multi_sorter_append (GTK_MULTI_SORTER (every), sorter);
for (j = 0; j < N_SORTERS; j++)
{
sorter = create_sorter (i);
gtk_sort_list_model_set_sorter (model2, sorter);
gtk_multi_sorter_append (GTK_MULTI_SORTER (every), sorter);
ensure_updated ();
assert_model_equal (G_LIST_MODEL (model2), G_LIST_MODEL (compare));
for (k = 0; k < 10; k++)
{
source = create_source_model (0, 1000);
gtk_sort_list_model_set_model (compare, source);
gtk_sort_list_model_set_model (model2, source);
g_object_unref (source);
ensure_updated ();
assert_model_equal (G_LIST_MODEL (model1), G_LIST_MODEL (compare));
}
gtk_multi_sorter_remove (GTK_MULTI_SORTER (every), 1);
}
gtk_multi_sorter_remove (GTK_MULTI_SORTER (every), 0);
}
g_object_unref (compare);
g_object_unref (model2);
g_object_unref (model1);
}
/* Run:
* source => sorter1 => sorter2
* and randomly add/remove sources and change the sorters and
* see if the two sorters stay identical
*/
static void
test_stability (gconstpointer model_id)
{
GListStore *store;
GtkFlattenListModel *flatten;
GtkSortListModel *sort1, *sort2;
GtkSorter *sorter;
gsize i;
sorter = create_random_sorter (TRUE);
store = g_list_store_new (G_TYPE_OBJECT);
flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
sort1 = create_sort_list_model (model_id, TRUE, G_LIST_MODEL (flatten), sorter);
sort2 = create_sort_list_model (model_id, FALSE, G_LIST_MODEL (sort1), sorter);
g_clear_object (&sorter);
for (i = 0; i < 500; i++)
{
gboolean add = FALSE, remove = FALSE;
guint position;
switch (g_test_rand_int_range (0, 4))
{
case 0:
/* change the sorter */
sorter = create_random_sorter (TRUE);
gtk_sort_list_model_set_sorter (sort1, sorter);
gtk_sort_list_model_set_sorter (sort2, sorter);
g_clear_object (&sorter);
break;
case 1:
/* remove a model */
remove = TRUE;
break;
case 2:
/* add a model */
add = TRUE;
break;
case 3:
/* replace a model */
remove = TRUE;
add = TRUE;
break;
default:
g_assert_not_reached ();
break;
}
position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
remove = FALSE;
if (add)
{
/* We want at least one element, otherwise the sorters will see no changes */
GListModel *source = create_source_model (1, 50);
g_list_store_splice (store,
position,
remove ? 1 : 0,
(gpointer *) &source, 1);
g_object_unref (source);
}
else if (remove)
{
g_list_store_remove (store, position);
}
if (g_test_rand_bit ())
{
ensure_updated ();
assert_model_equal (G_LIST_MODEL (sort1), G_LIST_MODEL (sort2));
}
}
g_object_unref (sort2);
g_object_unref (sort1);
g_object_unref (flatten);
g_object_unref (store);
}
static void
add_test_for_all_models (const char *name,
GTestDataFunc test_func)
{
guint i;
for (i = 0; i < N_MODELS; i++)
{
char *path = g_strdup_printf ("/sorterlistmodel/model%u/%s", i, name);
g_test_add_data_func (path, GUINT_TO_POINTER (i), test_func);
g_free (path);
}
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
setlocale (LC_ALL, "C");
add_test_for_all_models ("two-sorters", test_two_sorters);
add_test_for_all_models ("stability", test_stability);
return g_test_run ();
}

View File

@@ -306,9 +306,7 @@ test_set_sorter (void)
gtk_sort_list_model_set_sorter (sort, sorter);
g_object_unref (sorter);
assert_model (sort, "2 4 6 8 10");
/* Technically, this is correct, but we shortcut setting the sort func:
* assert_changes (sort, "0-4+4"); */
assert_changes (sort, "0-5+5");
assert_changes (sort, "0-4+4");
g_object_unref (store);
g_object_unref (sort);

253
testsuite/gtk/timsort.c Normal file
View File

@@ -0,0 +1,253 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <locale.h>
#include <gtk/gtk.h>
#include "gtk/gtktimsortprivate.h"
#define assert_sort_equal(a, b, size, n) \
g_assert_cmpmem (a, sizeof (size) * n, b, sizeof (size) * n)
static int
compare_int (gconstpointer a,
gconstpointer b,
gpointer unused)
{
int ia = *(const int *) a;
int ib = *(const int *) b;
return ia < ib ? -1 : (ia > ib);
}
static int
compare_pointer (gconstpointer a,
gconstpointer b,
gpointer unused)
{
gpointer pa = *(const gpointer *) a;
gpointer pb = *(const gpointer *) b;
return pa < pb ? -1 : (pa > pb);
}
G_GNUC_UNUSED static void
dump (int *a,
gsize n)
{
gsize i;
for (i = 0; i < n; i++)
{
if (i)
g_print(", ");
g_print ("%d", a[i]);
}
g_print ("\n");
}
static void
run_comparison (gpointer a,
gsize n,
gsize element_size,
GCompareDataFunc compare_func,
gpointer data)
{
gint64 start, mid, end;
gpointer b;
b = g_memdup (a, element_size * n);
start = g_get_monotonic_time ();
gtk_tim_sort (a, n, element_size, compare_func, data);
mid = g_get_monotonic_time ();
g_qsort_with_data (b, n, element_size, compare_func, data);
end = g_get_monotonic_time ();
g_test_message ("%zu items in %uus vs %uus (%u%%)",
n,
(guint) (mid - start),
(guint) (end - mid),
(guint) (100 * (mid - start) / MAX (1, end - mid)));
assert_sort_equal (a, b, int, n);
g_free (b);
}
static void
test_integers (void)
{
int *a;
gsize i, n, run;
a = g_new (int, 1000);
for (run = 0; run < 10; run++)
{
n = g_test_rand_int_range (0, 1000);
for (i = 0; i < n; i++)
a[i] = g_test_rand_int ();
run_comparison (a, n, sizeof (int), compare_int, NULL);
}
g_free (a);
}
static void
test_integers_runs (void)
{
int *a;
gsize i, j, n, run;
a = g_new (int, 1000);
for (run = 0; run < 10; run++)
{
n = g_test_rand_int_range (0, 1000);
for (i = 0; i < n; i++)
{
a[i] = g_test_rand_int ();
j = i + g_test_rand_int_range (0, 20);
j = MIN (n, j);
if (g_test_rand_bit ())
{
for (i++; i < j; i++)
a[i] = a[i - 1] + 1;
}
else
{
for (i++; i < j; i++)
a[i] = a[i - 1] - 1;
}
}
run_comparison (a, n, sizeof (int), compare_int, NULL);
}
g_free (a);
}
static void
test_integers_huge (void)
{
int *a;
gsize i, n;
n = g_test_rand_int_range (2 * 1000 * 1000, 5 * 1000 * 1000);
a = g_new (int, n);
for (i = 0; i < n; i++)
a[i] = g_test_rand_int ();
run_comparison (a, n, sizeof (int), compare_int, NULL);
g_free (a);
}
static void
test_pointers (void)
{
gpointer *a;
gsize i, n, run;
a = g_new (gpointer, 1000);
for (run = 0; run < 10; run++)
{
n = g_test_rand_int_range (0, 1000);
for (i = 0; i < n; i++)
a[i] = GINT_TO_POINTER (g_test_rand_int ());
run_comparison (a, n, sizeof (gpointer), compare_pointer, NULL);
}
g_free (a);
}
static void
test_pointers_huge (void)
{
gpointer *a;
gsize i, n;
n = g_test_rand_int_range (2 * 1000 * 1000, 5 * 1000 * 1000);
a = g_new (gpointer, n);
for (i = 0; i < n; i++)
a[i] = GINT_TO_POINTER (g_test_rand_int ());
run_comparison (a, n, sizeof (gpointer), compare_pointer, NULL);
g_free (a);
}
static void
test_steps (void)
{
GtkTimSortRun change;
GtkTimSort sort;
int *a, *b;
gsize i, n;
n = g_test_rand_int_range (20 * 1000, 50 * 1000);
a = g_new (int, n);
for (i = 0; i < n; i++)
a[i] = g_test_rand_int ();
b = g_memdup (a, sizeof (int) * n);
gtk_tim_sort_init (&sort, a, n, sizeof (int), compare_int, NULL);
gtk_tim_sort_set_max_merge_size (&sort, g_test_rand_int_range (512, 2048));
while (gtk_tim_sort_step (&sort, &change))
{
if (change.len)
{
int *a_start = change.base;
int *b_start = b + ((int *) change.base - a);
g_assert_cmpint (a_start[0], !=, b_start[0]);
g_assert_cmpint (a_start[change.len - 1], !=, b_start[change.len - 1]);
memcpy (b_start, a_start, change.len * sizeof (int));
}
assert_sort_equal (a, b, int, n);
}
g_qsort_with_data (b, n, sizeof (int), compare_int, NULL);
assert_sort_equal (a, b, int, n);
gtk_tim_sort_finish (&sort);
g_free (b);
g_free (a);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
setlocale (LC_ALL, "C");
g_test_add_func ("/timsort/integers", test_integers);
g_test_add_func ("/timsort/integers/runs", test_integers_runs);
g_test_add_func ("/timsort/integers/huge", test_integers_huge);
g_test_add_func ("/timsort/pointers", test_pointers);
g_test_add_func ("/timsort/pointers/huge", test_pointers_huge);
g_test_add_func ("/timsort/steps", test_steps);
return g_test_run ();
}