#include "Control.h" #include "PrintHandler.h" #include "LatexController.h" #include "layer/LayerController.h" #include "PageBackgroundChangeController.h" #include "gui/Cursor.h" #include "gui/dialog/AboutDialog.h" #include "gui/dialog/GotoDialog.h" #include "gui/dialog/FillTransparencyDialog.h" #include "gui/dialog/FormatDialog.h" #include "gui/dialog/PageTemplateDialog.h" #include "gui/dialog/SettingsDialog.h" #include "gui/dialog/SelectBackgroundColorDialog.h" #include "gui/dialog/toolbarCustomize/ToolbarDragDropHandler.h" #include "gui/dialog/ToolbarManageDialog.h" #include "gui/inputdevices/TouchHelper.h" #include "gui/TextEditor.h" #include "gui/toolbarMenubar/model/ToolbarData.h" #include "gui/toolbarMenubar/model/ToolbarModel.h" #include "gui/toolbarMenubar/ToolMenuHandler.h" #include "gui/XournalView.h" #include "jobs/AutosaveJob.h" #include "jobs/BlockingJob.h" #include "jobs/CustomExportJob.h" #include "jobs/PdfExportJob.h" #include "jobs/SaveJob.h" #include "xojfile/LoadHandler.h" #include "model/BackgroundImage.h" #include "model/FormatDefinitions.h" #include "model/XojPage.h" #include "pagetype/PageTypeHandler.h" #include "pagetype/PageTypeMenu.h" #include "settings/ButtonConfig.h" #include "stockdlg/XojOpenDlg.h" #include "undo/AddUndoAction.h" #include "undo/DeleteUndoAction.h" #include "undo/InsertDeletePageUndoAction.h" #include "undo/InsertUndoAction.h" #include "view/DocumentView.h" #include "view/TextView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Control::Control(GladeSearchpath* gladeSearchPath) { XOJ_INIT_TYPE(Control); this->win = NULL; this->recent = new RecentManager(); this->undoRedo = new UndoRedoHandler(this); this->recent->addListener(this); this->undoRedo->addUndoRedoListener(this); this->isBlocking = false; this->gladeSearchPath = gladeSearchPath; this->metadata = new MetadataManager(); this->cursor = new Cursor(this); this->lastAction = ACTION_NONE; this->lastGroup = GROUP_NOGROUP; this->lastEnabled = false; this->fullscreen = false; Path name = Path(g_get_home_dir()); name /= CONFIG_DIR; name /= SETTINGS_XML_FILE; this->settings = new Settings(name); this->settings->load(); TextView::setDpi(settings->getDisplayDpi()); this->pageTypes = new PageTypeHandler(gladeSearchPath); this->newPageType = new PageTypeMenu(this->pageTypes, settings, true, true); this->sidebar = NULL; this->searchBar = NULL; this->audioController = new AudioController(this->settings,this); this->scrollHandler = new ScrollHandler(this); this->scheduler = new XournalScheduler(); this->hiddenFullscreenWidgets = NULL; this->sidebarHidden = false; this->autosaveTimeout = 0; this->statusbar = NULL; this->lbState = NULL; this->pgState = NULL; this->maxState = 0; this->doc = new Document(this); // for crashhandling setEmergencyDocument(this->doc); this->zoom = new ZoomControl(); this->zoom->setZoom100(this->settings->getDisplayDpi() / 72.0); this->toolHandler = new ToolHandler(this, this, this->settings); this->toolHandler->loadSettings(); /** * This is needed to update the previews */ this->changeTimout = g_timeout_add_seconds(5, (GSourceFunc) checkChangedDocument, this); this->clipboardHandler = NULL; this->dragDropHandler = NULL; this->pageBackgroundChangeController = new PageBackgroundChangeController(this); this->layerController = new LayerController(this); this->layerController->registerListener(this); } Control::~Control() { XOJ_CHECK_TYPE(Control); g_source_remove(this->changeTimout); this->enableAutosave(false); deleteLastAutosaveFile(""); this->scheduler->stop(); for (XojPage* page : this->changedPages) { page->unreference(); } delete this->clipboardHandler; this->clipboardHandler = NULL; delete this->recent; this->recent = NULL; delete this->undoRedo; this->undoRedo = NULL; delete this->settings; this->settings = NULL; delete this->toolHandler; this->toolHandler = NULL; delete this->sidebar; this->sidebar = NULL; delete this->doc; this->doc = NULL; delete this->searchBar; this->searchBar = NULL; delete this->scrollHandler; this->scrollHandler = NULL; delete this->newPageType; this->newPageType = NULL; delete this->pageTypes; this->pageTypes = NULL; delete this->metadata; this->metadata = NULL; delete this->cursor; this->cursor = NULL; delete this->zoom; this->zoom = NULL; delete this->scheduler; this->scheduler = NULL; delete this->dragDropHandler; this->dragDropHandler = NULL; delete this->audioController; this->audioController = NULL; delete this->pageBackgroundChangeController; this->pageBackgroundChangeController = NULL; delete this->layerController; this->layerController = NULL; XOJ_RELEASE_TYPE(Control); } void Control::renameLastAutosaveFile() { XOJ_CHECK_TYPE(Control); if (this->lastAutosaveFilename.isEmpty()) { return; } Path filename = this->lastAutosaveFilename; Path renamed = Util::getAutosaveFilename(); renamed.clearExtensions(); renamed += filename.getFilename(); string error; if (filename.exists()) { int result = g_unlink(renamed.c_str()); if (result != 0) { error += FS(_F("Could not delete old autosave file \"{1}\"") % renamed.str()); } result = g_rename(filename.c_str(), renamed.c_str()); if (result != 0) { if (!error.empty()) { error += ",\n"; } error += FS(_F("Could not rename autosave file from \"{1}\" to \"{2}\"") % filename.str() % renamed.str()); } } else { int result = g_unlink(renamed.c_str()); if (result != 0) { error += FS(_F("Could not delete old autosave file \"{1}\"") % renamed.str()); } this->save(false); result = g_rename(filename.c_str(), renamed.c_str()); if (result != 0) { if (!error.empty()) { error += ",\n"; } error += FS(_F("Could not rename autosave file from \"{1}\" to \"{2}\"") % filename.str() % renamed.str()); } } if (!error.empty()) { string msg = FS(_F("Autosave failed with an error: {1}") % error); XojMsgBox::showErrorToUser(getGtkWindow(), msg); } } void Control::setLastAutosaveFile(Path newAutosaveFile) { this->lastAutosaveFilename = newAutosaveFile; } void Control::deleteLastAutosaveFile(Path newAutosaveFile) { XOJ_CHECK_TYPE(Control); if (!this->lastAutosaveFilename.isEmpty()) { // delete old autosave file g_unlink(this->lastAutosaveFilename.c_str()); } this->lastAutosaveFilename = newAutosaveFile; } bool Control::checkChangedDocument(Control* control) { XOJ_CHECK_TYPE_OBJ(control, Control); if (!control->doc->tryLock()) { // call again later return true; } for (XojPage* page : control->changedPages) { int p = control->doc->indexOf(page); if (p != -1) { control->firePageChanged(p); } page->unreference(); } control->changedPages.clear(); control->doc->unlock(); // Call again return true; } void Control::saveSettings() { XOJ_CHECK_TYPE(Control); this->toolHandler->saveSettings(); gint width = 0; gint height = 0; gtk_window_get_size(getGtkWindow(), &width, &height); if (!this->win->isMaximized()) { this->settings->setMainWndSize(width, height); } this->settings->setMainWndMaximized(this->win->isMaximized()); this->sidebar->saveSize(); } void Control::initWindow(MainWindow* win) { XOJ_CHECK_TYPE(Control); win->setRecentMenu(recent->getMenu()); selectTool(toolHandler->getToolType()); this->win = win; this->zoom->initZoomHandler(win->getXournal()->getWidget(), win->getXournal()); this->sidebar = new Sidebar(win, this); XojMsgBox::setDefaultWindow(getGtkWindow()); updatePageNumbers(0, size_t_npos); toolHandler->eraserTypeChanged(); this->searchBar = new SearchBar(this); // Disable undo buttons undoRedoChanged(); setViewTwoPages(settings->isShowTwoPages()); setViewPresentationMode(settings->isPresentationMode()); penSizeChanged(); eraserSizeChanged(); hilighterSizeChanged(); updateDeletePageButton(); this->clipboardHandler = new ClipboardHandler(this, win->getXournal()->getWidget()); this->enableAutosave(settings->isAutosaveEnabled()); win->setFontButtonFont(settings->getFont()); // rotation snapping enabled by default fireActionSelected(GROUP_SNAPPING, ACTION_ROTATION_SNAPPING); // grid snapping enabled by default fireActionSelected(GROUP_GRID_SNAPPING, ACTION_GRID_SNAPPING); } bool Control::autosaveCallback(Control* control) { XOJ_CHECK_TYPE_OBJ(control, Control); if (!control->undoRedo->isChangedAutosave()) { // do nothing, nothing changed return true; } else { g_message("Info: autosave document..."); } AutosaveJob* job = new AutosaveJob(control); control->scheduler->addJob(job, JOB_PRIORITY_NONE); job->unref(); return true; } void Control::enableAutosave(bool enable) { XOJ_CHECK_TYPE(Control); if (this->autosaveTimeout) { g_source_remove(this->autosaveTimeout); this->autosaveTimeout = 0; } if (enable) { int timeout = settings->getAutosaveTimeout() * 60; this->autosaveTimeout = g_timeout_add_seconds(timeout, (GSourceFunc) autosaveCallback, this); } } void Control::updatePageNumbers(size_t page, size_t pdfPage) { XOJ_CHECK_TYPE(Control); if (this->win == NULL) { return; } this->win->updatePageNumbers(page, this->doc->getPageCount(), pdfPage); this->sidebar->selectPageNr(page, pdfPage); this->metadata->storeMetadata(this->doc->getEvMetadataFilename().str(), page, getZoomControl()->getZoom()); int current = this->win->getXournal()->getCurrentPage(); int count = this->doc->getPageCount(); fireEnableAction(ACTION_GOTO_FIRST, current != 0); fireEnableAction(ACTION_GOTO_BACK, current != 0); fireEnableAction(ACTION_GOTO_PREVIOUS_ANNOTATED_PAGE, current != 0); fireEnableAction(ACTION_GOTO_PAGE, count > 1); fireEnableAction(ACTION_GOTO_NEXT, current < count - 1); fireEnableAction(ACTION_GOTO_LAST, current < count - 1); fireEnableAction(ACTION_GOTO_NEXT_ANNOTATED_PAGE, current < count - 1); } void Control::actionPerformed(ActionType type, ActionGroup group, GdkEvent* event, GtkMenuItem* menuitem, GtkToolButton* toolbutton, bool enabled) { XOJ_CHECK_TYPE(Control); if (layerController->actionPerformed(type)) { return; } switch (type) { // Menu File case ACTION_NEW: clearSelectionEndText(); newFile(); break; case ACTION_OPEN: openFile(); break; case ACTION_ANNOTATE_PDF: clearSelectionEndText(); annotatePdf("", false, false); break; case ACTION_SAVE: save(); break; case ACTION_SAVE_AS: saveAs(); break; case ACTION_EXPORT_AS_PDF: exportAsPdf(); break; case ACTION_EXPORT_AS: exportAs(); break; case ACTION_PRINT: print(); break; case ACTION_QUIT: quit(); break; // Menu Edit case ACTION_UNDO: this->clearSelection(); // Move out of text mode to allow textboxundo to work clearSelectionEndText(); undoRedo->undo(); this->resetShapeRecognizer(); break; case ACTION_REDO: this->clearSelection(); undoRedo->redo(); this->resetShapeRecognizer(); break; case ACTION_CUT: cut(); break; case ACTION_COPY: copy(); break; case ACTION_PASTE: paste(); break; case ACTION_SEARCH: clearSelectionEndText(); searchBar->showSearchBar(true); break; case ACTION_DELETE: if (!win->getXournal()->actionDelete()) { deleteSelection(); } break; case ACTION_SETTINGS: showSettings(); break; // Menu Navigation case ACTION_GOTO_FIRST: scrollHandler->scrollToPage(0); break; case ACTION_GOTO_BACK: scrollHandler->goToPreviousPage(); break; case ACTION_GOTO_PAGE: gotoPage(); break; case ACTION_GOTO_NEXT: scrollHandler->goToNextPage(); break; case ACTION_GOTO_LAST: scrollHandler->scrollToPage(this->doc->getPageCount() - 1); break; case ACTION_GOTO_NEXT_ANNOTATED_PAGE: scrollHandler->scrollToAnnotatedPage(true); break; case ACTION_GOTO_PREVIOUS_ANNOTATED_PAGE: scrollHandler->scrollToAnnotatedPage(false); break; // Menu Journal case ACTION_NEW_PAGE_BEFORE: insertNewPage(getCurrentPageNo()); break; case ACTION_NEW_PAGE_AFTER: insertNewPage(getCurrentPageNo() + 1); break; case ACTION_NEW_PAGE_AT_END: insertNewPage(this->doc->getPageCount()); break; case ACTION_DELETE_PAGE: deletePage(); break; case ACTION_PAPER_FORMAT: paperFormat(); break; case ACTION_CONFIGURE_PAGE_TEMPLATE: paperTemplate(); break; case ACTION_PAPER_BACKGROUND_COLOR: changePageBackgroundColor(); break; // Menu Tools case ACTION_TOOL_PEN: clearSelection(); if (enabled) { selectTool(TOOL_PEN); } break; case ACTION_TOOL_ERASER: clearSelection(); if (enabled) { selectTool(TOOL_ERASER); } break; case ACTION_TOOL_ERASER_STANDARD: if (enabled) { toolHandler->setEraserType(ERASER_TYPE_DEFAULT); } break; case ACTION_TOOL_ERASER_DELETE_STROKE: if (enabled) { toolHandler->setEraserType(ERASER_TYPE_DELETE_STROKE); } break; case ACTION_TOOL_ERASER_WHITEOUT: if (enabled) { toolHandler->setEraserType(ERASER_TYPE_WHITEOUT); } break; case ACTION_TOOL_HILIGHTER: clearSelection(); if (enabled) { selectTool(TOOL_HILIGHTER); } break; case ACTION_TOOL_TEXT: clearSelection(); if (enabled) { selectTool(TOOL_TEXT); } break; case ACTION_TOOL_IMAGE: clearSelection(); if (enabled) { selectTool(TOOL_IMAGE); } break; case ACTION_TOOL_SELECT_RECT: if (enabled) { selectTool(TOOL_SELECT_RECT); } break; case ACTION_TOOL_SELECT_REGION: if (enabled) { selectTool(TOOL_SELECT_REGION); } break; case ACTION_TOOL_SELECT_OBJECT: if (enabled) { selectTool(TOOL_SELECT_OBJECT); } break; case ACTION_TOOL_PLAY_OBJECT: if (enabled) { selectTool(TOOL_PLAY_OBJECT); } break; case ACTION_TOOL_VERTICAL_SPACE: clearSelection(); if (enabled) { selectTool(TOOL_VERTICAL_SPACE); } break; case ACTION_TOOL_HAND: if (enabled) { selectTool(TOOL_HAND); } break; case ACTION_TOOL_DRAW_RECT: case ACTION_TOOL_DRAW_CIRCLE: case ACTION_TOOL_DRAW_ARROW: case ACTION_TOOL_DRAW_COORDINATE_SYSTEM: case ACTION_RULER: case ACTION_SHAPE_RECOGNIZER: setShapeTool(type, enabled); break; case ACTION_TOOL_DEFAULT: if (enabled) { selectDefaultTool(); } break; case ACTION_TOOL_FILL: setFill(enabled); break; case ACTION_SIZE_VERY_THIN: if (enabled) { setToolSize(TOOL_SIZE_VERY_FINE); } break; case ACTION_SIZE_FINE: if (enabled) { setToolSize(TOOL_SIZE_FINE); } break; case ACTION_SIZE_MEDIUM: if (enabled) { setToolSize(TOOL_SIZE_MEDIUM); } break; case ACTION_SIZE_THICK: if (enabled) { setToolSize(TOOL_SIZE_THICK); } break; case ACTION_SIZE_VERY_THICK: if (enabled) { setToolSize(TOOL_SIZE_VERY_THICK); } break; case ACTION_TOOL_ERASER_SIZE_FINE: if (enabled) { this->toolHandler->setEraserSize(TOOL_SIZE_FINE); eraserSizeChanged(); } break; case ACTION_TOOL_ERASER_SIZE_MEDIUM: if (enabled) { this->toolHandler->setEraserSize(TOOL_SIZE_MEDIUM); eraserSizeChanged(); } break; case ACTION_TOOL_ERASER_SIZE_THICK: if (enabled) { this->toolHandler->setEraserSize(TOOL_SIZE_THICK); eraserSizeChanged(); } break; case ACTION_TOOL_PEN_SIZE_VERY_THIN: if (enabled) { this->toolHandler->setPenSize(TOOL_SIZE_VERY_FINE); penSizeChanged(); } break; case ACTION_TOOL_PEN_SIZE_FINE: if (enabled) { this->toolHandler->setPenSize(TOOL_SIZE_FINE); penSizeChanged(); } break; case ACTION_TOOL_PEN_SIZE_MEDIUM: if (enabled) { this->toolHandler->setPenSize(TOOL_SIZE_MEDIUM); penSizeChanged(); } break; case ACTION_TOOL_PEN_SIZE_THICK: if (enabled) { this->toolHandler->setPenSize(TOOL_SIZE_THICK); penSizeChanged(); } break; case ACTION_TOOL_PEN_SIZE_VERY_THICK: if (enabled) { this->toolHandler->setPenSize(TOOL_SIZE_VERY_THICK); penSizeChanged(); } break; case ACTION_TOOL_PEN_FILL: this->toolHandler->setPenFillEnabled(enabled); break; case ACTION_TOOL_PEN_FILL_TRANSPARENCY: selectFillAlpha(true); break; case ACTION_TOOL_HILIGHTER_SIZE_FINE: if (enabled) { this->toolHandler->setHilighterSize(TOOL_SIZE_FINE); hilighterSizeChanged(); } break; case ACTION_TOOL_HILIGHTER_SIZE_MEDIUM: if (enabled) { this->toolHandler->setHilighterSize(TOOL_SIZE_MEDIUM); hilighterSizeChanged(); } break; case ACTION_TOOL_HILIGHTER_SIZE_THICK: if (enabled) { this->toolHandler->setHilighterSize(TOOL_SIZE_THICK); hilighterSizeChanged(); } break; case ACTION_TOOL_HILIGHTER_FILL: this->toolHandler->setHilighterFillEnabled(enabled); break; case ACTION_TOOL_HILIGHTER_FILL_TRANSPARENCY: selectFillAlpha(false); break; case ACTION_FONT_BUTTON_CHANGED: fontChanged(); break; case ACTION_SELECT_FONT: if (win) { win->getToolMenuHandler()->showFontSelectionDlg(); } break; // Used for all colors case ACTION_SELECT_COLOR: case ACTION_SELECT_COLOR_CUSTOM: // nothing to do here, the color toolbar item handles the color break; case ACTION_TEX: runLatex(); break; // Menu View case ACTION_ZOOM_100: case ACTION_ZOOM_FIT: case ACTION_ZOOM_IN: case ACTION_ZOOM_OUT: Util::execInUiThread([=]() { zoomCallback(type); }); break; case ACTION_VIEW_TWO_PAGES: setViewTwoPages(enabled); break; case ACTION_VIEW_PRESENTATION_MODE: setViewPresentationMode(enabled); break; case ACTION_MANAGE_TOOLBAR: manageToolbars(); break; case ACTION_CUSTOMIZE_TOOLBAR: customizeToolbars(); break; case ACTION_FULLSCREEN: enableFullscreen(enabled); break; case ACTION_RECSTOP: audioController->recToggle(); break; case ACTION_ROTATION_SNAPPING: rotationSnappingToggle(); break; case ACTION_GRID_SNAPPING: gridSnappingToggle(); break; // Footer, not really an action, but need an identifier to case ACTION_FOOTER_PAGESPIN: case ACTION_FOOTER_ZOOM_SLIDER: // nothing to do here break; // Menu Help case ACTION_HELP: XojMsgBox::showHelp(getGtkWindow()); break; case ACTION_ABOUT: showAbout(); break; default: g_warning("Unhandled action event: %i / %i", type, group); Stacktrace::printStracktrace(); } if (type >= ACTION_TOOL_PEN && type <= ACTION_TOOL_HAND) { ActionType at = (ActionType) (toolHandler->getToolType() - TOOL_PEN + ACTION_TOOL_PEN); if (type == at && !enabled) { fireActionSelected(GROUP_TOOL, at); } } } bool Control::copy() { if (this->win && this->win->getXournal()->copy()) { return true; } return this->clipboardHandler->copy(); } bool Control::cut() { if (this->win && this->win->getXournal()->cut()) { return true; } return this->clipboardHandler->cut(); } bool Control::paste() { if (this->win && this->win->getXournal()->paste()) { return true; } return this->clipboardHandler->paste(); } void Control::selectFillAlpha(bool pen) { XOJ_CHECK_TYPE(Control); int alpha = 0; if (pen) { alpha = toolHandler->getPenFill(); } else { alpha = toolHandler->getHilighterFill(); } FillTransparencyDialog dlg(gladeSearchPath, alpha); dlg.show(getGtkWindow()); if (dlg.getResultAlpha() == -1) { return; } alpha = dlg.getResultAlpha(); if (pen) { toolHandler->setPenFill(alpha); } else { toolHandler->setHilighterFill(alpha); } } void Control::clearSelectionEndText() { XOJ_CHECK_TYPE(Control); clearSelection(); if (win) { win->getXournal()->endTextAllPages(); } } /** * Fire page selected, but first check if the page Number is valid * * @return the page ID or size_t_npos if the page is not found */ size_t Control::firePageSelected(PageRef page) { XOJ_CHECK_TYPE(Control); this->doc->lock(); size_t pageId = this->doc->indexOf(page); this->doc->unlock(); if (pageId == size_t_npos) { return size_t_npos; } DocumentHandler::firePageSelected(pageId); return pageId; } void Control::firePageSelected(size_t page) { XOJ_CHECK_TYPE(Control); DocumentHandler::firePageSelected(page); } void Control::manageToolbars() { XOJ_CHECK_TYPE(Control); ToolbarManageDialog dlg(this->gladeSearchPath, this->win->getToolbarModel()); dlg.show(GTK_WINDOW(this->win->getWindow())); this->win->updateToolbarMenu(); Path file = Util::getConfigFile(TOOLBAR_CONFIG); this->win->getToolbarModel()->save(file.str()); } void Control::customizeToolbars() { XOJ_CHECK_TYPE(Control); g_return_if_fail(this->win != NULL); if (this->win->getSelectedToolbar()->isPredefined()) { GtkWidget* dialog = gtk_message_dialog_new(getGtkWindow(), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", FC(_F("The Toolbarconfiguration \"{1}\" is predefined, " "would you create a copy to edit?") % this->win->getSelectedToolbar()->getName())); gtk_window_set_transient_for(GTK_WINDOW(dialog), getGtkWindow()); int res = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (res == -8) // Yes { ToolbarData* data = new ToolbarData(*this->win->getSelectedToolbar()); ToolbarModel* model = this->win->getToolbarModel(); for (int i = 0; i < 100; i++) { string id = data->getId() + " Copy"; if (i != 0) { id += " "; id += std::to_string(i); } if (!model->existsId(id)) { if (i != 0) { string filename = data->getName(); filename += " "; filename += _("Copy"); filename += " "; filename += std::to_string(i); data->setName(filename); } else { data->setName(data->getName() + " " + _("Copy")); } data->setId(id); break; } } model->add(data); this->win->toolbarSelected(data); this->win->updateToolbarMenu(); } else { return; } } if (!this->dragDropHandler) { this->dragDropHandler = new ToolbarDragDropHandler(this); } this->dragDropHandler->configure(); } void Control::endDragDropToolbar() { XOJ_CHECK_TYPE(Control); if (!this->dragDropHandler) { return; } this->dragDropHandler->clearToolbarsFromDragAndDrop(); } void Control::startDragDropToolbar() { XOJ_CHECK_TYPE(Control); if (!this->dragDropHandler) { return; } this->dragDropHandler->prepareToolbarsForDragAndDrop(); } bool Control::isInDragAndDropToolbar() { XOJ_CHECK_TYPE(Control); if (!this->dragDropHandler) { return false; } return this->dragDropHandler->isInDragAndDrop(); } void Control::setShapeTool(ActionType type, bool enabled) { XOJ_CHECK_TYPE(Control); if (enabled == false) { // Disable all entries this->toolHandler->setDrawingType(DRAWING_TYPE_DEFAULT); // fire disabled and return fireActionSelected(GROUP_RULER, ACTION_NONE); return; } // Check for nothing changed, and return in this case if ((this->toolHandler->getDrawingType() == DRAWING_TYPE_LINE && type == ACTION_RULER) || (this->toolHandler->getDrawingType() == DRAWING_TYPE_RECTANGLE && type == ACTION_TOOL_DRAW_RECT) || (this->toolHandler->getDrawingType() == DRAWING_TYPE_ARROW && type == ACTION_TOOL_DRAW_ARROW) || (this->toolHandler->getDrawingType() == DRAWING_TYPE_COORDINATE_SYSTEM && type == ACTION_TOOL_DRAW_COORDINATE_SYSTEM) || (this->toolHandler->getDrawingType() == DRAWING_TYPE_CIRCLE && type == ACTION_TOOL_DRAW_CIRCLE) || (this->toolHandler->getDrawingType() == DRAWING_TYPE_STROKE_RECOGNIZER && type == ACTION_SHAPE_RECOGNIZER)) { return; } switch (type) { case ACTION_TOOL_DRAW_RECT: this->toolHandler->setDrawingType(DRAWING_TYPE_RECTANGLE); break; case ACTION_TOOL_DRAW_CIRCLE: this->toolHandler->setDrawingType(DRAWING_TYPE_CIRCLE); break; case ACTION_TOOL_DRAW_ARROW: this->toolHandler->setDrawingType(DRAWING_TYPE_ARROW); break; case ACTION_TOOL_DRAW_COORDINATE_SYSTEM: this->toolHandler->setDrawingType(DRAWING_TYPE_COORDINATE_SYSTEM); break; case ACTION_RULER: this->toolHandler->setDrawingType(DRAWING_TYPE_LINE); break; case ACTION_SHAPE_RECOGNIZER: this->toolHandler->setDrawingType(DRAWING_TYPE_STROKE_RECOGNIZER); this->resetShapeRecognizer(); break; default: g_warning("Invalid type for setShapeTool: %i", type); break; } fireActionSelected(GROUP_RULER, type); } void Control::enableFullscreen(bool enabled, bool presentation) { XOJ_CHECK_TYPE(Control); if (enabled) { gtk_window_fullscreen((GtkWindow*) *win); string str = presentation ? settings->getPresentationHideElements() : settings->getFullscreenHideElements(); for (string s : StringUtils::split(str, ',')) { if ("sidebarContents" == s && settings->isSidebarVisible()) { this->sidebarHidden = true; win->setSidebarVisible(false); } else { GtkWidget* w = win->get(s.c_str()); if (w == NULL) { g_warning("Fullscreen: Try to hide \"%s\", but coulden't find it. Wrong entry in ~/" CONFIG_DIR "/" SETTINGS_XML_FILE "?", s.c_str()); } else { if (gtk_widget_get_visible(w)) { gtk_widget_hide(w); this->hiddenFullscreenWidgets = g_list_append(this->hiddenFullscreenWidgets, w); } } } } } else { gtk_window_unfullscreen((GtkWindow*) *win); for (GList* l = this->hiddenFullscreenWidgets; l != NULL; l = l->next) { gtk_widget_show(GTK_WIDGET(l->data)); } if (this->sidebarHidden) { this->sidebarHidden = false; win->setSidebarVisible(true); } g_list_free(this->hiddenFullscreenWidgets); this->hiddenFullscreenWidgets = NULL; } fireActionSelected(GROUP_FULLSCREEN, enabled ? ACTION_FULLSCREEN : ACTION_NONE); this->fullscreen = enabled; } void Control::disableSidebarTmp(bool disabled) { XOJ_CHECK_TYPE(Control); this->sidebar->setTmpDisabled(disabled); } void Control::addDefaultPage(string pageTemplate) { XOJ_CHECK_TYPE(Control); if (pageTemplate == "") { pageTemplate = settings->getPageTemplate(); } PageTemplateSettings model; model.parse(pageTemplate); PageRef page = new XojPage(model.getPageWidth(), model.getPageHeight()); page->setBackgroundColor(model.getBackgroundColor()); page->setBackgroundType(model.getBackgroundType()); this->doc->lock(); this->doc->addPage(page); this->doc->unlock(); updateDeletePageButton(); } void Control::updateDeletePageButton() { XOJ_CHECK_TYPE(Control); if (this->win) { GtkWidget* w = this->win->get("menuDeletePage"); gtk_widget_set_sensitive(w, this->doc->getPageCount() > 1); } } void Control::deletePage() { XOJ_CHECK_TYPE(Control); clearSelectionEndText(); // don't allow delete pages if we have less than 2 pages, // so we can be (more or less) sure there is at least one page. if (this->doc->getPageCount() < 2) { return; } size_t pNr = getCurrentPageNo(); if (pNr == size_t_npos || pNr > this->doc->getPageCount()) { // something went wrong... return; } this->doc->lock(); PageRef page = doc->getPage(pNr); this->doc->unlock(); // first send event, then delete page... firePageDeleted(pNr); this->doc->lock(); doc->deletePage(pNr); this->doc->unlock(); updateDeletePageButton(); this->undoRedo->addUndoAction(new InsertDeletePageUndoAction(page, pNr, false)); if (pNr >= this->doc->getPageCount()) { pNr = this->doc->getPageCount() - 1; } scrollHandler->scrollToPage(pNr, 0); } void Control::insertNewPage(size_t position) { XOJ_CHECK_TYPE(Control); pageBackgroundChangeController->insertNewPage(position); } void Control::insertPage(PageRef page, size_t position) { XOJ_CHECK_TYPE(Control); this->doc->lock(); this->doc->insertPage(page, position); this->doc->unlock(); firePageInserted(position); getCursor()->updateCursor(); int visibleHeight = 0; scrollHandler->isPageVisible(position, &visibleHeight); if (visibleHeight < 10) { Util::execInUiThread([=]() { scrollHandler->scrollToPage(position); }); } firePageSelected(position); updateDeletePageButton(); undoRedo->addUndoAction(new InsertDeletePageUndoAction(page, position, true)); } void Control::gotoPage() { GotoDialog* dlg = new GotoDialog(this->gladeSearchPath, this->doc->getPageCount()); dlg->show(GTK_WINDOW(this->win->getWindow())); int page = dlg->getSelectedPage(); if (page != -1) { this->scrollHandler->scrollToPage(page - 1, 0); } delete dlg; } void Control::updateBackgroundSizeButton() { XOJ_CHECK_TYPE(Control); if (this->win == NULL) { return; } // Update paper color button PageRef p = getCurrentPage(); if (!p.isValid() || this->win == NULL) { return; } GtkWidget* paperColor = win->get("menuJournalPaperColor"); GtkWidget* pageSize = win->get("menuJournalPaperFormat"); PageType bg = p->getBackgroundType(); gtk_widget_set_sensitive(paperColor, !bg.isSpecial()); // PDF page size is defined, you cannot change it gtk_widget_set_sensitive(pageSize, !bg.isPdfPage()); } void Control::paperTemplate() { XOJ_CHECK_TYPE(Control); PageTemplateDialog* dlg = new PageTemplateDialog(this->gladeSearchPath, settings, pageTypes); dlg->show(GTK_WINDOW(this->win->getWindow())); if (dlg->isSaved()) { newPageType->loadDefaultPage(); } delete dlg; } void Control::paperFormat() { XOJ_CHECK_TYPE(Control); PageRef page = getCurrentPage(); if (!page.isValid() || page->getBackgroundType().isPdfPage()) { return; } clearSelectionEndText(); FormatDialog* dlg = new FormatDialog(this->gladeSearchPath, settings, page->getWidth(), page->getHeight()); dlg->show(GTK_WINDOW(this->win->getWindow())); double width = dlg->getWidth(); double height = dlg->getHeight(); if (width > 0) { this->doc->lock(); this->doc->setPageSize(page, width, height); this->doc->unlock(); } delete dlg; } void Control::changePageBackgroundColor() { XOJ_CHECK_TYPE(Control); int pNr = getCurrentPageNo(); this->doc->lock(); PageRef p = this->doc->getPage(pNr); this->doc->unlock(); if (!p.isValid()) { return; } clearSelectionEndText(); PageType bg = p->getBackgroundType(); if (bg.isSpecial()) { return; } SelectBackgroundColorDialog dlg(this); dlg.show(GTK_WINDOW(this->win->getWindow())); int color = dlg.getSelectedColor(); if (color != -1) { p->setBackgroundColor(color); firePageChanged(pNr); } } void Control::calcZoomFitSize() { XOJ_CHECK_TYPE(Control); if (this->doc && this->win) { PageRef p = getCurrentPage(); if (!p.isValid()) { return; } double width = p->getWidth() + 20; GtkAllocation allocation = {0}; gtk_widget_get_allocation(win->getXournal()->getWidget(), &allocation); double factor = ((double) allocation.width) / width; zoom->setZoomFit(factor); } } void Control::zoomFit() { XOJ_CHECK_TYPE(Control); calcZoomFitSize(); zoom->zoomFit(); } void Control::setViewTwoPages(bool twoPages) { XOJ_CHECK_TYPE(Control); settings->setShowTwoPages(twoPages); fireActionSelected(GROUP_TWOPAGES, twoPages ? ACTION_VIEW_TWO_PAGES : ACTION_NOT_SELECTED); int currentPage = getCurrentPageNo(); win->getXournal()->layoutPages(); scrollHandler->scrollToPage(currentPage); } void Control::setViewPresentationMode(bool presentationMode) { XOJ_CHECK_TYPE(Control); settings->setPresentationMode(presentationMode); fireActionSelected(GROUP_PRESENTATION_MODE, presentationMode ? ACTION_VIEW_PRESENTATION_MODE : ACTION_NOT_SELECTED); int currentPage = getCurrentPageNo(); win->getXournal()->layoutPages(); scrollHandler->scrollToPage(currentPage); } /** * This callback is used by used to be called later in the UI Thread * On slower machine this feels more fluent, therefore this will not * be removed */ void Control::zoomCallback(ActionType type) { XOJ_CHECK_TYPE(Control); switch (type) { case ACTION_ZOOM_100: zoom->zoom100(); break; case ACTION_ZOOM_FIT: zoomFit(); break; case ACTION_ZOOM_IN: zoom->zoomIn(); break; case ACTION_ZOOM_OUT: zoom->zoomOut(); break; default: break; } } size_t Control::getCurrentPageNo() { XOJ_CHECK_TYPE(Control); if (this->win) { return this->win->getXournal()->getCurrentPage(); } return 0; } bool Control::searchTextOnPage(string text, int p, int* occures, double* top) { XOJ_CHECK_TYPE(Control); return getWindow()->getXournal()->searchTextOnPage(text, p, occures, top); } PageRef Control::getCurrentPage() { XOJ_CHECK_TYPE(Control); int page = this->win->getXournal()->getCurrentPage(); this->doc->lock(); PageRef p = this->doc->getPage(page); this->doc->unlock(); return p; } void Control::fileOpened(const char* uri) { XOJ_CHECK_TYPE(Control); openFile(uri); } void Control::undoRedoChanged() { XOJ_CHECK_TYPE(Control); fireEnableAction(ACTION_UNDO, undoRedo->canUndo()); fireEnableAction(ACTION_REDO, undoRedo->canRedo()); win->setUndoDescription(undoRedo->undoDescription()); win->setRedoDescription(undoRedo->redoDescription()); updateWindowTitle(); } void Control::undoRedoPageChanged(PageRef page) { XOJ_CHECK_TYPE(Control); for (XojPage* p : this->changedPages) { if (p == (XojPage*) page) { return; } } XojPage* p = (XojPage*) page; this->changedPages.push_back(p); p->reference(); } void Control::selectTool(ToolType type) { XOJ_CHECK_TYPE(Control); toolHandler->selectTool(type); if (win) { (win->getXournal()->getViewFor(getCurrentPageNo()))->rerenderPage(); } } void Control::selectDefaultTool() { XOJ_CHECK_TYPE(Control); ButtonConfig* cfg = settings->getDefaultButtonConfig(); cfg->acceptActions(toolHandler); } void Control::toolChanged() { XOJ_CHECK_TYPE(Control); ToolType type = toolHandler->getToolType(); // Convert enum values, enums has to be in the same order! ActionType at = (ActionType) (type - TOOL_PEN + ACTION_TOOL_PEN); fireActionSelected(GROUP_TOOL, at); fireEnableAction(ACTION_SELECT_COLOR, toolHandler->hasCapability(TOOL_CAP_COLOR)); fireEnableAction(ACTION_SELECT_COLOR_CUSTOM, toolHandler->hasCapability(TOOL_CAP_COLOR)); fireEnableAction(ACTION_RULER, toolHandler->hasCapability(TOOL_CAP_RULER)); fireEnableAction(ACTION_TOOL_DRAW_RECT, toolHandler->hasCapability(TOOL_CAP_RECTANGLE)); fireEnableAction(ACTION_TOOL_DRAW_CIRCLE, toolHandler->hasCapability(TOOL_CAP_CIRCLE)); fireEnableAction(ACTION_TOOL_DRAW_ARROW, toolHandler->hasCapability(TOOL_CAP_ARROW)); fireEnableAction(ACTION_TOOL_DRAW_COORDINATE_SYSTEM, toolHandler->hasCapability(TOOL_CAP_ARROW)); fireEnableAction(ACTION_SHAPE_RECOGNIZER, toolHandler->hasCapability(TOOL_CAP_RECOGNIZER)); bool enableSize = toolHandler->hasCapability(TOOL_CAP_SIZE); fireEnableAction(ACTION_SIZE_MEDIUM, enableSize); fireEnableAction(ACTION_SIZE_THICK, enableSize); fireEnableAction(ACTION_SIZE_FINE, enableSize); fireEnableAction(ACTION_SIZE_VERY_THICK, enableSize); fireEnableAction(ACTION_SIZE_VERY_THIN, enableSize); bool enableFill = toolHandler->hasCapability(TOOL_CAP_FILL); fireEnableAction(ACTION_TOOL_FILL, enableFill); if (enableSize) { toolSizeChanged(); } // Update color if (toolHandler->hasCapability(TOOL_CAP_COLOR)) { toolColorChanged(false); } ActionType rulerAction = ACTION_NOT_SELECTED; if (toolHandler->getDrawingType() == DRAWING_TYPE_STROKE_RECOGNIZER) { rulerAction = ACTION_SHAPE_RECOGNIZER; } else if (toolHandler->getDrawingType() == DRAWING_TYPE_LINE) { rulerAction = ACTION_RULER; } else if (toolHandler->getDrawingType() == DRAWING_TYPE_RECTANGLE) { rulerAction = ACTION_TOOL_DRAW_RECT; } else if (toolHandler->getDrawingType() == DRAWING_TYPE_CIRCLE) { rulerAction = ACTION_TOOL_DRAW_CIRCLE; } else if (toolHandler->getDrawingType() == DRAWING_TYPE_ARROW) { rulerAction = ACTION_TOOL_DRAW_ARROW; } else if (toolHandler->getDrawingType() == DRAWING_TYPE_COORDINATE_SYSTEM) { rulerAction = ACTION_TOOL_DRAW_COORDINATE_SYSTEM; } fireActionSelected(GROUP_RULER, rulerAction); getCursor()->updateCursor(); if (type != TOOL_TEXT) { if (win) { win->getXournal()->endTextAllPages(); } } } void Control::eraserSizeChanged() { XOJ_CHECK_TYPE(Control); switch (toolHandler->getEraserSize()) { case TOOL_SIZE_FINE: fireActionSelected(GROUP_ERASER_SIZE, ACTION_TOOL_ERASER_SIZE_FINE); break; case TOOL_SIZE_MEDIUM: fireActionSelected(GROUP_ERASER_SIZE, ACTION_TOOL_ERASER_SIZE_MEDIUM); break; case TOOL_SIZE_THICK: fireActionSelected(GROUP_ERASER_SIZE, ACTION_TOOL_ERASER_SIZE_THICK); break; default: break; } } void Control::penSizeChanged() { XOJ_CHECK_TYPE(Control); switch (toolHandler->getPenSize()) { case TOOL_SIZE_VERY_FINE: fireActionSelected(GROUP_PEN_SIZE, ACTION_TOOL_PEN_SIZE_VERY_THIN); break; case TOOL_SIZE_FINE: fireActionSelected(GROUP_PEN_SIZE, ACTION_TOOL_PEN_SIZE_FINE); break; case TOOL_SIZE_MEDIUM: fireActionSelected(GROUP_PEN_SIZE, ACTION_TOOL_PEN_SIZE_MEDIUM); break; case TOOL_SIZE_THICK: fireActionSelected(GROUP_PEN_SIZE, ACTION_TOOL_PEN_SIZE_THICK); break; case TOOL_SIZE_VERY_THICK: fireActionSelected(GROUP_PEN_SIZE, ACTION_TOOL_PEN_SIZE_VERY_THICK); break; default: break; } } void Control::hilighterSizeChanged() { XOJ_CHECK_TYPE(Control); switch (toolHandler->getHilighterSize()) { case TOOL_SIZE_FINE: fireActionSelected(GROUP_HILIGHTER_SIZE, ACTION_TOOL_HILIGHTER_SIZE_FINE); break; case TOOL_SIZE_MEDIUM: fireActionSelected(GROUP_HILIGHTER_SIZE, ACTION_TOOL_HILIGHTER_SIZE_MEDIUM); break; case TOOL_SIZE_THICK: fireActionSelected(GROUP_HILIGHTER_SIZE, ACTION_TOOL_HILIGHTER_SIZE_THICK); break; default: break; } } void Control::toolSizeChanged() { XOJ_CHECK_TYPE(Control); if (toolHandler->getToolType() == TOOL_PEN) { penSizeChanged(); } else if (toolHandler->getToolType() == TOOL_ERASER) { eraserSizeChanged(); } else if (toolHandler->getToolType() == TOOL_HILIGHTER) { hilighterSizeChanged(); } switch (toolHandler->getSize()) { case TOOL_SIZE_NONE: fireActionSelected(GROUP_SIZE, ACTION_NONE); break; case TOOL_SIZE_VERY_FINE: fireActionSelected(GROUP_SIZE, ACTION_SIZE_VERY_THICK); break; case TOOL_SIZE_FINE: fireActionSelected(GROUP_SIZE, ACTION_SIZE_FINE); break; case TOOL_SIZE_MEDIUM: fireActionSelected(GROUP_SIZE, ACTION_SIZE_MEDIUM); break; case TOOL_SIZE_THICK: fireActionSelected(GROUP_SIZE, ACTION_SIZE_THICK); break; case TOOL_SIZE_VERY_THICK: fireActionSelected(GROUP_SIZE, ACTION_SIZE_VERY_THIN); break; } } void Control::toolFillChanged() { XOJ_CHECK_TYPE(Control); fireActionSelected(GROUP_FILL, toolHandler->getFill() != -1 ? ACTION_TOOL_FILL : ACTION_NONE); } /** * Select the color for the tool * * @param userSelection * true if the user selected the color * false if the color is selected by a tool change * and therefore should not be applied to a selection */ void Control::toolColorChanged(bool userSelection) { XOJ_CHECK_TYPE(Control); fireActionSelected(GROUP_COLOR, ACTION_SELECT_COLOR); getCursor()->updateCursor(); if (userSelection && this->win && toolHandler->getColor() != -1) { EditSelection* sel = this->win->getXournal()->getSelection(); if (sel) { UndoAction* undo = sel->setColor(toolHandler->getColor()); undoRedo->addUndoAction(undo); } TextEditor* edit = getTextEditor(); if (this->toolHandler->getToolType() == TOOL_TEXT && edit != NULL) { UndoAction* undo = edit->setColor(toolHandler->getColor()); undoRedo->addUndoAction(undo); } } } void Control::setCustomColorSelected() { XOJ_CHECK_TYPE(Control); fireActionSelected(GROUP_COLOR, ACTION_SELECT_COLOR_CUSTOM); } void Control::showSettings() { XOJ_CHECK_TYPE(Control); int selectionColor = settings->getBorderColor(); bool verticalSpace = settings->getAddVerticalSpace(); bool horizontalSpace = settings->getAddHorizontalSpace(); bool bigCursor = settings->isShowBigCursor(); SettingsDialog* dlg = new SettingsDialog(this->gladeSearchPath, settings); dlg->show(GTK_WINDOW(this->win->getWindow())); if (selectionColor != settings->getBorderColor()) { win->getXournal()->forceUpdatePagenumbers(); } if (verticalSpace != settings->getAddVerticalSpace() || horizontalSpace != settings->getAddHorizontalSpace()) { int currentPage = getCurrentPageNo(); win->getXournal()->layoutPages(); scrollHandler->scrollToPage(currentPage); } if (bigCursor != settings->isShowBigCursor()) { getCursor()->updateCursor(); } win->updateScrollbarSidebarPosition(); enableAutosave(settings->isAutosaveEnabled()); getWindow()->getXournal()->setEventCompression(settings->isEventCompression()); this->zoom->setZoom100(settings->getDisplayDpi() / 72.0); getWindow()->getXournal()->getTouchHelper()->reload(); TextView::setDpi(settings->getDisplayDpi()); delete dlg; } bool Control::newFile(string pageTemplate) { XOJ_CHECK_TYPE(Control); if (!this->close()) { return false; } Document newDoc(this); this->doc->lock(); *doc = newDoc; this->doc->unlock(); addDefaultPage(pageTemplate); fireDocumentChanged(DOCUMENT_CHANGE_COMPLETE); fileLoaded(); return true; } /** * Check if this is an autosave file, return false in this case and display a user instruction */ bool Control::shouldFileOpen(string filename) { // Compare case insensitive, just in case (Windows, FAT Filesystem etc.) filename = StringUtils::toLowerCase(filename); string basename = StringUtils::toLowerCase(Util::getConfigSubfolder("").str()); if (basename.size() > filename.size()) { return true; } filename = filename.substr(0, basename.size()); if (filename == basename) { string msg = FS(_F("Do not open Autosave files. They may will be overwritten!\n" "Copy the files to another folder.\n" "Files from Folder {1} cannot be opened.") % basename); XojMsgBox::showErrorToUser(getGtkWindow(), msg); return false; } return true; } bool Control::openFile(Path filename, int scrollToPage, bool forceOpen) { XOJ_CHECK_TYPE(Control); if (!forceOpen && !shouldFileOpen(filename.str())) { return false; } if (!this->close()) { return false; } if (filename.isEmpty()) { bool attachPdf = false; XojOpenDlg dlg(getGtkWindow(), this->settings); filename = Path(dlg.showOpenDialog(false, attachPdf).str()); g_message("%s", (_F("Filename: {1}") % filename.str()).c_str()); if (filename.isEmpty()) { return false; } if (!shouldFileOpen(filename.str())) { return false; } } // Read template file if (filename.hasExtension(".xopt")) { return loadXoptTemplate(filename); } if (filename.hasExtension(".pdf")) { return loadPdf(filename, scrollToPage); } LoadHandler loadHandler; Document* loadedDocument = loadHandler.loadDocument(filename.str()); if ((loadedDocument != NULL && loadHandler.isAttachedPdfMissing()) || !loadHandler.getMissingPdfFilename().empty()) { // give the user a second chance to select a new PDF file, or to discard the PDF GtkWidget* dialog = gtk_message_dialog_new(getGtkWindow(), GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", loadHandler.isAttachedPdfMissing() ? _("The attached background PDF could not be found.") : _("The background PDF could not be found.")); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Select another PDF"), 1); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Remove PDF Background"), 2); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), 3); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(this->getWindow()->getWindow())); int res = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (res == 2) // remove PDF background { loadHandler.removePdfBackground(); loadedDocument = loadHandler.loadDocument(filename.str()); } else if (res == 1) // select another PDF background { bool attachToDocument = false; XojOpenDlg dlg(getGtkWindow(), this->settings); Path pdfFilename = Path(dlg.showOpenDialog(true, attachToDocument).str()); if (!pdfFilename.isEmpty()) { loadHandler.setPdfReplacement(pdfFilename.str(), attachToDocument); loadedDocument = loadHandler.loadDocument(filename.str()); } } } if (!loadedDocument) { string msg = FS(_F("Error opening file \"{1}\"") % filename.str()) + "\n" + loadHandler.getLastError(); XojMsgBox::showErrorToUser(getGtkWindow(), msg); fileLoaded(scrollToPage); return false; } else { this->doc->lock(); this->doc->clearDocument(); *this->doc = *loadedDocument; this->doc->unlock(); // Set folder as last save path, so the next save will be at the current document location // This is important because of the new .xopp format, where Xournal .xoj handled as import, // not as file to load settings->setLastSavePath(filename.getParentPath()); } fileLoaded(scrollToPage); return true; } bool Control::loadPdf(Path filename, int scrollToPage) { XOJ_CHECK_TYPE(Control); LoadHandler loadHandler; if (settings->isAutloadPdfXoj()) { Path f = filename; f.clearExtensions(); f += ".pdf.xopp"; Document* tmp = loadHandler.loadDocument(f.str()); if (tmp == NULL) { f = filename; f.clearExtensions(); f += ".pdf.xoj"; tmp = loadHandler.loadDocument(f.str()); } if (tmp) { this->doc->lock(); this->doc->clearDocument(); *this->doc = *tmp; this->doc->unlock(); fileLoaded(scrollToPage); return true; } } bool an = annotatePdf(filename, false, false); fileLoaded(scrollToPage); return an; } bool Control::loadXoptTemplate(Path filename) { XOJ_CHECK_TYPE(Control); string contents; if (!PathUtil::readString(contents, filename)) { return false; } newFile(contents); return true; } void Control::fileLoaded(int scrollToPage) { XOJ_CHECK_TYPE(Control); this->doc->lock(); Path file = this->doc->getEvMetadataFilename(); this->doc->unlock(); if (!file.isEmpty()) { MetadataEntry md = metadata->getForFile(file.str()); if (!md.valid) { md.zoom = -1; md.page = 0; } if (scrollToPage >= 0) { md.page = scrollToPage; } loadMetadata(md); recent->addRecentFileFilename(file); } else { this->zoom->zoomFit(); } updateWindowTitle(); win->getXournal()->forceUpdatePagenumbers(); getCursor()->updateCursor(); updateDeletePageButton(); } class MetadataCallbackData { public: Control* ctrl; MetadataEntry md; }; /** * Load the data after processing the document... */ bool Control::loadMetadataCallback(MetadataCallbackData* data) { if (!data->md.valid) { delete data; return false; } data->ctrl->scrollHandler->scrollToPage(data->md.page); data->ctrl->zoom->setZoom(data->md.zoom); delete data; // Do not call again! return false; } void Control::loadMetadata(MetadataEntry md) { MetadataCallbackData* data = new MetadataCallbackData(); data->md = md; data->ctrl = this; g_idle_add((GSourceFunc) loadMetadataCallback, data); } bool Control::annotatePdf(Path filename, bool attachPdf, bool attachToDocument) { XOJ_CHECK_TYPE(Control); if (!this->close()) { return false; } if (filename.isEmpty()) { XojOpenDlg dlg(getGtkWindow(), this->settings); filename = Path(dlg.showOpenDialog(true, attachToDocument).str()); if (filename.isEmpty()) { return false; } } getCursor()->setCursorBusy(true); this->doc->setFilename(""); bool res = this->doc->readPdf(filename, true, attachToDocument); if (res) { this->recent->addRecentFileFilename(filename.c_str()); this->doc->lock(); Path file = this->doc->getEvMetadataFilename(); this->doc->unlock(); MetadataEntry md = metadata->getForFile(file.str()); loadMetadata(md); } else { this->doc->lock(); string errMsg = doc->getLastErrorMsg(); this->doc->unlock(); string msg = FS(_F("Error annotate PDF file \"{1}\"\n{2}") % filename.str() % errMsg); XojMsgBox::showErrorToUser(getGtkWindow(), msg); } getCursor()->setCursorBusy(false); fireDocumentChanged(DOCUMENT_CHANGE_COMPLETE); getCursor()->updateCursor(); return true; } void Control::print() { XOJ_CHECK_TYPE(Control); PrintHandler print; this->doc->lock(); print.print(this->doc, getCurrentPageNo()); this->doc->unlock(); } void Control::block(string name) { XOJ_CHECK_TYPE(Control); if (this->isBlocking) { return; } // Disable all gui Control, to get full control over the application win->setControlTmpDisabled(true); getCursor()->setCursorBusy(true); disableSidebarTmp(true); this->statusbar = this->win->get("statusbar"); this->lbState = GTK_LABEL(this->win->get("lbState")); this->pgState = GTK_PROGRESS_BAR(this->win->get("pgState")); gtk_label_set_text(this->lbState, name.c_str()); gtk_widget_show(this->statusbar); this->maxState = 100; this->isBlocking = true; } void Control::unblock() { XOJ_CHECK_TYPE(Control); if (!this->isBlocking) { return; } this->win->setControlTmpDisabled(false); getCursor()->setCursorBusy(false); disableSidebarTmp(false); gtk_widget_hide(this->statusbar); this->isBlocking = false; } void Control::setMaximumState(int max) { XOJ_CHECK_TYPE(Control); this->maxState = max; } void Control::setCurrentState(int state) { XOJ_CHECK_TYPE(Control); Util::execInUiThread([=]() { gtk_progress_bar_set_fraction(this->pgState, gdouble(state) / this->maxState); }); } bool Control::save(bool synchron) { XOJ_CHECK_TYPE(Control); // clear selection before saving clearSelectionEndText(); this->doc->lock(); Path filename = this->doc->getFilename(); this->doc->unlock(); if (filename.isEmpty()) { if (!showSaveDialog()) { return false; } } SaveJob* job = new SaveJob(this); bool result = true; if (synchron) { result = job->save(); unblock(); } else { this->scheduler->addJob(job, JOB_PRIORITY_URGENT); } job->unref(); return result; } bool Control::showSaveDialog() { XOJ_CHECK_TYPE(Control); GtkWidget* dialog = gtk_file_chooser_dialog_new(_("Save File"), getGtkWindow(), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, NULL); gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), true); GtkFileFilter* filterXoj = gtk_file_filter_new(); gtk_file_filter_set_name(filterXoj, _("Xournal++ files")); gtk_file_filter_add_pattern(filterXoj, "*.xopp"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filterXoj); this->doc->lock(); Path suggested_folder = this->doc->createSaveFolder(this->settings->getLastSavePath()); Path suggested_name = this->doc->createSaveFilename(Document::XOPP, this->settings->getDefaultSaveName()); this->doc->unlock(); gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), suggested_folder.c_str()); gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), suggested_name.c_str()); gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), true); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(this->getWindow()->getWindow())); while (true) { if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_OK) { gtk_widget_destroy(dialog); return false; } Path filenameTmp = Path(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); filenameTmp.clearExtensions(); filenameTmp += ".xopp"; Path currentFolder(gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog))); // Since we add the extension after the OK button, we have to check manually on existing files if (checkExistingFile(currentFolder, filenameTmp)) { break; } } char* name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); string filename = name; char* folder = gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(dialog)); settings->setLastSavePath(folder); g_free(folder); g_free(name); gtk_widget_destroy(dialog); this->doc->lock(); this->doc->setFilename(filename); this->doc->unlock(); return true; } void Control::updateWindowTitle() { XOJ_CHECK_TYPE(Control); string title = ""; this->doc->lock(); if (doc->getFilename().isEmpty()) { if (doc->getPdfFilename().isEmpty()) { title = _("Unsaved Document"); } else { if (undoRedo->isChanged()) { title += "*"; } title += doc->getPdfFilename().str(); } } else { if (undoRedo->isChanged()) { title += "*"; } title += doc->getFilename().getFilename(); } this->doc->unlock(); title += " - Xournal++"; gtk_window_set_title(getGtkWindow(), title.c_str()); } void Control::exportAsPdf() { XOJ_CHECK_TYPE(Control); exportBase(new PdfExportJob(this)); } void Control::exportAs() { XOJ_CHECK_TYPE(Control); exportBase(new CustomExportJob(this)); } void Control::exportBase(BaseExportJob* job) { if (job->showFilechooser()) { this->scheduler->addJob(job, JOB_PRIORITY_NONE); } else { // The job blocked, so we have to unblock, because the job unblocks only after run unblock(); } job->unref(); } bool Control::saveAs() { XOJ_CHECK_TYPE(Control); if (!showSaveDialog()) { return false; } this->doc->lock(); Path filename = doc->getFilename(); this->doc->unlock(); if (filename.isEmpty()) { return false; } // no lock needed, this is an uncritically operation this->doc->setCreateBackupOnSave(false); return save(); } void Control::quit() { XOJ_CHECK_TYPE(Control); if (!this->close(true)) { return; } this->scheduler->lock(); audioController->recStartStop(false); settings->save(); this->scheduler->removeAllJobs(); this->scheduler->unlock(); gtk_main_quit(); } bool Control::close(bool destroy) { XOJ_CHECK_TYPE(Control); clearSelectionEndText(); metadata->documentChanged(); if (undoRedo->isChanged()) { GtkWidget* dialog = gtk_message_dialog_new(getGtkWindow(), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", _("This document is not saved yet.")); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Save"), 1); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Discard"), 2); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), 3); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(this->getWindow()->getWindow())); int resNotSaved = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); // save if (resNotSaved == 1) { if (this->save(true)) { return true; } else { // if not saved cancel, else close return false; } } // cancel or closed if (resNotSaved != 2) // 2 = discard { return false; } } if (!doc->getFilename().isEmpty()) { if (!this->doc->getFilename().exists()) { GtkWidget* dialog = gtk_message_dialog_new(getGtkWindow(), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "%s", _("Document file was removed.")); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Save As"), 1); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Discard"), 2); gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), 3); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(this->getWindow()->getWindow())); int resDocRemoved = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (resDocRemoved == 1) { if (this->saveAs()) { return true; } else { // if not saved cancel, else close return false; } } // cancel or closed if (resDocRemoved != 2) // 2 = discard { return false; } } } if (destroy) { undoRedo->clearContents(); this->doc->lock(); this->doc->clearDocument(destroy); this->doc->unlock(); //updateWindowTitle(); undoRedoChanged(); } return true; } bool Control::checkExistingFile(Path& folder, Path& filename) { XOJ_CHECK_TYPE(Control); if (filename.exists()) { string msg = FS(FORMAT_STR("The file {1} already exists! Do you want to replace it?") % filename.getFilename()); int res = XojMsgBox::replaceFileQuestion(getGtkWindow(), msg); return res != 1; // res != 1 when user clicks on Replace } return true; } void Control::resetShapeRecognizer() { XOJ_CHECK_TYPE(Control); if (this->win) { this->win->getXournal()->resetShapeRecognizer(); } } void Control::showAbout() { XOJ_CHECK_TYPE(Control); AboutDialog dlg(this->gladeSearchPath); dlg.show(GTK_WINDOW(this->win->getWindow())); } void Control::clipboardCutCopyEnabled(bool enabled) { XOJ_CHECK_TYPE(Control); fireEnableAction(ACTION_CUT, enabled); fireEnableAction(ACTION_COPY, enabled); } void Control::clipboardPasteEnabled(bool enabled) { XOJ_CHECK_TYPE(Control); fireEnableAction(ACTION_PASTE, enabled); } void Control::clipboardPasteText(string text) { XOJ_CHECK_TYPE(Control); Text* t = new Text(); t->setText(text); t->setFont(settings->getFont()); t->setColor(toolHandler->getColor()); clipboardPaste(t); } void Control::clipboardPasteImage(GdkPixbuf* img) { XOJ_CHECK_TYPE(Control); Image* image = new Image(); image->setImage(img); int width = gdk_pixbuf_get_width(img); int height = gdk_pixbuf_get_height(img); int pageNr = getCurrentPageNo(); if (pageNr == -1) { return; } this->doc->lock(); PageRef page = this->doc->getPage(pageNr); int pageWidth = page->getWidth(); int pageHeight = page->getHeight(); this->doc->unlock(); // Size: 3/4 of the page size pageWidth = pageWidth * 3 / 4; pageHeight = pageHeight * 3 / 4; int scaledWidth = width; int scaledHeight = height; if (width > pageWidth) { scaledWidth = pageWidth; scaledHeight = (scaledWidth * height) / width; } if (scaledHeight > pageHeight) { scaledHeight = pageHeight; scaledWidth = (scaledHeight * width) / height; } image->setWidth(scaledWidth); image->setHeight(scaledHeight); clipboardPaste(image); } void Control::clipboardPasteTex(GdkPixbuf* img, const char* text, int textLength) { XOJ_CHECK_TYPE(Control); TexImage* image = new TexImage(); image->setImage(img); int width = gdk_pixbuf_get_width(img); int height = gdk_pixbuf_get_height(img); image->setWidth(width); image->setHeight(height); image->setText(string(text, textLength)); clipboardPaste(image); } void Control::clipboardPaste(Element* e) { XOJ_CHECK_TYPE(Control); double x = 0; double y = 0; int pageNr = getCurrentPageNo(); if (pageNr == -1) { return; } XojPageView* view = win->getXournal()->getViewFor(pageNr); if (view == NULL) { return; } this->doc->lock(); PageRef page = this->doc->getPage(pageNr); Layer* layer = page->getSelectedLayer(); win->getXournal()->getPasteTarget(x, y); double width = e->getElementWidth(); e->setX(x - width / 2); e->setY(y); layer->addElement(e); this->doc->unlock(); undoRedo->addUndoAction(new InsertUndoAction(page, layer, e)); EditSelection* selection = new EditSelection(this->undoRedo, e, view, page); win->getXournal()->setSelection(selection); } void Control::clipboardPasteXournal(ObjectInputStream& in) { XOJ_CHECK_TYPE(Control); int pNr = getCurrentPageNo(); if (pNr == -1 && win != NULL) { return; } this->doc->lock(); PageRef page = this->doc->getPage(pNr); Layer* layer = page->getSelectedLayer(); XojPageView* view = win->getXournal()->getViewFor(pNr); if (!view || !page) { this->doc->unlock(); return; } EditSelection* selection = NULL; Element* element = NULL; try { string version = in.readString(); if (version != PROJECT_STRING) { g_warning("Paste from Xournal Version %s to Xournal Version %s", version.c_str(), PROJECT_STRING); } selection = new EditSelection(this->undoRedo, page, view); in >> selection; // document lock not needed anymore, because we don't change the document, we only change the selection this->doc->unlock(); int count = in.readInt(); AddUndoAction* pasteAddUndoAction = new AddUndoAction(page, false); //this will undo a group of elements that are inserted for (int i = 0; i < count; i++) { string name = in.getNextObjectName(); element = NULL; if (name == "Stroke") { element = new Stroke(); } else if (name == "Image") { element = new Image(); } else if (name == "TexImage") { element = new TexImage(); } else if (name == "Text") { element = new Text(); } else { throw InputStreamException(FS(FORMAT_STR("Get unknown object {1}") % name), __FILE__, __LINE__); } in >> element; //undoRedo->addUndoAction(new InsertUndoAction(page, layer, element, view)); pasteAddUndoAction->addElement(layer, element, layer->indexOf(element)); selection->addElement(element); element = NULL; } undoRedo->addUndoAction(pasteAddUndoAction); win->getXournal()->setSelection(selection); } catch (std::exception& e) { g_warning("could not paste, Exception occurred: %s", e.what()); Stacktrace::printStracktrace(); // cleanup if (element) { delete element; } if (selection) { for (Element* e : *selection->getElements()) delete e; delete selection; } } } void Control::deleteSelection() { XOJ_CHECK_TYPE(Control); if (win) { win->getXournal()->deleteSelection(); } } void Control::clearSelection() { XOJ_CHECK_TYPE(Control); if (this->win) { this->win->getXournal()->clearSelection(); } } void Control::setClipboardHandlerSelection(EditSelection* selection) { XOJ_CHECK_TYPE(Control); if (this->clipboardHandler) { this->clipboardHandler->setSelection(selection); } } void Control::setCopyPasteEnabled(bool enabled) { XOJ_CHECK_TYPE(Control); this->clipboardHandler->setCopyPasteEnabled(enabled); } void Control::setFill(bool fill) { XOJ_CHECK_TYPE(Control); EditSelection* sel = NULL; if (this->win) { sel = this->win->getXournal()->getSelection(); } if (sel) { UndoAction* undo = sel->setFill(fill ? toolHandler->getPenFill() : -1, fill ? toolHandler->getHilighterFill() : -1); undoRedo->addUndoAction(undo); } if (toolHandler->getToolType() == TOOL_PEN) { fireActionSelected(GROUP_PEN_FILL, fill ? ACTION_TOOL_PEN_FILL : ACTION_NONE); this->toolHandler->setPenFillEnabled(fill, false); } else if (toolHandler->getToolType() == TOOL_HILIGHTER) { fireActionSelected(GROUP_HILIGHTER_FILL, fill ? ACTION_TOOL_HILIGHTER_FILL : ACTION_NONE); this->toolHandler->setHilighterFillEnabled(fill, false); } } void Control::setToolSize(ToolSize size) { XOJ_CHECK_TYPE(Control); EditSelection* sel = NULL; if (this->win) { sel = this->win->getXournal()->getSelection(); } if (sel) { UndoAction* undo = sel->setSize(size, toolHandler->getToolThickness(TOOL_PEN), toolHandler->getToolThickness(TOOL_HILIGHTER), toolHandler->getToolThickness(TOOL_ERASER)); undoRedo->addUndoAction(undo); } this->toolHandler->setSize(size); } void Control::fontChanged() { XOJ_CHECK_TYPE(Control); XojFont font = win->getFontButtonFont(); settings->setFont(font); EditSelection* sel = NULL; if (this->win) { sel = this->win->getXournal()->getSelection(); } if (sel) { UndoAction* undo = sel->setFont(font); undoRedo->addUndoAction(undo); } TextEditor* editor = getTextEditor(); if (editor) { editor->setFont(font); } } /** * The core handler for inserting latex */ void Control::runLatex() { XOJ_CHECK_TYPE(Control); #ifdef ENABLE_MATHTEX LatexController latex(this); latex.run(); #else // This should never occur, as the menupoint is also hidden. g_warning("Mathtex is disabled. Recompile with cmake -DENABLE_MATHTEX=ON " "ensuring you have the mathtex command on your system."); #endif // ENABLE_MATHTEX } /** * GETTER / SETTER */ UndoRedoHandler* Control::getUndoRedoHandler() { XOJ_CHECK_TYPE(Control); return this->undoRedo; } ZoomControl* Control::getZoomControl() { XOJ_CHECK_TYPE(Control); return this->zoom; } Cursor* Control::getCursor() { XOJ_CHECK_TYPE(Control); return this->cursor; } RecentManager* Control::getRecentManager() { XOJ_CHECK_TYPE(Control); return this->recent; } Document* Control::getDocument() { XOJ_CHECK_TYPE(Control); return this->doc; } ToolHandler* Control::getToolHandler() { XOJ_CHECK_TYPE(Control); return this->toolHandler; } XournalScheduler* Control::getScheduler() { XOJ_CHECK_TYPE(Control); return this->scheduler; } MainWindow* Control::getWindow() { XOJ_CHECK_TYPE(Control); return this->win; } GtkWindow* Control::getGtkWindow() { XOJ_CHECK_TYPE(Control); return GTK_WINDOW(this->win->getWindow()); } bool Control::isFullscreen() { XOJ_CHECK_TYPE(Control); return this->fullscreen; } bool Control::isRotationSnapping() { XOJ_CHECK_TYPE(Control); return this->snapRotation; } bool Control::isGridSnapping() { XOJ_CHECK_TYPE(Control); return this->snapGrid; } void Control::rotationSnappingToggle() { XOJ_CHECK_TYPE(Control); if (!this->snapRotation) { this->snapRotation = true; } else { this->snapRotation = false; } } void Control::gridSnappingToggle() { XOJ_CHECK_TYPE(Control); if (!this->snapGrid) { this->snapGrid = true; } else { this->snapGrid = false; } } TextEditor* Control::getTextEditor() { XOJ_CHECK_TYPE(Control); if (this->win) { return this->win->getXournal()->getTextEditor(); } return NULL; } GladeSearchpath* Control::getGladeSearchPath() { XOJ_CHECK_TYPE(Control); return this->gladeSearchPath; } Settings* Control::getSettings() { XOJ_CHECK_TYPE(Control); return settings; } ScrollHandler* Control::getScrollHandler() { XOJ_CHECK_TYPE(Control); return this->scrollHandler; } MetadataManager* Control::getMetadataManager() { XOJ_CHECK_TYPE(Control); return this->metadata; } Sidebar* Control::getSidebar() { XOJ_CHECK_TYPE(Control); return this->sidebar; } SearchBar* Control::getSearchBar() { XOJ_CHECK_TYPE(Control); return this->searchBar; } AudioController* Control::getAudioController() { XOJ_CHECK_TYPE(Control); return this->audioController; } PageTypeHandler* Control::getPageTypes() { XOJ_CHECK_TYPE(Control); return this->pageTypes; } PageTypeMenu* Control::getNewPageType() { XOJ_CHECK_TYPE(Control); return this->newPageType; } PageBackgroundChangeController* Control::getPageBackgroundChangeController() { XOJ_CHECK_TYPE(Control); return this->pageBackgroundChangeController; } LayerController* Control::getLayerController() { XOJ_CHECK_TYPE(Control); return this->layerController; }