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.
930 lines
30 KiB
930 lines
30 KiB
#include "PageView.h" |
|
|
|
#include <algorithm> |
|
#include <cmath> |
|
#include <cstdlib> |
|
#include <memory> |
|
|
|
#include <gdk/gdk.h> |
|
|
|
#include "control/Control.h" |
|
#include "control/SearchControl.h" |
|
#include "control/jobs/BlockingJob.h" |
|
#include "control/settings/ButtonConfig.h" |
|
#include "control/settings/Settings.h" |
|
#include "control/tools/ArrowHandler.h" |
|
#include "control/tools/CoordinateSystemHandler.h" |
|
#include "control/tools/EllipseHandler.h" |
|
#include "control/tools/EraseHandler.h" |
|
#include "control/tools/ImageHandler.h" |
|
#include "control/tools/InputHandler.h" |
|
#include "control/tools/RectangleHandler.h" |
|
#include "control/tools/RulerHandler.h" |
|
#include "control/tools/Selection.h" |
|
#include "control/tools/SplineHandler.h" |
|
#include "control/tools/StrokeHandler.h" |
|
#include "control/tools/VerticalToolHandler.h" |
|
#include "model/Image.h" |
|
#include "model/Layer.h" |
|
#include "model/PageRef.h" |
|
#include "model/Stroke.h" |
|
#include "model/Text.h" |
|
#include "undo/DeleteUndoAction.h" |
|
#include "undo/InsertUndoAction.h" |
|
#include "undo/TextBoxUndoAction.h" |
|
#include "util/XojMsgBox.h" |
|
#include "view/TextView.h" |
|
#include "widgets/XournalWidget.h" |
|
|
|
#include "PageViewFindObjectHelper.h" |
|
#include "Range.h" |
|
#include "Rectangle.h" |
|
#include "RepaintHandler.h" |
|
#include "TextEditor.h" |
|
#include "XournalView.h" |
|
#include "XournalppCursor.h" |
|
#include "config-debug.h" |
|
#include "config-features.h" |
|
#include "config.h" |
|
#include "i18n.h" |
|
#include "pixbuf-utils.h" |
|
|
|
using std::string; |
|
|
|
XojPageView::XojPageView(XournalView* xournal, const PageRef& page) { |
|
this->page = page; |
|
this->registerListener(this->page); |
|
this->xournal = xournal; |
|
this->settings = xournal->getControl()->getSettings(); |
|
|
|
g_mutex_init(&this->drawingMutex); |
|
|
|
g_mutex_init(&this->repaintRectMutex); |
|
|
|
// this does not have to be deleted afterwards: |
|
// (we need it for undo commands) |
|
this->oldtext = nullptr; |
|
|
|
this->eraser = new EraseHandler(xournal->getControl()->getUndoRedoHandler(), xournal->getControl()->getDocument(), |
|
this->page, xournal->getControl()->getToolHandler(), this); |
|
} |
|
|
|
XojPageView::~XojPageView() { |
|
// Unregister listener before destroying this handler |
|
this->unregisterListener(); |
|
|
|
this->xournal->getControl()->getScheduler()->removePage(this); |
|
delete this->inputHandler; |
|
delete this->eraser; |
|
endText(); |
|
deleteViewBuffer(); |
|
delete this->search; |
|
} |
|
|
|
void XojPageView::setIsVisible(bool visible) { |
|
if (visible) { |
|
this->lastVisibleTime = 0; |
|
} else if (this->lastVisibleTime <= 0) { |
|
GTimeVal val; |
|
g_get_current_time(&val); |
|
this->lastVisibleTime = val.tv_sec; |
|
} |
|
} |
|
|
|
auto XojPageView::getLastVisibleTime() -> int { |
|
if (this->crBuffer == nullptr) { |
|
return -1; |
|
} |
|
|
|
return this->lastVisibleTime; |
|
} |
|
|
|
void XojPageView::deleteViewBuffer() { |
|
g_mutex_lock(&this->drawingMutex); |
|
if (this->crBuffer) { |
|
cairo_surface_destroy(this->crBuffer); |
|
this->crBuffer = nullptr; |
|
} |
|
g_mutex_unlock(&this->drawingMutex); |
|
} |
|
|
|
auto XojPageView::containsPoint(int x, int y, bool local) const -> bool { |
|
if (!local) { |
|
bool leftOk = this->getX() <= x; |
|
bool rightOk = x <= this->getX() + this->getDisplayWidth(); |
|
bool topOk = this->getY() <= y; |
|
bool bottomOk = y <= this->getY() + this->getDisplayHeight(); |
|
|
|
return leftOk && rightOk && topOk && bottomOk; |
|
} |
|
|
|
|
|
return x >= 0 && y >= 0 && x <= this->getWidth() && y <= this->getHeight(); |
|
} |
|
|
|
auto XojPageView::searchTextOnPage(string& text, int* occures, double* top) -> bool { |
|
if (this->search == nullptr) { |
|
if (text.empty()) { |
|
return true; |
|
} |
|
|
|
auto pNr = this->page->getPdfPageNr(); |
|
XojPdfPageSPtr pdf = nullptr; |
|
if (pNr != npos) { |
|
Document* doc = xournal->getControl()->getDocument(); |
|
|
|
doc->lock(); |
|
pdf = doc->getPdfPage(pNr); |
|
doc->unlock(); |
|
} |
|
this->search = new SearchControl(page, pdf); |
|
} |
|
|
|
bool found = this->search->search(text, occures, top); |
|
|
|
repaintPage(); |
|
|
|
return found; |
|
} |
|
|
|
void XojPageView::endText() { |
|
if (!this->textEditor) { |
|
return; |
|
} |
|
Text* txt = this->textEditor->getText(); |
|
Layer* layer = this->page->getSelectedLayer(); |
|
UndoRedoHandler* undo = xournal->getControl()->getUndoRedoHandler(); |
|
|
|
// Text deleted |
|
if (txt->getText().empty()) { |
|
// old element |
|
int pos = layer->indexOf(txt); |
|
if (pos != -1) { |
|
auto eraseDeleteUndoAction = std::make_unique<DeleteUndoAction>(page, true); |
|
layer->removeElement(txt, false); |
|
eraseDeleteUndoAction->addElement(layer, txt, pos); |
|
undo->addUndoAction(std::move(eraseDeleteUndoAction)); |
|
} |
|
} else { |
|
// new element |
|
if (layer->indexOf(txt) == -1) { |
|
undo->addUndoActionBefore(std::make_unique<InsertUndoAction>(page, layer, txt), |
|
this->textEditor->getFirstUndoAction()); |
|
layer->addElement(txt); |
|
this->textEditor->textCopyed(); |
|
} |
|
// or if the file was saved and reopened |
|
// and/or if we click away from the text window |
|
else { |
|
// TextUndoAction does not work because the textEdit object is destroyed |
|
// after endText() so we need to instead copy the information between an |
|
// old and new element that we can push and pop to recover. |
|
undo->addUndoAction(std::make_unique<TextBoxUndoAction>(page, layer, txt, this->oldtext)); |
|
} |
|
} |
|
|
|
delete this->textEditor; |
|
this->textEditor = nullptr; |
|
this->xournal->getControl()->getWindow()->setFontButtonFont(settings->getFont()); |
|
this->rerenderPage(); |
|
} |
|
|
|
void XojPageView::startText(double x, double y) { |
|
this->xournal->endTextAllPages(this); |
|
this->xournal->getControl()->getSearchBar()->showSearchBar(false); |
|
|
|
if (this->textEditor != nullptr) { |
|
Text* text = this->textEditor->getText(); |
|
GdkRectangle matchRect = {gint(x - 10), gint(y - 10), 20, 20}; |
|
if (!text->intersectsArea(&matchRect)) { |
|
endText(); |
|
} else { |
|
this->textEditor->mousePressed(x - text->getX(), y - text->getY()); |
|
} |
|
} |
|
|
|
if (this->textEditor == nullptr) { |
|
// Is there already a textfield? |
|
Text* text = nullptr; |
|
|
|
for (Element* e: *this->page->getSelectedLayer()->getElements()) { |
|
if (e->getType() == ELEMENT_TEXT) { |
|
GdkRectangle matchRect = {gint(x - 10), gint(y - 10), 20, 20}; |
|
if (e->intersectsArea(&matchRect)) { |
|
text = dynamic_cast<Text*>(e); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
bool ownText = false; |
|
if (text == nullptr) { |
|
ToolHandler* h = xournal->getControl()->getToolHandler(); |
|
ownText = true; |
|
text = new Text(); |
|
text->setColor(h->getColor()); |
|
text->setFont(settings->getFont()); |
|
text->setX(x); |
|
text->setY(y - text->getElementHeight() / 2); |
|
|
|
if (xournal->getControl()->getAudioController()->isRecording()) { |
|
string audioFilename = xournal->getControl()->getAudioController()->getAudioFilename(); |
|
size_t sttime = xournal->getControl()->getAudioController()->getStartTime(); |
|
size_t milliseconds = ((g_get_monotonic_time() / 1000) - sttime); |
|
text->setTimestamp(milliseconds); |
|
text->setAudioFilename(audioFilename); |
|
} |
|
} else { |
|
|
|
// We can try to add an undo action here. The initial text shows up in this |
|
// textEditor element. |
|
this->oldtext = text; |
|
// text = new Text(*oldtext); |
|
// need to clone the old text so that references still work properly. |
|
// cloning breaks things a little. do it manually |
|
text = new Text(); |
|
text->setX(oldtext->getX()); |
|
text->setY(oldtext->getY()); |
|
text->setColor(oldtext->getColor()); |
|
text->setFont(oldtext->getFont()); |
|
this->xournal->getControl()->getWindow()->setFontButtonFont(oldtext->getFont()); |
|
text->setText(oldtext->getText()); |
|
text->setTimestamp(oldtext->getTimestamp()); |
|
text->setAudioFilename(oldtext->getAudioFilename()); |
|
|
|
Layer* layer = this->page->getSelectedLayer(); |
|
layer->removeElement(this->oldtext, false); |
|
layer->addElement(text); |
|
// perform the old swap onto the new text drawn. |
|
} |
|
|
|
this->textEditor = new TextEditor(this, xournal->getWidget(), text, ownText); |
|
if (!ownText) { |
|
this->textEditor->mousePressed(x - text->getX(), y - text->getY()); |
|
} |
|
|
|
this->rerenderPage(); |
|
} |
|
} |
|
|
|
auto XojPageView::onButtonPressEvent(const PositionInputData& pos) -> bool { |
|
Control* control = xournal->getControl(); |
|
|
|
if (!this->selected) { |
|
control->firePageSelected(this->page); |
|
} |
|
|
|
ToolHandler* h = control->getToolHandler(); |
|
|
|
double x = pos.x; |
|
double y = pos.y; |
|
|
|
if (x < 0 || y < 0) { |
|
return false; |
|
} |
|
|
|
double zoom = xournal->getZoom(); |
|
x /= zoom; |
|
y /= zoom; |
|
|
|
XournalppCursor* cursor = xournal->getCursor(); |
|
cursor->setMouseDown(true); |
|
|
|
if ((h->getToolType() == TOOL_PEN || h->getToolType() == TOOL_HIGHLIGHTER) && |
|
h->getDrawingType() != DRAWING_TYPE_SPLINE || |
|
(h->getToolType() == TOOL_ERASER && h->getEraserType() == ERASER_TYPE_WHITEOUT)) { |
|
delete this->inputHandler; |
|
this->inputHandler = nullptr; |
|
|
|
if (h->getDrawingType() == DRAWING_TYPE_LINE) { |
|
this->inputHandler = new RulerHandler(this->xournal, this, getPage()); |
|
} else if (h->getDrawingType() == DRAWING_TYPE_RECTANGLE) { |
|
this->inputHandler = new RectangleHandler(this->xournal, this, getPage()); |
|
} else if (h->getDrawingType() == DRAWING_TYPE_ELLIPSE) { |
|
this->inputHandler = new EllipseHandler(this->xournal, this, getPage()); |
|
} else if (h->getDrawingType() == DRAWING_TYPE_ARROW) { |
|
this->inputHandler = new ArrowHandler(this->xournal, this, getPage()); |
|
} else if (h->getDrawingType() == DRAWING_TYPE_COORDINATE_SYSTEM) { |
|
this->inputHandler = new CoordinateSystemHandler(this->xournal, this, getPage()); |
|
} else { |
|
this->inputHandler = new StrokeHandler(this->xournal, this, getPage()); |
|
} |
|
|
|
this->inputHandler->onButtonPressEvent(pos); |
|
} else if ((h->getToolType() == TOOL_PEN || h->getToolType() == TOOL_HIGHLIGHTER) && |
|
h->getDrawingType() == DRAWING_TYPE_SPLINE) { |
|
if (!this->inputHandler) { |
|
this->inputHandler = new SplineHandler(this->xournal, this, getPage()); |
|
} |
|
this->inputHandler->onButtonPressEvent(pos); |
|
} else if (h->getToolType() == TOOL_ERASER) { |
|
this->eraser->erase(x, y); |
|
this->inEraser = true; |
|
} else if (h->getToolType() == TOOL_VERTICAL_SPACE) { |
|
this->verticalSpace = new VerticalToolHandler(this, this->page, this->settings, y, zoom); |
|
} else if (h->getToolType() == TOOL_SELECT_RECT || h->getToolType() == TOOL_SELECT_REGION || |
|
h->getToolType() == TOOL_PLAY_OBJECT || h->getToolType() == TOOL_SELECT_OBJECT) { |
|
if (h->getToolType() == TOOL_SELECT_RECT) { |
|
if (this->selection) { |
|
delete this->selection; |
|
this->selection = nullptr; |
|
repaintPage(); |
|
} |
|
this->selection = new RectSelection(x, y, this); |
|
} else if (h->getToolType() == TOOL_SELECT_REGION) { |
|
if (this->selection) { |
|
delete this->selection; |
|
this->selection = nullptr; |
|
repaintPage(); |
|
} |
|
this->selection = new RegionSelect(x, y, this); |
|
} else if (h->getToolType() == TOOL_SELECT_OBJECT) { |
|
SelectObject select(this); |
|
select.at(x, y); |
|
} else if (h->getToolType() == TOOL_PLAY_OBJECT) { |
|
PlayObject play(this); |
|
play.at(x, y); |
|
if (play.playbackStatus) { |
|
auto& status = *play.playbackStatus; |
|
if (!status.success) { |
|
string message = FS(_F("Unable to play audio recording {1}") % status.filename); |
|
XojMsgBox::showErrorToUser(this->xournal->getControl()->getGtkWindow(), message); |
|
} |
|
} |
|
} |
|
} else if (h->getToolType() == TOOL_TEXT) { |
|
startText(x, y); |
|
} else if (h->getToolType() == TOOL_IMAGE) { |
|
ImageHandler imgHandler(control, this); |
|
imgHandler.insertImage(x, y); |
|
} else if (h->getToolType() == TOOL_FLOATING_TOOLBOX) { |
|
gint wx = 0, wy = 0; |
|
GtkWidget* widget = xournal->getWidget(); |
|
gtk_widget_translate_coordinates(widget, gtk_widget_get_toplevel(widget), 0, 0, &wx, &wy); |
|
|
|
wx += std::lround(pos.x) + this->getX(); |
|
wy += std::lround(pos.y) + this->getY(); |
|
|
|
control->getWindow()->floatingToolbox->show(wx, wy); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
auto XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) -> bool { |
|
// This method assumes that it is called after onButtonPressEvent but before |
|
// onButtonReleaseEvent |
|
double zoom = this->xournal->getZoom(); |
|
double x = pos.x / zoom; |
|
double y = pos.y / zoom; |
|
if (x < 0 || y < 0) { |
|
return false; |
|
} |
|
|
|
ToolHandler* toolHandler = this->xournal->getControl()->getToolHandler(); |
|
ToolType toolType = toolHandler->getToolType(); |
|
bool isSelectTool = toolType == TOOL_SELECT_OBJECT || TOOL_SELECT_RECT || TOOL_SELECT_REGION; |
|
|
|
DrawingType drawingType = toolHandler->getDrawingType(); |
|
|
|
EditSelection* selection = xournal->getSelection(); |
|
bool hasNoModifiers = !pos.isShiftDown() && !pos.isControlDown(); |
|
|
|
if (hasNoModifiers && isSelectTool && selection != nullptr) { |
|
// Find a selected object under the cursor, if possible. The selection doesn't change the |
|
// element coordinates until it is finalized, so we need to use position relative to the |
|
// original coordinates of the selection. |
|
double origx = x - (selection->getXOnView() - selection->getOriginalXOnView()); |
|
double origy = y - (selection->getYOnView() - selection->getOriginalYOnView()); |
|
std::vector<Element*>* elems = selection->getElements(); |
|
auto it = std::find_if(elems->begin(), elems->end(), |
|
[&](Element*& elem) { return elem->intersectsArea(origx - 5, origy - 5, 5, 5); }); |
|
if (it != elems->end()) { |
|
// Enter editing mode on the selected object |
|
Element* object = *it; |
|
ElementType elemType = object->getType(); |
|
if (elemType == ELEMENT_TEXT) { |
|
this->xournal->clearSelection(); |
|
toolHandler->selectTool(TOOL_TEXT); |
|
toolHandler->fireToolChanged(); |
|
// Simulate a button press; there's too many things that we |
|
// could forget to do if we manually call startText |
|
this->onButtonPressEvent(pos); |
|
} else if (elemType == ELEMENT_TEXIMAGE) { |
|
Control* control = this->xournal->getControl(); |
|
this->xournal->clearSelection(); |
|
auto* sel = new EditSelection(control->getUndoRedoHandler(), object, this, this->getPage()); |
|
this->xournal->setSelection(sel); |
|
control->runLatex(); |
|
} |
|
} |
|
} else if (toolType == TOOL_TEXT) { |
|
this->startText(x, y); |
|
this->textEditor->selectAtCursor(TextEditor::SelectType::word); |
|
} else if (drawingType == DRAWING_TYPE_SPLINE) { |
|
if (this->inputHandler) { |
|
this->inputHandler->onButtonDoublePressEvent(pos); |
|
delete this->inputHandler; |
|
this->inputHandler = nullptr; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
auto XojPageView::onButtonTriplePressEvent(const PositionInputData& pos) -> bool { |
|
// This method assumes that it is called after onButtonDoubleEvent but before |
|
// onButtonReleaseEvent |
|
double zoom = this->xournal->getZoom(); |
|
double x = pos.x / zoom; |
|
double y = pos.y / zoom; |
|
if (x < 0 || y < 0) { |
|
return false; |
|
} |
|
|
|
ToolHandler* toolHandler = this->xournal->getControl()->getToolHandler(); |
|
|
|
if (toolHandler->getToolType() == TOOL_TEXT) { |
|
this->startText(x, y); |
|
this->textEditor->selectAtCursor(TextEditor::SelectType::paragraph); |
|
} |
|
return true; |
|
} |
|
|
|
void XojPageView::resetShapeRecognizer() { |
|
if (this->inputHandler != nullptr) { |
|
this->inputHandler->resetShapeRecognizer(); |
|
} |
|
} |
|
|
|
auto XojPageView::onMotionNotifyEvent(const PositionInputData& pos) -> bool { |
|
double zoom = xournal->getZoom(); |
|
double x = pos.x / zoom; |
|
double y = pos.y / zoom; |
|
|
|
ToolHandler* h = xournal->getControl()->getToolHandler(); |
|
|
|
if (containsPoint(std::lround(x), std::lround(y), true) && this->inputHandler && |
|
this->inputHandler->onMotionNotifyEvent(pos)) { |
|
// input handler used this event |
|
} else if (this->selection) { |
|
this->selection->currentPos(x, y); |
|
} else if (this->verticalSpace) { |
|
this->verticalSpace->currentPos(x, y); |
|
} else if (this->textEditor) { |
|
XournalppCursor* cursor = getXournal()->getCursor(); |
|
cursor->setInvisible(false); |
|
|
|
Text* text = this->textEditor->getText(); |
|
this->textEditor->mouseMoved(x - text->getX(), y - text->getY()); |
|
} else if (h->getToolType() == TOOL_ERASER && h->getEraserType() != ERASER_TYPE_WHITEOUT && this->inEraser) { |
|
this->eraser->erase(x, y); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void XojPageView::onMotionCancelEvent() { |
|
if (this->inputHandler) { |
|
this->inputHandler->onMotionCancelEvent(); |
|
} |
|
} |
|
|
|
auto XojPageView::onButtonReleaseEvent(const PositionInputData& pos) -> bool { |
|
Control* control = xournal->getControl(); |
|
|
|
if (this->inputHandler) { |
|
this->inputHandler->onButtonReleaseEvent(pos); |
|
|
|
if (this->inputHandler->userTapped) { |
|
bool doAction = control->getSettings()->getDoActionOnStrokeFiltered(); |
|
if (control->getSettings()->getTrySelectOnStrokeFiltered()) { |
|
double zoom = xournal->getZoom(); |
|
SelectObject select(this); |
|
if (select.at(pos.x / zoom, pos.y / zoom)) { |
|
doAction = false; // selection made.. no action. |
|
} |
|
} |
|
|
|
if (doAction) // pop up a menu |
|
{ |
|
gint wx = 0, wy = 0; |
|
GtkWidget* widget = xournal->getWidget(); |
|
gtk_widget_translate_coordinates(widget, gtk_widget_get_toplevel(widget), 0, 0, &wx, &wy); |
|
wx += std::lround(pos.x + this->getX()); |
|
wy += std::lround(pos.y + this->getY()); |
|
control->getWindow()->floatingToolbox->show(wx, wy); |
|
} |
|
} |
|
|
|
ToolHandler* h = control->getToolHandler(); |
|
bool isDrawingTypeSpline = h->getDrawingType() == DRAWING_TYPE_SPLINE; |
|
if (!isDrawingTypeSpline || !this->inputHandler->getStroke()) { // The Spline Tool finalizes drawing manually |
|
delete this->inputHandler; |
|
this->inputHandler = nullptr; |
|
} |
|
} |
|
|
|
if (this->inEraser) { |
|
this->inEraser = false; |
|
Document* doc = this->xournal->getControl()->getDocument(); |
|
doc->lock(); |
|
this->eraser->finalize(); |
|
doc->unlock(); |
|
} |
|
|
|
if (this->verticalSpace) { |
|
control->getUndoRedoHandler()->addUndoAction(this->verticalSpace->finalize()); |
|
delete this->verticalSpace; |
|
this->verticalSpace = nullptr; |
|
} |
|
|
|
if (this->selection) { |
|
if (this->selection->finalize(this->page)) { |
|
xournal->setSelection(new EditSelection(control->getUndoRedoHandler(), this->selection, this)); |
|
delete this->selection; |
|
this->selection = nullptr; |
|
} else { |
|
double zoom = xournal->getZoom(); |
|
if (this->selection->userTapped(zoom)) { |
|
SelectObject select(this); |
|
select.at(pos.x / zoom, pos.y / zoom); |
|
} |
|
delete this->selection; |
|
this->selection = nullptr; |
|
|
|
repaintPage(); |
|
} |
|
} else if (this->textEditor) { |
|
this->textEditor->mouseReleased(); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
auto XojPageView::onKeyPressEvent(GdkEventKey* event) -> bool { |
|
// Esc leaves text edition |
|
if (event->keyval == GDK_KEY_Escape) { |
|
if (this->textEditor) { |
|
endText(); |
|
return true; |
|
} |
|
if (xournal->getSelection()) { |
|
xournal->clearSelection(); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
if (this->textEditor) { |
|
return this->textEditor->onKeyPressEvent(event); |
|
} |
|
|
|
|
|
if (this->inputHandler) { |
|
return this->inputHandler->onKeyEvent(event); |
|
} |
|
|
|
|
|
return false; |
|
} |
|
|
|
auto XojPageView::onKeyReleaseEvent(GdkEventKey* event) -> bool { |
|
if (this->textEditor && this->textEditor->onKeyReleaseEvent(event)) { |
|
return true; |
|
} |
|
|
|
if (this->inputHandler && this->inputHandler->onKeyEvent(event)) { |
|
DrawingType drawingType = this->xournal->getControl()->getToolHandler()->getDrawingType(); |
|
if (drawingType == DRAWING_TYPE_SPLINE) { // Spline drawing has been finalized |
|
if (this->inputHandler) { |
|
delete this->inputHandler; |
|
this->inputHandler = nullptr; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void XojPageView::rerenderPage() { |
|
this->rerenderComplete = true; |
|
this->xournal->getControl()->getScheduler()->addRerenderPage(this); |
|
} |
|
|
|
void XojPageView::repaintPage() { xournal->getRepaintHandler()->repaintPage(this); } |
|
|
|
void XojPageView::repaintArea(double x1, double y1, double x2, double y2) { |
|
double zoom = xournal->getZoom(); |
|
xournal->getRepaintHandler()->repaintPageArea(this, std::lround(x1 * zoom) - 10, std::lround(y1 * zoom) - 10, |
|
std::lround(x2 * zoom) + 20, std::lround(y2 * zoom) + 20); |
|
} |
|
|
|
void XojPageView::rerenderRect(double x, double y, double width, double height) { |
|
int rx = std::lround(std::max(x - 10, 0.0)); |
|
int ry = std::lround(std::max(y - 10, 0.0)); |
|
int rwidth = std::lround(width + 20); |
|
int rheight = std::lround(height + 20); |
|
|
|
addRerenderRect(rx, ry, rwidth, rheight); |
|
} |
|
|
|
void XojPageView::addRerenderRect(double x, double y, double width, double height) { |
|
if (this->rerenderComplete) { |
|
return; |
|
} |
|
|
|
auto rect = Rectangle<double>{x, y, width, height}; |
|
|
|
g_mutex_lock(&this->repaintRectMutex); |
|
|
|
for (auto&& r: this->rerenderRects) { |
|
// its faster to redraw only one rect than repaint twice the same area |
|
// so loop through the rectangles to be redrawn, if new rectangle |
|
// intersects any of them, replace it by the union with the new one |
|
if (r.intersects(rect)) { |
|
r.unite(rect); |
|
g_mutex_unlock(&this->repaintRectMutex); |
|
return; |
|
} |
|
} |
|
|
|
this->rerenderRects.push_back(rect); |
|
g_mutex_unlock(&this->repaintRectMutex); |
|
|
|
this->xournal->getControl()->getScheduler()->addRerenderPage(this); |
|
} |
|
|
|
void XojPageView::setSelected(bool selected) { |
|
this->selected = selected; |
|
|
|
if (selected) { |
|
this->xournal->requestFocus(); |
|
this->xournal->getRepaintHandler()->repaintPageBorder(this); |
|
} |
|
} |
|
|
|
auto XojPageView::cut() -> bool { |
|
if (this->textEditor) { |
|
this->textEditor->cutToClipboard(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
auto XojPageView::copy() -> bool { |
|
if (this->textEditor) { |
|
this->textEditor->copyToCliboard(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
auto XojPageView::paste() -> bool { |
|
if (this->textEditor) { |
|
this->textEditor->pasteFromClipboard(); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
auto XojPageView::actionDelete() -> bool { |
|
if (this->textEditor) { |
|
this->textEditor->deleteFromCursor(GTK_DELETE_CHARS, 1); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
void XojPageView::drawLoadingPage(cairo_t* cr) { |
|
static const string txtLoading = _("Loading..."); |
|
|
|
double zoom = xournal->getZoom(); |
|
int dispWidth = getDisplayWidth(); |
|
int dispHeight = getDisplayHeight(); |
|
|
|
cairo_set_source_rgb(cr, 1, 1, 1); |
|
cairo_rectangle(cr, 0, 0, dispWidth, dispHeight); |
|
cairo_fill(cr); |
|
|
|
cairo_scale(cr, zoom, zoom); |
|
|
|
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); |
|
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); |
|
cairo_set_font_size(cr, 32.0); |
|
cairo_text_extents_t ex; |
|
cairo_text_extents(cr, txtLoading.c_str(), &ex); |
|
cairo_move_to(cr, (page->getWidth() - ex.width) / 2 - ex.x_bearing, |
|
(page->getHeight() - ex.height) / 2 - ex.y_bearing); |
|
cairo_show_text(cr, txtLoading.c_str()); |
|
|
|
rerenderPage(); |
|
} |
|
|
|
/** |
|
* Does the painting, called in synchronized block |
|
*/ |
|
void XojPageView::paintPageSync(cairo_t* cr, GdkRectangle* rect) { |
|
if (this->crBuffer == nullptr) { |
|
drawLoadingPage(cr); |
|
return; |
|
} |
|
|
|
double zoom = xournal->getZoom(); |
|
int dispWidth = getDisplayWidth(); |
|
|
|
cairo_save(cr); |
|
|
|
double width = cairo_image_surface_get_width(this->crBuffer); |
|
|
|
bool rerender = true; |
|
if (width / xournal->getDpiScaleFactor() == dispWidth) { |
|
rerender = false; |
|
} |
|
|
|
if (width != dispWidth) { |
|
double scale = (static_cast<double>(dispWidth)) / (width); |
|
|
|
// Scale current image to fit the zoom level |
|
cairo_scale(cr, scale, scale); |
|
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_FAST); |
|
|
|
cairo_set_source_surface(cr, this->crBuffer, 0, 0); |
|
|
|
if (rerender) { |
|
rerenderPage(); |
|
} |
|
|
|
rect = nullptr; |
|
} else { |
|
cairo_set_source_surface(cr, this->crBuffer, 0, 0); |
|
} |
|
|
|
if (rect) { |
|
cairo_rectangle(cr, rect->x, rect->y, rect->width, rect->height); |
|
cairo_fill(cr); |
|
|
|
#ifdef DEBUG_SHOW_PAINT_BOUNDS |
|
cairo_set_source_rgb(cr, 1.0, 0.5, 1.0); |
|
cairo_set_line_width(cr, 1. / zoom); |
|
cairo_rectangle(cr, rect->x, rect->y, rect->width, rect->height); |
|
cairo_stroke(cr); |
|
#endif |
|
} else { |
|
cairo_paint(cr); |
|
} |
|
|
|
cairo_restore(cr); |
|
|
|
// don't paint this with scale, because it needs a 1:1 zoom |
|
if (this->verticalSpace) { |
|
this->verticalSpace->paint(cr, rect, zoom); |
|
} |
|
|
|
if (this->textEditor) { |
|
cairo_scale(cr, zoom, zoom); |
|
this->textEditor->paint(cr, rect, zoom); |
|
} |
|
if (this->selection) { |
|
cairo_scale(cr, zoom, zoom); |
|
this->selection->paint(cr, rect, zoom); |
|
} |
|
|
|
if (this->search) { |
|
cairo_save(cr); |
|
cairo_scale(cr, zoom, zoom); |
|
this->search->paint(cr, rect, zoom, getSelectionColor()); |
|
cairo_restore(cr); |
|
} |
|
|
|
if (this->inputHandler) { |
|
int dpiScaleFactor = xournal->getDpiScaleFactor(); |
|
cairo_scale(cr, 1.0 / dpiScaleFactor, 1.0 / dpiScaleFactor); |
|
this->inputHandler->draw(cr); |
|
} |
|
} |
|
|
|
auto XojPageView::paintPage(cairo_t* cr, GdkRectangle* rect) -> bool { |
|
g_mutex_lock(&this->drawingMutex); |
|
|
|
paintPageSync(cr, rect); |
|
|
|
g_mutex_unlock(&this->drawingMutex); |
|
return true; |
|
} |
|
|
|
/** |
|
* GETTER / SETTER |
|
*/ |
|
|
|
auto XojPageView::isSelected() const -> bool { return selected; } |
|
|
|
auto XojPageView::getBufferPixels() -> int { |
|
if (crBuffer) { |
|
return cairo_image_surface_get_width(crBuffer) * cairo_image_surface_get_height(crBuffer); |
|
} |
|
return 0; |
|
} |
|
|
|
auto XojPageView::getSelectionColor() -> GdkRGBA { return Util::rgb_to_GdkRGBA(settings->getSelectionColor()); } |
|
|
|
auto XojPageView::getTextEditor() -> TextEditor* { return textEditor; } |
|
|
|
auto XojPageView::getX() const -> int { return this->dispX; } |
|
|
|
void XojPageView::setX(int x) { this->dispX = x; } |
|
|
|
auto XojPageView::getY() const -> int { return this->dispY; } |
|
|
|
void XojPageView::setY(int y) { this->dispY = y; } |
|
|
|
void XojPageView::setMappedRowCol(int row, int col) { |
|
this->mappedRow = row; |
|
this->mappedCol = col; |
|
} |
|
|
|
|
|
auto XojPageView::getMappedRow() const -> int { return this->mappedRow; } |
|
|
|
|
|
auto XojPageView::getMappedCol() const -> int { return this->mappedCol; } |
|
|
|
|
|
auto XojPageView::getPage() -> PageRef { return page; } |
|
|
|
auto XojPageView::getXournal() -> XournalView* { return this->xournal; } |
|
|
|
auto XojPageView::getHeight() const -> double { return this->page->getHeight(); } |
|
|
|
auto XojPageView::getWidth() const -> double { return this->page->getWidth(); } |
|
|
|
auto XojPageView::getDisplayWidth() const -> int { |
|
return std::lround(this->page->getWidth() * this->xournal->getZoom()); |
|
} |
|
|
|
auto XojPageView::getDisplayHeight() const -> int { |
|
return std::lround(this->page->getHeight() * this->xournal->getZoom()); |
|
} |
|
|
|
auto XojPageView::getDisplayWidthDouble() const -> double { return this->page->getWidth() * this->xournal->getZoom(); } |
|
|
|
auto XojPageView::getDisplayHeightDouble() const -> double { |
|
return this->page->getHeight() * this->xournal->getZoom(); |
|
} |
|
|
|
auto XojPageView::getSelectedTex() -> TexImage* { |
|
EditSelection* theSelection = this->xournal->getSelection(); |
|
if (!theSelection) { |
|
return nullptr; |
|
} |
|
|
|
for (Element* e: *theSelection->getElements()) { |
|
if (e->getType() == ELEMENT_TEXIMAGE) { |
|
return dynamic_cast<TexImage*>(e); |
|
} |
|
} |
|
return nullptr; |
|
} |
|
|
|
auto XojPageView::getSelectedText() -> Text* { |
|
EditSelection* theSelection = this->xournal->getSelection(); |
|
if (!theSelection) { |
|
return nullptr; |
|
} |
|
|
|
for (Element* e: *theSelection->getElements()) { |
|
if (e->getType() == ELEMENT_TEXT) { |
|
return dynamic_cast<Text*>(e); |
|
} |
|
} |
|
return nullptr; |
|
} |
|
|
|
auto XojPageView::getRect() const -> Rectangle<double> { |
|
return Rectangle<double>(getX(), getY(), getDisplayWidth(), getDisplayHeight()); |
|
} |
|
|
|
void XojPageView::rectChanged(Rectangle<double>& rect) { rerenderRect(rect.x, rect.y, rect.width, rect.height); } |
|
|
|
void XojPageView::rangeChanged(Range& range) { rerenderRange(range); } |
|
|
|
void XojPageView::pageChanged() { rerenderPage(); } |
|
|
|
void XojPageView::elementChanged(Element* elem) { |
|
if (this->inputHandler && elem == this->inputHandler->getStroke()) { |
|
g_mutex_lock(&this->drawingMutex); |
|
|
|
cairo_t* cr = cairo_create(this->crBuffer); |
|
|
|
this->inputHandler->draw(cr); |
|
|
|
cairo_destroy(cr); |
|
|
|
g_mutex_unlock(&this->drawingMutex); |
|
} else { |
|
rerenderElement(elem); |
|
} |
|
}
|
|
|