diff --git a/Telegram/SourceFiles/core/lambda.h b/Telegram/SourceFiles/core/lambda.h index ce2be188d..2990a7928 100644 --- a/Telegram/SourceFiles/core/lambda.h +++ b/Telegram/SourceFiles/core/lambda.h @@ -501,9 +501,9 @@ class guard { public: using return_type = typename lambda_type::return_type; - template - inline guard(PointersAndLambda&&... qobjectsAndLambda) : _data(std::make_unique>(std::forward(qobjectsAndLambda)...)) { - static_assert(sizeof...(PointersAndLambda) == N + 1, "Wrong argument count!"); + template + inline guard(Pointer &&qobject, Other &&other, PointersAndLambda&&... qobjectsAndLambda) : _data(std::make_unique>(std::forward(qobject), std::forward(other), std::forward(qobjectsAndLambda)...)) { + static_assert(1 + 1 + sizeof...(PointersAndLambda) == N + 1, "Wrong argument count!"); } inline guard(const guard &other) : _data(std::make_unique>(static_cast &>(*other._data))) { diff --git a/Telegram/SourceFiles/platform/linux/file_dialog_linux.cpp b/Telegram/SourceFiles/platform/linux/file_dialog_linux.cpp index 6e91d710e..84aed9fbb 100644 --- a/Telegram/SourceFiles/platform/linux/file_dialog_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_dialog_linux.cpp @@ -31,6 +31,19 @@ QStringList qt_make_filter_list(const QString &filter); namespace Platform { namespace FileDialog { +namespace { + +// GTK file chooser image preview: thanks to Chromium + +// The size of the preview we display for selected image files. We set height +// larger than width because generally there is more free space vertically +// than horiztonally (setting the preview image will alway expand the width of +// the dialog, but usually not the height). The image's aspect ratio will always +// be preserved. +constexpr auto kPreviewWidth = 256; +constexpr auto kPreviewHeight = 512; + +} // namespace using Type = ::FileDialog::internal::Type; @@ -70,6 +83,11 @@ bool Supported() { && (Libs::gtk_file_filter_new != nullptr); } +bool PreviewSupported() { + return Supported() + && (Libs::gdk_pixbuf_new_from_file_at_size != nullptr); +} + bool Get(QStringList &files, QByteArray &remoteContent, const QString &caption, const QString &filter, Type type, QString startFile) { auto parent = App::wnd() ? App::wnd()->filedialogParent() : nullptr; internal::GtkFileDialog dialog(parent, caption, QString(), filter); @@ -117,7 +135,12 @@ namespace internal { QGtkDialog::QGtkDialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget) { Libs::g_signal_connect_swapped_helper(Libs::g_object_cast(gtkWidget), "response", GCallback(onResponse), this); - Libs::g_signal_connect_helper(Libs::g_object_cast(gtkWidget), "delete-event", GCallback(Libs::gtk_widget_hide_on_delete), NULL); + Libs::g_signal_connect_helper(Libs::g_object_cast(gtkWidget), "delete-event", GCallback(Libs::gtk_widget_hide_on_delete), nullptr); + if (PreviewSupported()) { + _preview = Libs::gtk_image_new(); + Libs::g_signal_connect_swapped_helper(Libs::g_object_cast(gtkWidget), "update-preview", GCallback(onUpdatePreview), this); + Libs::gtk_file_chooser_set_preview_widget(Libs::gtk_file_chooser_cast(gtkWidget), _preview); + } } QGtkDialog::~QGtkDialog() { @@ -179,6 +202,32 @@ void QGtkDialog::onResponse(QGtkDialog *dialog, int response) { emit dialog->reject(); } +void QGtkDialog::onUpdatePreview(QGtkDialog* dialog) { + auto filename = Libs::gtk_file_chooser_get_preview_filename(Libs::gtk_file_chooser_cast(dialog->gtkWidget)); + if (!filename) { + Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false); + return; + } + + // Don't attempt to open anything which isn't a regular file. If a named pipe, + // this may hang. See https://crbug.com/534754. + struct stat stat_buf; + if (stat(filename, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) { + Libs::g_free(filename); + Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), false); + return; + } + + // This will preserve the image's aspect ratio. + auto pixbuf = Libs::gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth, kPreviewHeight, nullptr); + Libs::g_free(filename); + if (pixbuf) { + Libs::gtk_image_set_from_pixbuf(Libs::gtk_image_cast(dialog->_preview), pixbuf); + Libs::g_object_unref(pixbuf); + } + Libs::gtk_file_chooser_set_preview_widget_active(Libs::gtk_file_chooser_cast(dialog->gtkWidget), pixbuf ? true : false); +} + void QGtkDialog::onParentWindowDestroyed() { // The Gtk*DialogHelper classes own this object. Make sure the parent doesn't delete it. setParent(nullptr); diff --git a/Telegram/SourceFiles/platform/linux/file_dialog_linux.h b/Telegram/SourceFiles/platform/linux/file_dialog_linux.h index c96cccc30..a9971c7ad 100644 --- a/Telegram/SourceFiles/platform/linux/file_dialog_linux.h +++ b/Telegram/SourceFiles/platform/linux/file_dialog_linux.h @@ -48,42 +48,44 @@ namespace internal { // We need to be able to work with gtk2 and gtk3, because // we use gtk3 to work with appindicator3. class QGtkDialog : public QWindow { - Q_OBJECT + Q_OBJECT public: - QGtkDialog(GtkWidget *gtkWidget); - ~QGtkDialog(); + QGtkDialog(GtkWidget *gtkWidget); + ~QGtkDialog(); - GtkDialog *gtkDialog() const; + GtkDialog *gtkDialog() const; - void exec(); - void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); - void hide(); + void exec(); + void show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); + void hide(); signals: - void accept(); - void reject(); + void accept(); + void reject(); protected: - static void onResponse(QGtkDialog *dialog, int response); + static void onResponse(QGtkDialog *dialog, int response); + static void onUpdatePreview(QGtkDialog *dialog); private slots: - void onParentWindowDestroyed(); + void onParentWindowDestroyed(); private: - GtkWidget *gtkWidget; + GtkWidget *gtkWidget; + GtkWidget *_preview = nullptr; }; class GtkFileDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - GtkFileDialog(QWidget *parent = Q_NULLPTR, - const QString &caption = QString(), - const QString &directory = QString(), - const QString &filter = QString()); - ~GtkFileDialog(); + GtkFileDialog(QWidget *parent = Q_NULLPTR, + const QString &caption = QString(), + const QString &directory = QString(), + const QString &filter = QString()); + ~GtkFileDialog(); void setVisible(bool visible) override; @@ -104,26 +106,26 @@ public: } } - int exec() override; + int exec() override; - bool defaultNameFilterDisables() const; - void setDirectory(const QString &directory); - QDir directory() const; - void selectFile(const QString &filename); - QStringList selectedFiles() const; - void setFilter(); - void selectNameFilter(const QString &filter); - QString selectedNameFilter() const; + bool defaultNameFilterDisables() const; + void setDirectory(const QString &directory); + QDir directory() const; + void selectFile(const QString &filename); + QStringList selectedFiles() const; + void setFilter(); + void selectNameFilter(const QString &filter); + QString selectedNameFilter() const; private slots: - void onAccepted(); + void onAccepted(); void onRejected(); private: - static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper); - static void onCurrentFolderChanged(GtkFileDialog *helper); - void applyOptions(); - void setNameFilters(const QStringList &filters); + static void onSelectionChanged(GtkDialog *dialog, GtkFileDialog *helper); + static void onCurrentFolderChanged(GtkFileDialog *helper); + void applyOptions(); + void setNameFilters(const QStringList &filters); void showHelper(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent); void hideHelper(); @@ -137,11 +139,11 @@ private: QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; - QString _dir; - QStringList _selection; - QHash _filters; - QHash _filterNames; - QScopedPointer d; + QString _dir; + QStringList _selection; + QHash _filters; + QHash _filterNames; + QScopedPointer d; }; } // namespace internal diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.cpp b/Telegram/SourceFiles/platform/linux/linux_libs.cpp index 06cb5f132..792f1ce0d 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_libs.cpp @@ -67,6 +67,7 @@ bool setupGtkBase(QLibrary &lib_gtk) { if (!load(lib_gtk, "gtk_clipboard_store", gtk_clipboard_store)) return false; if (!load(lib_gtk, "gtk_file_chooser_dialog_new", gtk_file_chooser_dialog_new)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_type", gtk_file_chooser_get_type)) return false; + if (!load(lib_gtk, "gtk_image_get_type", gtk_image_get_type)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_current_folder", gtk_file_chooser_set_current_folder)) return false; if (!load(lib_gtk, "gtk_file_chooser_get_current_folder", gtk_file_chooser_get_current_folder)) return false; if (!load(lib_gtk, "gtk_file_chooser_set_current_name", gtk_file_chooser_set_current_name)) return false; @@ -84,7 +85,12 @@ bool setupGtkBase(QLibrary &lib_gtk) { if (!load(lib_gtk, "gtk_file_filter_set_name", gtk_file_filter_set_name)) return false; if (!load(lib_gtk, "gtk_file_filter_add_pattern", gtk_file_filter_add_pattern)) return false; if (!load(lib_gtk, "gtk_file_chooser_add_filter", gtk_file_chooser_add_filter)) return false; + if (!load(lib_gtk, "gtk_file_chooser_set_preview_widget", gtk_file_chooser_set_preview_widget)) return false; + if (!load(lib_gtk, "gtk_file_chooser_get_preview_filename", gtk_file_chooser_get_preview_filename)) return false; + if (!load(lib_gtk, "gtk_file_chooser_set_preview_widget_active", gtk_file_chooser_set_preview_widget_active)) return false; if (!load(lib_gtk, "gtk_file_filter_new", gtk_file_filter_new)) return false; + if (!load(lib_gtk, "gtk_image_new", gtk_image_new)) return false; + if (!load(lib_gtk, "gtk_image_set_from_pixbuf", gtk_image_set_from_pixbuf)) return false; if (!load(lib_gtk, "gdk_window_set_modal_hint", gdk_window_set_modal_hint)) return false; if (!load(lib_gtk, "gdk_window_focus", gdk_window_focus)) return false; @@ -149,6 +155,7 @@ f_gtk_clipboard_get gtk_clipboard_get = nullptr; f_gtk_clipboard_store gtk_clipboard_store = nullptr; f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new = nullptr; f_gtk_file_chooser_get_type gtk_file_chooser_get_type = nullptr; +f_gtk_image_get_type gtk_image_get_type = nullptr; f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder = nullptr; f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder = nullptr; f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name = nullptr; @@ -166,7 +173,12 @@ f_gtk_file_chooser_remove_filter gtk_file_chooser_remove_filter = nullptr; f_gtk_file_filter_set_name gtk_file_filter_set_name = nullptr; f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern = nullptr; f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter = nullptr; +f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget = nullptr; +f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename = nullptr; +f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active = nullptr; f_gtk_file_filter_new gtk_file_filter_new = nullptr; +f_gtk_image_new gtk_image_new = nullptr; +f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf = nullptr; f_gtk_dialog_get_widget_for_response gtk_dialog_get_widget_for_response = nullptr; f_gtk_button_set_label gtk_button_set_label = nullptr; f_gtk_button_get_type gtk_button_get_type = nullptr; @@ -185,6 +197,7 @@ f_app_indicator_set_icon_full app_indicator_set_icon_full = nullptr; f_gdk_init_check gdk_init_check = nullptr; f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data = nullptr; f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file = nullptr; +f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size = nullptr; f_gtk_status_icon_new_from_pixbuf gtk_status_icon_new_from_pixbuf = nullptr; f_gtk_status_icon_set_from_pixbuf gtk_status_icon_set_from_pixbuf = nullptr; f_gtk_status_icon_new_from_file gtk_status_icon_new_from_file = nullptr; @@ -248,6 +261,7 @@ void start() { load(lib_gtk, "gdk_init_check", gdk_init_check); load(lib_gtk, "gdk_pixbuf_new_from_data", gdk_pixbuf_new_from_data); load(lib_gtk, "gdk_pixbuf_new_from_file", gdk_pixbuf_new_from_file); + load(lib_gtk, "gdk_pixbuf_new_from_file_at_size", gdk_pixbuf_new_from_file_at_size); load(lib_gtk, "gtk_status_icon_new_from_pixbuf", gtk_status_icon_new_from_pixbuf); load(lib_gtk, "gtk_status_icon_set_from_pixbuf", gtk_status_icon_set_from_pixbuf); load(lib_gtk, "gtk_status_icon_new_from_file", gtk_status_icon_new_from_file); diff --git a/Telegram/SourceFiles/platform/linux/linux_libs.h b/Telegram/SourceFiles/platform/linux/linux_libs.h index 1ecae7025..224212187 100644 --- a/Telegram/SourceFiles/platform/linux/linux_libs.h +++ b/Telegram/SourceFiles/platform/linux/linux_libs.h @@ -112,13 +112,13 @@ extern f_gtk_file_chooser_dialog_new gtk_file_chooser_dialog_new; typedef gboolean (*f_gtk_file_chooser_set_current_folder)(GtkFileChooser *chooser, const gchar *filename); extern f_gtk_file_chooser_set_current_folder gtk_file_chooser_set_current_folder; -typedef gchar *(*f_gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser); +typedef gchar* (*f_gtk_file_chooser_get_current_folder)(GtkFileChooser *chooser); extern f_gtk_file_chooser_get_current_folder gtk_file_chooser_get_current_folder; typedef void (*f_gtk_file_chooser_set_current_name)(GtkFileChooser *chooser, const gchar *name); extern f_gtk_file_chooser_set_current_name gtk_file_chooser_set_current_name; -typedef gboolean (*f_gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const char *filename); +typedef gboolean (*f_gtk_file_chooser_select_filename)(GtkFileChooser *chooser, const gchar *filename); extern f_gtk_file_chooser_select_filename gtk_file_chooser_select_filename; typedef GSList* (*f_gtk_file_chooser_get_filenames)(GtkFileChooser *chooser); @@ -163,9 +163,24 @@ extern f_gtk_file_filter_add_pattern gtk_file_filter_add_pattern; typedef void (*f_gtk_file_chooser_add_filter)(GtkFileChooser *chooser, GtkFileFilter *filter); extern f_gtk_file_chooser_add_filter gtk_file_chooser_add_filter; +typedef void (*f_gtk_file_chooser_set_preview_widget)(GtkFileChooser *chooser, GtkWidget *preview_widget); +extern f_gtk_file_chooser_set_preview_widget gtk_file_chooser_set_preview_widget; + +typedef gchar* (*f_gtk_file_chooser_get_preview_filename)(GtkFileChooser *chooser); +extern f_gtk_file_chooser_get_preview_filename gtk_file_chooser_get_preview_filename; + +typedef void (*f_gtk_file_chooser_set_preview_widget_active)(GtkFileChooser *chooser, gboolean active); +extern f_gtk_file_chooser_set_preview_widget_active gtk_file_chooser_set_preview_widget_active; + typedef GtkFileFilter* (*f_gtk_file_filter_new)(void); extern f_gtk_file_filter_new gtk_file_filter_new; +typedef GtkWidget* (*f_gtk_image_new)(void); +extern f_gtk_image_new gtk_image_new; + +typedef void (*f_gtk_image_set_from_pixbuf)(GtkImage *image, GdkPixbuf *pixbuf); +extern f_gtk_image_set_from_pixbuf gtk_image_set_from_pixbuf; + typedef void (*f_gdk_window_set_modal_hint)(GdkWindow *window, gboolean modal); extern f_gdk_window_set_modal_hint gdk_window_set_modal_hint; @@ -211,6 +226,14 @@ inline GtkFileChooser *gtk_file_chooser_cast(Object *obj) { return g_type_cic_helper(obj, gtk_file_chooser_get_type()); } +typedef GType (*f_gtk_image_get_type)(void) G_GNUC_CONST; +extern f_gtk_image_get_type gtk_image_get_type; + +template +inline GtkImage *gtk_image_cast(Object *obj) { + return g_type_cic_helper(obj, gtk_image_get_type()); +} + typedef GType (*f_gtk_button_get_type)(void) G_GNUC_CONST; extern f_gtk_button_get_type gtk_button_get_type; @@ -279,6 +302,9 @@ extern f_gdk_pixbuf_new_from_data gdk_pixbuf_new_from_data; typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file)(const gchar *filename, GError **error); extern f_gdk_pixbuf_new_from_file gdk_pixbuf_new_from_file; +typedef GdkPixbuf* (*f_gdk_pixbuf_new_from_file_at_size)(const gchar *filename, int width, int height, GError **error); +extern f_gdk_pixbuf_new_from_file_at_size gdk_pixbuf_new_from_file_at_size; + typedef GtkStatusIcon* (*f_gtk_status_icon_new_from_pixbuf)(GdkPixbuf *pixbuf); extern f_gtk_status_icon_new_from_pixbuf gtk_status_icon_new_from_pixbuf; diff --git a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp index bcf4b6015..5b2e9fca1 100644 --- a/Telegram/SourceFiles/platform/linux/main_window_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/main_window_linux.cpp @@ -378,7 +378,6 @@ bool MainWindow::psHasNativeNotifications() { } void MainWindow::LibsLoaded() { - auto cdesktop = Libs::CurrentDesktopStrings(); noQtTrayIcon = !DesktopEnvironment::TryQtTrayIcon(); tryAppIndicator = !DesktopEnvironment::PreferAppIndicatorTrayIcon();