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.
 
 
 
 
 
 

343 lines
7.9 KiB

#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 <config-features.h>
#include <gdk/gdk.h>
#include <cmath>
#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<Point> 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;
}
}