1 | /* Lists/Words |
2 | * #Keywords: GtkListView, GtkFilterListModel |
3 | * |
4 | * This demo shows filtering a long list - of words. |
5 | * |
6 | * You should have the file `/usr/share/dict/words` installed for |
7 | * this demo to work. |
8 | */ |
9 | |
10 | #include <gtk/gtk.h> |
11 | |
12 | static GtkWidget *window = NULL; |
13 | static GtkWidget *progress; |
14 | |
15 | const char *factory_text = |
16 | "<?xml version='1.0' encoding='UTF-8'?>\n" |
17 | "<interface>\n" |
18 | " <template class='GtkListItem'>\n" |
19 | " <property name='child'>\n" |
20 | " <object class='GtkLabel'>\n" |
21 | " <property name='ellipsize'>end</property>\n" |
22 | " <property name='xalign'>0</property>\n" |
23 | " <binding name='label'>\n" |
24 | " <lookup name='string' type='GtkStringObject'>\n" |
25 | " <lookup name='item'>GtkListItem</lookup>\n" |
26 | " </lookup>\n" |
27 | " </binding>\n" |
28 | " </object>\n" |
29 | " </property>\n" |
30 | " </template>\n" |
31 | "</interface>\n" ; |
32 | |
33 | static void |
34 | update_title_cb (GtkFilterListModel *model) |
35 | { |
36 | guint total; |
37 | char *title; |
38 | guint pending; |
39 | |
40 | total = g_list_model_get_n_items (list: gtk_filter_list_model_get_model (self: model)); |
41 | pending = gtk_filter_list_model_get_pending (self: model); |
42 | |
43 | title = g_strdup_printf (format: "%u lines" , g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model))); |
44 | |
45 | gtk_widget_set_visible (widget: progress, visible: pending != 0); |
46 | gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), fraction: (total - pending) / (double) total); |
47 | gtk_window_set_title (GTK_WINDOW (window), title); |
48 | g_free (mem: title); |
49 | } |
50 | |
51 | static void |
52 | read_lines_cb (GObject *object, |
53 | GAsyncResult *result, |
54 | gpointer data) |
55 | { |
56 | GBufferedInputStream *stream = G_BUFFERED_INPUT_STREAM (object); |
57 | GtkStringList *stringlist = data; |
58 | GError *error = NULL; |
59 | gsize size; |
60 | GPtrArray *lines; |
61 | gssize n_filled; |
62 | const char *buffer, *newline; |
63 | |
64 | n_filled = g_buffered_input_stream_fill_finish (stream, result, error: &error); |
65 | if (n_filled < 0) |
66 | { |
67 | g_print (format: "Could not read data: %s\n" , error->message); |
68 | g_clear_error (err: &error); |
69 | g_object_unref (object: stringlist); |
70 | return; |
71 | } |
72 | |
73 | buffer = g_buffered_input_stream_peek_buffer (stream, count: &size); |
74 | |
75 | if (n_filled == 0) |
76 | { |
77 | if (size) |
78 | gtk_string_list_take (self: stringlist, string: g_utf8_make_valid (str: buffer, len: size)); |
79 | g_object_unref (object: stringlist); |
80 | return; |
81 | } |
82 | |
83 | lines = NULL; |
84 | while ((newline = memchr (s: buffer, c: '\n', n: size))) |
85 | { |
86 | if (newline > buffer) |
87 | { |
88 | if (lines == NULL) |
89 | lines = g_ptr_array_new_with_free_func (element_free_func: g_free); |
90 | g_ptr_array_add (array: lines, data: g_utf8_make_valid (str: buffer, len: newline - buffer)); |
91 | } |
92 | if (g_input_stream_skip (G_INPUT_STREAM (stream), count: newline - buffer + 1, NULL, error: &error) < 0) |
93 | { |
94 | g_clear_error (err: &error); |
95 | break; |
96 | } |
97 | buffer = g_buffered_input_stream_peek_buffer (stream, count: &size); |
98 | } |
99 | if (lines == NULL) |
100 | { |
101 | g_buffered_input_stream_set_buffer_size (stream, size: g_buffered_input_stream_get_buffer_size (stream) + 4096); |
102 | } |
103 | else |
104 | { |
105 | g_ptr_array_add (array: lines, NULL); |
106 | gtk_string_list_splice (self: stringlist, position: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: stringlist)), n_removals: 0, additions: (const char **) lines->pdata); |
107 | g_ptr_array_free (array: lines, TRUE); |
108 | } |
109 | |
110 | g_buffered_input_stream_fill_async (stream, count: -1, G_PRIORITY_HIGH_IDLE, NULL, callback: read_lines_cb, user_data: data); |
111 | } |
112 | |
113 | static void |
114 | file_is_open_cb (GObject *file, |
115 | GAsyncResult *result, |
116 | gpointer data) |
117 | { |
118 | GError *error = NULL; |
119 | GFileInputStream *file_stream; |
120 | GBufferedInputStream *stream; |
121 | |
122 | file_stream = g_file_read_finish (G_FILE (file), res: result, error: &error); |
123 | if (file_stream == NULL) |
124 | { |
125 | g_print (format: "Could not open file: %s\n" , error->message); |
126 | g_error_free (error); |
127 | g_object_unref (object: data); |
128 | return; |
129 | } |
130 | |
131 | stream = G_BUFFERED_INPUT_STREAM (g_buffered_input_stream_new (G_INPUT_STREAM (file_stream))); |
132 | g_buffered_input_stream_fill_async (stream, count: -1, G_PRIORITY_HIGH_IDLE, NULL, callback: read_lines_cb, user_data: data); |
133 | g_object_unref (object: stream); |
134 | } |
135 | |
136 | static void |
137 | load_file (GtkStringList *list, |
138 | GFile *file) |
139 | { |
140 | gtk_string_list_splice (self: list, position: 0, n_removals: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: list)), NULL); |
141 | g_file_read_async (file, G_PRIORITY_HIGH_IDLE, NULL, callback: file_is_open_cb, g_object_ref (list)); |
142 | } |
143 | |
144 | static void |
145 | open_response_cb (GtkNativeDialog *dialog, |
146 | int response, |
147 | GtkStringList *stringlist) |
148 | { |
149 | gtk_native_dialog_hide (self: dialog); |
150 | |
151 | if (response == GTK_RESPONSE_ACCEPT) |
152 | { |
153 | GFile *file; |
154 | |
155 | file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); |
156 | load_file (list: stringlist, file); |
157 | g_object_unref (object: file); |
158 | } |
159 | |
160 | gtk_native_dialog_destroy (self: dialog); |
161 | } |
162 | |
163 | static void |
164 | file_open_cb (GtkWidget *button, |
165 | GtkStringList *stringlist) |
166 | { |
167 | GtkFileChooserNative *dialog; |
168 | |
169 | dialog = gtk_file_chooser_native_new (title: "Open file" , |
170 | GTK_WINDOW (gtk_widget_get_root (button)), |
171 | action: GTK_FILE_CHOOSER_ACTION_OPEN, |
172 | accept_label: "_Load" , |
173 | cancel_label: "_Cancel" ); |
174 | gtk_native_dialog_set_modal (self: GTK_NATIVE_DIALOG (ptr: dialog), TRUE); |
175 | |
176 | g_signal_connect (dialog, "response" , G_CALLBACK (open_response_cb), stringlist); |
177 | gtk_native_dialog_show (self: GTK_NATIVE_DIALOG (ptr: dialog)); |
178 | } |
179 | |
180 | GtkWidget * |
181 | do_listview_words (GtkWidget *do_widget) |
182 | { |
183 | if (window == NULL) |
184 | { |
185 | GtkWidget *, *listview, *sw, *vbox, *search_entry, *open_button, *overlay; |
186 | GtkFilterListModel *filter_model; |
187 | GtkStringList *stringlist; |
188 | GtkFilter *filter; |
189 | GFile *file; |
190 | |
191 | file = g_file_new_for_path (path: "/usr/share/dict/words" ); |
192 | if (g_file_query_exists (file, NULL)) |
193 | { |
194 | stringlist = gtk_string_list_new (NULL); |
195 | load_file (list: stringlist, file); |
196 | } |
197 | else |
198 | { |
199 | char **words; |
200 | words = g_strsplit (string: "lorem ipsum dolor sit amet consectetur adipisci elit sed eiusmod tempor incidunt labore et dolore magna aliqua ut enim ad minim veniam quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat" , delimiter: " " , max_tokens: -1); |
201 | stringlist = gtk_string_list_new (strings: (const char **) words); |
202 | g_strfreev (str_array: words); |
203 | } |
204 | g_object_unref (object: file); |
205 | |
206 | filter = GTK_FILTER (ptr: gtk_string_filter_new (expression: gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, property_name: "string" ))); |
207 | filter_model = gtk_filter_list_model_new (model: G_LIST_MODEL (ptr: stringlist), filter); |
208 | gtk_filter_list_model_set_incremental (self: filter_model, TRUE); |
209 | |
210 | window = gtk_window_new (); |
211 | gtk_window_set_default_size (GTK_WINDOW (window), width: 400, height: 600); |
212 | |
213 | header = gtk_header_bar_new (); |
214 | gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), TRUE); |
215 | open_button = gtk_button_new_with_mnemonic (label: "_Open" ); |
216 | g_signal_connect (open_button, "clicked" , G_CALLBACK (file_open_cb), stringlist); |
217 | gtk_header_bar_pack_start (GTK_HEADER_BAR (header), child: open_button); |
218 | gtk_window_set_titlebar (GTK_WINDOW (window), titlebar: header); |
219 | |
220 | gtk_window_set_display (GTK_WINDOW (window), |
221 | display: gtk_widget_get_display (widget: do_widget)); |
222 | g_object_add_weak_pointer (G_OBJECT (window), weak_pointer_location: (gpointer*)&window); |
223 | |
224 | vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0); |
225 | gtk_window_set_child (GTK_WINDOW (window), child: vbox); |
226 | |
227 | search_entry = gtk_search_entry_new (); |
228 | g_object_bind_property (source: search_entry, source_property: "text" , target: filter, target_property: "search" , flags: 0); |
229 | gtk_box_append (GTK_BOX (vbox), child: search_entry); |
230 | |
231 | overlay = gtk_overlay_new (); |
232 | gtk_box_append (GTK_BOX (vbox), child: overlay); |
233 | |
234 | progress = gtk_progress_bar_new (); |
235 | gtk_widget_set_halign (widget: progress, align: GTK_ALIGN_FILL); |
236 | gtk_widget_set_valign (widget: progress, align: GTK_ALIGN_START); |
237 | gtk_widget_set_hexpand (widget: progress, TRUE); |
238 | gtk_overlay_add_overlay (GTK_OVERLAY (overlay), widget: progress); |
239 | |
240 | sw = gtk_scrolled_window_new (); |
241 | gtk_widget_set_vexpand (widget: sw, TRUE); |
242 | gtk_overlay_set_child (GTK_OVERLAY (overlay), child: sw); |
243 | |
244 | listview = gtk_list_view_new ( |
245 | model: GTK_SELECTION_MODEL (ptr: gtk_no_selection_new (model: G_LIST_MODEL (ptr: filter_model))), |
246 | factory: gtk_builder_list_item_factory_new_from_bytes (NULL, |
247 | bytes: g_bytes_new_static (data: factory_text, size: strlen (s: factory_text)))); |
248 | gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: listview); |
249 | |
250 | g_signal_connect (filter_model, "items-changed" , G_CALLBACK (update_title_cb), progress); |
251 | g_signal_connect (filter_model, "notify::pending" , G_CALLBACK (update_title_cb), progress); |
252 | update_title_cb (model: filter_model); |
253 | |
254 | } |
255 | |
256 | if (!gtk_widget_get_visible (widget: window)) |
257 | gtk_widget_show (widget: window); |
258 | else |
259 | gtk_window_destroy (GTK_WINDOW (window)); |
260 | |
261 | return window; |
262 | } |
263 | |