You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2831 lines
84 KiB

#include "Control.h"
#include <ctime>
#include <fstream>
#include <memory>
#include <numeric>
#include <sstream>
#include <utility>
#include <gio/gio.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gui/TextEditor.h"
#include "gui/XournalView.h"
#include "gui/XournalppCursor.h"
#include "gui/dialog/AboutDialog.h"
#include "gui/dialog/FillTransparencyDialog.h"
#include "gui/dialog/FormatDialog.h"
#include "gui/dialog/GotoDialog.h"
#include "gui/dialog/PageTemplateDialog.h"
#include "gui/dialog/SelectBackgroundColorDialog.h"
#include "gui/dialog/SettingsDialog.h"
#include "gui/dialog/ToolbarManageDialog.h"
#include "gui/dialog/toolbarCustomize/ToolbarDragDropHandler.h"
#include "gui/inputdevices/HandRecognition.h"
#include "gui/toolbarMenubar/ToolMenuHandler.h"
#include "gui/toolbarMenubar/model/ToolbarData.h"
#include "gui/toolbarMenubar/model/ToolbarModel.h"
#include "jobs/AutosaveJob.h"
#include "jobs/BlockingJob.h"
#include "jobs/CustomExportJob.h"
#include "jobs/PdfExportJob.h"
#include "jobs/SaveJob.h"
#include "layer/LayerController.h"
#include "model/BackgroundImage.h"
#include "model/FormatDefinitions.h"
#include "model/StrokeStyle.h"
#include "model/XojPage.h"
#include "pagetype/PageTypeHandler.h"
#include "pagetype/PageTypeMenu.h"
#include "plugin/PluginController.h"
#include "serializing/ObjectInputStream.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 "xojfile/LoadHandler.h"
#include "CrashHandler.h"
#include "FullscreenHandler.h"
#include "LatexController.h"
#include "PageBackgroundChangeController.h"
#include "PathUtil.h"
#include "PrintHandler.h"
#include "Stacktrace.h"
#include "StringUtils.h"
#include "UndoRedoController.h"
#include "Util.h"
#include "XojMsgBox.h"
#include "config-dev.h"
#include "config-features.h"
#include "config.h"
#include "i18n.h"
Control::Control(GladeSearchpath* gladeSearchPath) {
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 XournalppCursor(this);
this->lastAction = ACTION_NONE;
this->lastGroup = GROUP_NOGROUP;
this->lastEnabled = false;
auto name = Util::getConfigFile(SETTINGS_XML_FILE);
this->settings = new Settings(std::move(name));
this->settings->load();
this->applyPreferredLanguage();
TextView::setDpi(settings->getDisplayDpi());
this->pageTypes = new PageTypeHandler(gladeSearchPath);
this->newPageType = new PageTypeMenu(this->pageTypes, settings, true, true);
this->audioController = new AudioController(this->settings, this);
this->scrollHandler = new ScrollHandler(this);
this->scheduler = new XournalScheduler();
this->doc = new Document(this);
// for crashhandling
setEmergencyDocument(this->doc);
this->zoom = new ZoomControl();
this->zoom->setZoomStep(this->settings->getZoomStep() / 100.0);
this->zoom->setZoomStepScroll(this->settings->getZoomStepScroll() / 100.0);
this->zoom->setZoom100Value(this->settings->getDisplayDpi() / Util::DPI_NORMALIZATION_FACTOR);
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, reinterpret_cast<GSourceFunc>(checkChangedDocument), this);
this->pageBackgroundChangeController = new PageBackgroundChangeController(this);
this->layerController = new LayerController(this);
this->layerController->registerListener(this);
this->fullscreenHandler = new FullscreenHandler(settings);
this->pluginController = new PluginController(this);
this->pluginController->registerToolbar();
}
Control::~Control() {
g_source_remove(this->changeTimout);
this->enableAutosave(false);
deleteLastAutosaveFile("");
this->scheduler->stop();
this->changedPages.clear(); // can be removed, will be done by implicit destructor
delete this->pluginController;
this->pluginController = nullptr;
delete this->clipboardHandler;
this->clipboardHandler = nullptr;
delete this->recent;
this->recent = nullptr;
delete this->undoRedo;
this->undoRedo = nullptr;
delete this->settings;
this->settings = nullptr;
delete this->toolHandler;
this->toolHandler = nullptr;
delete this->sidebar;
this->sidebar = nullptr;
delete this->doc;
this->doc = nullptr;
delete this->searchBar;
this->searchBar = nullptr;
delete this->scrollHandler;
this->scrollHandler = nullptr;
delete this->newPageType;
this->newPageType = nullptr;
delete this->pageTypes;
this->pageTypes = nullptr;
delete this->metadata;
this->metadata = nullptr;
delete this->cursor;
this->cursor = nullptr;
delete this->zoom;
this->zoom = nullptr;
delete this->scheduler;
this->scheduler = nullptr;
delete this->dragDropHandler;
this->dragDropHandler = nullptr;
delete this->audioController;
this->audioController = nullptr;
delete this->pageBackgroundChangeController;
this->pageBackgroundChangeController = nullptr;
delete this->layerController;
this->layerController = nullptr;
delete this->fullscreenHandler;
this->fullscreenHandler = nullptr;
}
void Control::renameLastAutosaveFile() {
if (this->lastAutosaveFilename.empty()) {
return;
}
auto const& filename = this->lastAutosaveFilename;
auto renamed = Util::getAutosaveFilepath();
Util::clearExtensions(renamed);
if (!filename.empty() && filename.string().front() != '.') {
// This file must be a fresh, unsaved document. Since this file is
// already in ~/.xournalpp/autosave/, we need to change the renamed filename.
renamed += ".old.autosave.xopp";
} else {
// The file is a saved document with the form ".<filename>.autosave.xopp"
renamed += filename.filename();
}
g_message("%s", FS(_F("Autosave renamed from {1} to {2}") % this->lastAutosaveFilename.string() % renamed.string())
.c_str());
if (!fs::exists(filename)) {
this->save(false);
}
std::vector<string> errors;
try {
Util::safeRenameFile(filename, renamed);
} catch (fs::filesystem_error const& e) {
auto fmtstr = _F("Could not rename autosave file from \"{1}\" to \"{2}\": {3}");
errors.emplace_back(FS(fmtstr % filename.string() % renamed.string() % e.what()));
}
if (!errors.empty()) {
string error = std::accumulate(errors.begin() + 1, errors.end(), *errors.begin(),
[](const string& e1, const string& e2) { return e1 + "\n" + e2; });
Util::execInUiThread([=]() {
string msg = FS(_F("Autosave failed with an error: {1}") % error);
XojMsgBox::showErrorToUser(getGtkWindow(), msg);
});
}
}
void Control::setLastAutosaveFile(fs::path newAutosaveFile) { this->lastAutosaveFilename = std::move(newAutosaveFile); }
void Control::deleteLastAutosaveFile(fs::path newAutosaveFile) {
fs::remove(this->lastAutosaveFilename);
this->lastAutosaveFilename = std::move(newAutosaveFile);
}
auto Control::checkChangedDocument(Control* control) -> bool {
if (!control->doc->tryLock()) {
// call again later
return true;
}
for (auto const& page: control->changedPages) {
int p = control->doc->indexOf(page);
if (p != -1) {
control->firePageChanged(p);
}
}
control->changedPages.clear();
control->doc->unlock();
// Call again
return true;
}
void Control::saveSettings() {
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) {
win->setRecentMenu(recent->getMenu());
selectTool(toolHandler->getToolType());
this->win = win;
this->sidebar = new Sidebar(win, this);
XojMsgBox::setDefaultWindow(getGtkWindow());
updatePageNumbers(0, npos);
toolHandler->eraserTypeChanged();
this->searchBar = new SearchBar(this);
// Disable undo buttons
undoRedoChanged();
if (settings->isPresentationMode()) {
setViewPresentationMode(true);
} else if (settings->isViewFixedRows()) {
setViewRows(settings->getViewRows());
} else {
setViewColumns(settings->getViewColumns());
}
setViewLayoutVert(settings->getViewLayoutVert());
setViewLayoutR2L(settings->getViewLayoutR2L());
setViewLayoutB2T(settings->getViewLayoutB2T());
setViewPairedPages(settings->isShowPairedPages());
penSizeChanged();
eraserSizeChanged();
hilighterSizeChanged();
updateDeletePageButton();
toolFillChanged();
toolLineStyleChanged();
this->clipboardHandler = new ClipboardHandler(this, win->getXournal()->getWidget());
this->enableAutosave(settings->isAutosaveEnabled());
win->setFontButtonFont(settings->getFont());
this->pluginController->registerMenu();
fireActionSelected(GROUP_SNAPPING, settings->isSnapRotation() ? ACTION_ROTATION_SNAPPING : ACTION_NONE);
fireActionSelected(GROUP_GRID_SNAPPING, settings->isSnapGrid() ? ACTION_GRID_SNAPPING : ACTION_NONE);
}
auto Control::autosaveCallback(Control* control) -> bool {
if (!control->undoRedo->isChangedAutosave()) {
// do nothing, nothing changed
return true;
}
g_message("Info: autosave document...");
auto* job = new AutosaveJob(control);
control->scheduler->addJob(job, JOB_PRIORITY_NONE);
job->unref();
return true;
}
void Control::enableAutosave(bool enable) {
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, reinterpret_cast<GSourceFunc>(autosaveCallback), this);
}
}
void Control::updatePageNumbers(size_t page, size_t pdfPage) {
if (this->win == nullptr) {
return;
}
this->win->updatePageNumbers(page, this->doc->getPageCount(), pdfPage);
this->sidebar->selectPageNr(page, pdfPage);
this->metadata->storeMetadata(this->doc->getEvMetadataFilename(), page, getZoomControl()->getZoomReal());
int current = getCurrentPageNo();
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) {
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:
UndoRedoController::undo(this);
break;
case ACTION_REDO:
UndoRedoController::redo(this);
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_FLOATING_TOOLBOX:
if (enabled) {
selectTool(TOOL_FLOATING_TOOLBOX);
}
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_TOOL_DRAW_SPLINE:
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_FINE:
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_LINE_STYLE_PLAIN:
setLineStyle("plain");
break;
case ACTION_TOOL_LINE_STYLE_DASH:
setLineStyle("dash");
break;
case ACTION_TOOL_LINE_STYLE_DASH_DOT:
setLineStyle("dashdot");
break;
case ACTION_TOOL_LINE_STYLE_DOT:
setLineStyle("dot");
break;
case ACTION_TOOL_ERASER_SIZE_VERY_FINE:
if (enabled) {
this->toolHandler->setEraserSize(TOOL_SIZE_VERY_FINE);
eraserSizeChanged();
}
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_ERASER_SIZE_VERY_THICK:
if (enabled) {
this->toolHandler->setEraserSize(TOOL_SIZE_VERY_THICK);
eraserSizeChanged();
}
break;
case ACTION_TOOL_PEN_SIZE_VERY_FINE:
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_VERY_FINE:
if (enabled) {
this->toolHandler->setHilighterSize(TOOL_SIZE_VERY_FINE);
hilighterSizeChanged();
}
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_SIZE_VERY_THICK:
if (enabled) {
this->toolHandler->setHilighterSize(TOOL_SIZE_VERY_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, enabled); });
break;
case ACTION_VIEW_PAIRED_PAGES:
setViewPairedPages(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:
setFullscreen(enabled);
break;
case ACTION_SET_COLUMNS_1:
setViewColumns(1);
break;
case ACTION_SET_COLUMNS_2:
setViewColumns(2);
break;
case ACTION_SET_COLUMNS_3:
setViewColumns(3);
break;
case ACTION_SET_COLUMNS_4:
setViewColumns(4);
break;
case ACTION_SET_COLUMNS_5:
setViewColumns(5);
break;
case ACTION_SET_COLUMNS_6:
setViewColumns(6);
break;
case ACTION_SET_COLUMNS_7:
setViewColumns(7);
break;
case ACTION_SET_COLUMNS_8:
setViewColumns(8);
break;
case ACTION_SET_ROWS_1:
setViewRows(1);
break;
case ACTION_SET_ROWS_2:
setViewRows(2);
break;
case ACTION_SET_ROWS_3:
setViewRows(3);
break;
case ACTION_SET_ROWS_4:
setViewRows(4);
break;
case ACTION_SET_ROWS_5:
setViewRows(5);
break;
case ACTION_SET_ROWS_6:
setViewRows(6);
break;
case ACTION_SET_ROWS_7:
setViewRows(7);
break;
case ACTION_SET_ROWS_8:
setViewRows(8);
break;
case ACTION_SET_LAYOUT_HORIZONTAL:
setViewLayoutVert(false);
break;
case ACTION_SET_LAYOUT_VERTICAL:
setViewLayoutVert(true);
break;
case ACTION_SET_LAYOUT_L2R:
setViewLayoutR2L(false);
break;
case ACTION_SET_LAYOUT_R2L:
setViewLayoutR2L(true);
break;
case ACTION_SET_LAYOUT_T2B:
setViewLayoutB2T(false);
break;
case ACTION_SET_LAYOUT_B2T:
setViewLayoutB2T(true);
break;
case ACTION_AUDIO_RECORD: {
bool result = false;
if (enabled) {
result = audioController->startRecording();
} else {
result = audioController->stopRecording();
}
if (!result) {
Util::execInUiThread([=]() {
gtk_toggle_tool_button_set_active(reinterpret_cast<GtkToggleToolButton*>(toolbutton), !enabled);
string msg = _("Recorder could not be started.");
g_warning("%s", msg.c_str());
XojMsgBox::showErrorToUser(Control::getGtkWindow(), msg);
});
}
break;
}
case ACTION_AUDIO_PAUSE_PLAYBACK:
if (enabled) {
this->getAudioController()->pausePlayback();
} else {
this->getAudioController()->continuePlayback();
}
break;
case ACTION_AUDIO_SEEK_FORWARDS:
this->getAudioController()->seekForwards();
break;
case ACTION_AUDIO_SEEK_BACKWARDS:
this->getAudioController()->seekBackwards();
break;
case ACTION_AUDIO_STOP_PLAYBACK:
this->getAudioController()->stopPlayback();
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;
// Plugin menu
case ACTION_PLUGIN_MANAGER:
this->pluginController->showPluginManager();
break;
// Menu Help
case ACTION_HELP:
XojMsgBox::showHelp(getGtkWindow());
break;
case ACTION_ABOUT:
showAbout();
break;
default:
g_warning("Unhandled action event: %s / %s (%i / %i)", ActionType_toString(type).c_str(),
ActionGroup_toString(group).c_str(), type, group);
Stacktrace::printStracktrace();
}
if (type >= ACTION_TOOL_PEN && type <= ACTION_TOOL_HAND) {
auto at = static_cast<ActionType>(toolHandler->getToolType() - TOOL_PEN + ACTION_TOOL_PEN);
if (type == at && !enabled) {
fireActionSelected(GROUP_TOOL, at);
}
}
}
auto Control::copy() -> bool {
if (this->win && this->win->getXournal()->copy()) {
return true;
}
return this->clipboardHandler->copy();
}
auto Control::cut() -> bool {
if (this->win && this->win->getXournal()->cut()) {
return true;
}
return this->clipboardHandler->cut();
}
auto Control::paste() -> bool {
if (this->win && this->win->getXournal()->paste()) {
return true;
}
return this->clipboardHandler->paste();
}
void Control::selectFillAlpha(bool pen) {
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() {
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
*/
auto Control::firePageSelected(const PageRef& page) -> size_t {
this->doc->lock();
size_t pageId = this->doc->indexOf(page);
this->doc->unlock();
if (pageId == npos) {
return npos;
}
DocumentHandler::firePageSelected(pageId);
return pageId;
}
void Control::firePageSelected(size_t page) { DocumentHandler::firePageSelected(page); }
void Control::manageToolbars() {
ToolbarManageDialog dlg(this->gladeSearchPath, this->win->getToolbarModel());
dlg.show(GTK_WINDOW(this->win->getWindow()));
this->win->updateToolbarMenu();
auto filepath = Util::getConfigFile(TOOLBAR_CONFIG);
this->win->getToolbarModel()->save(filepath);
}
void Control::customizeToolbars() {
g_return_if_fail(this->win != nullptr);
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
{
auto* data = new ToolbarData(*this->win->getSelectedToolbar());
ToolbarModel* model = this->win->getToolbarModel();
model->initCopyNameId(data);
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() {
if (!this->dragDropHandler) {
return;
}
this->dragDropHandler->clearToolbarsFromDragAndDrop();
}
void Control::startDragDropToolbar() {
if (!this->dragDropHandler) {
return;
}
this->dragDropHandler->prepareToolbarsForDragAndDrop();
}
auto Control::isInDragAndDropToolbar() -> bool {
if (!this->dragDropHandler) {
return false;
}
return this->dragDropHandler->isInDragAndDrop();
}
void Control::setShapeTool(ActionType type, bool enabled) {
if (!enabled) {
// 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_SPLINE && type == ACTION_TOOL_DRAW_SPLINE) ||
(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_TOOL_DRAW_SPLINE:
this->toolHandler->setDrawingType(DRAWING_TYPE_SPLINE);
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::setFullscreen(bool enabled) {
fullscreenHandler->setFullscreen(win, enabled);
fireActionSelected(GROUP_FULLSCREEN, enabled ? ACTION_FULLSCREEN : ACTION_NONE);
}
void Control::disableSidebarTmp(bool disabled) { this->sidebar->setTmpDisabled(disabled); }
void Control::addDefaultPage(string pageTemplate) {
if (pageTemplate.empty()) {
pageTemplate = settings->getPageTemplate();
}
PageTemplateSettings model;
model.parse(pageTemplate);
auto page = std::make_shared<XojPage>(model.getPageWidth(), model.getPageHeight());
page->setBackgroundColor(model.getBackgroundColor());
page->setBackgroundType(model.getBackgroundType());
this->doc->lock();
this->doc->addPage(std::move(page));
this->doc->unlock();
updateDeletePageButton();
}
void Control::updateDeletePageButton() {
if (this->win) {
GtkWidget* w = this->win->get("menuDeletePage");
gtk_widget_set_sensitive(w, this->doc->getPageCount() > 1);
}
}
void Control::deletePage() {
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 == 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(std::make_unique<InsertDeletePageUndoAction>(page, pNr, false));
if (pNr >= this->doc->getPageCount()) {
pNr = this->doc->getPageCount() - 1;
}
scrollHandler->scrollToPage(pNr, 0);
this->win->getXournal()->forceUpdatePagenumbers();
}
void Control::insertNewPage(size_t position) { pageBackgroundChangeController->insertNewPage(position); }
void Control::insertPage(const PageRef& page, size_t position) {
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(std::make_unique<InsertDeletePageUndoAction>(page, position, true));
}
void Control::gotoPage() {
auto* 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() {
if (this->win == nullptr) {
return;
}
// Update paper color button
auto const& p = getCurrentPage();
if (!p || this->win == nullptr) {
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() {
auto* dlg = new PageTemplateDialog(this->gladeSearchPath, settings, pageTypes);
dlg->show(GTK_WINDOW(this->win->getWindow()));
if (dlg->isSaved()) {
newPageType->loadDefaultPage();
}
delete dlg;
}
void Control::paperFormat() {
auto const& page = getCurrentPage();
if (!page || page->getBackgroundType().isPdfPage()) {
return;
}
clearSelectionEndText();
auto* 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();
Document::setPageSize(page, width, height);
this->doc->unlock();
}
size_t pageNo = doc->indexOf(page);
if (pageNo != npos && pageNo < doc->getPageCount()) {
this->firePageSizeChanged(pageNo);
}
delete dlg;
}
void Control::changePageBackgroundColor() {
int pNr = getCurrentPageNo();
this->doc->lock();
auto const& p = this->doc->getPage(pNr);
this->doc->unlock();
if (!p) {
return;
}
clearSelectionEndText();
PageType bg = p->getBackgroundType();
if (bg.isSpecial()) {
return;
}
SelectBackgroundColorDialog dlg(this);
dlg.show(GTK_WINDOW(this->win->getWindow()));
if (auto optColor = dlg.getSelectedColor(); optColor) {
p->setBackgroundColor(*optColor);
firePageChanged(pNr);
}
}
void Control::setViewPairedPages(bool enabled) {
settings->setShowPairedPages(enabled);
fireActionSelected(GROUP_PAIRED_PAGES, enabled ? ACTION_VIEW_PAIRED_PAGES : ACTION_NOT_SELECTED);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setViewPresentationMode(bool enabled) {
if (enabled) {
bool success = zoom->updateZoomPresentationValue();
if (!success) {
g_warning("Error calculating zoom value");
fireActionSelected(GROUP_PRESENTATION_MODE, ACTION_NOT_SELECTED);
return;
}
} else {
if (settings->isViewFixedRows()) {
setViewRows(settings->getViewRows());
} else {
setViewColumns(settings->getViewColumns());
}
setViewLayoutVert(settings->getViewLayoutVert());
setViewLayoutR2L(settings->getViewLayoutR2L());
setViewLayoutB2T(settings->getViewLayoutB2T());
}
zoom->setZoomPresentationMode(enabled);
settings->setPresentationMode(enabled);
// Disable Zoom
fireEnableAction(ACTION_ZOOM_IN, !enabled);
fireEnableAction(ACTION_ZOOM_OUT, !enabled);
fireEnableAction(ACTION_ZOOM_FIT, !enabled);
fireEnableAction(ACTION_ZOOM_100, !enabled);
fireEnableAction(ACTION_FOOTER_ZOOM_SLIDER, !enabled);
gtk_widget_set_sensitive(win->get("menuitemLayout"), !enabled);
gtk_widget_set_sensitive(win->get("menuitemViewDimensions"), !enabled);
// disable selection of scroll hand tool
fireEnableAction(ACTION_TOOL_HAND, !enabled);
fireActionSelected(GROUP_PRESENTATION_MODE, enabled ? ACTION_VIEW_PRESENTATION_MODE : ACTION_NOT_SELECTED);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setPairsOffset(int numOffset) {
settings->setPairsOffset(numOffset);
fireActionSelected(GROUP_PAIRED_PAGES, numOffset ? ACTION_SET_PAIRS_OFFSET : ACTION_NOT_SELECTED);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setViewColumns(int numColumns) {
settings->setViewColumns(numColumns);
settings->setViewFixedRows(false);
ActionType action{};
switch (numColumns) {
case 1:
action = ACTION_SET_COLUMNS_1;
break;
case 2:
action = ACTION_SET_COLUMNS_2;
break;
case 3:
action = ACTION_SET_COLUMNS_3;
break;
case 4:
action = ACTION_SET_COLUMNS_4;
break;
case 5:
action = ACTION_SET_COLUMNS_5;
break;
case 6:
action = ACTION_SET_COLUMNS_6;
break;
case 7:
action = ACTION_SET_COLUMNS_7;
break;
case 8:
action = ACTION_SET_COLUMNS_8;
break;
default:
action = ACTION_SET_COLUMNS;
}
fireActionSelected(GROUP_FIXED_ROW_OR_COLS, action);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setViewRows(int numRows) {
settings->setViewRows(numRows);
settings->setViewFixedRows(true);
ActionType action{};
switch (numRows) {
case 1:
action = ACTION_SET_ROWS_1;
break;
case 2:
action = ACTION_SET_ROWS_2;
break;
case 3:
action = ACTION_SET_ROWS_3;
break;
case 4:
action = ACTION_SET_ROWS_4;
break;
case 5:
action = ACTION_SET_ROWS_5;
break;
case 6:
action = ACTION_SET_ROWS_6;
break;
case 7:
action = ACTION_SET_ROWS_7;
break;
case 8:
action = ACTION_SET_ROWS_8;
break;
default:
action = ACTION_SET_ROWS;
}
fireActionSelected(GROUP_FIXED_ROW_OR_COLS, action);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setViewLayoutVert(bool vert) {
settings->setViewLayoutVert(vert);
ActionType action{};
if (vert) {
action = ACTION_SET_LAYOUT_VERTICAL;
} else {
action = ACTION_SET_LAYOUT_HORIZONTAL;
}
fireActionSelected(GROUP_LAYOUT_HORIZONTAL, action);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setViewLayoutR2L(bool r2l) {
settings->setViewLayoutR2L(r2l);
ActionType action{};
if (r2l) {
action = ACTION_SET_LAYOUT_R2L;
} else {
action = ACTION_SET_LAYOUT_L2R;
}
fireActionSelected(GROUP_LAYOUT_LR, action);
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
void Control::setViewLayoutB2T(bool b2t) {
settings->setViewLayoutB2T(b2t);
ActionType action{};
if (b2t) {
action = ACTION_SET_LAYOUT_B2T;
} else {
action = ACTION_SET_LAYOUT_T2B;
}
fireActionSelected(GROUP_LAYOUT_TB, action);
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, bool enabled) {
switch (type) {
case ACTION_ZOOM_100:
zoom->zoom100();
break;
case ACTION_ZOOM_FIT:
if (enabled) {
zoom->updateZoomFitValue();
}
// enable/disable ZoomFit
zoom->setZoomFitMode(enabled);
break;
case ACTION_ZOOM_IN:
zoom->zoomOneStep(ZOOM_IN);
break;
case ACTION_ZOOM_OUT:
zoom->zoomOneStep(ZOOM_OUT);
break;
default:
break;
}
}
auto Control::getCurrentPageNo() -> size_t {
if (this->win) {
return this->win->getXournal()->getCurrentPage();
}
return 0;
}
auto Control::searchTextOnPage(string text, int p, int* occures, double* top) -> bool {
return getWindow()->getXournal()->searchTextOnPage(std::move(text), p, occures, top);
}
auto Control::getCurrentPage() -> PageRef {
this->doc->lock();
PageRef p = this->doc->getPage(getCurrentPageNo());
this->doc->unlock();
return p;
}
void Control::fileOpened(fs::path const& path) { openFile(path); }
void Control::undoRedoChanged() {
fireEnableAction(ACTION_UNDO, undoRedo->canUndo());
fireEnableAction(ACTION_REDO, undoRedo->canRedo());
win->setUndoDescription(undoRedo->undoDescription());
win->setRedoDescription(undoRedo->redoDescription());
updateWindowTitle();
}
void Control::undoRedoPageChanged(PageRef page) {
if (std::find(begin(this->changedPages), end(this->changedPages), page) == end(this->changedPages)) {
return;
}
this->changedPages.emplace_back(std::move(page));
}
void Control::selectTool(ToolType type) {
toolHandler->selectTool(type);
if (win) {
(win->getXournal()->getViewFor(getCurrentPageNo()))->rerenderPage();
}
}
void Control::selectDefaultTool() {
ButtonConfig* cfg = settings->getDefaultButtonConfig();
cfg->acceptActions(toolHandler);
}
void Control::toolChanged() {
ToolType type = toolHandler->getToolType();
// Convert enum values, enums has to be in the same order!
auto at = static_cast<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_TOOL_DRAW_SPLINE, toolHandler->hasCapability(TOOL_CAP_SPLINE));
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_FINE, 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;
} else if (toolHandler->getDrawingType() == DRAWING_TYPE_SPLINE) {
rulerAction = ACTION_TOOL_DRAW_SPLINE;
}
fireActionSelected(GROUP_RULER, rulerAction);
getCursor()->updateCursor();
if (type != TOOL_TEXT) {
if (win) {
win->getXournal()->endTextAllPages();
}
}
}
void Control::eraserSizeChanged() {
switch (toolHandler->getEraserSize()) {
case TOOL_SIZE_VERY_FINE:
fireActionSelected(GROUP_ERASER_SIZE, ACTION_TOOL_ERASER_SIZE_VERY_FINE);
break;
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;
case TOOL_SIZE_VERY_THICK:
fireActionSelected(GROUP_ERASER_SIZE, ACTION_TOOL_ERASER_SIZE_VERY_THICK);
break;
default:
break;
}
}
void Control::penSizeChanged() {
switch (toolHandler->getPenSize()) {
case TOOL_SIZE_VERY_FINE:
fireActionSelected(GROUP_PEN_SIZE, ACTION_TOOL_PEN_SIZE_VERY_FINE);
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() {
switch (toolHandler->getHilighterSize()) {
case TOOL_SIZE_VERY_FINE:
fireActionSelected(GROUP_HILIGHTER_SIZE, ACTION_TOOL_HILIGHTER_SIZE_VERY_FINE);
break;
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;
case TOOL_SIZE_VERY_THICK:
fireActionSelected(GROUP_HILIGHTER_SIZE, ACTION_TOOL_HILIGHTER_SIZE_VERY_THICK);
break;
default:
break;
}
}
void Control::toolSizeChanged() {
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_FINE);
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_THICK);
break;
}
getCursor()->updateCursor();
}
void Control::toolFillChanged() {
fireActionSelected(GROUP_FILL, toolHandler->getFill() != -1 ? ACTION_TOOL_FILL : ACTION_NONE);
fireActionSelected(GROUP_PEN_FILL, toolHandler->getPenFillEnabled() ? ACTION_TOOL_PEN_FILL : ACTION_NONE);
fireActionSelected(GROUP_HILIGHTER_FILL,
toolHandler->getHilighterFillEnabled() ? ACTION_TOOL_HILIGHTER_FILL : ACTION_NONE);
}
void Control::toolLineStyleChanged() {
const LineStyle& lineStyle = toolHandler->getTool(TOOL_PEN).getLineStyle();
string style = StrokeStyle::formatStyle(lineStyle);
if (style == "dash") {
fireActionSelected(GROUP_LINE_STYLE, ACTION_TOOL_LINE_STYLE_DASH);
} else if (style == "dashdot") {
fireActionSelected(GROUP_LINE_STYLE, ACTION_TOOL_LINE_STYLE_DASH_DOT);
} else if (style == "dot") {
fireActionSelected(GROUP_LINE_STYLE, ACTION_TOOL_LINE_STYLE_DOT);
} else {
fireActionSelected(GROUP_LINE_STYLE, ACTION_TOOL_LINE_STYLE_PLAIN);
}
}
/**
* 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) {
fireActionSelected(GROUP_COLOR, ACTION_SELECT_COLOR);
getCursor()->updateCursor();
if (userSelection && this->win && toolHandler->getColor() != Color(-1)) {
EditSelection* sel = this->win->getXournal()->getSelection();
if (sel) {
UndoAction* undo = sel->setColor(toolHandler->getColor());
// move into selection
undoRedo->addUndoAction(UndoActionPtr(undo));
}
TextEditor* edit = getTextEditor();
if (this->toolHandler->getToolType() == TOOL_TEXT && edit != nullptr) {
// Todo move into selection
undoRedo->addUndoAction(UndoActionPtr(edit->setColor(toolHandler->getColor())));
}
}
}
void Control::setCustomColorSelected() { fireActionSelected(GROUP_COLOR, ACTION_SELECT_COLOR_CUSTOM); }
void Control::showSettings() {
// take note of some settings before to compare with after
auto selectionColor = settings->getBorderColor();
bool verticalSpace = settings->getAddVerticalSpace();
int verticalSpaceAmount = settings->getAddVerticalSpaceAmount();
bool horizontalSpace = settings->getAddHorizontalSpace();
int horizontalSpaceAmount = settings->getAddHorizontalSpaceAmount();
StylusCursorType stylusCursorType = settings->getStylusCursorType();
bool highlightPosition = settings->isHighlightPosition();
auto* dlg = new SettingsDialog(this->gladeSearchPath, settings, this);
dlg->show(GTK_WINDOW(this->win->getWindow()));
// note which settings have changed and act accordingly
if (selectionColor != settings->getBorderColor()) {
win->getXournal()->forceUpdatePagenumbers();
}
if (verticalSpace != settings->getAddVerticalSpace() || horizontalSpace != settings->getAddHorizontalSpace() ||
verticalSpaceAmount != settings->getAddVerticalSpaceAmount() ||
horizontalSpaceAmount != settings->getAddHorizontalSpaceAmount()) {
int currentPage = getCurrentPageNo();
win->getXournal()->layoutPages();
scrollHandler->scrollToPage(currentPage);
}
if (stylusCursorType != settings->getStylusCursorType() || highlightPosition != settings->isHighlightPosition()) {
getCursor()->updateCursor();
}
win->updateScrollbarSidebarPosition();
enableAutosave(settings->isAutosaveEnabled());
this->zoom->setZoomStep(settings->getZoomStep() / 100.0);
this->zoom->setZoomStepScroll(settings->getZoomStepScroll() / 100.0);
this->zoom->setZoom100Value(settings->getDisplayDpi() / Util::DPI_NORMALIZATION_FACTOR);
getWindow()->getXournal()->getHandRecognition()->reload();
TextView::setDpi(settings->getDisplayDpi());
delete dlg;
}
auto Control::newFile(string pageTemplate, fs::path filepath) -> bool {
if (!this->close(true)) {
return false;
}
Document newDoc(this);
this->doc->lock();
*doc = newDoc;
if (!filepath.empty()) {
this->doc->setFilepath(std::move(filepath));
}
this->doc->unlock();
addDefaultPage(std::move(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
*/
auto Control::shouldFileOpen(fs::path const& filepath) const -> bool {
auto basePath = Util::getConfigSubfolder("");
auto isChild = Util::isChildOrEquivalent(filepath, basePath);
if (isChild) {
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.") %
basePath.u8string());
XojMsgBox::showErrorToUser(getGtkWindow(), msg);
}
return !isChild;
}
auto Control::openFile(fs::path filepath, int scrollToPage, bool forceOpen) -> bool {
if (filepath.empty()) {
bool attachPdf = false;
XojOpenDlg dlg(getGtkWindow(), this->settings);
filepath = dlg.showOpenDialog(false, attachPdf);
g_message("%s", (_F("file: {1}") % filepath.string()).c_str());
}
if (filepath.empty() || (!forceOpen && !shouldFileOpen(filepath))) {
return false;
}
if (!this->close(false)) {
return false;
}
// Read template file
if (filepath.extension() == ".xopt") {
return loadXoptTemplate(filepath);
}
if (filepath.extension() == ".pdf") {
return loadPdf(filepath, scrollToPage);
}
LoadHandler loadHandler;
Document* loadedDocument = loadHandler.loadDocument(filepath);
if ((loadedDocument != nullptr && loadHandler.isAttachedPdfMissing()) ||
!loadHandler.getMissingPdfFilename().empty()) {
// give the user a second chance to select a new PDF filepath, 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(filepath);
} else if (res == 1) // select another PDF background
{
bool attachToDocument = false;
XojOpenDlg dlg(getGtkWindow(), this->settings);
auto pdfFilename = dlg.showOpenDialog(true, attachToDocument);
if (!pdfFilename.empty()) {
loadHandler.setPdfReplacement(pdfFilename, attachToDocument);
loadedDocument = loadHandler.loadDocument(filepath);
}
}
}
if (!loadedDocument) {
string msg = FS(_F("Error opening file \"{1}\"") % filepath.string()) + "\n" + loadHandler.getLastError();
XojMsgBox::showErrorToUser(getGtkWindow(), msg);
fileLoaded(scrollToPage);
return false;
} else if (loadHandler.getFileVersion() > FILE_FORMAT_VERSION) {
GtkWidget* dialog = gtk_message_dialog_new(
getGtkWindow(), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO, "%s",
_("The file being loaded has a file format version newer than the one currently supported by this "
"version of Xournal++, so it may not load properly. Open anyways?"));
int response = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
if (response != GTK_RESPONSE_YES) {
loadedDocument->clearDocument();
return false;
}
}
this->closeDocument();
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(filepath.parent_path());
fileLoaded(scrollToPage);
return true;
}
auto Control::loadPdf(const fs::path& filepath, int scrollToPage) -> bool {
LoadHandler loadHandler;
if (settings->isAutloadPdfXoj()) {
fs::path f = filepath;
Util::clearExtensions(f);
f += ".xopp";
Document* tmp = loadHandler.loadDocument(f);
if (tmp == nullptr) {
f = filepath;
Util::clearExtensions(f);
f += ".xoj";
tmp = loadHandler.loadDocument(f);
}
if (tmp) {
this->doc->lock();
this->doc->clearDocument();
*this->doc = *tmp;
this->doc->unlock();
fileLoaded(scrollToPage);
return true;
}
}
bool an = annotatePdf(filepath, false, false);
fileLoaded(scrollToPage);
return an;
}
auto Control::loadXoptTemplate(fs::path const& filepath) -> bool {
auto contents = Util::readString(filepath);
if (!contents.has_value()) {
return false;
}
newFile(*contents);
return true;
}
void Control::fileLoaded(int scrollToPage) {
this->doc->lock();
auto filepath = this->doc->getEvMetadataFilename();
this->doc->unlock();
if (!filepath.empty()) {
MetadataEntry md = MetadataManager::getForFile(filepath);
if (!md.valid) {
md.zoom = -1;
md.page = 0;
}
if (scrollToPage >= 0) {
md.page = scrollToPage;
}
loadMetadata(md);
RecentManager::addRecentFileFilename(filepath);
} else {
zoom->updateZoomFitValue();
zoom->setZoomFitMode(true);
}
updateWindowTitle();
win->getXournal()->forceUpdatePagenumbers();
getCursor()->updateCursor();
updateDeletePageButton();
}
class MetadataCallbackData {
public:
Control* ctrl{};
MetadataEntry md;
};
/**
* Load the data after processing the document...
*/
auto Control::loadMetadataCallback(MetadataCallbackData* data) -> bool {
if (!data->md.valid) {
delete data;
return false;
}
ZoomControl* zoom = data->ctrl->zoom;
if (zoom->isZoomPresentationMode()) {
data->ctrl->setViewPresentationMode(true);
} else if (zoom->isZoomFitMode()) {
zoom->updateZoomFitValue();
zoom->setZoomFitMode(true);
} else {
zoom->setZoomFitMode(false);
zoom->setZoom(data->md.zoom * zoom->getZoom100Value());
}
data->ctrl->scrollHandler->scrollToPage(data->md.page);
delete data;
// Do not call again!
return false;
}
void Control::loadMetadata(MetadataEntry md) {
auto* data = new MetadataCallbackData();
data->md = std::move(md);
data->ctrl = this;
g_idle_add(reinterpret_cast<GSourceFunc>(loadMetadataCallback), data);
}
auto Control::annotatePdf(fs::path filepath, bool /*attachPdf*/, bool attachToDocument) -> bool {
if (!this->close(false)) {
return false;
}
if (filepath.empty()) {
XojOpenDlg dlg(getGtkWindow(), this->settings);
filepath = dlg.showOpenDialog(true, attachToDocument);
if (filepath.empty()) {
return false;
}
}
this->closeDocument();
getCursor()->setCursorBusy(true);
this->doc->setFilepath("");
bool res = this->doc->readPdf(filepath, true, attachToDocument);
if (res) {
RecentManager::addRecentFileFilename(filepath.c_str());
this->doc->lock();
auto filepath = this->doc->getEvMetadataFilename();
this->doc->unlock();
MetadataEntry md = MetadataManager::getForFile(filepath);
loadMetadata(md);
} else {
this->doc->lock();
string errMsg = doc->getLastErrorMsg();
this->doc->unlock();
string msg = FS(_F("Error annotate PDF file \"{1}\"\n{2}") % filepath.string() % errMsg);
XojMsgBox::showErrorToUser(getGtkWindow(), msg);
}
getCursor()->setCursorBusy(false);
fireDocumentChanged(DOCUMENT_CHANGE_COMPLETE);
getCursor()->updateCursor();
return true;
}
void Control::print() {
PrintHandler print;
this->doc->lock();
print.print(this->doc, getCurrentPageNo());
this->doc->unlock();
}
void Control::block(const string& name) {
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() {
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) { this->maxState = max; }
void Control::setCurrentState(int state) {
Util::execInUiThread([=]() { gtk_progress_bar_set_fraction(this->pgState, gdouble(state) / this->maxState); });
}
auto Control::save(bool synchron) -> bool {
// clear selection before saving
clearSelectionEndText();
this->doc->lock();
fs::path filepath = this->doc->getFilepath();
this->doc->unlock();
if (filepath.empty()) {
if (!showSaveDialog()) {
return false;
}
}
auto* job = new SaveJob(this);
bool result = true;
if (synchron) {
result = job->save();
unblock();
this->resetSavedStatus();
} else {
this->scheduler->addJob(job, JOB_PRIORITY_URGENT);
}
job->unref();
return result;
}
auto Control::showSaveDialog() -> bool {
GtkWidget* dialog =
gtk_file_chooser_dialog_new(_("Save File"), getGtkWindow(), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"),
GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, nullptr);
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();
auto suggested_folder = this->doc->createSaveFolder(this->settings->getLastSavePath());
fs::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.u8string().c_str());
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), suggested_name.u8string().c_str());
gtk_file_chooser_add_shortcut_folder(GTK_FILE_CHOOSER(dialog), this->settings->getLastOpenPath().u8string().c_str(),
nullptr);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), false); // handled below
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;
}
auto fileTmp = Util::fromGtkFilename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
Util::clearExtensions(fileTmp);
fileTmp += ".xopp";
// Since we add the extension after the OK button, we have to check manually on existing files
if (askToReplace(fileTmp)) {
break;
}
}
auto filename = Util::fromGtkFilename(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)));
settings->setLastSavePath(filename.parent_path());
gtk_widget_destroy(dialog);
this->doc->lock();
this->doc->setFilepath(filename);
this->doc->unlock();
return true;
}
void Control::updateWindowTitle() {
string title{};
this->doc->lock();
if (doc->getFilepath().empty()) {
if (doc->getPdfFilepath().empty()) {
title = _("Unsaved Document");
} else {
if (undoRedo->isChanged()) {
title += "*";
}
title += doc->getPdfFilepath().filename().string();
}
} else {
if (undoRedo->isChanged()) {
title += "*";
}
title += doc->getFilepath().filename().string();
}
this->doc->unlock();
title += " - Xournal++";
gtk_window_set_title(getGtkWindow(), title.c_str());
}
void Control::exportAsPdf() {
this->clearSelectionEndText();
exportBase(new PdfExportJob(this));
}
void Control::exportAs() {
this->clearSelectionEndText();
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();
}
auto Control::saveAs() -> bool {
if (!showSaveDialog()) {
return false;
}
this->doc->lock();
auto filepath = doc->getFilepath();
this->doc->unlock();
if (filepath.empty()) {
return false;
}
// no lock needed, this is an uncritically operation
this->doc->setCreateBackupOnSave(false);
return save();
}
void Control::resetSavedStatus() {
this->doc->lock();
auto filepath = this->doc->getFilepath();
this->doc->unlock();
this->undoRedo->documentSaved();
RecentManager::addRecentFileFilename(filepath);
this->updateWindowTitle();
}
void Control::quit(bool allowCancel) {
if (!this->close(false, allowCancel)) {
if (!allowCancel) {
// Cancel is not allowed, and the user close or did not save
// This is probably called from macOS, where the Application
// now will be killed - therefore do an emergency save.
emergencySave();
}
return;
}
this->closeDocument();
this->scheduler->lock();
audioController->stopRecording();
settings->save();
this->scheduler->removeAllJobs();
this->scheduler->unlock();
gtk_main_quit();
}
auto Control::close(const bool allowDestroy, const bool allowCancel) -> bool {
clearSelectionEndText();
metadata->documentChanged();
bool discard = false;
const bool fileRemoved = !doc->getFilepath().empty() && !fs::exists(this->doc->getFilepath());
if (undoRedo->isChanged()) {
const auto message = fileRemoved ? _("Document file was removed.") : _("This document is not saved yet.");
const auto saveLabel = fileRemoved ? _("Save As...") : _("Save");
GtkWidget* dialog = gtk_message_dialog_new(getGtkWindow(), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING,
GTK_BUTTONS_NONE, "%s", message);
gtk_dialog_add_button(GTK_DIALOG(dialog), saveLabel, GTK_RESPONSE_ACCEPT);
gtk_dialog_add_button(GTK_DIALOG(dialog), _("Discard"), GTK_RESPONSE_REJECT);
if (allowCancel) {
gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
}
gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(this->getWindow()->getWindow()));
const auto dialogResponse = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
switch (dialogResponse) {
case GTK_RESPONSE_ACCEPT:
if (fileRemoved) {
return this->saveAs();
} else {
return this->save(true);
}
break;
case GTK_RESPONSE_REJECT:
discard = true;
break;
default:
return false;
break;
}
}
if (allowDestroy && discard) {
this->closeDocument();
}
return true;
}
auto Control::closeAndDestroy(bool allowCancel) -> bool {
// We don't want to "double close", so disallow it first.
auto retval = this->close(false, allowCancel);
this->closeDocument();
return retval;
}
void Control::closeDocument() {
this->undoRedo->clearContents();
this->doc->lock();
this->doc->clearDocument(true);
this->doc->unlock();
this->undoRedoChanged();
}
void Control::applyPreferredLanguage() { setenv("LANGUAGE", this->settings->getPreferredLocale().c_str(), 1); }
auto Control::askToReplace(fs::path const& filepath) const -> bool {
if (fs::exists(filepath)) {
string msg = FS(FORMAT_STR("The file {1} already exists! Do you want to replace it?") %
filepath.filename().string());
int res = XojMsgBox::replaceFileQuestion(getGtkWindow(), msg);
return res == GTK_RESPONSE_OK;
}
return true;
}
void Control::resetShapeRecognizer() {
if (this->win) {
this->win->getXournal()->resetShapeRecognizer();
}
}
void Control::showAbout() {
AboutDialog dlg(this->gladeSearchPath);
dlg.show(GTK_WINDOW(this->win->getWindow()));
}
void Control::clipboardCutCopyEnabled(bool enabled) {
fireEnableAction(ACTION_CUT, enabled);
fireEnableAction(ACTION_COPY, enabled);
}
void Control::clipboardPasteEnabled(bool enabled) { fireEnableAction(ACTION_PASTE, enabled); }
void Control::clipboardPasteText(string text) {
Text* t = new Text();
t->setText(text);
t->setFont(settings->getFont());
t->setColor(toolHandler->getColor());
clipboardPaste(t);
}
void Control::clipboardPasteImage(GdkPixbuf* img) {
auto image = new Image();
image->setImage(img);
auto width =
static_cast<double>(gdk_pixbuf_get_width(img)) / settings->getDisplayDpi() * Util::DPI_NORMALIZATION_FACTOR;
auto height = static_cast<double>(gdk_pixbuf_get_height(img)) / settings->getDisplayDpi() *
Util::DPI_NORMALIZATION_FACTOR;
int pageNr = getCurrentPageNo();
if (pageNr == -1) {
return;
}
this->doc->lock();
PageRef page = this->doc->getPage(pageNr);
auto pageWidth = page->getWidth();
auto pageHeight = page->getHeight();
this->doc->unlock();
// Size: 3/4 of the page size
pageWidth = pageWidth * 3.0 / 4.0;
pageHeight = pageHeight * 3.0 / 4.0;
auto scaledWidth = width;
auto 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::clipboardPaste(Element* e) {
double x = 0;
double y = 0;
int pageNr = getCurrentPageNo();
if (pageNr == -1) {
return;
}
XojPageView* view = win->getXournal()->getViewFor(pageNr);
if (view == nullptr) {
return;
}
this->doc->lock();
PageRef page = this->doc->getPage(pageNr);
Layer* layer = page->getSelectedLayer();
win->getXournal()->getPasteTarget(x, y);
double width = e->getElementWidth();
double height = e->getElementHeight();
x = std::max(0.0, x - width / 2);
y = std::max(0.0, y - height / 2);
e->setX(x);
e->setY(y);
layer->addElement(e);
this->doc->unlock();
undoRedo->addUndoAction(std::make_unique<InsertUndoAction>(page, layer, e));
auto* selection = new EditSelection(this->undoRedo, e, view, page);
win->getXournal()->setSelection(selection);
}
void Control::clipboardPasteXournal(ObjectInputStream& in) {
int pNr = getCurrentPageNo();
if (pNr == -1 && win != nullptr) {
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 = nullptr;
try {
std::unique_ptr<Element> element;
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);
selection->readSerialized(in);
// document lock not needed anymore, because we don't change the document, we only change the selection
this->doc->unlock();
int count = in.readInt();
auto pasteAddUndoAction = std::make_unique<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.reset();
if (name == "Stroke") {
element = std::make_unique<Stroke>();
} else if (name == "Image") {
element = std::make_unique<Image>();
} else if (name == "TexImage") {
element = std::make_unique<TexImage>();
} else if (name == "Text") {
element = std::make_unique<Text>();
} else {
throw InputStreamException(FS(FORMAT_STR("Get unknown object {1}") % name), __FILE__, __LINE__);
}
element->readSerialized(in);
pasteAddUndoAction->addElement(layer, element.get(), layer->indexOf(element.get()));
// Todo: unique_ptr
selection->addElement(element.release(), Layer::InvalidElementIndex);
}
undoRedo->addUndoAction(std::move(pasteAddUndoAction));
double x = 0;
double y = 0;
// calculate x/y of paste target, see clipboardPaste(Element* e)
win->getXournal()->getPasteTarget(x, y);
x = std::max(0.0, x - selection->getWidth() / 2);
y = std::max(0.0, y - selection->getHeight() / 2);
// calculate difference between current selection position and destination
auto dx = x - selection->getXOnView();
auto dy = y - selection->getYOnView();
selection->moveSelection(dx, dy);
// update all Elements (same procedure as moving a element selection by hand and releasing the mouse button)
selection->mouseUp();
win->getXournal()->setSelection(selection);
} catch (std::exception& e) {
g_warning("could not paste, Exception occurred: %s", e.what());
Stacktrace::printStracktrace();
if (selection) {
for (Element* e: *selection->getElements()) {
delete e;
}
delete selection;
}
}
}
void Control::deleteSelection() {
if (win) {
win->getXournal()->deleteSelection();
}
}
void Control::clearSelection() {
if (this->win) {
this->win->getXournal()->clearSelection();
}
}
void Control::setClipboardHandlerSelection(EditSelection* selection) {
if (this->clipboardHandler) {
this->clipboardHandler->setSelection(selection);
}
}
void Control::setCopyPasteEnabled(bool enabled) { this->clipboardHandler->setCopyPasteEnabled(enabled); }
void Control::setFill(bool fill) {
EditSelection* sel = nullptr;
if (this->win) {
sel = this->win->getXournal()->getSelection();
}
if (sel) {
undoRedo->addUndoAction(UndoActionPtr(
sel->setFill(fill ? toolHandler->getPenFill() : -1, fill ? toolHandler->getHilighterFill() : -1)));
}
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::setLineStyle(const string& style) {
LineStyle stl = StrokeStyle::parseStyle(style.c_str());
EditSelection* sel = nullptr;
if (this->win) {
sel = this->win->getXournal()->getSelection();
}
// TODO(fabian): allow to change selection
if (sel) {
// UndoAction* undo = sel->setSize(size, toolHandler->getToolThickness(TOOL_PEN),
// toolHandler->getToolThickness(TOOL_HILIGHTER),
// toolHandler->getToolThickness(TOOL_ERASER));
// undoRedo->addUndoAction(undo);
}
this->toolHandler->setLineStyle(stl);
}
void Control::setToolSize(ToolSize size) {
EditSelection* sel = nullptr;
if (this->win) {
sel = this->win->getXournal()->getSelection();
}
if (sel) {
undoRedo->addUndoAction(UndoActionPtr(sel->setSize(size, toolHandler->getToolThickness(TOOL_PEN),
toolHandler->getToolThickness(TOOL_HILIGHTER),
toolHandler->getToolThickness(TOOL_ERASER))));
}
this->toolHandler->setSize(size);
}
void Control::fontChanged() {
XojFont font = win->getFontButtonFont();
settings->setFont(font);
EditSelection* sel = nullptr;
if (this->win) {
sel = this->win->getXournal()->getSelection();
}
if (sel) {
undoRedo->addUndoAction(UndoActionPtr(sel->setFont(font)));
}
TextEditor* editor = getTextEditor();
if (editor) {
editor->setFont(font);
}
}
/**
* The core handler for inserting latex
*/
void Control::runLatex() {
LatexController latex(this);
latex.run();
}
/**
* GETTER / SETTER
*/
auto Control::getUndoRedoHandler() -> UndoRedoHandler* { return this->undoRedo; }
auto Control::getZoomControl() -> ZoomControl* { return this->zoom; }
auto Control::getCursor() -> XournalppCursor* { return this->cursor; }
auto Control::getRecentManager() -> RecentManager* { return this->recent; }
auto Control::getDocument() -> Document* { return this->doc; }
auto Control::getToolHandler() -> ToolHandler* { return this->toolHandler; }
auto Control::getScheduler() -> XournalScheduler* { return this->scheduler; }
auto Control::getWindow() -> MainWindow* { return this->win; }
auto Control::getGtkWindow() const -> GtkWindow* { return GTK_WINDOW(this->win->getWindow()); }
auto Control::isFullscreen() -> bool { return this->fullscreenHandler->isFullscreen(); }
void Control::rotationSnappingToggle() {
settings->setSnapRotation(!settings->isSnapRotation());
fireActionSelected(GROUP_SNAPPING, settings->isSnapRotation() ? ACTION_ROTATION_SNAPPING : ACTION_NONE);
}
void Control::gridSnappingToggle() {
settings->setSnapGrid(!settings->isSnapGrid());
fireActionSelected(GROUP_GRID_SNAPPING, settings->isSnapGrid() ? ACTION_GRID_SNAPPING : ACTION_NONE);
}
auto Control::getTextEditor() -> TextEditor* {
if (this->win) {
return this->win->getXournal()->getTextEditor();
}
return nullptr;
}
auto Control::getGladeSearchPath() -> GladeSearchpath* { return this->gladeSearchPath; }
auto Control::getSettings() -> Settings* { return settings; }
auto Control::getScrollHandler() -> ScrollHandler* { return this->scrollHandler; }
auto Control::getMetadataManager() -> MetadataManager* { return this->metadata; }
auto Control::getSidebar() -> Sidebar* { return this->sidebar; }
auto Control::getSearchBar() -> SearchBar* { return this->searchBar; }
auto Control::getAudioController() -> AudioController* { return this->audioController; }
auto Control::getPageTypes() -> PageTypeHandler* { return this->pageTypes; }
auto Control::getNewPageType() -> PageTypeMenu* { return this->newPageType; }
auto Control::getPageBackgroundChangeController() -> PageBackgroundChangeController* {
return this->pageBackgroundChangeController;
}
auto Control::getLayerController() -> LayerController* { return this->layerController; }