1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Inkview - An SVG file viewer.
5 */
6 /*
7 * Authors:
8 * Tavmjong Bah
9 *
10 * Copyright (C) 2018 Authors
11 *
12 * The contents of this file may be used under the GNU General Public License Version 2 or later.
13 * Read the file 'COPYING' for more information.
14 *
15 */
16
17
18 #include "inkview-window.h"
19
20 #include <iostream>
21
22 #include "document.h"
23
24 #include "ui/monitor.h"
25 #include "ui/view/svg-view-widget.h"
26
27 #include "util/units.h"
28
InkviewWindow(const Gio::Application::type_vec_files files,bool fullscreen,bool recursive,int timer,double scale,bool preload)29 InkviewWindow::InkviewWindow(const Gio::Application::type_vec_files files,
30 bool fullscreen,
31 bool recursive,
32 int timer,
33 double scale,
34 bool preload
35 )
36 : _files(files)
37 , _fullscreen(fullscreen)
38 , _recursive(recursive)
39 , _timer(timer)
40 , _scale(scale)
41 , _preload(preload)
42 , _index(-1)
43 , _view(nullptr)
44 , _controlwindow(nullptr)
45 {
46 _files = create_file_list(_files);
47
48 if (_preload) {
49 preload_documents();
50 }
51
52 if (_files.empty()) {
53 throw NoValidFilesException();
54 }
55
56 _documents.resize( _files.size(), nullptr); // We keep _documents and _files in sync.
57
58 // Callbacks
59 signal_key_press_event().connect(sigc::mem_fun(*this, &InkviewWindow::key_press), false);
60
61 if (_timer) {
62 Glib::signal_timeout().connect_seconds(sigc::mem_fun(*this, &InkviewWindow::on_timer), _timer);
63 }
64
65 // Actions
66 add_action( "show_first", sigc::mem_fun(*this, &InkviewWindow::show_first) );
67 add_action( "show_prev", sigc::mem_fun(*this, &InkviewWindow::show_prev) );
68 add_action( "show_next", sigc::mem_fun(*this, &InkviewWindow::show_next) );
69 add_action( "show_last", sigc::mem_fun(*this, &InkviewWindow::show_last) );
70
71 // ToDo: Add Pause, Resume.
72
73 if (_fullscreen) {
74 Gtk::Window::fullscreen();
75 }
76
77 // Show first file
78 activate_action( "show_first" );
79 }
80
81 std::vector<Glib::RefPtr<Gio::File> >
create_file_list(const std::vector<Glib::RefPtr<Gio::File>> & files)82 InkviewWindow::create_file_list(const std::vector<Glib::RefPtr<Gio::File > >& files)
83 {
84 std::vector<Glib::RefPtr<Gio::File> > valid_files;
85
86 static bool first = true;
87
88 for (auto file : files) {
89 Gio::FileType type = file->query_file_type();
90 switch (type) {
91 case Gio::FILE_TYPE_NOT_KNOWN:
92 std::cerr << "InkviewWindow: File or directory does not exist: "
93 << file->get_basename() << std::endl;
94 break;
95
96 case Gio::FILE_TYPE_REGULAR:
97 {
98 // Only look at SVG and SVGZ files.
99 std::string basename = file->get_basename();
100 std::string extension = basename.substr(basename.find_last_of(".") + 1);
101 if (extension == "svg" || extension == "svgz") {
102 valid_files.push_back(file);
103 }
104 break;
105 }
106
107 case Gio::FILE_TYPE_DIRECTORY:
108 {
109 if (_recursive || first) {
110 // No easy way to get children of directory!
111 Glib::RefPtr<Gio::FileEnumerator> children = file->enumerate_children();
112 Glib::RefPtr<Gio::FileInfo> info;
113 std::vector<Glib::RefPtr<Gio::File> > input;
114 while ((info = children->next_file())) {
115 input.push_back(children->get_child(info));
116 }
117 auto new_files = create_file_list(input);
118 valid_files.insert(valid_files.end(), new_files.begin(), new_files.end());
119 }
120 break;
121 }
122 default:
123 std::cerr << "InkviewWindow: Unknown file type: " << type << std::endl;
124 }
125 first = false;
126 }
127
128 return valid_files;
129 }
130
131 void
update_title()132 InkviewWindow::update_title()
133 {
134 Glib::ustring title(_documents[_index]->getDocumentName());
135
136 if (_documents.size() > 1) {
137 title += Glib::ustring::compose(" (%1/%2)", _index+1, _documents.size());
138 }
139
140 set_title(title);
141 }
142
143 // Returns true if successfully shows document.
144 bool
show_document(SPDocument * document)145 InkviewWindow::show_document(SPDocument* document)
146 {
147 document->ensureUpToDate(); // Crashes on some documents if this isn't called!
148
149 // Resize window: (Might be better to use get_monitor_geometry_at_window(this))
150 Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_primary();
151 int width = MIN((int)document->getWidth().value("px") * _scale, monitor_geometry.get_width());
152 int height = MIN((int)document->getHeight().value("px") * _scale, monitor_geometry.get_height());
153 resize (width, height);
154
155 if (_view) {
156 _view->setDocument(document);
157 } else {
158 _view = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(document));
159 add (*_view);
160 }
161
162 update_title();
163
164 return true;
165 }
166
167
168 // Load document, if fail, remove entry from lists.
169 SPDocument*
load_document()170 InkviewWindow::load_document()
171 {
172 SPDocument* document = _documents[_index];
173
174 if (!document) {
175 // We need to load document. ToDo: Pass Gio::File. Is get_base_name() better?
176 document = SPDocument::createNewDoc (_files[_index]->get_parse_name().c_str(), true, false);
177 if (document) {
178 // We've successfully loaded it!
179 _documents[_index] = document;
180 }
181 }
182
183 if (!document) {
184 // Failed to load document, remove from vectors.
185 _documents.erase(std::next(_documents.begin(), _index));
186 _files.erase( std::next( _files.begin(), _index));
187 }
188
189 return document;
190 }
191
192
193
194 void
preload_documents()195 InkviewWindow::preload_documents()
196 {
197 for (auto it =_files.begin(); it != _files.end(); ) {
198
199 SPDocument* document =
200 SPDocument::createNewDoc ((*it)->get_parse_name().c_str(), true, false);
201 if (document) {
202 _documents.push_back(document);
203 ++it;
204 } else {
205 it = _files.erase(it);
206 }
207 }
208 }
209
210 static std::string window_markup = R"(
211 <interface>
212 <object class="GtkWindow" id="ControlWindow">
213 <child>
214 <object class="GtkButtonBox">
215 <child>
216 <object class="GtkButton" id="show-first">
217 <property name="visible">True</property>
218 <property name="can_focus">True</property>
219 <child>
220 <object class="GtkImage">
221 <property name="visible">True</property>
222 <property name="icon_name">go-first</property>
223 </object>
224 </child>
225 </object>
226 </child>
227 <child>
228 <object class="GtkButton" id="show-prev">
229 <property name="visible">True</property>
230 <property name="can_focus">True</property>
231 <child>
232 <object class="GtkImage">
233 <property name="visible">True</property>
234 <property name="icon_name">go-previous</property>
235 </object>
236 </child>
237 </object>
238 </child>
239 <child>
240 <object class="GtkButton" id="show-next">
241 <property name="visible">True</property>
242 <property name="can_focus">False</property>
243 <child>
244 <object class="GtkImage">
245 <property name="visible">True</property>
246 <property name="icon_name">go-next</property>
247 </object>
248 </child>
249 </object>
250 </child>
251 <child>
252 <object class="GtkButton" id="show-last">
253 <property name="visible">True</property>
254 <property name="can_focus">False</property>
255 <child>
256 <object class="GtkImage">
257 <property name="visible">True</property>
258 <property name="icon_name">go-last</property>
259 </object>
260 </child>
261 </object>
262 </child>
263 </object>
264 </child>
265 </object>
266 </interface>
267 )";
268
269 void
show_control()270 InkviewWindow::show_control()
271 {
272 if (!_controlwindow) {
273
274 auto builder = Gtk::Builder::create();
275 try
276 {
277 builder->add_from_string(window_markup);
278 }
279 catch (const Glib::Error& err)
280 {
281 std::cerr << "InkviewWindow::show_control: builder failed: " << err.what() << std::endl;
282 return;
283 }
284
285
286 builder->get_widget("ControlWindow", _controlwindow);
287 if (!_controlwindow) {
288 std::cerr << "InkviewWindow::show_control: Control Window not found!" << std::endl;
289 return;
290 }
291
292 // Need to give control window access to viewer window's actions.
293 Glib::RefPtr<Gio::ActionGroup> viewer = get_action_group("win");
294 if (viewer) {
295 _controlwindow->insert_action_group("viewer", viewer);
296 }
297
298 // Gtk::Button not derived from Gtk::Actionable due to ABI issues. Must use Gtk.
299 // Fixed in Gtk4. In Gtk4 this can be replaced by setting the action in the interface.
300 Gtk::Button* button;
301 builder->get_widget("show-first", button);
302 gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_first");
303 builder->get_widget("show-prev", button);
304 gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_prev");
305 builder->get_widget("show-next", button);
306 gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_next");
307 builder->get_widget("show-last", button);
308 gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_last");
309
310 _controlwindow->set_resizable(false);
311 _controlwindow->set_transient_for(*this);
312 _controlwindow->show_all();
313
314 } else {
315 _controlwindow->present();
316 }
317 }
318
319 // Next document
320 void
show_next()321 InkviewWindow::show_next()
322 {
323 ++_index;
324
325 SPDocument* document = nullptr;
326
327 while (_index < _documents.size() && !document) {
328 document = load_document();
329 }
330
331 if (document) {
332 // Show new document
333 show_document(document);
334 } else {
335 // Failed to load new document, keep current.
336 --_index;
337 }
338 }
339
340 // Previous document
341 void
show_prev()342 InkviewWindow::show_prev()
343 {
344 SPDocument* document = nullptr;
345 int old_index = _index;
346
347 while (_index > 0 && !document) {
348 --_index;
349 document = load_document();
350 }
351
352 if (document) {
353 // Show new document
354 show_document(document);
355 } else {
356 // Failed to load new document, keep current.
357 _index = old_index;
358 }
359 }
360
361 // Show first document
362 void
show_first()363 InkviewWindow::show_first()
364 {
365 _index = -1;
366 show_next();
367 }
368
369 // Show last document
370 void
show_last()371 InkviewWindow::show_last()
372 {
373 _index = _documents.size();
374 show_prev();
375 }
376
377 bool
key_press(GdkEventKey * event)378 InkviewWindow::key_press(GdkEventKey* event)
379 {
380 switch (event->keyval) {
381 case GDK_KEY_Up:
382 case GDK_KEY_Home:
383 show_first();
384 break;
385
386 case GDK_KEY_Down:
387 case GDK_KEY_End:
388 show_last();
389 break;
390
391 case GDK_KEY_F11:
392 if (_fullscreen) {
393 unfullscreen();
394 _fullscreen = false;
395 } else {
396 fullscreen();
397 _fullscreen = true;
398 }
399 break;
400
401 case GDK_KEY_Return:
402 show_control();
403 break;
404
405 case GDK_KEY_KP_Page_Down:
406 case GDK_KEY_Page_Down:
407 case GDK_KEY_Right:
408 case GDK_KEY_space:
409 show_next();
410 break;
411
412 case GDK_KEY_KP_Page_Up:
413 case GDK_KEY_Page_Up:
414 case GDK_KEY_Left:
415 case GDK_KEY_BackSpace:
416 show_prev();
417 break;
418
419 case GDK_KEY_Escape:
420 case GDK_KEY_q:
421 case GDK_KEY_Q:
422 close();
423 break;
424
425 default:
426 break;
427 }
428 return false;
429 }
430
431 bool
on_timer()432 InkviewWindow::on_timer()
433 {
434 show_next();
435
436 // Stop if at end.
437 if (_index >= _documents.size() - 1) {
438 return false;
439 }
440 return true;
441 }
442
443 /*
444 Local Variables:
445 mode:c++
446 c-file-style:"stroustrup"
447 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
448 indent-tabs-mode:nil
449 fill-column:99
450 End:
451 */
452 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
453