From 663b93a8070db905e1e7741f9e6b1cea915f61c7 Mon Sep 17 00:00:00 2001 From: ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com> Date: Tue, 7 Dec 2021 11:24:35 +0100 Subject: [PATCH] Fix for issue #278: Can we use the qt/kde file picker instead of gtk? Under the X11 platform, class Fl_Native_File_Chooser will behave as follows : - if the KDE desktop is used and if command "kdialog" is available in the path, the Qt/KDE file chooser is used; - otherwise, if the GTK library is available at run-time, the GTK file chooser is used; - otherwise, the FLTK file chooser is used. In addition, when Fl::OPTION_FNFC_USES_GTK is off, the FLTK file chooser is always used. --- FL/Fl_Native_File_Chooser.H | 2 + src/CMakeLists.txt | 1 + src/Fl_Native_File_Chooser_GTK.cxx | 46 +++-- src/Fl_Native_File_Chooser_Kdialog.H | 49 ++++++ src/Fl_Native_File_Chooser_Kdialog.cxx | 229 +++++++++++++++++++++++++ src/Makefile | 3 +- 6 files changed, 317 insertions(+), 13 deletions(-) create mode 100644 src/Fl_Native_File_Chooser_Kdialog.H create mode 100644 src/Fl_Native_File_Chooser_Kdialog.cxx diff --git a/FL/Fl_Native_File_Chooser.H b/FL/Fl_Native_File_Chooser.H index 423560ccc..aac45eb67 100644 --- a/FL/Fl_Native_File_Chooser.H +++ b/FL/Fl_Native_File_Chooser.H @@ -27,7 +27,9 @@ class Fl_Native_File_Chooser_FLTK_Driver <== this API implementation is the default FLTK file chooser class Fl_GTK_Native_File_Chooser_Driver <== this API implementation runs a GTK file chooser + class Fl_Kdialog_Native_File_Chooser_Driver <== this API implementation runs a KDE file chooser it is determined at run-time if the GTK dynamic libraries are available + and the KDE file chooser runs under the KDE desktop class Fl_Quartz_Native_File_Chooser_Driver <== this API implementation runs a Mac OS X file chooser diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 475712ccf..507b106ec 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -213,6 +213,7 @@ if (USE_X11) fl_dnd_x.cxx Fl_Native_File_Chooser_FLTK.cxx Fl_Native_File_Chooser_GTK.cxx + Fl_Native_File_Chooser_Kdialog.cxx Fl_get_key.cxx ) if (USE_XFT) diff --git a/src/Fl_Native_File_Chooser_GTK.cxx b/src/Fl_Native_File_Chooser_GTK.cxx index 269405e48..5c927d02e 100644 --- a/src/Fl_Native_File_Chooser_GTK.cxx +++ b/src/Fl_Native_File_Chooser_GTK.cxx @@ -1,7 +1,7 @@ // // FLTK native file chooser widget wrapper for GTK's GtkFileChooserDialog // -// Copyright 1998-2018 by Bill Spitzak and others. +// Copyright 1998-2021 by Bill Spitzak and others. // Copyright 2012 IMM // // This library is free software. Distribution and use rights are outlined in @@ -17,6 +17,7 @@ #include #include +#include "Fl_Native_File_Chooser_Kdialog.H" #if HAVE_DLSYM && HAVE_DLFCN_H #include @@ -907,17 +908,38 @@ void Fl_GTK_Native_File_Chooser_Driver::probe_for_GTK_libs(void) { #endif // HAVE_DLSYM && HAVE_DLFCN_H Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) { -#if HAVE_DLSYM && HAVE_DLFCN_H - if (Fl_GTK_Native_File_Chooser_Driver::have_looked_for_GTK_libs == 0) { - // First Time here, try to find the GTK libs if they are installed - if (Fl::option(Fl::OPTION_FNFC_USES_GTK)) { - Fl_GTK_Native_File_Chooser_Driver::probe_for_GTK_libs(); + // Use kdialog if available at run-time and if using the KDE desktop, + // else, use GTK dialog if available at run-time + // otherwise, use FLTK file chooser. + platform_fnfc = NULL; + if (Fl::option(Fl::OPTION_FNFC_USES_GTK)) { + const char *desktop = getenv("XDG_CURRENT_DESKTOP"); + if (desktop && strcmp(desktop, "KDE") == 0) { + if (!Fl_Kdialog_Native_File_Chooser_Driver::have_looked_for_kdialog) { + // First Time here, try to find kdialog + FILE *pipe = popen("kdialog -v 2> /dev/null", "r"); + if (pipe) { + char line[100] = ""; + fgets(line, sizeof(line), pipe); + if (strlen(line) > 0) Fl_Kdialog_Native_File_Chooser_Driver::did_find_kdialog = true; + pclose(pipe); + } + Fl_Kdialog_Native_File_Chooser_Driver::have_looked_for_kdialog = true; + } + // if we found kdialog, we will use the Fl_Kdialog_Native_File_Chooser_Driver + if (Fl_Kdialog_Native_File_Chooser_Driver::did_find_kdialog) platform_fnfc = new Fl_Kdialog_Native_File_Chooser_Driver(val); + } +#if HAVE_DLSYM && HAVE_DLFCN_H + if (!platform_fnfc) { + if ( Fl_GTK_Native_File_Chooser_Driver::have_looked_for_GTK_libs == 0) { + // First Time here, try to find the GTK libs if they are installed + Fl_GTK_Native_File_Chooser_Driver::probe_for_GTK_libs(); + Fl_GTK_Native_File_Chooser_Driver::have_looked_for_GTK_libs = -1; + } + // if we found all the GTK functions we need, we will use the GtkFileChooserDialog + if (Fl_GTK_Native_File_Chooser_Driver::did_find_GTK_libs) platform_fnfc = new Fl_GTK_Native_File_Chooser_Driver(val); } - Fl_GTK_Native_File_Chooser_Driver::have_looked_for_GTK_libs = -1; - } - // if we found all the GTK functions we need, we will use the GtkFileChooserDialog - if (Fl_GTK_Native_File_Chooser_Driver::did_find_GTK_libs) platform_fnfc = new Fl_GTK_Native_File_Chooser_Driver(val); - else #endif // HAVE_DLSYM && HAVE_DLFCN_H - platform_fnfc = new Fl_Native_File_Chooser_FLTK_Driver(val); + } + if (!platform_fnfc) platform_fnfc = new Fl_Native_File_Chooser_FLTK_Driver(val); } diff --git a/src/Fl_Native_File_Chooser_Kdialog.H b/src/Fl_Native_File_Chooser_Kdialog.H new file mode 100644 index 000000000..730b25e29 --- /dev/null +++ b/src/Fl_Native_File_Chooser_Kdialog.H @@ -0,0 +1,49 @@ +// +// FLTK native file chooser widget : KDE version +// +// Copyright 2021 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#ifndef FL_KDIALOG_NATIVE_FILE_CHOOSER_H +#define FL_KDIALOG_NATIVE_FILE_CHOOSER_H 1 + +#include + +class FL_EXPORT Fl_Kdialog_Native_File_Chooser_Driver : public Fl_Native_File_Chooser_FLTK_Driver { + friend class Fl_Native_File_Chooser; + char **_pathnames; + int _tpathnames; + char *_directory; + char *_preset_file; + char *_title; + static bool did_find_kdialog; + static bool have_looked_for_kdialog; + Fl_Kdialog_Native_File_Chooser_Driver(int val); + ~Fl_Kdialog_Native_File_Chooser_Driver(); + int count() const; + const char *filename() const; + const char *filename(int i) const; + int show(); + char *parse_filter(const char *f); + const char *filter() const; + virtual void filter(const char *f); + int filters() const; + void preset_file(const char *val); + const char *preset_file() const; + void directory(const char *val); + const char *directory() const; + void title(const char *val); + const char *title() const; +}; + +#endif // FL_KDIALOG_NATIVE_FILE_CHOOSER_H diff --git a/src/Fl_Native_File_Chooser_Kdialog.cxx b/src/Fl_Native_File_Chooser_Kdialog.cxx new file mode 100644 index 000000000..37980ba0a --- /dev/null +++ b/src/Fl_Native_File_Chooser_Kdialog.cxx @@ -0,0 +1,229 @@ +// +// FLTK native file chooser widget : KDE version +// +// Copyright 2021 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include +#include +#include "Fl_Native_File_Chooser_Kdialog.H" + +#include +#include +#include + + +/* Fl_Kdialog_Native_File_Chooser_Driver : file chooser based on the "kdialog" command */ + +bool Fl_Kdialog_Native_File_Chooser_Driver::did_find_kdialog = false; +bool Fl_Kdialog_Native_File_Chooser_Driver::have_looked_for_kdialog = false; + + +Fl_Kdialog_Native_File_Chooser_Driver::Fl_Kdialog_Native_File_Chooser_Driver(int val) : Fl_Native_File_Chooser_FLTK_Driver(val) { + _tpathnames = 0; + _pathnames = NULL; + _directory = NULL; + _preset_file = NULL; + _title = NULL; +} + + +Fl_Kdialog_Native_File_Chooser_Driver::~Fl_Kdialog_Native_File_Chooser_Driver() { + for (int i = 0; i < _tpathnames; i++) delete[] _pathnames[i]; + delete[] _pathnames; + if (_preset_file) free(_preset_file); + if (_directory) free(_directory); + if (_title) free(_title); +} + + +int Fl_Kdialog_Native_File_Chooser_Driver::show() { + Fl::flush(); // to close menus if necessary + const char *option; + switch (_btype) { + case Fl_Native_File_Chooser::BROWSE_DIRECTORY: + case Fl_Native_File_Chooser::BROWSE_MULTI_DIRECTORY: // not supported + option = "--getexistingdirectory"; + break; + + case Fl_Native_File_Chooser::BROWSE_SAVE_FILE: + case Fl_Native_File_Chooser::BROWSE_SAVE_DIRECTORY: + option = "--getsavefilename"; + break; + + case Fl_Native_File_Chooser::BROWSE_MULTI_FILE: + option = "--multiple --getopenfilename"; + break; + + default: + option = "--getopenfilename"; + } + const char *preset = "."; + if (_preset_file) preset = _preset_file; + else if (_directory) preset = _directory; + char *command = new char[strlen(option) + strlen(preset) + (_title?strlen(_title)+11:0) + + (_parsedfilt?strlen(_parsedfilt):0) + 50]; + strcpy(command, "kdialog "); + if (_title) { + sprintf(command+strlen(command), " --title '%s'", _title); + } + sprintf(command+strlen(command), " %s %s ", option, preset); + if (_parsedfilt) sprintf(command+strlen(command), " \"%s\" ", _parsedfilt); + strcat(command, "2> /dev/null"); // get rid of stderr output +//puts(command); + FILE *pipe = popen(command, "r"); + char *all_files = NULL; + if (pipe) { + char *p, tmp[FL_PATH_MAX]; + do { + p = fgets(tmp, sizeof(tmp), pipe); + if (p) all_files = strapp(all_files, p); + } + while (p); + pclose(pipe); + if (all_files) { + if (all_files[strlen(all_files)-1] == '\n') all_files[strlen(all_files)-1] = 0; + for (int i = 0; i < _tpathnames; i++) delete[] _pathnames[i]; + delete[] _pathnames; + p = all_files; + int count = 1; + while ((p = strchr(p+1, ' '))) count++; + _pathnames = new char*[count]; + _tpathnames = 0; + char *q = strtok(all_files, " "); + while (q) { + _pathnames[_tpathnames] = new char[strlen(q)+1]; + strcpy(_pathnames[_tpathnames], q); + _tpathnames++; + q = strtok(NULL, " "); + } + } + } + delete[] command; + if (_title) { free(_title); _title = NULL; } + if (!pipe) return -1; + return (all_files == NULL ? 1 : 0); +} + + +const char *Fl_Kdialog_Native_File_Chooser_Driver::filename() const { + return _tpathnames >= 1 ? _pathnames[0] : NULL; +} + +const char *Fl_Kdialog_Native_File_Chooser_Driver::filename (int i) const { + return _tpathnames > i ? _pathnames[i] : NULL; +} + +const char *Fl_Kdialog_Native_File_Chooser_Driver::filter() const { + return _filter; +} + +int Fl_Kdialog_Native_File_Chooser_Driver::filters() const { + return (_nfilters ? _nfilters - 1 : 0); +} + +int Fl_Kdialog_Native_File_Chooser_Driver::count() const { + return _tpathnames; +} + +char *Fl_Kdialog_Native_File_Chooser_Driver::parse_filter(const char *f) { + //In: "*.H\n" or "*.H" Out: "(*.H)" + //In: "Headers\t*.H\n" Out: "Headers (*.H)" + //In: "Headers\t*.{H,h}\n" Out: "Headers (*.H *.h)" + const char *p = strchr(f, '\t'); + if (!p) p = f - 1; + const char *q = strchr(f, '\n'); if (!q) q = f + strlen(f); + const char *r = strchr(f, '{'); + char *developed = NULL; + if (r) { // with {} + char *lead = new char[r-p]; + memcpy(lead, p+1, (r-p)-1); lead[(r-p)-1] = 0; + const char *r2 = strchr(r, '}'); + char *ends = new char[r2-r]; + memcpy(ends, r+1, (r2-r)-1); ends[(r2-r)-1] = 0; + char *ptr; + char *part = strtok_r(ends, ",", &ptr); + while (part) { + developed = strapp(developed, lead); + developed = strapp(developed, part); + developed = strapp(developed, " "); + part = strtok_r(NULL, ",", &ptr); + } + if (developed[strlen(developed)-1] == ' ') developed[strlen(developed)-1] = 0; + delete[] lead; + delete[] ends; + } + int lout = (p>f?p-f:0) + 2 + (r?strlen(developed):((q-p)-1)) + 2; + char *out = new char[lout]; *out = 0; + if (p > f) {memcpy(out, f, p-f); out[p-f] = 0; } + strcat(out, " ("); + if (r) { + strcpy(out+strlen(out), developed); + strfree(developed); + } + else memcpy(out+strlen(out), p+1, (q-p)); + strcat(out, ")"); +//puts(out); + return out; +} + + +void Fl_Kdialog_Native_File_Chooser_Driver::filter(const char *f) { + _parsedfilt = strfree(_parsedfilt); // clear previous parsed filter (if any) + _nfilters = 0; + if (!f) return; + _filter = strdup(f); + char *f2 = strdup(f); + char *ptr; + char *part = strtok_r(f2, "\n", &ptr); + while (part) { + char *p = parse_filter(part); + _parsedfilt = strapp(_parsedfilt, p); + _parsedfilt = strapp(_parsedfilt, "\\n"); + delete[] p; + _nfilters++; + part = strtok_r(NULL, "\n", &ptr); + } + free(f2); + _parsedfilt = strapp(_parsedfilt, "All files (*)"); + _nfilters++; +//puts(_parsedfilt); +} + +void Fl_Kdialog_Native_File_Chooser_Driver::preset_file(const char *val) { + if (_preset_file) free(_preset_file); + _preset_file = strdup(val); +} + +const char *Fl_Kdialog_Native_File_Chooser_Driver::preset_file() const { + return _preset_file; +} + +void Fl_Kdialog_Native_File_Chooser_Driver::directory(const char *val) { + if (_directory) free(_directory); + _directory = strdup(val); +} + +const char *Fl_Kdialog_Native_File_Chooser_Driver::directory() const { + return _directory; +} + +void Fl_Kdialog_Native_File_Chooser_Driver::title(const char *val) +{ + if (_title) free(_title); + _title = strdup(val); +} + +const char *Fl_Kdialog_Native_File_Chooser_Driver::title() const { + return _title; +} diff --git a/src/Makefile b/src/Makefile index 1b854ad57..3c1225b21 100644 --- a/src/Makefile +++ b/src/Makefile @@ -267,7 +267,8 @@ XLIBCPPFILES = \ Fl_x.cxx \ fl_dnd_x.cxx \ Fl_Native_File_Chooser_FLTK.cxx \ - Fl_Native_File_Chooser_GTK.cxx \ + Fl_Native_File_Chooser_GTK.cxx\ + Fl_Native_File_Chooser_Kdialog.cxx \ Fl_get_key.cxx # This C file is used under condition: BUILD_X11