1 /*
2 * util.c
3 * Copyright 2010-2012 John Lindgren and Michał Lipski
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions, and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions, and the following disclaimer in the documentation
13 * provided with the distribution.
14 *
15 * This software is provided "as is" and without any warranty, express or
16 * implied. In no event shall the authors be liable for any damages arising from
17 * the use of this software.
18 */
19
20 #include <math.h>
21 #include <string.h>
22
23 #ifdef _WIN32
24 #include <windows.h>
25 #endif
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtk.h>
29
30 #define AUD_GLIB_INTEGRATION
31 #include <libaudcore/audstrings.h>
32 #include <libaudcore/hook.h>
33 #include <libaudcore/i18n.h>
34 #include <libaudcore/runtime.h>
35
36 #include "internal.h"
37 #include "libaudgui.h"
38 #include "libaudgui-gtk.h"
39
40 #define PORTABLE_DPI 96
41
audgui_get_dpi()42 EXPORT int audgui_get_dpi ()
43 {
44 static int dpi = 0;
45
46 if (! dpi)
47 {
48 #ifdef _WIN32
49 HDC screen = GetDC (nullptr);
50 dpi = (GetDeviceCaps (screen, LOGPIXELSX) + GetDeviceCaps (screen, LOGPIXELSY)) / 2;
51 ReleaseDC (nullptr, screen);
52 #else
53 GdkScreen * screen = gdk_screen_get_default ();
54
55 /* force GTK settings to be loaded for the GDK screen */
56 (void) gtk_settings_get_for_screen (screen);
57
58 dpi = round (gdk_screen_get_resolution (screen));
59 #endif
60
61 dpi = aud::max (PORTABLE_DPI, dpi);
62 }
63
64 return dpi;
65 }
66
audgui_to_native_dpi(int size)67 EXPORT int audgui_to_native_dpi (int size)
68 {
69 return aud::rescale (size, PORTABLE_DPI, audgui_get_dpi ());
70 }
71
audgui_to_portable_dpi(int size)72 EXPORT int audgui_to_portable_dpi (int size)
73 {
74 return aud::rescale (size, audgui_get_dpi (), PORTABLE_DPI);
75 }
76
audgui_get_digit_width(GtkWidget * widget)77 EXPORT int audgui_get_digit_width (GtkWidget * widget)
78 {
79 int width;
80 PangoLayout * layout = gtk_widget_create_pango_layout (widget, "0123456789");
81 PangoFontDescription * desc = pango_font_description_new ();
82 pango_font_description_set_weight (desc, PANGO_WEIGHT_BOLD);
83 pango_layout_set_font_description (layout, desc);
84 pango_layout_get_pixel_size (layout, & width, nullptr);
85 pango_font_description_free (desc);
86 g_object_unref (layout);
87 return (width + 9) / 10;
88 }
89
audgui_get_mouse_coords(GtkWidget * widget,int * x,int * y)90 EXPORT void audgui_get_mouse_coords (GtkWidget * widget, int * x, int * y)
91 {
92 gtk_widget_get_pointer (widget, x, y);
93 }
94
audgui_get_mouse_coords(GdkScreen * screen,int * x,int * y)95 EXPORT void audgui_get_mouse_coords (GdkScreen * screen, int * x, int * y)
96 {
97 gdk_display_get_pointer (gdk_screen_get_display (screen), nullptr, x, y, nullptr);
98 }
99
audgui_get_monitor_geometry(GdkScreen * screen,int x,int y,GdkRectangle * geom)100 EXPORT void audgui_get_monitor_geometry (GdkScreen * screen, int x, int y, GdkRectangle * geom)
101 {
102 int monitors = gdk_screen_get_n_monitors (screen);
103
104 for (int i = 0; i < monitors; i ++)
105 {
106 gdk_screen_get_monitor_geometry (screen, i, geom);
107 if (x >= geom->x && x < geom->x + geom->width && y >= geom->y && y < geom->y + geom->height)
108 return;
109 }
110
111 /* fall back to entire screen */
112 geom->x = 0;
113 geom->y = 0;
114 geom->width = gdk_screen_get_width (screen);
115 geom->height = gdk_screen_get_height (screen);
116 }
117
escape_destroy_cb(GtkWidget * widget,GdkEventKey * event)118 static gboolean escape_destroy_cb (GtkWidget * widget, GdkEventKey * event)
119 {
120 if (event->keyval == GDK_KEY_Escape)
121 {
122 gtk_widget_destroy (widget);
123 return true;
124 }
125
126 return false;
127 }
128
audgui_destroy_on_escape(GtkWidget * widget)129 EXPORT void audgui_destroy_on_escape (GtkWidget * widget)
130 {
131 g_signal_connect (widget, "key-press-event", (GCallback) escape_destroy_cb, nullptr);
132 }
133
audgui_button_new(const char * text,const char * icon,AudguiCallback callback,void * data)134 EXPORT GtkWidget * audgui_button_new (const char * text, const char * icon,
135 AudguiCallback callback, void * data)
136 {
137 GtkWidget * button = gtk_button_new_with_mnemonic (text);
138
139 if (icon)
140 {
141 GtkWidget * image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
142 gtk_button_set_image ((GtkButton *) button, image);
143 }
144
145 if (callback)
146 g_signal_connect_swapped (button, "clicked", (GCallback) callback, data);
147
148 return button;
149 }
150
151 struct FileEntryData {
152 GtkFileChooserAction action;
153 String title;
154 };
155
entry_response_cb(GtkWidget * dialog,int response,GtkWidget * entry)156 static void entry_response_cb (GtkWidget * dialog, int response, GtkWidget * entry)
157 {
158 if (response == GTK_RESPONSE_ACCEPT)
159 {
160 CharPtr uri (gtk_file_chooser_get_uri ((GtkFileChooser *) dialog));
161 if (uri)
162 audgui_file_entry_set_uri (entry, uri);
163 }
164
165 gtk_widget_destroy (dialog);
166 }
167
entry_browse_cb(GtkWidget * entry,GtkEntryIconPosition pos,GdkEvent * event,const FileEntryData * data)168 static void entry_browse_cb (GtkWidget * entry, GtkEntryIconPosition pos,
169 GdkEvent * event, const FileEntryData * data)
170 {
171 GtkWidget * dialog = gtk_file_chooser_dialog_new (data->title, nullptr,
172 data->action, _("Open"), GTK_RESPONSE_ACCEPT, _("Cancel"),
173 GTK_RESPONSE_REJECT, nullptr);
174
175 gtk_file_chooser_set_local_only ((GtkFileChooser *) dialog, false);
176
177 String uri = audgui_file_entry_get_uri (entry);
178 if (uri)
179 gtk_file_chooser_set_uri ((GtkFileChooser *) dialog, uri);
180
181 g_signal_connect (dialog, "response", (GCallback) entry_response_cb, entry);
182 g_signal_connect_object (entry, "destroy", (GCallback) gtk_widget_destroy,
183 dialog, G_CONNECT_SWAPPED);
184
185 gtk_widget_show (dialog);
186 }
187
audgui_file_entry_new(GtkFileChooserAction action,const char * title)188 EXPORT GtkWidget * audgui_file_entry_new (GtkFileChooserAction action, const char * title)
189 {
190 GtkWidget * entry = gtk_entry_new ();
191
192 auto data = new FileEntryData {action, String (title)};
193 g_object_set_data_full ((GObject *) entry, "file-entry-data", data,
194 aud::delete_obj<FileEntryData>);
195
196 gtk_entry_set_icon_from_icon_name ((GtkEntry *) entry,
197 GTK_ENTRY_ICON_SECONDARY, "document-open");
198 g_signal_connect (entry, "icon-press", (GCallback) entry_browse_cb, data);
199
200 return entry;
201 }
202
audgui_file_entry_get_uri(GtkWidget * entry)203 EXPORT String audgui_file_entry_get_uri (GtkWidget * entry)
204 {
205 const char * text = gtk_entry_get_text ((GtkEntry *) entry);
206
207 if (! text[0])
208 return String ();
209 else if (strstr (text, "://"))
210 return String (text);
211 else
212 return String (filename_to_uri (filename_normalize (filename_expand (str_copy (text)))));
213 }
214
audgui_file_entry_set_uri(GtkWidget * entry,const char * uri)215 EXPORT void audgui_file_entry_set_uri (GtkWidget * entry, const char * uri)
216 {
217 if (! uri || ! uri[0])
218 {
219 gtk_entry_set_text ((GtkEntry *) entry, "");
220 return;
221 }
222
223 StringBuf path = uri_to_filename (uri, false);
224 gtk_entry_set_text ((GtkEntry *) entry, path ? filename_contract (std::move (path)) : uri);
225 gtk_editable_set_position ((GtkEditable *) entry, -1);
226 }
227
set_label_wrap(GtkWidget * label,void *)228 static void set_label_wrap (GtkWidget * label, void *)
229 {
230 if (GTK_IS_LABEL (label))
231 gtk_label_set_line_wrap_mode ((GtkLabel *) label, PANGO_WRAP_WORD_CHAR);
232 }
233
audgui_dialog_new(GtkMessageType type,const char * title,const char * text,GtkWidget * button1,GtkWidget * button2)234 EXPORT GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title,
235 const char * text, GtkWidget * button1, GtkWidget * button2)
236 {
237 GtkWidget * dialog = gtk_message_dialog_new (nullptr, (GtkDialogFlags) 0, type,
238 GTK_BUTTONS_NONE, "%s", text);
239 gtk_window_set_title ((GtkWindow *) dialog, title);
240
241 GtkWidget * box = gtk_message_dialog_get_message_area ((GtkMessageDialog *) dialog);
242 gtk_container_foreach ((GtkContainer *) box, set_label_wrap, nullptr);
243
244 if (button2)
245 {
246 gtk_dialog_add_action_widget ((GtkDialog *) dialog, button2, GTK_RESPONSE_NONE);
247 g_signal_connect_swapped (button2, "clicked", (GCallback) gtk_widget_destroy, dialog);
248 }
249
250 gtk_dialog_add_action_widget ((GtkDialog *) dialog, button1, GTK_RESPONSE_NONE);
251 g_signal_connect_swapped (button1, "clicked", (GCallback) gtk_widget_destroy, dialog);
252
253 gtk_widget_set_can_default (button1, true);
254 gtk_widget_grab_default (button1);
255
256 return dialog;
257 }
258
audgui_dialog_add_widget(GtkWidget * dialog,GtkWidget * widget)259 EXPORT void audgui_dialog_add_widget (GtkWidget * dialog, GtkWidget * widget)
260 {
261 GtkWidget * box = gtk_message_dialog_get_message_area ((GtkMessageDialog *) dialog);
262 gtk_box_pack_start ((GtkBox *) box, widget, false, false, 0);
263 }
264
audgui_simple_message(GtkWidget ** widget,GtkMessageType type,const char * title,const char * text)265 EXPORT void audgui_simple_message (GtkWidget * * widget, GtkMessageType type,
266 const char * title, const char * text)
267 {
268 if (type == GTK_MESSAGE_ERROR)
269 AUDERR ("%s\n", text);
270 else if (type == GTK_MESSAGE_WARNING)
271 AUDWARN ("%s\n", text);
272 else if (type == GTK_MESSAGE_INFO)
273 AUDINFO ("%s\n", text);
274
275 if (* widget)
276 {
277 char * old = nullptr;
278 g_object_get ((GObject *) * widget, "text", & old, nullptr);
279 g_return_if_fail (old);
280
281 int messages = GPOINTER_TO_INT (g_object_get_data ((GObject *) * widget, "messages"));
282 if (messages > 10)
283 text = _("\n(Further messages have been hidden.)");
284
285 if (! strstr (old, text))
286 {
287 StringBuf both = str_concat ({old, "\n", text});
288 g_object_set ((GObject *) * widget, "text", (const char *) both, nullptr);
289 g_object_set_data ((GObject *) * widget, "messages", GINT_TO_POINTER (messages + 1));
290 }
291
292 g_free (old);
293 gtk_window_present ((GtkWindow *) * widget);
294 }
295 else
296 {
297 GtkWidget * button = audgui_button_new (_("_Close"), "window-close", nullptr, nullptr);
298 * widget = audgui_dialog_new (type, title, text, button, nullptr);
299
300 g_object_set_data ((GObject *) * widget, "messages", GINT_TO_POINTER (1));
301 g_signal_connect (* widget, "destroy", (GCallback) gtk_widget_destroyed, widget);
302
303 gtk_widget_show_all (* widget);
304 }
305 }
306
audgui_dark_bg_gradient(const GdkColor & base,int height)307 EXPORT cairo_pattern_t * audgui_dark_bg_gradient (const GdkColor & base, int height)
308 {
309 float r = 1, g = 1, b = 1;
310
311 /* in a dark theme, try to match the tone of the base color */
312 int v = aud::max (aud::max (base.red, base.green), base.blue);
313
314 if (v >= 10*256 && v < 80*256)
315 {
316 r = (float) base.red / v;
317 g = (float) base.green / v;
318 b = (float) base.blue / v;
319 }
320
321 cairo_pattern_t * gradient = cairo_pattern_create_linear (0, 0, 0, height);
322 cairo_pattern_add_color_stop_rgb (gradient, 0, 0.16 * r, 0.16 * g, 0.16 * b);
323 cairo_pattern_add_color_stop_rgb (gradient, 0.45, 0.11 * r, 0.11 * g, 0.11 * b);
324 cairo_pattern_add_color_stop_rgb (gradient, 0.55, 0.06 * r, 0.06 * g, 0.06 * b);
325 cairo_pattern_add_color_stop_rgb (gradient, 1, 0.09 * r, 0.09 * g, 0.09 * b);
326 return gradient;
327 }
328
rgb_to_hsv(float r,float g,float b,float * h,float * s,float * v)329 static void rgb_to_hsv (float r, float g, float b, float * h, float * s, float * v)
330 {
331 float max = aud::max (aud::max (r, g), b);
332 float min = aud::min (aud::min (r, g), b);
333
334 * v = max;
335
336 if (max == min)
337 {
338 * h = 0;
339 * s = 0;
340 return;
341 }
342
343 if (r == max)
344 * h = 1 + (g - b) / (max - min);
345 else if (g == max)
346 * h = 3 + (b - r) / (max - min);
347 else
348 * h = 5 + (r - g) / (max - min);
349
350 * s = (max - min) / max;
351 }
352
hsv_to_rgb(float h,float s,float v,float * r,float * g,float * b)353 static void hsv_to_rgb (float h, float s, float v, float * r, float * g, float * b)
354 {
355 for (; h >= 2; h -= 2)
356 {
357 float * p = r;
358 r = g;
359 g = b;
360 b = p;
361 }
362
363 if (h < 1)
364 {
365 * r = 1;
366 * g = 0;
367 * b = 1 - h;
368 }
369 else
370 {
371 * r = 1;
372 * g = h - 1;
373 * b = 0;
374 }
375
376 * r = v * (1 - s * (1 - * r));
377 * g = v * (1 - s * (1 - * g));
378 * b = v * (1 - s * (1 - * b));
379 }
380
audgui_vis_bar_color(const GdkColor & hue,int bar,int n_bars,float & r,float & g,float & b)381 EXPORT void audgui_vis_bar_color (const GdkColor & hue, int bar, int n_bars,
382 float & r, float & g, float & b)
383 {
384 float h, s, v;
385 rgb_to_hsv (hue.red / 65535.0, hue.green / 65535.0, hue.blue / 65535.0, & h, & s, & v);
386
387 if (s < 0.1) /* monochrome theme? use blue instead */
388 h = 4.6;
389
390 s = 1 - 0.9 * bar / (n_bars - 1);
391 v = 0.75 + 0.25 * bar / (n_bars - 1);
392
393 hsv_to_rgb (h, s, v, & r, & g, & b);
394 }
395