Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
parameter-path.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * Patrick Storz <eduard.braun2@gmx.de>
7 *
8 * Copyright (C) 2019 Authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "parameter-path.h"
14
15#include <algorithm>
16#include <cstdlib>
17#include <cstring>
18#include <functional>
19#include <utility>
20#include <boost/algorithm/string/case_conv.hpp>
21#include <boost/algorithm/string/join.hpp>
22#include <glibmm/fileutils.h>
23#include <glibmm/i18n.h>
24#include <glibmm/miscutils.h>
25#include <glibmm/regex.h>
26#include <giomm/file.h>
27#include <giomm/liststore.h>
28#include <gtkmm/box.h>
29#include <gtkmm/button.h>
30#include <gtkmm/dialog.h>
31#include <gtkmm/entry.h>
32#include <gtkmm/error.h>
33#include <gtkmm/filedialog.h>
34#include <gtkmm/filefilter.h>
35#include <gtkmm/label.h>
36#include <sigc++/adaptors/bind.h>
37#include <sigc++/functors/mem_fun.h>
38
39#include "extension/extension.h"
40#include "preferences.h"
42#include "ui/pack.h"
43#include "xml/node.h"
44
45namespace Inkscape::Extension {
46
48 : InxParameter(xml, ext)
49{
50 // get value
51 const char *value = nullptr;
52 if (xml->firstChild()) {
53 value = xml->firstChild()->content();
54 }
55
57 _value = prefs->getString(pref_name()).raw();
58
59 if (_value.empty() && value) {
60 _value = value;
61 }
62
63 // parse selection mode
64 const char *mode = xml->attribute("mode");
65 if (mode) {
66 if (!strcmp(mode, "file")) {
67 _mode = Mode::file;
68 } else if (!strcmp(mode, "files")) {
69 _mode = Mode::file;
70 _select_multiple = true;
71 } else if (!strcmp(mode, "folder")) {
72 _mode = Mode::folder;
73 } else if (!strcmp(mode, "folders")) {
74 _mode = Mode::folder;
75 _select_multiple = true;
76 } else if (!strcmp(mode, "file_new")) {
77 _mode = Mode::file_new;
78 } else if (!strcmp(mode, "folder_new")) {
79 _mode = Mode::folder_new;
80 } else {
81 g_warning("Invalid value ('%s') for mode of parameter '%s' in extension '%s'",
83 }
84 }
85
86 // parse filetypes
87 const char *filetypes = xml->attribute("filetypes");
88 if (filetypes) {
89 _filetypes = Glib::Regex::split_simple("," , filetypes);
90 }
91}
92
101const std::string& ParamPath::set(const std::string &in)
102{
103 _value = in;
104
106 prefs->setString(pref_name(), _value);
107
108 return _value;
109}
110
111std::string ParamPath::value_to_string() const
112{
113 if (!Glib::path_is_absolute(_value) && !_value.empty()) {
114 return Glib::build_filename(_extension->get_base_directory(), _value);
115 } else {
116 return _value;
117 }
118}
119
120void ParamPath::string_to_value(const std::string &in)
121{
122 _value = in;
123}
124
126class ParamPathEntry : public Gtk::Entry {
127private:
128 ParamPath *_pref;
129 sigc::signal<void ()> *_changeSignal;
130public:
136 ParamPathEntry(ParamPath *pref, sigc::signal<void ()> *changeSignal)
137 : Gtk::Entry()
138 , _pref(pref)
139 , _changeSignal(changeSignal)
140 {
141 this->set_text(_pref->get());
142 this->signal_changed().connect(sigc::mem_fun(*this, &ParamPathEntry::changed_text));
143 };
144 void changed_text();
145};
146
147
154void ParamPathEntry::changed_text()
155{
156 auto data = this->get_text();
157 _pref->set(data.c_str());
158 if (_changeSignal != nullptr) {
159 _changeSignal->emit();
160 }
161}
162
168Gtk::Widget *ParamPath::get_widget(sigc::signal<void ()> *changeSignal)
169{
170 if (_hidden) {
171 return nullptr;
172 }
173
174 auto const hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, GUI_PARAM_WIDGETS_SPACING);
175 auto const label = Gtk::make_managed<Gtk::Label>(_text, Gtk::Align::START);
176 label->set_visible(true);
177 UI::pack_start(*hbox, *label, false, false);
178
179 auto const textbox = Gtk::make_managed<ParamPathEntry>(this, changeSignal);
180 textbox->set_visible(true);
181 UI::pack_start(*hbox, *textbox, true, true);
182 _entry = textbox;
183
184 auto const button = Gtk::make_managed<Gtk::Button>("…");
185 button->set_visible(true);
186 UI::pack_end(*hbox, *button, false, false);
187 button->signal_clicked().connect(sigc::mem_fun(*this, &ParamPath::on_button_clicked));
188
189 hbox->set_visible(true);
190 return hbox;
191}
192
198{
199 Glib::ustring dialog_title;
200 if (_mode == Mode::file) {
201 if (_select_multiple) {
202 dialog_title = _("Select existing files");
203 } else {
204 dialog_title = _("Select existing file");
205 }
206 } else if (_mode == Mode::folder) {
207 if (_select_multiple) {
208 dialog_title = _("Select existing folders");
209 } else {
210 dialog_title = _("Select existing folder");
211 }
212 } else if (_mode == Mode::file_new) {
213 dialog_title = _("Choose file name");
214 } else if (_mode == Mode::folder_new) {
215 dialog_title = _("Choose folder name");
216 } else {
217 g_assert_not_reached();
218 }
219 dialog_title += "…";
220
221 auto const file_dialog = create_file_dialog(dialog_title, _("Select"));
222
223 // set FileFilter according to 'filetype' attribute
224 if (!_filetypes.empty() && _mode != Mode::folder && _mode != Mode::folder_new) {
225 Glib::RefPtr<Gtk::FileFilter> file_filter = Gtk::FileFilter::create();
226
227 for (auto const &filetype : _filetypes) {
228 file_filter->add_pattern(Glib::ustring::compose("*.%1", filetype));
229 }
230
231 std::string filter_name = boost::algorithm::join(_filetypes, "+");
232 boost::algorithm::to_upper(filter_name);
233 file_filter->set_name(filter_name);
234
235 auto file_filters = Gio::ListStore<Gtk::FileFilter>::create();
236 file_filters->append(file_filter);
237 set_filters(*file_dialog, file_filters);
238 }
239
240 // set current file/folder suitable for current value
241 // (use basepath of first filename; relative paths are considered relative to .inx file's location)
242 if (!_value.empty()) {
243 auto first_filename = _value.substr(0, _value.find("|"));
244
245 if (!Glib::path_is_absolute(first_filename)) {
246 first_filename = Glib::build_filename(_extension->get_base_directory(), first_filename);
247 }
248
249 auto const dirname = Glib::path_get_dirname(first_filename);
250 if (Glib::file_test(dirname, Glib::FileTest::IS_DIR)) {
251 file_dialog->set_initial_folder(Gio::File::create_for_path(dirname));
252 }
253
254 if(_mode == Mode::file_new || _mode == Mode::folder_new) {
255 file_dialog->set_initial_name(Glib::path_get_basename(first_filename));
256 } else {
257 if (Glib::file_test(first_filename, Glib::FileTest::EXISTS)) {
258 // TODO: This does not seem to work (at least on Windows)
259 // TODO: GTK4: It has been rewritten. Does it work now?
260 file_dialog->set_initial_file(Gio::File::create_for_path(first_filename));
261 }
262 }
263 }
264
265 // show dialog and parse result
266 // TODO: GTK4: Double-check this all works right once we can run it. It builds fine, but yʼknow
267 auto slot = sigc::bind(sigc::mem_fun(*this, &ParamPath::on_file_dialog_response), file_dialog);
268 switch (_mode) {
269 case Mode::file:
270 if (_select_multiple) {
271 file_dialog->open_multiple(std::move(slot));
272 } else {
273 file_dialog->open(std::move(slot));
274 }
275 break;
276
277 case Mode::folder:
278 if (_select_multiple) {
279 file_dialog->select_multiple_folders(std::move(slot));
280 } else {
281 file_dialog->select_folder(std::move(slot));
282 }
283 break;
284
285 case Mode::file_new:
286 case Mode::folder_new:
287 file_dialog->save(std::move(slot));
288 }
289}
290
292
293[[nodiscard]] static std::vector<Glib::RefPtr<Gio::File>>
294get_files(Glib::RefPtr<Gio::AsyncResult> const &result ,
295 Glib::RefPtr<Gtk::FileDialog > const &file_dialog,
296 Mode const mode, bool const select_multiple)
297try
298{
299 switch (mode) {
300 case Mode::file:
301 if (select_multiple) {
302 return file_dialog->open_multiple_finish(result);
303 } else {
304 return {file_dialog->open_finish(result)};
305 }
306
307 case Mode::folder:
308 if (select_multiple) {
309 return file_dialog->select_multiple_folders_finish(result);
310 } else {
311 return {file_dialog->select_folder_finish(result)};
312 }
313
314 case Mode::file_new:
315 case Mode::folder_new:
316 return {file_dialog->save_finish(result)};
317 }
318
319 std::abort(); // should be unreachable
320} catch (Gtk::DialogError const &error) {
321 if (error.code() == Gtk::DialogError::Code::DISMISSED) {
322 return {}; // This is OK, not an error. Return empty
323 }
324 throw;
325}
326
327void ParamPath::on_file_dialog_response(Glib::RefPtr<Gio::AsyncResult> const &result ,
328 Glib::RefPtr<Gtk::FileDialog > const &file_dialog)
329{
330 auto const files = get_files(result, file_dialog, _mode, _select_multiple);
331 if (files.empty()) return;
332
333 std::vector<Glib::ustring> filenames(files.size());
334 std::transform(files.cbegin(), files.cend(), filenames.begin(),
335 [](auto const &file){ return file->get_path(); });
336 auto const filenames_joined = boost::algorithm::join(filenames, "|");
337 _entry->set_text(filenames_joined); // let the ParamPathEntry handle the rest (including setting the preference)
338}
339
340} // namespace Inkscape::Extension
341
342/*
343 Local Variables:
344 mode:c++
345 c-file-style:"stroustrup"
346 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
347 indent-tabs-mode:nil
348 fill-column:99
349 End:
350*/
351// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
The object that is the basis for the Extension system.
Definition extension.h:133
char const * get_id() const
Get the ID of this extension - not a copy don't delete!
auto const & get_base_directory() const
Definition extension.h:192
A class to represent the parameter of an extension.
Definition parameter.h:38
char * _name
The name of this parameter.
Definition parameter.h:137
static constexpr int GUI_PARAM_WIDGETS_SPACING
Recommended spacing between the widgets making up a single Parameter (e.g.
Definition parameter.h:123
Glib::ustring pref_name() const
Build preference name for the current parameter.
char * _text
Parameter text to show as the GUI label.
Definition parameter.h:140
Inkscape::Extension::Extension * _extension
Which extension is this Widget attached to.
Definition widget.h:102
bool _hidden
Whether the widget is visible.
Definition widget.h:108
ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
Gtk::Widget * get_widget(sigc::signal< void()> *changeSignal) override
Creates a text box for the string parameter.
void on_file_dialog_response(Glib::RefPtr< Gio::AsyncResult > const &result, Glib::RefPtr< Gtk::FileDialog > const &file_dialog)
const std::string & set(const std::string &in) override
A function to set the _value.
std::string _value
Internal value.
ParamPathEntry * _entry
pointer to the parameters text entry keep this around, so we can update the value accordingly in on_b...
const std::string & get() const
Returns _value, with a \i const to protect it.
void on_button_clicked()
Create and show the file chooser dialog when the "…" button is clicked Then set the value of the Para...
std::vector< Glib::ustring > _filetypes
filetypes that should be selectable in file chooser
std::string value_to_string() const override
Gets the current value of the parameter in a string form.
Mode _mode
selection mode for the file chooser: files or folders?
void string_to_value(const std::string &in) override
Sets the current value of the parameter from a string.
bool _select_multiple
selection mode for the file chooser: multiple items?
Preference storage class.
Definition preferences.h:61
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * firstChild()=0
Get the first child of this node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual char const * content() const =0
Get the content of a text or comment node.
Css & result
Inkscape::Extension::Extension: Frontend to certain, possibly pluggable, actions.
Glib::ustring label
Definition desktop.h:50
Extension support.
static std::vector< Glib::RefPtr< Gio::File > > get_files(Glib::RefPtr< Gio::AsyncResult > const &result, Glib::RefPtr< Gtk::FileDialog > const &file_dialog, Mode const mode, bool const select_multiple)
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
Definition pack.cpp:153
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
Glib::RefPtr< Gtk::FileDialog > create_file_dialog(Glib::ustring const &title, Glib::ustring const &accept_label)
Create a Gtk::FileDialog with the given title and label for its default/accept button.
void set_filters(Gtk::FileDialog &file_dialog, Glib::RefPtr< Gio::ListStore< Gtk::FileFilter > > const &filters)
Set available filters to a given list, & default to its 1st filter (if any).
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Path parameter for extensions.
int mode
Singleton class to access the preferences file in a convenient way.
static const Point data[]
char const * get_text(Gtk::Editable const &editable)
Get the text from a GtkEditable without the temporary copy imposed by gtkmm.
Definition util.cpp:534
Interface for XML nodes.