// 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 #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 > InkviewWindow::create_file_list(const std::vector >& files) { std::vector > 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 children = file->enumerate_children(); Glib::RefPtr info; std::vector > 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"( True True True go-first True True True go-previous True False True go-next True False True go-last )"; 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 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 :