Skip to content

Commit 91e1a83

Browse files
committed
Add and use EntryListModel, much better than DirectoryList
1 parent 5194e4c commit 91e1a83

4 files changed

Lines changed: 118 additions & 41 deletions

File tree

src/gtk/entry_list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Gio from "gi://Gio?version=2.0"
55
import { GObjectify } from "../utils/gobjectify.js"
66
import { Entry } from "../utils/entry.js"
77
import { EntryRow } from "./entry_row.js"
8+
import type { EntryListModel } from "../utils/entry_list_model.js"
89

910
export namespace EntryList {
1011
export interface ConstructorProps extends Partial<Adw.Bin.ConstructorProps> {
@@ -31,7 +32,7 @@ export class EntryList extends Adw.Bin {
3132
public accessor loading!: boolean
3233

3334
@GObjectify.Property(Gio.ListModel, { flags: "CONSTRUCT_ONLY" })
34-
public accessor entry_list_model!: Gio.ListModel<Entry>
35+
public accessor entry_list_model!: EntryListModel
3536

3637
public constructor(params: EntryList.ConstructorProps) {
3738
super(params)

src/pages/entries_page.blp

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,25 @@
11
using Gtk 4.0;
22
using Adw 1;
33

4-
CustomFilter only_entries_filter {}
5-
64
CustomSorter entry_sorter {}
75

86
SortListModel top_home_model {
97
sorter: entry_sorter;
108
items-changed => $_mark_overrides();
9+
items-changed => $_start_loading();
1110

12-
model: FilterListModel {
13-
filter: only_entries_filter;
14-
15-
model: MapListModel home_map_model {
16-
model: DirectoryList {
17-
file: bind template.home_autostart_dir;
18-
};
19-
};
11+
model: $EntryListModel {
12+
file: bind template.home_autostart_dir;
2013
};
2114
}
2215

2316
SortListModel top_root_model {
2417
sorter: entry_sorter;
2518
items-changed => $_mark_overrides();
19+
items-changed => $_start_loading();
2620

27-
model: FilterListModel {
28-
filter: only_entries_filter;
29-
30-
model: MapListModel root_map_model {
31-
model: DirectoryList {
32-
file: bind template.root_autostart_dir;
33-
};
34-
};
21+
model: $EntryListModel {
22+
file: bind template.root_autostart_dir;
3523
};
3624
}
3725

@@ -41,6 +29,7 @@ template $EntriesPage: Adw.NavigationPage {
4129
child: $LoadingGroup {
4230
title: _("Loading Entries");
4331
description: _("This should only take a moment.");
32+
loading: bind template.is_loading;
4433

4534
content: Adw.ToolbarView {
4635
[top]

src/pages/entries_page.ts

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,18 @@ import Adw from "gi://Adw?version=1"
22
import Gio from "gi://Gio?version=2.0"
33
import Gtk from "gi://Gtk?version=4.0"
44

5+
import { Entry } from "../utils/entry.js"
56
import { GObjectify } from "../utils/gobjectify.js"
67
import { SharedVars } from "../utils/shared_vars.js"
7-
import { Entry } from "../utils/entry.js"
8-
import { try_catch } from "../utils/safe.js"
9-
import { chunked_idler } from "../utils/async.js"
108
import { make_iterable } from "../utils/list_model_utils.js"
9+
import { chunked_idler } from "../utils/async.js"
10+
import "../utils/entry_list_model.js"
1111
import "../gtk/loading_group.js"
1212
import "../gtk/search_group.js"
1313
import "../gtk/entry_list.js"
1414

1515
@GObjectify.Class({ template: "/io/github/flattool/Ignition/pages/entries_page" })
1616
export class EntriesPage extends Adw.NavigationPage {
17-
@GObjectify.Child
18-
public accessor only_entries_filter!: Gtk.CustomFilter
19-
20-
@GObjectify.Child
21-
public accessor home_map_model!: Gtk.MapListModel<Entry>
22-
23-
@GObjectify.Child
24-
public accessor root_map_model!: Gtk.MapListModel<Entry>
25-
2617
@GObjectify.Child
2718
public accessor top_home_model!: Gio.ListModel<Entry>
2819

@@ -38,25 +29,19 @@ export class EntriesPage extends Adw.NavigationPage {
3829
@GObjectify.Property(Gio.File)
3930
public accessor root_autostart_dir: Gio.File
4031

32+
@GObjectify.Property("bool", { default: true })
33+
public accessor is_loading!: boolean
34+
4135
public constructor(params: Partial<Adw.NavigationPage.ConstructorProps>) {
4236
super(params)
4337
this.home_autostart_dir = SharedVars.home_autostart_dir
4438
this.root_autostart_dir = SharedVars.root_autostart_dir
4539

46-
this.only_entries_filter.set_filter_func((item) => item instanceof Entry)
47-
const map_func = (item: Gio.FileInfo): Entry | null => try_catch(
48-
() => new Entry({ file: (item as Gio.FileInfo).get_attribute_object("standard::file") }),
49-
() => null,
50-
)
51-
this.home_map_model.set_map_func((item) => map_func(item as Gio.FileInfo) ?? item)
52-
this.root_map_model.set_map_func((item) => map_func(item as Gio.FileInfo) ?? item)
5340
this.entry_sorter.set_sort_func((one: Entry, two: Entry): number => {
5441
const one_first = -1
5542
const one_last = 1
5643

57-
if (one.enabled !== two.enabled) {
58-
return one.enabled ? one_first : one_last
59-
}
44+
if (one.enabled !== two.enabled) return one.enabled ? one_first : one_last
6045
if (one.override_state !== two.override_state) {
6146
if (one.override_state === "overridden") return one_last
6247
if (two.override_state === "overridden") return one_first
@@ -65,6 +50,9 @@ export class EntriesPage extends Adw.NavigationPage {
6550
})
6651
}
6752

53+
@GObjectify.Debounce(200, { trigger: "leading" })
54+
protected async _start_loading(): Promise<void> { this.is_loading = true }
55+
6856
@GObjectify.Debounce(200)
6957
protected async _mark_overrides(): Promise<void> {
7058
let something_changed = false
@@ -92,5 +80,6 @@ export class EntriesPage extends Adw.NavigationPage {
9280
}
9381

9482
if (something_changed) this.entry_sorter.changed(Gtk.SorterChange.DIFFERENT)
83+
this.is_loading = false
9584
}
9685
}

src/utils/entry_list_model.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import GObject from "gi://GObject?version=2.0"
2+
import Gio from "gi://Gio?version=2.0"
3+
4+
import { GObjectify } from "./gobjectify.js"
5+
import { Entry } from "./entry.js"
6+
import { chunked_idler } from "./async.js"
7+
8+
@GObjectify.Class({ implements: [Gio.ListModel], manual_gtype_name: "EntryListModel" })
9+
class EntryListInternal extends GObject.Object {
10+
@GObjectify.Property(Gio.File, { effect(file) { this.on_file_set(file) } })
11+
public accessor file!: Gio.File | null
12+
13+
private monitor: Gio.FileMonitor | null = null
14+
private change_connect_id: number | null = null
15+
private list = new Array<Entry>()
16+
17+
// Satisfy Gio.ListModel interface
18+
public vfunc_get_item(position: number): Entry | null {
19+
return this.list[position] ?? null
20+
}
21+
22+
public vfunc_get_item_type(): GObject.GType {
23+
return Entry.$gtype
24+
}
25+
26+
public vfunc_get_n_items(): number {
27+
return this.list.length
28+
}
29+
//
30+
31+
public [Symbol.iterator](): ArrayIterator<Entry> {
32+
return this.list[Symbol.iterator]()
33+
}
34+
35+
private cleanup(): void {
36+
if (this.change_connect_id !== null) {
37+
this.disconnect(this.change_connect_id)
38+
this.change_connect_id = null
39+
}
40+
this.monitor?.cancel()
41+
this.monitor = null
42+
}
43+
44+
private on_file_set(file: Gio.File | null): void {
45+
if (!file) {
46+
this.cleanup()
47+
return
48+
} else if (file.query_file_type(null, null) !== Gio.FileType.DIRECTORY) {
49+
this.cleanup()
50+
throw new Error("EntryListInternal, file: Cannot set a file that is not a directory")
51+
}
52+
this.monitor?.cancel()
53+
this.monitor = file.monitor_directory(Gio.FileMonitorFlags.NONE, null)
54+
this.change_connect_id = this.monitor.connect("changed", () => this.changed().catch(log))
55+
this.changed()
56+
}
57+
58+
@GObjectify.Debounce(200)
59+
private async changed(): Promise<void> {
60+
const old_length = this.list.length
61+
const dir = this.file
62+
if (!dir) {
63+
this.list = []
64+
if (old_length > 0) $(this).items_changed(0, old_length, 0)
65+
return
66+
}
67+
68+
const idler = chunked_idler(100)
69+
const enumerator = dir.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null)
70+
const new_list = new Array<Entry>()
71+
72+
try {
73+
let info: Gio.FileInfo | null
74+
while ((info = enumerator.next_file(null)) !== null) {
75+
await idler()
76+
try {
77+
new_list.push(new Entry({ file: dir.get_child(info.get_name()) }))
78+
} catch {}
79+
}
80+
} finally {
81+
enumerator.close(null)
82+
}
83+
84+
const new_length = new_list.length
85+
this.list = new_list
86+
$(this).items_changed(0, old_length, new_length)
87+
}
88+
}
89+
90+
function $(item: EntryListInternal): Gio.ListModel<Entry> {
91+
return item as unknown as Gio.ListModel<Entry>
92+
}
93+
94+
export const EntryListModel = EntryListInternal as unknown as new(
95+
...args: ConstructorParameters<typeof EntryListInternal>
96+
)=> EntryListInternal & Gio.ListModel<Entry>
97+
98+
export type EntryListModel = EntryListInternal & Gio.ListModel<Entry>

0 commit comments

Comments
 (0)