Skip to content

Commit c384de1

Browse files
Introduce own AppChooserButton instead of deprecated Gtk.AppChooserButton (#269)
* Introduce own AppChooserButton instead of deprecated Gtk.AppChooserButton * Fix wrong cast from DropDown to AppInfo * Setup initial selection * Only list recommended apps * Consistent variable name with the existing code * Correct line order, leave comment * Update variable name for consistency * Leave comments, increase reference count * Add spacing between icon and label --------- Co-authored-by: Danielle Foré <[email protected]>
1 parent 2b24bf3 commit c384de1

File tree

5 files changed

+384
-244
lines changed

5 files changed

+384
-244
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ You'll need the following dependencies:
1010
* libadwaita-1-dev
1111
* libswitchboard-3-dev
1212
* libflatpak-dev
13-
* libgranite-7-dev >= 7.4.0
13+
* libgranite-7-dev >= 7.6.0
1414
* libgtk-4-dev
1515
* meson >= 0.58.0
1616
* valac

src/Defaults/AppChooserButton.vala

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2011-2025 elementary, Inc. (https://elementary.io)
4+
*/
5+
6+
public class Defaults.AppChooserButton : Granite.Bin {
7+
public string content_type { get; construct; }
8+
9+
public AppChooserButton (string content_type) {
10+
Object (
11+
content_type: content_type
12+
);
13+
}
14+
15+
construct {
16+
var factory = new Gtk.SignalListItemFactory ();
17+
factory.setup.connect (factory_setup);
18+
factory.bind.connect (factory_bind);
19+
20+
var apps_store = new ListStore (typeof (AppInfo));
21+
22+
// Ignore result of load so that we always continue to setup UI
23+
// with blank app_store
24+
load_apps (apps_store, content_type);
25+
26+
// Expression to retrieve a reference of app_store
27+
var app_expr = new Gtk.ConstantExpression (typeof (ListStore), apps_store);
28+
29+
// Expression to retrieve a name of each AppInfo element in app_store
30+
var app_name_expr = new Gtk.CClosureExpression (
31+
typeof (string), null, { app_expr },
32+
(Callback) get_app_name,
33+
null, null
34+
);
35+
36+
var dropdown = new Gtk.DropDown (apps_store, app_name_expr) {
37+
factory = factory
38+
};
39+
40+
dropdown.selected = find_default_app_pos (apps_store, content_type);
41+
42+
dropdown.notify["selected-item"].connect (() => run_in_thread (() => {
43+
var app = (AppInfo) dropdown.selected_item;
44+
change_default (app, content_type);
45+
return null;
46+
}));
47+
48+
apps_store.bind_property ("n-items", dropdown, "sensitive", DEFAULT | SYNC_CREATE,
49+
(binding, _n_items, ref _sensitive) => {
50+
_sensitive = ((uint) _n_items > 0);
51+
return true;
52+
}
53+
);
54+
55+
child = dropdown;
56+
}
57+
58+
private void load_apps (ListStore store, string content_type) {
59+
store.remove_all ();
60+
61+
var apps = AppInfo.get_recommended_for_type (content_type);
62+
if (apps == null) {
63+
warning ("AppInfo.get_all_for_type() error. content_type=%s", content_type);
64+
return;
65+
}
66+
67+
apps.foreach ((item) => {
68+
store.append (item);
69+
});
70+
}
71+
72+
private uint find_default_app_pos (ListStore store, string content_type) {
73+
var default_app = AppInfo.get_default_for_type (content_type, false);
74+
if (default_app == null) {
75+
warning ("AppInfo.get_default_for_type() error. content_type=%s", content_type);
76+
return Gtk.INVALID_LIST_POSITION;
77+
}
78+
79+
uint pos;
80+
bool found = store.find_with_equal_func (default_app,
81+
((a, b) => {
82+
return ((AppInfo) a).get_id () == ((AppInfo) b).get_id ();
83+
}),
84+
out pos
85+
);
86+
if (!found) {
87+
// Wouldn't happen, probably store is not initialized
88+
warning ("BUG: default app not found in all apps store! default_app=%s", default_app.get_id ());
89+
return Gtk.INVALID_LIST_POSITION;
90+
}
91+
92+
return pos;
93+
}
94+
95+
private void factory_setup (Object object) {
96+
var item = object as Gtk.ListItem;
97+
98+
var row = new AppChooserButtonRow ();
99+
item.child = row;
100+
}
101+
102+
private void factory_bind (Object object) {
103+
var item = object as Gtk.ListItem;
104+
var app = item.item as AppInfo;
105+
var row = item.child as AppChooserButtonRow;
106+
107+
row.app_icon = app.get_icon ();
108+
row.app_name = app.get_name ();
109+
}
110+
111+
private string get_app_name (AppInfo app) {
112+
return app.get_name ();
113+
}
114+
115+
private void run_in_thread (owned ThreadFunc<void*> func) {
116+
try {
117+
new Thread<void*>.try (null, (owned) func);
118+
} catch (Error e) {
119+
warning ("Could not create a new thread: %s", e.message);
120+
}
121+
}
122+
123+
private void change_default (AppInfo app, string content_type) {
124+
var types = get_types_for_app (content_type);
125+
var supported_types = app.get_supported_types ();
126+
127+
foreach (unowned var type in types) {
128+
AppInfo.reset_type_associations (type);
129+
if (type in supported_types) {
130+
try {
131+
app.set_as_default_for_type (type);
132+
debug ("%s now default for content type %s", app.get_name (), type);
133+
} catch (Error e) {
134+
critical ("Error setting default app: %s", e.message);
135+
}
136+
} else {
137+
critical ("%s does not support content type %s", app.get_name (), type);
138+
}
139+
}
140+
}
141+
142+
private string[] get_types_for_app (string app) {
143+
switch (app) {
144+
case "x-scheme-handler/mailto":
145+
case "text/calendar":
146+
case "x-scheme-handler/geo":
147+
case "application/pdf":
148+
return { app };
149+
150+
case "x-scheme-handler/https":
151+
return {
152+
"x-scheme-handler/http",
153+
"x-scheme-handler/https",
154+
"text/html",
155+
"application/xhtml+xml",
156+
};
157+
158+
case "video/x-ogm+ogg":
159+
return {
160+
"application/x-quicktimeplayer",
161+
"application/vnd.rn-realmedia",
162+
"application/asx",
163+
"application/x-mplayer2",
164+
"application/x-ms-wmv",
165+
"video/quicktime",
166+
"video/x-quicktime",
167+
"video/vnd.rn-realvideo",
168+
"video/x-ms-asf-plugin",
169+
"video/x-msvideo",
170+
"video/msvideo",
171+
"video/x-ms-asf",
172+
"video/x-ms-wm",
173+
"video/x-ms-wmv",
174+
"video/x-ms-wmp",
175+
"video/x-ms-wvx",
176+
"video/mpeg",
177+
"video/x-mpeg",
178+
"video/x-mpeg2",
179+
"video/mp4",
180+
"video/3gpp",
181+
"video/fli",
182+
"video/x-fli",
183+
"video/x-flv",
184+
"video/vnd.vivo",
185+
"video/x-matroska",
186+
"video/matroska",
187+
"video/x-mng",
188+
"video/webm",
189+
"video/x-webm",
190+
"video/mp2t",
191+
"video/vnd.mpegurl",
192+
"video/x-ogm+ogg"
193+
};
194+
195+
case "audio/x-vorbis+ogg":
196+
return {
197+
"audio/ogg",
198+
"audio/mpeg",
199+
"audio/mp4",
200+
"audio/flac",
201+
"application/x-musepack",
202+
"application/musepack",
203+
"application/x-ape",
204+
"application/x-id3",
205+
"application/ogg",
206+
"application/x-ogg",
207+
"application/x-vorbis+ogg",
208+
"application/x-flac",
209+
"application/vnd.rn-realaudio",
210+
"application/x-nsv-vp3-mp3",
211+
"audio/x-musepack",
212+
"audio/musepack",
213+
"audio/ape",
214+
"audio/x-ape",
215+
"audio/x-mp3",
216+
"audio/mpeg",
217+
"audio/x-mpeg",
218+
"audio/x-mpeg-3",
219+
"audio/mpeg3",
220+
"audio/mp3",
221+
"audio/mp4",
222+
"audio/x-m4a",
223+
"audio/mpc",
224+
"audio/x-mpc",
225+
"audio/mp",
226+
"audio/x-mp",
227+
"audio/x-vorbis+ogg",
228+
"audio/vorbis",
229+
"audio/x-vorbis",
230+
"audio/ogg",
231+
"audio/x-ogg",
232+
"audio/x-flac",
233+
"audio/flac",
234+
"audio/x-s3m",
235+
"audio/x-mod",
236+
"audio/x-xm",
237+
"audio/x-it",
238+
"audio/x-pn-realaudio",
239+
"audio/x-realaudio",
240+
"audio/x-pn-realaudio-plugin",
241+
"audio/x-ms-wmv",
242+
"audio/x-ms-wax",
243+
"audio/x-ms-wma",
244+
"audio/wav",
245+
"audio/x-wav",
246+
"audio/mpeg2",
247+
"audio/x-mpeg2",
248+
"audio/x-mpeg3",
249+
"audio/x-mpegurl",
250+
"audio/basic",
251+
"audio/x-basic",
252+
"audio/midi",
253+
"audio/x-scpls",
254+
"audio/webm",
255+
"audio/x-webm",
256+
"x-content/audio-player"
257+
};
258+
259+
case "image/jpeg":
260+
return {
261+
"image/jpeg",
262+
"image/jpg",
263+
"image/pjpeg",
264+
"image/png",
265+
"image/tiff",
266+
"image/x-3fr",
267+
"image/x-adobe-dng",
268+
"image/x-arw",
269+
"image/x-bay",
270+
"image/x-bmp",
271+
"image/x-canon-cr2",
272+
"image/x-canon-crw",
273+
"image/x-cap",
274+
"image/x-cr2",
275+
"image/x-crw",
276+
"image/x-dcr",
277+
"image/x-dcraw",
278+
"image/x-dcs",
279+
"image/x-dng",
280+
"image/x-drf",
281+
"image/x-eip",
282+
"image/x-erf",
283+
"image/x-fff",
284+
"image/x-fuji-raf",
285+
"image/x-iiq",
286+
"image/x-k25",
287+
"image/x-kdc",
288+
"image/x-mef",
289+
"image/x-minolta-mrw",
290+
"image/x-mos",
291+
"image/x-mrw",
292+
"image/x-nef",
293+
"image/x-nikon-nef",
294+
"image/x-nrw",
295+
"image/x-olympus-orf",
296+
"image/x-orf",
297+
"image/x-panasonic-raw",
298+
"image/x-pef",
299+
"image/x-pentax-pef",
300+
"image/x-png",
301+
"image/x-ptx",
302+
"image/x-pxn",
303+
"image/x-r3d",
304+
"image/x-raf",
305+
"image/x-raw",
306+
"image/x-raw",
307+
"image/x-rw2",
308+
"image/x-rwl",
309+
"image/x-rwz",
310+
"image/x-sigma-x3f",
311+
"image/x-sony-arw",
312+
"image/x-sony-sr2",
313+
"image/x-sony-srf",
314+
"image/x-sr2",
315+
"image/x-srf",
316+
"image/x-x3f"
317+
};
318+
319+
case "text/plain":
320+
return {
321+
"application/xml",
322+
"application/x-perl",
323+
"text/mathml",
324+
"text/plain",
325+
"text/xml",
326+
"text/x-c++hdr",
327+
"text/x-c++src",
328+
"text/x-xsrc",
329+
"text/x-chdr",
330+
"text/x-csrc",
331+
"text/x-dtd",
332+
"text/x-java",
333+
"text/x-python",
334+
"text/x-sql"
335+
};
336+
337+
case "inode/directory":
338+
return {
339+
"inode/directory",
340+
"x-directory/normal",
341+
"x-directory/gnome-default-handler"
342+
};
343+
344+
default:
345+
return {};
346+
}
347+
}
348+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2011-2025 elementary, Inc. (https://elementary.io)
4+
*/
5+
6+
public class AppChooserButtonRow : Gtk.Box {
7+
public Icon app_icon { get; set; }
8+
public string app_name { get; set; }
9+
10+
construct {
11+
orientation = HORIZONTAL;
12+
spacing = 6;
13+
14+
var icon = new Gtk.Image () {
15+
halign = START
16+
};
17+
18+
var label = new Gtk.Label (null) {
19+
halign = START,
20+
hexpand = true,
21+
ellipsize = END
22+
};
23+
24+
bind_property ("app-icon", icon, "gicon");
25+
bind_property ("app-name", label, "label");
26+
27+
append (icon);
28+
append (label);
29+
}
30+
}

0 commit comments

Comments
 (0)