#include "StrokeHandler.h" #include "control/layer/LayerController.h" #include "gui/XournalView.h" #include "gui/PageView.h" #include "control/Control.h" #include "undo/InsertUndoAction.h" #include "control/shaperecognizer/ShapeRecognizerResult.h" #include "undo/RecognizerUndoAction.h" #include #include #include #define IGNORE_STROKE_POINTS 8 //this many point in IGNORE_STROKE_TIME_MS will be ignored unless successive - pen input requires larger count #define IGNORE_STROKE_TIME_MS 300 #define DO_NOT_IGNORE_SUCCESSIVE_TIME 500 //only ignore once every this ms guint32 StrokeHandler::lastIgnorePointTime; //persist for next stroke StrokeHandler::StrokeHandler(XournalView* xournal, XojPageView* redrawable, PageRef page) : InputHandler(xournal, redrawable, page), surfMask(NULL), crMask(NULL), reco(NULL) { XOJ_INIT_TYPE(StrokeHandler); } StrokeHandler::~StrokeHandler() { XOJ_CHECK_TYPE(StrokeHandler); destroySurface(); delete reco; reco = NULL; XOJ_RELEASE_TYPE(StrokeHandler); } void StrokeHandler::draw(cairo_t* cr) { XOJ_CHECK_TYPE(StrokeHandler); if (!stroke) { return; } view.applyColor(cr, stroke); if (stroke->getToolType() == STROKE_TOOL_HIGHLIGHTER) { cairo_set_operator(cr, CAIRO_OPERATOR_MULTIPLY); } else { cairo_set_operator(cr, CAIRO_OPERATOR_OVER); } cairo_mask_surface(cr, surfMask, 0, 0); } bool StrokeHandler::onKeyEvent(GdkEventKey* event ) { return false; } bool StrokeHandler::onMotionNotifyEvent(const PositionInputData& pos) { XOJ_CHECK_TYPE(StrokeHandler); if (!stroke) { return false; } double zoom = xournal->getZoom(); double x = pos.x / zoom; double y = pos.y / zoom; int pointCount = stroke->getPointCount(); Point currentPoint(x, y); if (pointCount > 0) { if(!validMotion(currentPoint, stroke->getPoint(pointCount - 1))) { return true; } } if (Point::NO_PRESURE != pos.pressure && stroke->getToolType() == STROKE_TOOL_PEN) { stroke->setLastPressure(pos.pressure * stroke->getWidth()); } stroke->addPoint(currentPoint); if ((stroke->getFill() != -1 || stroke->getLineStyle().hasDashes()) && !(stroke->getFill() != -1 && stroke->getToolType() == STROKE_TOOL_HIGHLIGHTER)) { // Clear surface // for debugging purposes // cairo_set_source_rgba(crMask, 1, 0, 0, 1); cairo_set_source_rgba(crMask, 0, 0, 0, 0); cairo_rectangle(crMask, 0, 0, cairo_image_surface_get_width(surfMask), cairo_image_surface_get_height(surfMask)); cairo_fill(crMask); view.drawStroke(crMask, stroke, 0, 1, true, true); } else { if (pointCount > 0) { Point prevPoint(stroke->getPoint(pointCount - 1)); Stroke lastSegment; lastSegment.addPoint(prevPoint); lastSegment.addPoint(currentPoint); lastSegment.setWidth(stroke->getWidth()); cairo_set_operator(crMask, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(crMask, 1, 1, 1, 1); view.drawStroke(crMask, &lastSegment, 0, 1, false); } } const double w = stroke->getWidth(); this->redrawable->repaintRect(stroke->getX() - w, stroke->getY() - w, stroke->getElementWidth() + 2*w, stroke->getElementHeight() + 2*w); return true; } void StrokeHandler::onButtonReleaseEvent(const PositionInputData& pos) { XOJ_CHECK_TYPE(StrokeHandler); if (!stroke) { return; } int pointCount = stroke->getPointCount(); if ( pointCount < IGNORE_STROKE_POINTS && pos.time - this->startStrokeTime < IGNORE_STROKE_TIME_MS) { if ( pos.time - this->lastIgnorePointTime < DO_NOT_IGNORE_SUCCESSIVE_TIME ) { this->lastIgnorePointTime = pos.time; g_print("NOT_IGNORED: %d\n",pos.time - startStrokeTime); } else { this->lastIgnorePointTime = pos.time; g_print("IGNORED: %d\tlength:%d\n",pos.time - startStrokeTime, pointCount); //stroke not being added to layer... delete here. delete stroke; stroke = NULL; return; } } // Backward compatibility and also easier to handle for me;-) // I cannot draw a line with one point, to draw a visible line I need two points, // twice the same Point is also OK if (pointCount == 1) { ArrayIterator it = stroke->pointIterator(); if (it.hasNext()) { stroke->addPoint(it.next()); } // No pressure sensitivity stroke->clearPressure(); } stroke->freeUnusedPointItems(); Control* control = xournal->getControl(); control->getLayerController()->ensureLayerExists(page); Layer* layer = page->getSelectedLayer(); UndoRedoHandler* undo = control->getUndoRedoHandler(); undo->addUndoAction(new InsertUndoAction(page, layer, stroke)); ToolHandler* h = control->getToolHandler(); if (h->getDrawingType() == DRAWING_TYPE_STROKE_RECOGNIZER) { if (reco == NULL) { reco = new ShapeRecognizer(); } ShapeRecognizerResult* result = reco->recognizePatterns(stroke); if (result) { strokeRecognizerDetected(result, layer); // Full repaint is done anyway // So repaint don't need to be done here stroke = NULL; return; } } if (stroke->getFill() != -1 && stroke->getToolType() == STROKE_TOOL_HIGHLIGHTER) { // The stroke is not filled on drawing time // If the stroke has fill values, it needs to be re-rendered // else the fill will not be visible. view.drawStroke(crMask, stroke, 0, 1, true, true); } layer->addElement(stroke); page->fireElementChanged(stroke); //Manually force the rendering of the stroke, if no motion event occurred inbetween, that would rerender the page. if (stroke->getPointCount() == 2) { this->redrawable->rerenderElement(stroke); } stroke = NULL; return; } void StrokeHandler::strokeRecognizerDetected(ShapeRecognizerResult* result, Layer* layer) { XOJ_CHECK_TYPE(StrokeHandler); Stroke* recognized = result->getRecognized(); recognized->setWidth(stroke->hasPressure() ? stroke->getAvgPressure() : stroke->getWidth()); RecognizerUndoAction* recognizerUndo = new RecognizerUndoAction(page, layer, stroke, recognized); UndoRedoHandler* undo = xournal->getControl()->getUndoRedoHandler(); undo->addUndoAction(recognizerUndo); layer->addElement(result->getRecognized()); Range range(recognized->getX(), recognized->getY()); range.addPoint(recognized->getX() + recognized->getElementWidth(), recognized->getY() + recognized->getElementHeight()); range.addPoint(stroke->getX(), stroke->getY()); range.addPoint(stroke->getX() + stroke->getElementWidth(), stroke->getY() + stroke->getElementHeight()); for (Stroke* s : *result->getSources()) { layer->removeElement(s, false); recognizerUndo->addSourceElement(s); range.addPoint(s->getX(), s->getY()); range.addPoint(s->getX() + s->getElementWidth(), s->getY() + s->getElementHeight()); } page->fireRangeChanged(range); // delete the result object, this is not needed anymore, the stroke are not deleted with this delete result; } void StrokeHandler::onButtonPressEvent(const PositionInputData& pos) { XOJ_CHECK_TYPE(StrokeHandler); destroySurface(); double zoom = xournal->getZoom(); PageRef page = redrawable->getPage(); int dpiScaleFactor = xournal->getDpiScaleFactor(); double width = page->getWidth() * zoom * dpiScaleFactor; double height = page->getHeight() * zoom * dpiScaleFactor; surfMask = cairo_image_surface_create(CAIRO_FORMAT_A8, width, height); crMask = cairo_create(surfMask); // for debugging purposes // cairo_set_source_rgba(crMask, 0, 0, 0, 1); cairo_set_source_rgba(crMask, 0, 0, 0, 0); cairo_rectangle(crMask, 0, 0, width, height); cairo_fill(crMask); cairo_scale(crMask, zoom * dpiScaleFactor, zoom * dpiScaleFactor); if (!stroke) { double x = pos.x / zoom; double y = pos.y / zoom; createStroke(Point(x, y)); } this->startStrokeTime = pos.time; } void StrokeHandler::destroySurface() { XOJ_CHECK_TYPE(StrokeHandler); if (surfMask || crMask) { cairo_destroy(crMask); cairo_surface_destroy(surfMask); surfMask = NULL; crMask = NULL; } } void StrokeHandler::resetShapeRecognizer() { XOJ_CHECK_TYPE(StrokeHandler); if (reco) { delete reco; reco = NULL; } }