summaryrefslogtreecommitdiffstats
path: root/src/inkview-window.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/inkview-window.cpp452
1 files changed, 452 insertions, 0 deletions
diff --git a/src/inkview-window.cpp b/src/inkview-window.cpp
new file mode 100644
index 0000000..b299a64
--- /dev/null
+++ b/src/inkview-window.cpp
@@ -0,0 +1,452 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Inkview - An SVG file viewer.
+ */
+/*
+ * Authors:
+ * Tavmjong Bah
+ *
+ * Copyright (C) 2018 Authors
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ * Read the file 'COPYING' for more information.
+ *
+ */
+
+
+#include "inkview-window.h"
+
+#include <iostream>
+
+#include "document.h"
+
+#include "ui/monitor.h"
+#include "ui/view/svg-view-widget.h"
+
+#include "util/units.h"
+
+InkviewWindow::InkviewWindow(const Gio::Application::type_vec_files files,
+ bool fullscreen,
+ bool recursive,
+ int timer,
+ double scale,
+ bool preload
+ )
+ : _files(files)
+ , _fullscreen(fullscreen)
+ , _recursive(recursive)
+ , _timer(timer)
+ , _scale(scale)
+ , _preload(preload)
+ , _index(-1)
+ , _view(nullptr)
+ , _controlwindow(nullptr)
+{
+ _files = create_file_list(_files);
+
+ if (_preload) {
+ preload_documents();
+ }
+
+ if (_files.empty()) {
+ throw NoValidFilesException();
+ }
+
+ _documents.resize( _files.size(), nullptr); // We keep _documents and _files in sync.
+
+ // Callbacks
+ signal_key_press_event().connect(sigc::mem_fun(*this, &InkviewWindow::key_press), false);
+
+ if (_timer) {
+ Glib::signal_timeout().connect_seconds(sigc::mem_fun(*this, &InkviewWindow::on_timer), _timer);
+ }
+
+ // Actions
+ add_action( "show_first", sigc::mem_fun(*this, &InkviewWindow::show_first) );
+ add_action( "show_prev", sigc::mem_fun(*this, &InkviewWindow::show_prev) );
+ add_action( "show_next", sigc::mem_fun(*this, &InkviewWindow::show_next) );
+ add_action( "show_last", sigc::mem_fun(*this, &InkviewWindow::show_last) );
+
+ // ToDo: Add Pause, Resume.
+
+ if (_fullscreen) {
+ Gtk::Window::fullscreen();
+ }
+
+ // Show first file
+ activate_action( "show_first" );
+}
+
+std::vector<Glib::RefPtr<Gio::File> >
+InkviewWindow::create_file_list(const std::vector<Glib::RefPtr<Gio::File > >& files)
+{
+ std::vector<Glib::RefPtr<Gio::File> > valid_files;
+
+ static bool first = true;
+
+ for (auto file : files) {
+ Gio::FileType type = file->query_file_type();
+ switch (type) {
+ case Gio::FILE_TYPE_NOT_KNOWN:
+ std::cerr << "InkviewWindow: File or directory does not exist: "
+ << file->get_basename() << std::endl;
+ break;
+
+ case Gio::FILE_TYPE_REGULAR:
+ {
+ // Only look at SVG and SVGZ files.
+ std::string basename = file->get_basename();
+ std::string extension = basename.substr(basename.find_last_of(".") + 1);
+ if (extension == "svg" || extension == "svgz") {
+ valid_files.push_back(file);
+ }
+ break;
+ }
+
+ case Gio::FILE_TYPE_DIRECTORY:
+ {
+ if (_recursive || first) {
+ // No easy way to get children of directory!
+ Glib::RefPtr<Gio::FileEnumerator> children = file->enumerate_children();
+ Glib::RefPtr<Gio::FileInfo> info;
+ std::vector<Glib::RefPtr<Gio::File> > input;
+ while ((info = children->next_file())) {
+ input.push_back(children->get_child(info));
+ }
+ auto new_files = create_file_list(input);
+ valid_files.insert(valid_files.end(), new_files.begin(), new_files.end());
+ }
+ break;
+ }
+ default:
+ std::cerr << "InkviewWindow: Unknown file type: " << type << std::endl;
+ }
+ first = false;
+ }
+
+ return valid_files;
+}
+
+void
+InkviewWindow::update_title()
+{
+ Glib::ustring title(_documents[_index]->getDocumentName());
+
+ if (_documents.size() > 1) {
+ title += Glib::ustring::compose(" (%1/%2)", _index+1, _documents.size());
+ }
+
+ set_title(title);
+}
+
+// Returns true if successfully shows document.
+bool
+InkviewWindow::show_document(SPDocument* document)
+{
+ document->ensureUpToDate(); // Crashes on some documents if this isn't called!
+
+ // Resize window: (Might be better to use get_monitor_geometry_at_window(this))
+ Gdk::Rectangle monitor_geometry = Inkscape::UI::get_monitor_geometry_primary();
+ int width = MIN((int)document->getWidth().value("px") * _scale, monitor_geometry.get_width());
+ int height = MIN((int)document->getHeight().value("px") * _scale, monitor_geometry.get_height());
+ resize (width, height);
+
+ if (_view) {
+ _view->setDocument(document);
+ } else {
+ _view = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(document));
+ add (*_view);
+ }
+
+ update_title();
+
+ return true;
+}
+
+
+// Load document, if fail, remove entry from lists.
+SPDocument*
+InkviewWindow::load_document()
+{
+ SPDocument* document = _documents[_index];
+
+ if (!document) {
+ // We need to load document. ToDo: Pass Gio::File. Is get_base_name() better?
+ document = SPDocument::createNewDoc (_files[_index]->get_parse_name().c_str(), true, false);
+ if (document) {
+ // We've successfully loaded it!
+ _documents[_index] = document;
+ }
+ }
+
+ if (!document) {
+ // Failed to load document, remove from vectors.
+ _documents.erase(std::next(_documents.begin(), _index));
+ _files.erase( std::next( _files.begin(), _index));
+ }
+
+ return document;
+}
+
+
+
+void
+InkviewWindow::preload_documents()
+{
+ for (auto it =_files.begin(); it != _files.end(); ) {
+
+ SPDocument* document =
+ SPDocument::createNewDoc ((*it)->get_parse_name().c_str(), true, false);
+ if (document) {
+ _documents.push_back(document);
+ ++it;
+ } else {
+ it = _files.erase(it);
+ }
+ }
+}
+
+static std::string window_markup = R"(
+<interface>
+ <object class="GtkWindow" id="ControlWindow">
+ <child>
+ <object class="GtkButtonBox">
+ <child>
+ <object class="GtkButton" id="show-first">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">go-first</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="show-prev">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">go-previous</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="show-next">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">go-next</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="show-last">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon_name">go-last</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
+)";
+
+void
+InkviewWindow::show_control()
+{
+ if (!_controlwindow) {
+
+ auto builder = Gtk::Builder::create();
+ try
+ {
+ builder->add_from_string(window_markup);
+ }
+ catch (const Glib::Error& err)
+ {
+ std::cerr << "InkviewWindow::show_control: builder failed: " << err.what().raw() << std::endl;
+ return;
+ }
+
+
+ builder->get_widget("ControlWindow", _controlwindow);
+ if (!_controlwindow) {
+ std::cerr << "InkviewWindow::show_control: Control Window not found!" << std::endl;
+ return;
+ }
+
+ // Need to give control window access to viewer window's actions.
+ Glib::RefPtr<Gio::ActionGroup> viewer = get_action_group("win");
+ if (viewer) {
+ _controlwindow->insert_action_group("viewer", viewer);
+ }
+
+ // Gtk::Button not derived from Gtk::Actionable due to ABI issues. Must use Gtk.
+ // Fixed in Gtk4. In Gtk4 this can be replaced by setting the action in the interface.
+ Gtk::Button* button;
+ builder->get_widget("show-first", button);
+ gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_first");
+ builder->get_widget("show-prev", button);
+ gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_prev");
+ builder->get_widget("show-next", button);
+ gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_next");
+ builder->get_widget("show-last", button);
+ gtk_actionable_set_action_name( GTK_ACTIONABLE(button->gobj()), "viewer.show_last");
+
+ _controlwindow->set_resizable(false);
+ _controlwindow->set_transient_for(*this);
+ _controlwindow->show_all();
+
+ } else {
+ _controlwindow->present();
+ }
+}
+
+// Next document
+void
+InkviewWindow::show_next()
+{
+ ++_index;
+
+ SPDocument* document = nullptr;
+
+ while (_index < _documents.size() && !document) {
+ document = load_document();
+ }
+
+ if (document) {
+ // Show new document
+ show_document(document);
+ } else {
+ // Failed to load new document, keep current.
+ --_index;
+ }
+}
+
+// Previous document
+void
+InkviewWindow::show_prev()
+{
+ SPDocument* document = nullptr;
+ int old_index = _index;
+
+ while (_index > 0 && !document) {
+ --_index;
+ document = load_document();
+ }
+
+ if (document) {
+ // Show new document
+ show_document(document);
+ } else {
+ // Failed to load new document, keep current.
+ _index = old_index;
+ }
+}
+
+// Show first document
+void
+InkviewWindow::show_first()
+{
+ _index = -1;
+ show_next();
+}
+
+// Show last document
+void
+InkviewWindow::show_last()
+{
+ _index = _documents.size();
+ show_prev();
+}
+
+bool
+InkviewWindow::key_press(GdkEventKey* event)
+{
+ switch (event->keyval) {
+ case GDK_KEY_Up:
+ case GDK_KEY_Home:
+ show_first();
+ break;
+
+ case GDK_KEY_Down:
+ case GDK_KEY_End:
+ show_last();
+ break;
+
+ case GDK_KEY_F11:
+ if (_fullscreen) {
+ unfullscreen();
+ _fullscreen = false;
+ } else {
+ fullscreen();
+ _fullscreen = true;
+ }
+ break;
+
+ case GDK_KEY_Return:
+ show_control();
+ break;
+
+ case GDK_KEY_KP_Page_Down:
+ case GDK_KEY_Page_Down:
+ case GDK_KEY_Right:
+ case GDK_KEY_space:
+ show_next();
+ break;
+
+ case GDK_KEY_KP_Page_Up:
+ case GDK_KEY_Page_Up:
+ case GDK_KEY_Left:
+ case GDK_KEY_BackSpace:
+ show_prev();
+ break;
+
+ case GDK_KEY_Escape:
+ case GDK_KEY_q:
+ case GDK_KEY_Q:
+ close();
+ break;
+
+ default:
+ break;
+ }
+ return false;
+}
+
+bool
+InkviewWindow::on_timer()
+{
+ show_next();
+
+ // Stop if at end.
+ if (_index >= _documents.size() - 1) {
+ return false;
+ }
+ return true;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :