1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qgtk3dialoghelpers.h" |
5 | #include "qgtk3theme.h" |
6 | |
7 | #include <qeventloop.h> |
8 | #include <qwindow.h> |
9 | #include <qcolor.h> |
10 | #include <qdebug.h> |
11 | #include <qfont.h> |
12 | #include <qfileinfo.h> |
13 | |
14 | #include <private/qguiapplication_p.h> |
15 | #include <private/qgenericunixservices_p.h> |
16 | #include <qpa/qplatformintegration.h> |
17 | #include <qpa/qplatformfontdatabase.h> |
18 | |
19 | #undef signals |
20 | #include <gtk/gtk.h> |
21 | #include <gdk/gdk.h> |
22 | #include <pango/pango.h> |
23 | |
24 | #if QT_CONFIG(xlib) && defined(GDK_WINDOWING_X11) |
25 | #include <gdk/gdkx.h> |
26 | #endif |
27 | |
28 | #ifdef GDK_WINDOWING_WAYLAND |
29 | #include <gdk/gdkwayland.h> |
30 | #endif |
31 | |
32 | // The size of the preview we display for selected image files. We set height |
33 | // larger than width because generally there is more free space vertically |
34 | // than horizontally (setting the preview image will always expand the width of |
35 | // the dialog, but usually not the height). The image's aspect ratio will always |
36 | // be preserved. |
37 | #define PREVIEW_WIDTH 256 |
38 | #define PREVIEW_HEIGHT 512 |
39 | |
40 | QT_BEGIN_NAMESPACE |
41 | |
42 | using namespace Qt::StringLiterals; |
43 | |
44 | class QGtk3Dialog |
45 | { |
46 | public: |
47 | QGtk3Dialog(GtkWidget *gtkWidget, QPlatformDialogHelper *helper); |
48 | ~QGtk3Dialog(); |
49 | |
50 | GtkDialog *gtkDialog() const; |
51 | |
52 | void exec(); |
53 | bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); |
54 | void hide(); |
55 | |
56 | protected: |
57 | static void onResponse(QPlatformDialogHelper *helper, int response); |
58 | |
59 | private: |
60 | GtkWidget *gtkWidget; |
61 | QPlatformDialogHelper *helper; |
62 | Qt::WindowModality modality; |
63 | }; |
64 | |
65 | QGtk3Dialog::QGtk3Dialog(GtkWidget *gtkWidget, QPlatformDialogHelper *helper) |
66 | : gtkWidget(gtkWidget) |
67 | , helper(helper) |
68 | { |
69 | g_signal_connect_swapped(G_OBJECT(gtkWidget), "response" , G_CALLBACK(onResponse), helper); |
70 | g_signal_connect(G_OBJECT(gtkWidget), "delete-event" , G_CALLBACK(gtk_widget_hide_on_delete), NULL); |
71 | } |
72 | |
73 | QGtk3Dialog::~QGtk3Dialog() |
74 | { |
75 | gtk_clipboard_store(clipboard: gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); |
76 | gtk_widget_destroy(widget: gtkWidget); |
77 | } |
78 | |
79 | GtkDialog *QGtk3Dialog::gtkDialog() const |
80 | { |
81 | return GTK_DIALOG(gtkWidget); |
82 | } |
83 | |
84 | void QGtk3Dialog::exec() |
85 | { |
86 | if (modality == Qt::ApplicationModal) { |
87 | // block input to the whole app, including other GTK dialogs |
88 | gtk_dialog_run(dialog: gtkDialog()); |
89 | } else { |
90 | // block input to the window, allow input to other GTK dialogs |
91 | QEventLoop loop; |
92 | loop.connect(asender: helper, SIGNAL(accept()), SLOT(quit())); |
93 | loop.connect(asender: helper, SIGNAL(reject()), SLOT(quit())); |
94 | loop.exec(); |
95 | } |
96 | } |
97 | |
98 | bool QGtk3Dialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) |
99 | { |
100 | Q_UNUSED(flags); |
101 | this->modality = modality; |
102 | |
103 | gtk_widget_realize(widget: gtkWidget); // creates X window |
104 | |
105 | GdkWindow *gdkWindow = gtk_widget_get_window(widget: gtkWidget); |
106 | if (parent) { |
107 | if (false) { |
108 | #if defined(GDK_WINDOWING_WAYLAND) && GTK_CHECK_VERSION(3, 22, 0) |
109 | } else if (GDK_IS_WAYLAND_WINDOW(gdkWindow)) { |
110 | const auto unixServices = dynamic_cast<QGenericUnixServices *>( |
111 | QGuiApplicationPrivate::platformIntegration()->services()); |
112 | if (unixServices) { |
113 | const auto handle = unixServices->portalWindowIdentifier(window: parent); |
114 | if (handle.startsWith(s: "wayland:"_L1 )) { |
115 | auto handleBa = handle.sliced(pos: 8).toUtf8(); |
116 | gdk_wayland_window_set_transient_for_exported(window: gdkWindow, parent_handle_str: handleBa.data()); |
117 | } |
118 | } |
119 | #endif |
120 | #if QT_CONFIG(xlib) && defined(GDK_WINDOWING_X11) |
121 | } else if (GDK_IS_X11_WINDOW(gdkWindow)) { |
122 | GdkDisplay *gdkDisplay = gdk_window_get_display(window: gdkWindow); |
123 | XSetTransientForHint(gdk_x11_display_get_xdisplay(display: gdkDisplay), |
124 | gdk_x11_window_get_xid(window: gdkWindow), |
125 | parent->winId()); |
126 | #endif |
127 | } |
128 | } |
129 | |
130 | if (modality != Qt::NonModal) { |
131 | gdk_window_set_modal_hint(window: gdkWindow, modal: true); |
132 | } |
133 | |
134 | gtk_widget_show(widget: gtkWidget); |
135 | gdk_window_focus(window: gdkWindow, GDK_CURRENT_TIME); |
136 | return true; |
137 | } |
138 | |
139 | void QGtk3Dialog::hide() |
140 | { |
141 | gtk_widget_hide(widget: gtkWidget); |
142 | } |
143 | |
144 | void QGtk3Dialog::onResponse(QPlatformDialogHelper *helper, int response) |
145 | { |
146 | if (response == GTK_RESPONSE_OK) |
147 | emit helper->accept(); |
148 | else |
149 | emit helper->reject(); |
150 | } |
151 | |
152 | QGtk3ColorDialogHelper::QGtk3ColorDialogHelper() |
153 | { |
154 | d.reset(other: new QGtk3Dialog(gtk_color_chooser_dialog_new(title: "" , parent: nullptr), this)); |
155 | g_signal_connect_swapped(d->gtkDialog(), "notify::rgba" , G_CALLBACK(onColorChanged), this); |
156 | } |
157 | |
158 | QGtk3ColorDialogHelper::~QGtk3ColorDialogHelper() |
159 | { |
160 | } |
161 | |
162 | bool QGtk3ColorDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) |
163 | { |
164 | applyOptions(); |
165 | return d->show(flags, modality, parent); |
166 | } |
167 | |
168 | void QGtk3ColorDialogHelper::exec() |
169 | { |
170 | d->exec(); |
171 | } |
172 | |
173 | void QGtk3ColorDialogHelper::hide() |
174 | { |
175 | d->hide(); |
176 | } |
177 | |
178 | void QGtk3ColorDialogHelper::setCurrentColor(const QColor &color) |
179 | { |
180 | GtkDialog *gtkDialog = d->gtkDialog(); |
181 | if (color.alpha() < 255) |
182 | gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(gtkDialog), use_alpha: true); |
183 | GdkRGBA gdkColor; |
184 | gdkColor.red = color.redF(); |
185 | gdkColor.green = color.greenF(); |
186 | gdkColor.blue = color.blueF(); |
187 | gdkColor.alpha = color.alphaF(); |
188 | gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(gtkDialog), color: &gdkColor); |
189 | } |
190 | |
191 | QColor QGtk3ColorDialogHelper::currentColor() const |
192 | { |
193 | GtkDialog *gtkDialog = d->gtkDialog(); |
194 | GdkRGBA gdkColor; |
195 | gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(gtkDialog), color: &gdkColor); |
196 | return QColor::fromRgbF(r: gdkColor.red, g: gdkColor.green, b: gdkColor.blue, a: gdkColor.alpha); |
197 | } |
198 | |
199 | void QGtk3ColorDialogHelper::onColorChanged(QGtk3ColorDialogHelper *dialog) |
200 | { |
201 | emit dialog->currentColorChanged(color: dialog->currentColor()); |
202 | } |
203 | |
204 | void QGtk3ColorDialogHelper::applyOptions() |
205 | { |
206 | GtkDialog *gtkDialog = d->gtkDialog(); |
207 | gtk_window_set_title(GTK_WINDOW(gtkDialog), qUtf8Printable(options()->windowTitle())); |
208 | |
209 | gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(gtkDialog), use_alpha: options()->testOption(option: QColorDialogOptions::ShowAlphaChannel)); |
210 | } |
211 | |
212 | QGtk3FileDialogHelper::QGtk3FileDialogHelper() |
213 | { |
214 | d.reset(other: new QGtk3Dialog(gtk_file_chooser_dialog_new(title: "" , parent: nullptr, |
215 | action: GTK_FILE_CHOOSER_ACTION_OPEN, |
216 | qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Cancel)), GTK_RESPONSE_CANCEL, |
217 | qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Ok)), GTK_RESPONSE_OK, |
218 | NULL), this)); |
219 | |
220 | g_signal_connect(GTK_FILE_CHOOSER(d->gtkDialog()), "selection-changed" , G_CALLBACK(onSelectionChanged), this); |
221 | g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "current-folder-changed" , G_CALLBACK(onCurrentFolderChanged), this); |
222 | g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "notify::filter" , G_CALLBACK(onFilterChanged), this); |
223 | |
224 | previewWidget = gtk_image_new(); |
225 | g_signal_connect(G_OBJECT(d->gtkDialog()), "update-preview" , G_CALLBACK(onUpdatePreview), this); |
226 | gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(d->gtkDialog()), preview_widget: previewWidget); |
227 | } |
228 | |
229 | QGtk3FileDialogHelper::~QGtk3FileDialogHelper() |
230 | { |
231 | } |
232 | |
233 | bool QGtk3FileDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) |
234 | { |
235 | _dir.clear(); |
236 | _selection.clear(); |
237 | |
238 | applyOptions(); |
239 | return d->show(flags, modality, parent); |
240 | } |
241 | |
242 | void QGtk3FileDialogHelper::exec() |
243 | { |
244 | d->exec(); |
245 | } |
246 | |
247 | void QGtk3FileDialogHelper::hide() |
248 | { |
249 | // After GtkFileChooserDialog has been hidden, gtk_file_chooser_get_current_folder() |
250 | // & gtk_file_chooser_get_filenames() will return bogus values -> cache the actual |
251 | // values before hiding the dialog |
252 | _dir = directory(); |
253 | _selection = selectedFiles(); |
254 | |
255 | d->hide(); |
256 | } |
257 | |
258 | bool QGtk3FileDialogHelper::defaultNameFilterDisables() const |
259 | { |
260 | return false; |
261 | } |
262 | |
263 | void QGtk3FileDialogHelper::setDirectory(const QUrl &directory) |
264 | { |
265 | GtkDialog *gtkDialog = d->gtkDialog(); |
266 | gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), qUtf8Printable(directory.toLocalFile())); |
267 | } |
268 | |
269 | QUrl QGtk3FileDialogHelper::directory() const |
270 | { |
271 | // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_current_folder() |
272 | // returns a bogus value -> return the cached value before hiding |
273 | if (!_dir.isEmpty()) |
274 | return _dir; |
275 | |
276 | QString ret; |
277 | GtkDialog *gtkDialog = d->gtkDialog(); |
278 | gchar *folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(gtkDialog)); |
279 | if (folder) { |
280 | ret = QString::fromUtf8(utf8: folder); |
281 | g_free(mem: folder); |
282 | } |
283 | return QUrl::fromLocalFile(localfile: ret); |
284 | } |
285 | |
286 | void QGtk3FileDialogHelper::selectFile(const QUrl &filename) |
287 | { |
288 | setFileChooserAction(); |
289 | selectFileInternal(filename); |
290 | } |
291 | |
292 | void QGtk3FileDialogHelper::selectFileInternal(const QUrl &filename) |
293 | { |
294 | GtkDialog *gtkDialog = d->gtkDialog(); |
295 | if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { |
296 | QFileInfo fi(filename.toLocalFile()); |
297 | gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(gtkDialog), qUtf8Printable(fi.path())); |
298 | gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(gtkDialog), qUtf8Printable(fi.fileName())); |
299 | } else { |
300 | gtk_file_chooser_select_filename(GTK_FILE_CHOOSER(gtkDialog), qUtf8Printable(filename.toLocalFile())); |
301 | } |
302 | } |
303 | |
304 | QList<QUrl> QGtk3FileDialogHelper::selectedFiles() const |
305 | { |
306 | // While GtkFileChooserDialog is hidden, gtk_file_chooser_get_filenames() |
307 | // returns a bogus value -> return the cached value before hiding |
308 | if (!_selection.isEmpty()) |
309 | return _selection; |
310 | |
311 | QList<QUrl> selection; |
312 | GtkDialog *gtkDialog = d->gtkDialog(); |
313 | GSList *filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(gtkDialog)); |
314 | for (GSList *it = filenames; it; it = it->next) |
315 | selection += QUrl::fromLocalFile(localfile: QString::fromUtf8(utf8: (const char*)it->data)); |
316 | g_slist_free(list: filenames); |
317 | return selection; |
318 | } |
319 | |
320 | void QGtk3FileDialogHelper::setFilter() |
321 | { |
322 | applyOptions(); |
323 | } |
324 | |
325 | void QGtk3FileDialogHelper::selectNameFilter(const QString &filter) |
326 | { |
327 | GtkFileFilter *gtkFilter = _filters.value(key: filter); |
328 | if (gtkFilter) { |
329 | GtkDialog *gtkDialog = d->gtkDialog(); |
330 | gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(gtkDialog), filter: gtkFilter); |
331 | } |
332 | } |
333 | |
334 | QString QGtk3FileDialogHelper::selectedNameFilter() const |
335 | { |
336 | GtkDialog *gtkDialog = d->gtkDialog(); |
337 | GtkFileFilter *gtkFilter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(gtkDialog)); |
338 | return _filterNames.value(key: gtkFilter); |
339 | } |
340 | |
341 | void QGtk3FileDialogHelper::onSelectionChanged(GtkDialog *gtkDialog, QGtk3FileDialogHelper *helper) |
342 | { |
343 | QString selection; |
344 | gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(gtkDialog)); |
345 | if (filename) { |
346 | selection = QString::fromUtf8(utf8: filename); |
347 | g_free(mem: filename); |
348 | } |
349 | emit helper->currentChanged(path: QUrl::fromLocalFile(localfile: selection)); |
350 | } |
351 | |
352 | void QGtk3FileDialogHelper::onCurrentFolderChanged(QGtk3FileDialogHelper *dialog) |
353 | { |
354 | emit dialog->directoryEntered(directory: dialog->directory()); |
355 | } |
356 | |
357 | void QGtk3FileDialogHelper::onFilterChanged(QGtk3FileDialogHelper *dialog) |
358 | { |
359 | emit dialog->filterSelected(filter: dialog->selectedNameFilter()); |
360 | } |
361 | |
362 | void QGtk3FileDialogHelper::onUpdatePreview(GtkDialog *gtkDialog, QGtk3FileDialogHelper *helper) |
363 | { |
364 | gchar *filename = gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(gtkDialog)); |
365 | if (!filename) { |
366 | gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), active: false); |
367 | return; |
368 | } |
369 | |
370 | // Don't attempt to open anything which isn't a regular file. If a named pipe, |
371 | // this may hang. |
372 | QFileInfo fileinfo(filename); |
373 | if (!fileinfo.exists() || !fileinfo.isFile()) { |
374 | g_free(mem: filename); |
375 | gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), active: false); |
376 | return; |
377 | } |
378 | |
379 | // This will preserve the image's aspect ratio. |
380 | GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(filename, PREVIEW_WIDTH, PREVIEW_HEIGHT, error: 0); |
381 | g_free(mem: filename); |
382 | if (pixbuf) { |
383 | gtk_image_set_from_pixbuf(GTK_IMAGE(helper->previewWidget), pixbuf); |
384 | g_object_unref(object: pixbuf); |
385 | } |
386 | gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), active: pixbuf ? true : false); |
387 | } |
388 | |
389 | static GtkFileChooserAction gtkFileChooserAction(const QSharedPointer<QFileDialogOptions> &options) |
390 | { |
391 | switch (options->fileMode()) { |
392 | case QFileDialogOptions::AnyFile: |
393 | case QFileDialogOptions::ExistingFile: |
394 | case QFileDialogOptions::ExistingFiles: |
395 | if (options->acceptMode() == QFileDialogOptions::AcceptOpen) |
396 | return GTK_FILE_CHOOSER_ACTION_OPEN; |
397 | else |
398 | return GTK_FILE_CHOOSER_ACTION_SAVE; |
399 | case QFileDialogOptions::Directory: |
400 | case QFileDialogOptions::DirectoryOnly: |
401 | default: |
402 | if (options->acceptMode() == QFileDialogOptions::AcceptOpen) |
403 | return GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; |
404 | else |
405 | return GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; |
406 | } |
407 | } |
408 | |
409 | void QGtk3FileDialogHelper::setFileChooserAction() |
410 | { |
411 | GtkDialog *gtkDialog = d->gtkDialog(); |
412 | |
413 | const GtkFileChooserAction action = gtkFileChooserAction(options: options()); |
414 | gtk_file_chooser_set_action(GTK_FILE_CHOOSER(gtkDialog), action); |
415 | } |
416 | |
417 | void QGtk3FileDialogHelper::applyOptions() |
418 | { |
419 | GtkDialog *gtkDialog = d->gtkDialog(); |
420 | const QSharedPointer<QFileDialogOptions> &opts = options(); |
421 | |
422 | gtk_window_set_title(GTK_WINDOW(gtkDialog), qUtf8Printable(opts->windowTitle())); |
423 | gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(gtkDialog), local_only: true); |
424 | |
425 | setFileChooserAction(); |
426 | |
427 | const bool selectMultiple = opts->fileMode() == QFileDialogOptions::ExistingFiles; |
428 | gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(gtkDialog), select_multiple: selectMultiple); |
429 | |
430 | const bool confirmOverwrite = !opts->testOption(option: QFileDialogOptions::DontConfirmOverwrite); |
431 | gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(gtkDialog), do_overwrite_confirmation: confirmOverwrite); |
432 | |
433 | const bool readOnly = opts->testOption(option: QFileDialogOptions::ReadOnly); |
434 | gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(gtkDialog), create_folders: !readOnly); |
435 | |
436 | const QStringList nameFilters = opts->nameFilters(); |
437 | if (!nameFilters.isEmpty()) |
438 | setNameFilters(nameFilters); |
439 | |
440 | if (opts->initialDirectory().isLocalFile()) |
441 | setDirectory(opts->initialDirectory()); |
442 | |
443 | foreach (const QUrl &filename, opts->initiallySelectedFiles()) |
444 | selectFileInternal(filename); |
445 | |
446 | const QString initialNameFilter = opts->initiallySelectedNameFilter(); |
447 | if (!initialNameFilter.isEmpty()) |
448 | selectNameFilter(filter: initialNameFilter); |
449 | |
450 | GtkWidget *acceptButton = gtk_dialog_get_widget_for_response(dialog: gtkDialog, response_id: GTK_RESPONSE_OK); |
451 | if (acceptButton) { |
452 | if (opts->isLabelExplicitlySet(label: QFileDialogOptions::Accept)) |
453 | gtk_button_set_label(GTK_BUTTON(acceptButton), qUtf8Printable(opts->labelText(QFileDialogOptions::Accept))); |
454 | else if (opts->acceptMode() == QFileDialogOptions::AcceptOpen) |
455 | gtk_button_set_label(GTK_BUTTON(acceptButton), qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Open))); |
456 | else |
457 | gtk_button_set_label(GTK_BUTTON(acceptButton), qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Save))); |
458 | } |
459 | |
460 | GtkWidget *rejectButton = gtk_dialog_get_widget_for_response(dialog: gtkDialog, response_id: GTK_RESPONSE_CANCEL); |
461 | if (rejectButton) { |
462 | if (opts->isLabelExplicitlySet(label: QFileDialogOptions::Reject)) |
463 | gtk_button_set_label(GTK_BUTTON(rejectButton), qUtf8Printable(opts->labelText(QFileDialogOptions::Reject))); |
464 | else |
465 | gtk_button_set_label(GTK_BUTTON(rejectButton), qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Cancel))); |
466 | } |
467 | } |
468 | |
469 | void QGtk3FileDialogHelper::setNameFilters(const QStringList &filters) |
470 | { |
471 | GtkDialog *gtkDialog = d->gtkDialog(); |
472 | foreach (GtkFileFilter *filter, _filters) |
473 | gtk_file_chooser_remove_filter(GTK_FILE_CHOOSER(gtkDialog), filter); |
474 | |
475 | _filters.clear(); |
476 | _filterNames.clear(); |
477 | |
478 | foreach (const QString &filter, filters) { |
479 | GtkFileFilter *gtkFilter = gtk_file_filter_new(); |
480 | const QString name = filter.left(n: filter.indexOf(c: u'(')); |
481 | const QStringList extensions = cleanFilterList(filter); |
482 | |
483 | gtk_file_filter_set_name(filter: gtkFilter, qUtf8Printable(name.isEmpty() ? extensions.join(", "_L1 ) : name)); |
484 | foreach (const QString &ext, extensions) |
485 | gtk_file_filter_add_pattern(filter: gtkFilter, qUtf8Printable(ext)); |
486 | |
487 | gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(gtkDialog), filter: gtkFilter); |
488 | |
489 | _filters.insert(key: filter, value: gtkFilter); |
490 | _filterNames.insert(key: gtkFilter, value: filter); |
491 | } |
492 | } |
493 | |
494 | QGtk3FontDialogHelper::QGtk3FontDialogHelper() |
495 | { |
496 | d.reset(other: new QGtk3Dialog(gtk_font_chooser_dialog_new(title: "" , parent: nullptr), this)); |
497 | g_signal_connect_swapped(d->gtkDialog(), "notify::font" , G_CALLBACK(onFontChanged), this); |
498 | } |
499 | |
500 | QGtk3FontDialogHelper::~QGtk3FontDialogHelper() |
501 | { |
502 | } |
503 | |
504 | bool QGtk3FontDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) |
505 | { |
506 | applyOptions(); |
507 | return d->show(flags, modality, parent); |
508 | } |
509 | |
510 | void QGtk3FontDialogHelper::exec() |
511 | { |
512 | d->exec(); |
513 | } |
514 | |
515 | void QGtk3FontDialogHelper::hide() |
516 | { |
517 | d->hide(); |
518 | } |
519 | |
520 | static QString qt_fontToString(const QFont &font) |
521 | { |
522 | PangoFontDescription *desc = pango_font_description_new(); |
523 | pango_font_description_set_size(desc, size: (font.pointSizeF() > 0.0 ? font.pointSizeF() : QFontInfo(font).pointSizeF()) * PANGO_SCALE); |
524 | pango_font_description_set_family(desc, qUtf8Printable(QFontInfo(font).family())); |
525 | |
526 | int weight = font.weight(); |
527 | if (weight >= QFont::Black) |
528 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_HEAVY); |
529 | else if (weight >= QFont::ExtraBold) |
530 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_ULTRABOLD); |
531 | else if (weight >= QFont::Bold) |
532 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_BOLD); |
533 | else if (weight >= QFont::DemiBold) |
534 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_SEMIBOLD); |
535 | else if (weight >= QFont::Medium) |
536 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_MEDIUM); |
537 | else if (weight >= QFont::Normal) |
538 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_NORMAL); |
539 | else if (weight >= QFont::Light) |
540 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_LIGHT); |
541 | else if (weight >= QFont::ExtraLight) |
542 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_ULTRALIGHT); |
543 | else |
544 | pango_font_description_set_weight(desc, weight: PANGO_WEIGHT_THIN); |
545 | |
546 | int style = font.style(); |
547 | if (style == QFont::StyleItalic) |
548 | pango_font_description_set_style(desc, style: PANGO_STYLE_ITALIC); |
549 | else if (style == QFont::StyleOblique) |
550 | pango_font_description_set_style(desc, style: PANGO_STYLE_OBLIQUE); |
551 | else |
552 | pango_font_description_set_style(desc, style: PANGO_STYLE_NORMAL); |
553 | |
554 | char *str = pango_font_description_to_string(desc); |
555 | QString name = QString::fromUtf8(utf8: str); |
556 | pango_font_description_free(desc); |
557 | g_free(mem: str); |
558 | return name; |
559 | } |
560 | |
561 | static QFont qt_fontFromString(const QString &name) |
562 | { |
563 | QFont font; |
564 | PangoFontDescription *desc = pango_font_description_from_string(qUtf8Printable(name)); |
565 | font.setPointSizeF(static_cast<float>(pango_font_description_get_size(desc)) / PANGO_SCALE); |
566 | |
567 | QString family = QString::fromUtf8(utf8: pango_font_description_get_family(desc)); |
568 | if (!family.isEmpty()) |
569 | font.setFamilies(QStringList{family}); |
570 | |
571 | font.setWeight(QFont::Weight(pango_font_description_get_weight(desc))); |
572 | |
573 | PangoStyle style = pango_font_description_get_style(desc); |
574 | if (style == PANGO_STYLE_ITALIC) |
575 | font.setStyle(QFont::StyleItalic); |
576 | else if (style == PANGO_STYLE_OBLIQUE) |
577 | font.setStyle(QFont::StyleOblique); |
578 | else |
579 | font.setStyle(QFont::StyleNormal); |
580 | |
581 | pango_font_description_free(desc); |
582 | return font; |
583 | } |
584 | |
585 | void QGtk3FontDialogHelper::setCurrentFont(const QFont &font) |
586 | { |
587 | GtkFontChooser *gtkDialog = GTK_FONT_CHOOSER(d->gtkDialog()); |
588 | gtk_font_chooser_set_font(fontchooser: gtkDialog, qUtf8Printable(qt_fontToString(font))); |
589 | } |
590 | |
591 | QFont QGtk3FontDialogHelper::currentFont() const |
592 | { |
593 | GtkFontChooser *gtkDialog = GTK_FONT_CHOOSER(d->gtkDialog()); |
594 | gchar *name = gtk_font_chooser_get_font(fontchooser: gtkDialog); |
595 | QFont font = qt_fontFromString(name: QString::fromUtf8(utf8: name)); |
596 | g_free(mem: name); |
597 | return font; |
598 | } |
599 | |
600 | void QGtk3FontDialogHelper::onFontChanged(QGtk3FontDialogHelper *dialog) |
601 | { |
602 | emit dialog->currentFontChanged(font: dialog->currentFont()); |
603 | } |
604 | |
605 | void QGtk3FontDialogHelper::applyOptions() |
606 | { |
607 | GtkDialog *gtkDialog = d->gtkDialog(); |
608 | const QSharedPointer<QFontDialogOptions> &opts = options(); |
609 | |
610 | gtk_window_set_title(GTK_WINDOW(gtkDialog), qUtf8Printable(opts->windowTitle())); |
611 | } |
612 | |
613 | QT_END_NAMESPACE |
614 | |
615 | #include "moc_qgtk3dialoghelpers.cpp" |
616 | |