Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
dialog-manager.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2
3#include "dialog-manager.h"
4
5#include <limits>
6#include <glibmm/keyfile.h>
7#include <glibmm/fileutils.h>
8#include <glibmm/miscutils.h>
9#include <glibmm/ustring.h>
10#include <gdkmm/monitor.h>
11#include <gtkmm/window.h>
12
13#ifdef G_OS_WIN32
14#include <filesystem>
15namespace filesystem = std::filesystem;
16#else
17// Waiting for compiler on MacOS to catch up to C++x17
18#include <boost/filesystem.hpp>
19namespace filesystem = boost::filesystem;
20#endif // G_OS_WIN32
21
22#include "io/resource.h"
24#include "dialog-base.h"
25#include "dialog-container.h"
26#include "dialog-window.h"
27#include "enums.h"
28#include "preferences.h"
29
30namespace Inkscape::UI::Dialog {
31
32std::optional<window_position_t> dm_get_window_position(Gtk::Window &window)
33{
34 std::optional<window_position_t> position = std::nullopt;
35
36 int width = window.get_width();
37 int height = window.get_height();
38
39 if (width > 0 && height > 0) {
40 position = window_position_t{0, 0, width, height};
41 }
42
43 return position;
44}
45
46void dm_restore_window_position(Gtk::Window &window, const window_position_t &position)
47{
48 // note: Gtk window methods are recommended over low-level Gdk ones to resize and position window
49 window.set_default_size(position.width, position.height);
50}
51
53
55{
56 static DialogManager dm;
57 return dm;
58}
59
60// store complete dialog window state (including its container state)
62{
63 if (auto pos = dm_get_window_position(wnd)) {
64 if (auto container = wnd.get_container()) {
65 auto state = container->get_container_state(&*pos);
66 for (auto const &[name, dialog] : container->get_dialogs()) {
67 _floating_dialogs[name] = state;
68 }
69 }
70 }
71}
72
73bool DialogManager::should_open_floating(const Glib::ustring& dialog_type)
74{
75 return _floating_dialogs.count(dialog_type) > 0;
76}
77
79 if (!wnd) return;
80
81 if (show) {
82 if (wnd->is_visible()) return;
83
84 // wnd->present(); - not sure which one is better, show or present...
85 wnd->set_visible(true);
86 _hidden_dlg_windows.erase(wnd);
87 // re-add it to application; hiding removed it
88 if (auto app = InkscapeApplication::instance()) {
89 app->gtk_app()->add_window(*wnd);
90 }
91 }
92 else {
93 if (!wnd->is_visible()) return;
94
95 _hidden_dlg_windows.insert(wnd);
96 wnd->set_visible(false);
97 }
98}
99
101 std::vector<Gtk::Window*> windows = InkscapeApplication::instance()->gtk_app()->get_windows();
102
103 std::vector<DialogWindow*> result(_hidden_dlg_windows.begin(), _hidden_dlg_windows.end());
104 for (auto wnd : windows) {
105 if (auto dlg_wnd = dynamic_cast<DialogWindow*>(wnd)) {
106 result.push_back(dlg_wnd);
107 }
108 }
109
110 return result;
111}
112
114 auto windows = get_all_floating_dialog_windows();
115
116 for (auto dlg_wnd : windows) {
117 if (auto container = dlg_wnd->get_container()) {
118 if (container->get_dialog(dialog_type)) {
119 return dlg_wnd;
120 }
121 }
122 }
123
124 return nullptr;
125}
126
127DialogBase *DialogManager::find_floating_dialog(const Glib::ustring& dialog_type)
128{
129 auto windows = get_all_floating_dialog_windows();
130
131 for (auto dlg_wnd : windows) {
132 if (auto container = dlg_wnd->get_container()) {
133 if (auto dlg = container->get_dialog(dialog_type)) {
134 return dlg;
135 }
136 }
137 }
138
139 return nullptr;
140}
141
142std::shared_ptr<Glib::KeyFile> DialogManager::find_dialog_state(const Glib::ustring& dialog_type)
143{
144 auto it = _floating_dialogs.find(dialog_type);
145 if (it != _floating_dialogs.end()) {
146 return it->second;
147 }
148 return nullptr;
149}
150
151const char dialogs_state[] = "dialogs-state-ex.ini";
152const char save_dialog_position[] = "/options/savedialogposition/value";
153const char transient_group[] = "transient";
154
155// list of dialogs sharing the same state
156std::vector<Glib::ustring> DialogManager::count_dialogs(const Glib::KeyFile *state) const
157{
158 std::vector<Glib::ustring> dialogs;
159 if (!state) return dialogs;
160
161 for (auto dlg : _floating_dialogs) {
162 if (dlg.second.get() == state) {
163 dialogs.emplace_back(dlg.first);
164 }
165 }
166 return dialogs;
167}
168
170{
171 if (!docking_container) return;
172
173 // check if we want to save the state
175 int save_state = prefs->getInt(save_dialog_position, PREFS_DIALOGS_STATE_SAVE);
176 if (save_state == PREFS_DIALOGS_STATE_NONE) return;
177
178 // save state of docked dialogs and currently open floating ones
179 auto keyfile = docking_container->save_container_state();
180
181 // save transient state of floating dialogs that user might have opened interacting with the app
182 int idx = 1;
183 for (auto dlg : _floating_dialogs) {
184 auto state = dlg.second.get();
185 auto&& type = dlg.first;
186 auto index = std::to_string(idx++);
187 // state may be empty; all that means it that dialog hasn't been opened yet,
188 // but when it is, then it should be open in a floating state
189 keyfile->set_string(transient_group, "state" + index, state ? state->to_data() : "");
190 auto dialogs = count_dialogs(state);
191 if (!state) {
192 dialogs.emplace_back(type);
193 }
194 keyfile->set_string_list(transient_group, "dialogs" + index, dialogs);
195 }
196 keyfile->set_integer(transient_group, "count", _floating_dialogs.size());
197
198 std::string filename = Glib::build_filename(Inkscape::IO::Resource::profile_path(), dialogs_state);
199 try {
200 keyfile->save_to_file(filename);
201 } catch (Glib::FileError const &error) {
202 std::cerr << G_STRFUNC << ": " << error.what() << std::endl;
203 }
204}
205
206// load transient dialog state - it includes state of floating dialogs that may or may not be open
207void DialogManager::load_transient_state(Glib::KeyFile *file)
208{
209 int count = file->get_integer(transient_group, "count");
210 for (int i = 0; i < count; ++i) {
211 auto index = std::to_string(i + 1);
212 auto dialogs = file->get_string_list(transient_group, "dialogs" + index);
213 auto state = file->get_string(transient_group, "state" + index);
214
215 auto keyfile = Glib::KeyFile::create();
216 if (!state.empty()) {
217 keyfile->load_from_data(state);
218 }
219 for (auto type : dialogs) {
220 _floating_dialogs[type] = keyfile;
221 }
222 }
223}
224
225bool file_exists(const std::string& filepath) {
226#ifdef G_OS_WIN32
227 bool exists = filesystem::exists(filesystem::u8path(filepath));
228#else
229 bool exists = filesystem::exists(filesystem::path(filepath));
230#endif
231 return exists;
232}
233
234// restore state of dialogs; populate docking container and open visible floating dialogs
235void DialogManager::restore_dialogs_state(DialogContainer *docking_container, bool include_floating)
236{
237 if (!docking_container) return;
238
240 int save_state = prefs->getInt(save_dialog_position, PREFS_DIALOGS_STATE_SAVE);
241 if (save_state == PREFS_DIALOGS_STATE_NONE) return;
242
243 try {
244 auto keyfile = Glib::KeyFile::create();
245 std::string filename = Glib::build_filename(Inkscape::IO::Resource::profile_path(), dialogs_state);
246
247 bool exists = file_exists(filename);
248
249 if (exists && keyfile->load_from_file(filename)) {
250 // restore visible dialogs first; that state is up-to-date
251 docking_container->load_container_state(keyfile.get(), include_floating);
252
253 // then load transient data too; it may be older than above
254 if (include_floating) {
255 try {
256 load_transient_state(keyfile.get());
257 } catch (Glib::Error const &error) {
258 std::cerr << G_STRFUNC << ": transient state not loaded - " << error.what() << std::endl;
259 }
260 }
261 }
262 else {
263 // state not available or not valid; prepare defaults
264 dialog_defaults(docking_container);
265 }
266 } catch (Glib::Error const &error) {
267 std::cerr << G_STRFUNC << ": dialogs state not loaded - " << error.what() << std::endl;
268 }
269}
270
271void DialogManager::remove_dialog_floating_state(const Glib::ustring& dialog_type) {
272 auto it = _floating_dialogs.find(dialog_type);
273 if (it != _floating_dialogs.end()) {
274 _floating_dialogs.erase(it);
275 }
276}
277
278// apply defaults when dialog state cannot be loaded / doesn't exist:
279// here we load defaults from dedicated ini file
281 auto keyfile = Glib::KeyFile::create();
282 // default/initial state used when running Inkscape for the first time
283 std::string filename = Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::UIS, "default-dialog-state.ini");
284
285 bool exists = file_exists(filename);
286
287 if (exists && keyfile->load_from_file(filename)) {
288 // populate info about floating dialogs, so when users try opening them,
289 // they will pop up in a window, not docked
290 load_transient_state(keyfile.get());
291 // create docked dialogs only, if any
292 docking_container->load_container_state(keyfile.get(), false);
293 }
294 else {
295 g_warning("Cannot load default dialog state %s", filename.c_str());
296 }
297}
298
299} // namespace Inkscape::UI::Dialog
300
301/*
302 Local Variables:
303 mode:c++
304 c-file-style:"stroustrup"
305 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
306 indent-tabs-mode:nil
307 fill-column:99
308 End:
309*/
310// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
static InkscapeApplication * instance()
Singleton instance.
Gtk::Application * gtk_app()
The Gtk application instance, or NULL if running headless without display.
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
A widget that manages DialogNotebook's and other widgets inside a horizontal DialogMultipaned contain...
Glib::RefPtr< Glib::KeyFile > save_container_state()
Save container state.
void load_container_state(Glib::KeyFile *keyfile, bool include_floating)
Load last open window's dialog configuration state.
void remove_dialog_floating_state(const Glib::ustring &dialog_type)
std::vector< Glib::ustring > count_dialogs(const Glib::KeyFile *state) const
static DialogManager & singleton()
std::set< DialogWindow * > _hidden_dlg_windows
void restore_dialogs_state(DialogContainer *docking_container, bool include_floating)
void load_transient_state(Glib::KeyFile *keyfile)
void save_dialogs_state(DialogContainer *docking_container)
void set_floating_dialog_visibility(DialogWindow *wnd, bool show)
bool should_open_floating(const Glib::ustring &dialog_type)
void dialog_defaults(DialogContainer *docking_container)
std::map< std::string, std::shared_ptr< Glib::KeyFile > > _floating_dialogs
std::shared_ptr< Glib::KeyFile > find_dialog_state(const Glib::ustring &dialog_type)
void store_state(DialogWindow &wnd)
std::vector< DialogWindow * > get_all_floating_dialog_windows()
DialogWindow * find_floating_dialog_window(const Glib::ustring &dialog_type)
DialogBase * find_floating_dialog(const Glib::ustring &dialog_type)
DialogWindow holds DialogContainer instances for undocked dialogs.
Css & result
A base class for all dialogs.
A widget that manages DialogNotebook's and other widgets inside a horizontal DialogMultipaned.
A window for floating docks.
@ PREFS_DIALOGS_STATE_SAVE
Definition enums.h:128
@ PREFS_DIALOGS_STATE_NONE
Definition enums.h:127
std::string get_filename(Type type, char const *filename, bool localized, bool silent)
Definition resource.cpp:170
std::string profile_path()
Definition resource.cpp:415
Dialog code.
Definition desktop.h:117
std::optional< window_position_t > dm_get_window_position(Gtk::Window &window)
bool file_exists(const std::string &filepath)
const char save_dialog_position[]
void dm_restore_window_position(Gtk::Window &window, const window_position_t &position)
static constexpr int height
Singleton class to access the preferences file in a convenient way.
Inkscape::IO::Resource - simple resource API.
int index
double width
Glib::ustring name
Definition toolbars.cpp:55