actions() const;
/**
* Retrieve an action by name.
*/
QAction *action(const QString &name) const;
/**
* Called when (one of) the mouse or stylus buttons is pressed.
* Implementors should call event->ignore() if they do not actually use the event.
* @param event state and reason of this mouse or stylus press
*/
virtual void mousePressEvent(KoPointerEvent *event) = 0;
/**
* Called when (one of) the mouse or stylus buttons is double clicked.
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this mouse or stylus press
*/
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
- /**
- * Called when (one of) the mouse or stylus buttons is triple clicked.
- * Implementors should call event->ignore() if they do not actually use the event.
- * Default implementation ignores this event.
- * @param event state and reason of this mouse or stylus press
- */
- virtual void mouseTripleClickEvent(KoPointerEvent *event);
-
/**
* Called when the mouse or stylus moved over the canvas.
* Implementors should call event->ignore() if they do not actually use the event.
* @param event state and reason of this mouse or stylus move
*/
virtual void mouseMoveEvent(KoPointerEvent *event) = 0;
/**
* Called when (one of) the mouse or stylus buttons is released.
* Implementors should call event->ignore() if they do not actually use the event.
* @param event state and reason of this mouse or stylus release
*/
virtual void mouseReleaseEvent(KoPointerEvent *event) = 0;
/**
* Called when a key is pressed.
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this key press
*/
virtual void keyPressEvent(QKeyEvent *event);
/**
* Called when a key is released
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this key release
*/
virtual void keyReleaseEvent(QKeyEvent *event);
- /**
- * Called when the scrollwheel is used
- * Implementors should call event->ignore() if they do not actually use the event
- * @param event state of this wheel event
- */
- virtual void wheelEvent(KoPointerEvent *event);
-
- virtual void touchEvent(QTouchEvent *event);
-
/**
* @brief explicitUserStrokeEndRequest is called by the input manager
* when the user presses Enter key or any equivalent. This callback
* comes before requestStrokeEnd(), which comes from a different source.
*/
virtual void explicitUserStrokeEndRequest();
/**
* This method is used to query a set of properties of the tool to be
* able to support complex input method operations as support for surrounding
* text and reconversions.
* Default implementation returns simple defaults, for tools that want to provide
* a more responsive text entry experience for CJK languages it would be good to reimplemnt.
* @param query specifies which property is queried.
* @param converter the view converter for the current canvas.
*/
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const;
/**
* Text entry of complex text, like CJK, can be made more interactive if a tool
* implements this and the InputMethodQuery() methods.
* Reimplementing this only provides the user with a more responsive text experience, since the
* default implementation forwards the typed text as key pressed events.
* @param event the input method event.
*/
virtual void inputMethodEvent(QInputMethodEvent *event);
/**
- * @return true if synthetic mouse events on the canvas should be eaten.
- *
- * For example, the guides tool should allow click and drag with touch,
- * while the same touch events should be rejected by the freehand tool.
- *
- * These events are sent by the OS in Windows
+ * Called when (one of) a custom device buttons is pressed.
+ * Implementors should call event->ignore() if they do not actually use the event.
+ * @param event state and reason of this custom device press
*/
- bool maskSyntheticEvents() const;
+ virtual void customPressEvent(KoPointerEvent *event);
+ /**
+ * Called when (one of) a custom device buttons is released.
+ * Implementors should call event->ignore() if they do not actually use the event.
+ * @param event state and reason of this custom device release
+ */
+ virtual void customReleaseEvent(KoPointerEvent *event);
/**
- * @return true if the tool will accept raw QTouchEvents.
+ * Called when a custom device moved over the canvas.
+ * Implementors should call event->ignore() if they do not actually use the event.
+ * @param event state and reason of this custom device move
*/
- virtual bool wantsTouch() const;
+ virtual void customMoveEvent(KoPointerEvent *event);
/**
- * Set the identifier code from the KoToolFactoryBase that created this tool.
- * @param id the identifier code
- * @see KoToolFactoryBase::id()
+ * @return true if synthetic mouse events on the canvas should be eaten.
+ *
+ * For example, the guides tool should allow click and drag with touch,
+ * while the same touch events should be rejected by the freehand tool.
+ *
+ * These events are sent by the OS in Windows
*/
- void setToolId(const QString &id);
+ bool maskSyntheticEvents() const;
/**
* get the identifier code from the KoToolFactoryBase that created this tool.
* @return the toolId.
* @see KoToolFactoryBase::id()
*/
Q_INVOKABLE QString toolId() const;
/// return the last emitted cursor
QCursor cursor() const;
/**
* Returns the internal selection object of this tool.
* Each tool can have a selection which is private to that tool and the specified shape that it comes with.
* The default returns 0.
*/
virtual KoToolSelection *selection();
/**
* @returns true if the tool has selected data.
*/
virtual bool hasSelection();
/**
* copies the tools selection to the clipboard.
* The default implementation is empty to aid tools that don't have any selection.
* @see selection()
*/
virtual void copy() const;
/**
* Delete the tools selection.
* The default implementation is empty to aid tools that don't have any selection.
* @see selection()
*/
virtual void deleteSelection();
/**
* Cut the tools selection and copy it to the clipboard.
* The default implementation calls copy() and then deleteSelection()
* @see copy()
* @see deleteSelection()
*/
virtual void cut();
/**
* Paste the clipboard selection.
* A tool typically has one or more shapes selected and pasting should do something meaningful
* for this specific shape and tool combination. Inserting text in a text tool, for example.
* @return will return true if pasting succeeded. False if nothing happened.
*/
virtual bool paste();
/**
* Handle the dragMoveEvent
* A tool typically has one or more shapes selected and dropping into should do
* something meaningful for this specific shape and tool combination. For example
* dropping text in a text tool.
* The tool should Accept the event if it is meaningful; Default implementation does not.
*/
virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point);
/**
* Handle the dragLeaveEvent
* Basically just a noticification that the drag is no long relevant
* The tool should Accept the event if it is meaningful; Default implementation does not.
*/
virtual void dragLeaveEvent(QDragLeaveEvent *event);
/**
* Handle the dropEvent
* A tool typically has one or more shapes selected and dropping into should do
* something meaningful for this specific shape and tool combination. For example
* dropping text in a text tool.
* The tool should Accept the event if it is meaningful; Default implementation does not.
*/
virtual void dropEvent(QDropEvent *event, const QPointF &point);
/**
* @return a menu with context-aware actions for the currect selection. If
* the returned value is null, no context menu is shown.
*/
virtual QMenu* popupActionsMenu();
/// Returns the canvas the tool is working on
KoCanvasBase *canvas() const;
/**
* This method can be reimplemented in a subclass.
* @return returns true, if the tool is in text mode. that means, that there is
* any kind of textual input and all single key shortcuts should be eaten.
*/
bool isInTextMode() const;
+public Q_SLOTS:
+
/**
* Called when the user requested undo while the stroke is
* active. If you tool supports undo of the part of its actions,
* override this method and do the needed work there.
*
* NOTE: Default implementation forwards this request to
* requestStrokeCancellation() method, so that the stroke
* would be cancelled.
*/
virtual void requestUndoDuringStroke();
/**
* Called when the user requested the cancellation of the current
* stroke. If you tool supports cancelling, override this method
* and do the needed work there
*/
virtual void requestStrokeCancellation();
/**
* Called when the image decided that the stroke should better be
* ended. If you tool supports long strokes, override this method
* and do the needed work there
*/
virtual void requestStrokeEnd();
-public Q_SLOTS:
/**
* This method is called when this tool instance is activated.
* For any main window there is only one tool active at a time, which then gets all
* user input. Switching between tools will call deactivate on one and activate on the
* new tool allowing the tool to flush items (like a selection)
* when it is not in use.
*
* There is one case where two tools are activated at the same. This is the case
* where one tool delegates work to another temporarily. For example, while shift is
* being held down. The second tool will get activated with temporary=true and
* it should emit done() when the state that activated it is ended.
*
One of the important tasks of activate is to call useCursor()
*
* @param shapes the set of shapes that are selected or suggested for editing by a
* selected shape for the tool to work on. Not all shapes will be meant for this
* tool.
* @param toolActivation if TemporaryActivation, this tool is only temporarily actived
* and should emit done when it is done.
* @see deactivate()
*/
virtual void activate(ToolActivation toolActivation, const QSet &shapes);
/**
* This method is called whenever this tool is no longer the
* active tool
* @see activate()
*/
virtual void deactivate();
/**
* This method is called whenever a property in the resource
* provider associated with the canvas this tool belongs to
* changes. An example is currently selected foreground color.
*/
virtual void canvasResourceChanged(int key, const QVariant &res);
/**
* This method is called whenever a property in the resource
* provider associated with the document this tool belongs to
* changes. An example is the handle radius
*/
virtual void documentResourceChanged(int key, const QVariant &res);
/**
* This method just relays the given text via the tools statusTextChanged signal.
* @param statusText the new status text
*/
void setStatusText(const QString &statusText);
Q_SIGNALS:
/**
* Emitted when this tool wants itself to be replaced by another tool.
*
* @param id the identification of the desired tool
* @see toolId(), KoToolFactoryBase::id()
*/
void activateTool(const QString &id);
/**
* Emitted when this tool wants itself to temporarily be replaced by another tool.
* For instance, a paint tool could desire to be
* temporarily replaced by a pan tool which could be temporarily
* replaced by a colorpicker.
* @param id the identification of the desired tool
*/
void activateTemporary(const QString &id);
/**
* Emitted when the tool has been temporarily activated and wants
* to notify the world that it's done.
*/
void done();
/**
* Emitted by useCursor() when the cursor to display on the canvas is changed.
* The KoToolManager should connect to this signal to handle cursors further.
*/
void cursorChanged(const QCursor &cursor);
/**
* A tool can have a selection that is copy-able, this signal is emitted when that status changes.
* @param hasSelection is true when the tool holds selected data.
*/
void selectionChanged(bool hasSelection);
/**
* Emitted when the tool wants to display a different status text
* @param statusText the new status text
*/
void statusTextChanged(const QString &statusText);
protected:
/**
* Classes inheriting from this one can call this method to signify which cursor
* the tool wants to display at this time. Logical place to call it is after an
* incoming event has been handled.
* @param cursor the new cursor.
*/
void useCursor(const QCursor &cursor);
/**
* Reimplement this if your tool actually has an option widget.
* Sets the option widget to 0 by default.
*/
virtual QWidget *createOptionWidget();
virtual QList > createOptionWidgets();
/**
* Add an action under the given name to the collection.
*
* Inserting an action under a name that is already used for another action will replace
* the other action in the collection.
*
* @param name The name by which the action be retrieved again from the collection.
* @param action The action to add.
* @param readWrite set this to ReadOnlyAction to keep the action available on
* read-only documents
*/
void addAction(const QString &name, QAction *action);
/// Convenience function to get the current handle radius
uint handleRadius() const;
/// Convencience function to get the current grab sensitivity
uint grabSensitivity() const;
/**
* Returns a handle grab rect at the given position.
*
* The position is expected to be in document coordinates. The grab sensitivity
* canvas resource is used for the dimension of the rectangle.
*
* @return the handle rectangle in document coordinates
*/
QRectF handleGrabRect(const QPointF &position) const;
/**
* Returns a handle paint rect at the given position.
*
* The position is expected to be in document coordinates. The handle radius
* canvas resource is used for the dimension of the rectangle.
*
* @return the handle rectangle in document coordinates
*/
QRectF handlePaintRect(const QPointF &position) const;
/**
* You should set the text mode to true in subclasses, if this tool is in text input mode, eg if the users
* are able to type. If you don't set it, then single key shortcuts will get the key event and not this tool.
*/
void setTextMode(bool value);
/**
* Allows subclasses to specify whether synthetic mouse events should be accepted.
*/
void setMaskSyntheticEvents(bool value);
/**
* Returns true if activate() has been called (more times than deactivate :) )
*/
bool isActivated() const;
protected:
KoToolBase(KoToolBasePrivate &dd);
KoToolBasePrivate *d_ptr;
private:
+
+ friend class ToolHelper;
+
+ /**
+ * Set the identifier code from the KoToolFactoryBase that created this tool.
+ * @param id the identifier code
+ * @see KoToolFactoryBase::id()
+ */
+ void setToolId(const QString &id);
+
+
+
KoToolBase();
KoToolBase(const KoToolBase&);
KoToolBase& operator=(const KoToolBase&);
Q_DECLARE_PRIVATE(KoToolBase)
};
#endif /* KOTOOL_H */
diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp
index 0eb44063f4..d0d1db9e49 100644
--- a/libs/flake/KoToolManager.cpp
+++ b/libs/flake/KoToolManager.cpp
@@ -1,1054 +1,1007 @@
/* This file is part of the KDE project
*
* Copyright (c) 2005-2010 Boudewijn Rempt
* Copyright (C) 2006-2008 Thomas Zander
* Copyright (C) 2006 Thorsten Zachmann
* Copyright (C) 2008 Jan Hambrecht
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
// flake
#include "KoToolManager.h"
#include "KoToolManager_p.h"
#include "KoToolRegistry.h"
#include "KoToolProxy.h"
#include "KoToolProxy_p.h"
#include "KoSelection.h"
#include "KoCanvasController.h"
#include "KoCanvasControllerWidget.h"
#include "KoShape.h"
#include "KoShapeLayer.h"
#include "KoShapeRegistry.h"
#include "KoShapeManager.h"
#include "KoSelectedShapesProxy.h"
#include "KoCanvasBase.h"
#include "KoInputDeviceHandlerRegistry.h"
#include "KoInputDeviceHandlerEvent.h"
#include "KoPointerEvent.h"
#include "tools/KoCreateShapesTool.h"
#include "tools/KoZoomTool.h"
#include "kis_action_registry.h"
#include "KoToolFactoryBase.h"
#include
// Qt + kde
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
Q_GLOBAL_STATIC(KoToolManager, s_instance)
class CanvasData
{
public:
CanvasData(KoCanvasController *cc, const KoInputDevice &id)
: activeTool(0),
canvas(cc),
inputDevice(id),
dummyToolWidget(0),
dummyToolLabel(0)
{
}
~CanvasData()
{
// the dummy tool widget does not necessarily have a parent and we create it, so we delete it.
delete dummyToolWidget;
}
void activateToolActions()
{
disabledDisabledActions.clear();
disabledActions.clear();
disabledCanvasShortcuts.clear();
// we do several things here
// 1. enable the actions of the active tool
// 2. disable conflicting actions
// 3. replace conflicting actions in the action collection
KActionCollection *canvasActionCollection = canvas->actionCollection();
QHash toolActions = activeTool->actions();
QHash::const_iterator it(toolActions.constBegin());
for (; it != toolActions.constEnd(); ++it) {
if (canvasActionCollection) {
QString toolActionID = it.key();
QAction *toolAction = it.value();
QAction * action = qobject_cast(canvasActionCollection->action(it.key()));
if (action) {
canvasActionCollection->takeAction(action);
if (action != it.value()) {
if (action->isEnabled()) {
action->setEnabled(false);
disabledActions.append(action);
} else {
disabledDisabledActions.append(action);
}
}
}
Q_FOREACH (QAction *a, canvasActionCollection->actions()) {
QAction *canvasAction = dynamic_cast(a);
if (canvasAction && canvasAction->shortcut().toString() != "" && canvasAction->shortcut() == toolAction->shortcut()) {
warnFlake << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString();
disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString();
canvasAction->setShortcut(QKeySequence());
}
}
canvasActionCollection->addAction(toolActionID, toolAction);
}
it.value()->setEnabled(true);
}
canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime.
}
void deactivateToolActions()
{
if (!activeTool)
return;
// disable actions of active tool
Q_FOREACH (QAction *action, activeTool->actions()) {
action->setEnabled(false);
}
// enable actions which where disabled on activating the active tool
// and re-add them to the action collection
KActionCollection *ac = canvas->actionCollection();
Q_FOREACH (QPointer action, disabledDisabledActions) {
if (action) {
if (ac) {
ac->addAction(action->objectName(), action);
}
}
}
disabledDisabledActions.clear();
Q_FOREACH (QPointer action, disabledActions) {
if (action) {
action->setEnabled(true);
if(ac) {
ac->addAction(action->objectName(), action);
}
}
}
disabledActions.clear();
QMap, QString>::const_iterator it(disabledCanvasShortcuts.constBegin());
for (; it != disabledCanvasShortcuts.constEnd(); ++it) {
QAction *action = it.key();
QString shortcut = it.value();
action->setShortcut(shortcut);
}
disabledCanvasShortcuts.clear();
}
KoToolBase *activeTool; // active Tool
QString activeToolId; // the id of the active Tool
QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to.
QHash allTools; // all the tools that are created for this canvas.
QStack stack; // stack of temporary tools
KoCanvasController *const canvas;
const KoInputDevice inputDevice;
QWidget *dummyToolWidget; // the widget shown in the toolDocker.
QLabel *dummyToolLabel;
QList > disabledActions; ///< disabled conflicting actions
QList > disabledDisabledActions; ///< disabled conflicting actions that were already disabled
QMap, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides
};
// ******** KoToolManager **********
KoToolManager::KoToolManager()
: QObject(),
d(new Private(this))
{
connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
this, SLOT(movedFocus(QWidget*, QWidget*)));
}
KoToolManager::~KoToolManager()
{
delete d;
}
QList KoToolManager::toolActionList() const
{
QList answer;
answer.reserve(d->tools.count());
Q_FOREACH (ToolHelper *tool, d->tools) {
if (tool->id() == KoCreateShapesTool_ID)
continue; // don't show this one.
answer.append(tool->toolAction());
}
return answer;
}
void KoToolManager::requestToolActivation(KoCanvasController * controller)
{
if (d->canvasses.contains(controller)) {
QString activeToolId = d->canvasses.value(controller).first()->activeToolId;
Q_FOREACH (ToolHelper * th, d->tools) {
if (th->id() == activeToolId) {
d->toolActivated(th);
break;
}
}
}
}
KoInputDevice KoToolManager::currentInputDevice() const
{
return d->inputDevice;
}
void KoToolManager::registerToolActions(KActionCollection *ac, KoCanvasController *controller)
{
Q_ASSERT(controller);
Q_ASSERT(ac);
d->setup();
if (!d->canvasses.contains(controller)) {
return;
}
// Actions available during the use of individual tools
CanvasData *cd = d->canvasses.value(controller).first();
Q_FOREACH (KoToolBase *tool, cd->allTools) {
QHash actions = tool->actions();
QHash::const_iterator action(actions.constBegin());
for (; action != actions.constEnd(); ++action) {
if (!ac->action(action.key()))
ac->addAction(action.key(), action.value());
}
}
// Actions used to switch tools via shortcuts
Q_FOREACH (ToolHelper * th, d->tools) {
if (ac->action(th->id())) {
continue;
}
ShortcutToolAction* action = th->createShortcutToolAction(ac);
ac->addCategorizedAction(th->id(), action, "tool-shortcuts");
}
}
void KoToolManager::addController(KoCanvasController *controller)
{
Q_ASSERT(controller);
if (d->canvasses.contains(controller))
return;
d->setup();
d->attachCanvas(controller);
connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*)));
connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
}
void KoToolManager::removeCanvasController(KoCanvasController *controller)
{
Q_ASSERT(controller);
disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
d->detachCanvas(controller);
}
void KoToolManager::attemptCanvasControllerRemoval(QObject* controller)
{
KoCanvasControllerProxyObject* controllerActual = qobject_cast(controller);
if (controllerActual) {
removeCanvasController(controllerActual->canvasController());
}
}
-void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController)
-{
- if (!d->canvasses.contains(canvasController))
- return;
-
- QList canvasses = d->canvasses[canvasController];
- Q_FOREACH (CanvasData *canvas, canvasses) {
- Q_FOREACH (KoToolBase *tool, canvas->allTools.values()) {
- tool->updateShapeController(shapeController);
- }
- }
-}
-
void KoToolManager::switchToolRequested(const QString & id)
{
Q_ASSERT(d->canvasData);
if (!d->canvasData) return;
while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack
d->canvasData->stack.pop();
d->switchTool(id, false);
}
void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id)
{
if (!d->canvasData) return;
d->switchInputDevice(id);
}
void KoToolManager::switchToolTemporaryRequested(const QString &id)
{
d->switchTool(id, true);
}
void KoToolManager::switchBackRequested()
{
if (!d->canvasData) return;
if (d->canvasData->stack.isEmpty()) {
// default to changing to the interactionTool
d->switchTool(KoInteractionTool_ID, false);
return;
}
d->switchTool(d->canvasData->stack.pop(), false);
}
KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas) {
KoCreateShapesTool *createTool = dynamic_cast
(d->canvasData->allTools.value(KoCreateShapesTool_ID));
Q_ASSERT(createTool /* ID changed? */);
return createTool;
}
}
Q_ASSERT(0); // this should not happen
return 0;
}
KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas)
return d->canvasData->allTools.value(id);
}
return 0;
}
KoCanvasController *KoToolManager::activeCanvasController() const
{
if (! d->canvasData) return 0;
return d->canvasData->canvas;
}
QString KoToolManager::preferredToolForSelection(const QList &shapes)
{
QList types;
Q_FOREACH (KoShape *shape, shapes) {
types << shape->shapeId();
}
KritaUtils::makeContainerUnique(types);
QString toolType = KoInteractionTool_ID;
int prio = INT_MAX;
Q_FOREACH (ToolHelper *helper, d->tools) {
if (helper->id() == KoCreateShapesTool_ID) continue;
if (helper->priority() >= prio)
continue;
bool toolWillWork = false;
foreach (const QString &type, types) {
if (helper->activationShapeId().split(',').contains(type)) {
toolWillWork = true;
break;
}
}
if (toolWillWork) {
toolType = helper->id();
prio = helper->priority();
}
}
return toolType;
}
-void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory)
-{
- ToolHelper *tool = new ToolHelper(toolFactory);
- // make sure all plugins are loaded as otherwise we will not load them
- d->setup();
- d->tools.append(tool);
-
- // connect to all tools so we can hear their button-clicks
- connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*)));
-
- // now create tools for all existing canvases
- Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
-
- // this canvascontroller is unknown, which is weird
- if (!d->canvasses.contains(controller)) {
- continue;
- }
-
- // create a tool for all canvasdata objects (i.e., all input devices on this canvas)
- foreach (CanvasData *cd, d->canvasses[controller]) {
- QPair toolPair = createTools(controller, tool);
- if (toolPair.second) {
- cd->allTools.insert(toolPair.first, toolPair.second);
- }
- }
-
- // Then create a button for the toolbox for this canvas
- if (tool->id() == KoCreateShapesTool_ID) {
- continue;
- }
-
- emit addedTool(tool->toolAction(), controller);
- }
-}
QPair KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool)
{
// XXX: maybe this method should go into the private class?
QHash origHash;
if (d->canvasses.contains(controller)) {
origHash = d->canvasses.value(controller).first()->allTools;
}
if (origHash.contains(tool->id())) {
return QPair(tool->id(), origHash.value(tool->id()));
}
debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority();
KoToolBase *tl = tool->createTool(controller->canvas());
if (tl) {
d->uniqueToolIds.insert(tl, tool->uniqueId());
tl->setObjectName(tool->id());
Q_FOREACH (QAction *action, tl->actions()) {
action->setEnabled(false);
}
}
KoZoomTool *zoomTool = dynamic_cast(tl);
if (zoomTool) {
zoomTool->setCanvasController(controller);
}
return QPair(tool->id(), tl);
}
// NOT IMPLEMENTED
void KoToolManager::updateToolShortcuts()
{
// auto actionRegistry = KisActionRegistry::instance();
// foreach (KoToolBase *t, allTools) {
// for (auto it = t->actions().constBegin();
// it != t->actions().constEnd();
// ++it;) {
// actionRegistry->updateShortcut(it.key(), it.value());
// }
// }
}
void KoToolManager::initializeCurrentToolForCanvas()
{
d->postSwitchTool(false);
}
KoToolManager* KoToolManager::instance()
{
return s_instance;
}
QString KoToolManager::activeToolId() const
{
if (!d->canvasData) return QString();
return d->canvasData->activeToolId;
}
KoToolManager::Private *KoToolManager::priv()
{
return d;
}
/**** KoToolManager::Private ****/
KoToolManager::Private::Private(KoToolManager *qq)
: q(qq),
canvasData(0),
layerExplicitlyDisabled(false)
{
}
KoToolManager::Private::~Private()
{
qDeleteAll(tools);
}
// helper method.
CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device)
{
QHash toolsHash;
Q_FOREACH (ToolHelper *tool, tools) {
QPair toolPair = q->createTools(controller, tool);
if (toolPair.second) { // only if a real tool was created
toolsHash.insert(toolPair.first, toolPair.second);
}
}
KoCreateShapesTool *createShapesTool = dynamic_cast(toolsHash.value(KoCreateShapesTool_ID));
Q_ASSERT(createShapesTool);
QString id = KoShapeRegistry::instance()->keys()[0];
createShapesTool->setShapeId(id);
CanvasData *cd = new CanvasData(controller, device);
cd->allTools = toolsHash;
return cd;
}
void KoToolManager::Private::setup()
{
if (tools.size() > 0)
return;
KoShapeRegistry::instance();
KoToolRegistry *registry = KoToolRegistry::instance();
Q_FOREACH (const QString & id, registry->keys()) {
ToolHelper *t = new ToolHelper(registry->value(id));
tools.append(t);
}
// connect to all tools so we can hear their button-clicks
Q_FOREACH (ToolHelper *tool, tools)
connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*)));
// load pluggable input devices
KoInputDeviceHandlerRegistry::instance();
}
void KoToolManager::Private::connectActiveTool()
{
if (canvasData->activeTool) {
connect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor &)),
q, SLOT(updateCursor(const QCursor &)));
connect(canvasData->activeTool, SIGNAL(activateTool(const QString &)),
q, SLOT(switchToolRequested(const QString &)));
connect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)),
q, SLOT(switchToolTemporaryRequested(const QString &)));
connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
connect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)),
q, SIGNAL(changedStatusText(const QString &)));
}
// we expect the tool to emit a cursor on activation.
updateCursor(Qt::ForbiddenCursor);
}
void KoToolManager::Private::disconnectActiveTool()
{
if (canvasData->activeTool) {
canvasData->deactivateToolActions();
// repaint the decorations before we deactivate the tool as it might deleted
// data needed for the repaint
emit q->aboutToChangeTool(canvasData->canvas);
canvasData->activeTool->deactivate();
disconnect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor&)),
q, SLOT(updateCursor(const QCursor&)));
disconnect(canvasData->activeTool, SIGNAL(activateTool(const QString &)),
q, SLOT(switchToolRequested(const QString &)));
disconnect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)),
q, SLOT(switchToolTemporaryRequested(const QString &)));
disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)),
q, SIGNAL(changedStatusText(const QString &)));
}
// emit a empty status text to clear status text from last active tool
emit q->changedStatusText(QString());
}
void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary)
{
Q_ASSERT(tool);
if (canvasData == 0)
return;
if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID)
return;
disconnectActiveTool();
canvasData->activeTool = tool;
connectActiveTool();
postSwitchTool(temporary);
}
void KoToolManager::Private::switchTool(const QString &id, bool temporary)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (canvasData->activeTool && temporary)
canvasData->stack.push(canvasData->activeToolId);
canvasData->activeToolId = id;
KoToolBase *tool = canvasData->allTools.value(id);
if (! tool) {
return;
}
Q_FOREACH (ToolHelper *th, tools) {
if (th->id() == id) {
canvasData->activationShapeId = th->activationShapeId();
break;
}
}
switchTool(tool, temporary);
}
void KoToolManager::Private::postSwitchTool(bool temporary)
{
#ifndef NDEBUG
int canvasCount = 1;
Q_FOREACH (QList list, canvasses) {
bool first = true;
Q_FOREACH (CanvasData *data, list) {
if (first) {
debugFlake << "Canvas" << canvasCount++;
}
debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : "");
first = false;
}
}
#endif
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase::ToolActivation toolActivation;
if (temporary)
toolActivation = KoToolBase::TemporaryActivation;
else
toolActivation = KoToolBase::DefaultActivation;
QSet shapesToOperateOn;
if (canvasData->activeTool
&& canvasData->activeTool->canvas()
&& canvasData->activeTool->canvas()->shapeManager()) {
KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection();
Q_ASSERT(selection);
shapesToOperateOn = QSet::fromList(selection->selectedEditableShapesAndDelegates());
}
if (canvasData->canvas->canvas()) {
// Caller of postSwitchTool expect this to be called to update the selected tool
updateToolForProxy();
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
KoCanvasBase *canvas = canvasData->canvas->canvas();
canvas->updateInputMethodInfo();
} else {
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
}
QList > optionWidgetList = canvasData->activeTool->optionWidgets();
if (optionWidgetList.empty()) { // no option widget.
QWidget *toolWidget;
QString title;
Q_FOREACH (ToolHelper *tool, tools) {
if (tool->id() == canvasData->activeTool->toolId()) {
title = tool->toolTip();
break;
}
}
toolWidget = canvasData->dummyToolWidget;
if (toolWidget == 0) {
toolWidget = new QWidget();
toolWidget->setObjectName("DummyToolWidget");
QVBoxLayout *layout = new QVBoxLayout(toolWidget);
layout->setMargin(3);
canvasData->dummyToolLabel = new QLabel(toolWidget);
layout->addWidget(canvasData->dummyToolLabel);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
toolWidget->setLayout(layout);
canvasData->dummyToolWidget = toolWidget;
}
canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title));
optionWidgetList.append(toolWidget);
}
// Activate the actions for the currently active tool
canvasData->activateToolActions();
emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool));
emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList);
}
void KoToolManager::Private::switchCanvasData(CanvasData *cd)
{
Q_ASSERT(cd);
KoCanvasBase *oldCanvas = 0;
KoInputDevice oldInputDevice;
if (canvasData) {
oldCanvas = canvasData->canvas->canvas();
oldInputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
disconnectActiveTool();
}
KoToolProxy *proxy = proxies.value(oldCanvas);
Q_ASSERT(proxy);
proxy->setActiveTool(0);
}
canvasData = cd;
inputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
connectActiveTool();
postSwitchTool(false);
}
if (oldInputDevice != canvasData->inputDevice) {
emit q->inputDeviceChanged(canvasData->inputDevice);
}
if (oldCanvas != canvasData->canvas->canvas()) {
emit q->changedCanvas(canvasData->canvas->canvas());
}
}
void KoToolManager::Private::toolActivated(ToolHelper *tool)
{
Q_ASSERT(tool);
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase *t = canvasData->allTools.value(tool->id());
Q_ASSERT(t);
canvasData->activeToolId = tool->id();
canvasData->activationShapeId = tool->activationShapeId();
switchTool(t, false);
}
void KoToolManager::Private::detachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
// check if we are removing the active canvas controller
if (canvasData && canvasData->canvas == controller) {
KoCanvasController *newCanvas = 0;
// try to find another canvas controller beside the one we are removing
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas != controller) {
// yay found one
newCanvas = canvas;
break;
}
}
if (newCanvas) {
switchCanvasData(canvasses.value(newCanvas).first());
} else {
emit q->toolOptionWidgetsChanged(controller, QList >());
// as a last resort just set a blank one
canvasData = 0;
}
}
KoToolProxy *proxy = proxies.value(controller->canvas());
if (proxy)
proxy->setActiveTool(0);
QList tools;
Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) {
Q_FOREACH (KoToolBase *tool, canvasData->allTools) {
if (! tools.contains(tool)) {
tools.append(tool);
}
}
delete canvasData;
}
Q_FOREACH (KoToolBase *tool, tools) {
uniqueToolIds.remove(tool);
delete tool;
}
canvasses.remove(controller);
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::attachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse());
// switch to new canvas as the active one.
switchCanvasData(cd);
inputDevice = cd->inputDevice;
QList canvasses_;
canvasses_.append(cd);
canvasses[controller] = canvasses_;
KoToolProxy *tp = proxies[controller->canvas()];
if (tp)
tp->priv()->setCanvasController(controller);
if (cd->activeTool == 0) {
// no active tool, so we activate the highest priority main tool
int highestPriority = INT_MAX;
ToolHelper * helper = 0;
Q_FOREACH (ToolHelper * th, tools) {
if (th->section() == KoToolFactoryBase::mainToolType()) {
if (th->priority() < highestPriority) {
highestPriority = qMin(highestPriority, th->priority());
helper = th;
}
}
}
if (helper)
toolActivated(helper);
}
Connector *connector = new Connector(controller->canvas()->shapeManager());
connect(connector, SIGNAL(selectionChanged(QList)), q,
SLOT(selectionChanged(QList)));
connect(controller->canvas()->selectedShapesProxy(),
SIGNAL(currentLayerChanged(const KoShapeLayer*)),
q, SLOT(currentLayerChanged(const KoShapeLayer*)));
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to)
{
Q_UNUSED(from);
// no canvas anyway or no focus set anyway?
if (!canvasData || to == 0) {
return;
}
// Check if this app is about QWidget-based KoCanvasControllerWidget canvasses
// XXX: Focus handling for non-qwidget based canvases!
KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast(canvasData->canvas);
if (!canvasControllerWidget) {
return;
}
// canvasWidget is set as focusproxy for KoCanvasControllerWidget,
// so all focus checks are to be done against canvasWidget objects
// focus returned to current canvas?
if (to == canvasData->canvas->canvas()->canvasWidget()) {
// nothing to do
return;
}
// if the 'to' is one of our canvasWidgets, then switch.
// for code simplicity the current canvas will be checked again,
// but would have been catched already in the lines above, so no issue
KoCanvasController *newCanvas = 0;
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas->canvas()->canvasWidget() == to) {
newCanvas = canvas;
break;
}
}
// none of our canvasWidgets got focus?
if (newCanvas == 0) {
return;
}
// switch to canvasdata matching inputdevice used last with this app instance
Q_FOREACH (CanvasData *data, canvasses.value(newCanvas)) {
if (data->inputDevice == inputDevice) {
switchCanvasData(data);
return;
}
}
// if no such inputDevice for this canvas, then simply fallback to first one
switchCanvasData(canvasses.value(newCanvas).first());
}
void KoToolManager::Private::updateCursor(const QCursor &cursor)
{
Q_ASSERT(canvasData);
Q_ASSERT(canvasData->canvas);
Q_ASSERT(canvasData->canvas->canvas());
canvasData->canvas->canvas()->setCursor(cursor);
}
void KoToolManager::Private::selectionChanged(const QList &shapes)
{
QList types;
Q_FOREACH (KoShape *shape, shapes) {
QSet delegates = shape->toolDelegates();
if (delegates.isEmpty()) { // no delegates, just the orig shape
delegates << shape;
}
foreach (KoShape *shape2, delegates) {
Q_ASSERT(shape2);
if (! types.contains(shape2->shapeId())) {
types.append(shape2->shapeId());
}
}
}
// check if there is still a shape selected the active tool can work on
// there needs to be at least one shape for a tool without an activationShapeId
// to work
// if not change the current tool to the default tool
if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0)
&& canvasData->activationShapeId != "flake/always"
&& canvasData->activationShapeId != "flake/edit") {
bool currentToolWorks = false;
foreach (const QString &type, types) {
if (canvasData->activationShapeId.split(',').contains(type)) {
currentToolWorks = true;
break;
}
}
if (!currentToolWorks) {
switchTool(KoInteractionTool_ID, false);
}
}
emit q->toolCodesSelected(types);
}
void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer)
{
emit q->currentLayerChanged(canvasData->canvas, layer);
layerExplicitlyDisabled = layer && !layer->isEditable();
updateToolForProxy();
debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled;
}
void KoToolManager::Private::updateToolForProxy()
{
KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas());
if(!proxy) return;
bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always"));
proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0);
}
void KoToolManager::Private::switchInputDevice(const KoInputDevice &device)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (inputDevice == device) return;
if (inputDevice.isMouse() && device.isMouse()) return;
if (device.isMouse() && !inputDevice.isMouse()) {
// we never switch back to mouse from a tablet input device, so the user can use the
// mouse to edit the settings for a tool activated by a tablet. See bugs
// https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501.
// We do continue to switch between tablet devices, thought.
return;
}
QList items = canvasses[canvasData->canvas];
// disable all actions for all tools in the all canvasdata objects for this canvas.
Q_FOREACH (CanvasData *cd, items) {
Q_FOREACH (KoToolBase* tool, cd->allTools) {
Q_FOREACH (QAction * action, tool->actions()) {
action->setEnabled(false);
}
}
}
// search for a canvasdata object for the current input device
Q_FOREACH (CanvasData *cd, items) {
if (cd->inputDevice == device) {
switchCanvasData(cd);
if (!canvasData->activeTool) {
switchTool(KoInteractionTool_ID, false);
}
return;
}
}
// still here? That means we need to create a new CanvasData instance with the current InputDevice.
CanvasData *cd = createCanvasData(canvasData->canvas, device);
// switch to new canvas as the active one.
QString oldTool = canvasData->activeToolId;
items.append(cd);
canvasses[cd->canvas] = items;
switchCanvasData(cd);
q->switchToolRequested(oldTool);
}
void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas)
{
proxies.insert(canvas, proxy);
Q_FOREACH (KoCanvasController *controller, canvasses.keys()) {
if (controller->canvas() == canvas) {
proxy->priv()->setCanvasController(controller);
break;
}
}
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoToolManager.cpp"
diff --git a/libs/flake/KoToolManager.h b/libs/flake/KoToolManager.h
index 689fa37dc2..768da0a5f1 100644
--- a/libs/flake/KoToolManager.h
+++ b/libs/flake/KoToolManager.h
@@ -1,351 +1,338 @@
/* This file is part of the KDE project
* Copyright (c) 2005-2006 Boudewijn Rempt
* Copyright (C) 2006, 2008 Thomas Zander
* Copyright (C) 2006 Thorsten Zachmann
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KO_TOOL_MANAGER
#define KO_TOOL_MANAGER
#include "KoInputDevice.h"
#include "kritaflake_export.h"
#include
#include
class KoCanvasController;
class KoShapeBasedDocumentBase;
class KoToolFactoryBase;
class KoCanvasBase;
class KoToolBase;
class KoCreateShapesTool;
class KActionCollection;
class KoShape;
class KoInputDeviceHandlerEvent;
class KoShapeLayer;
class ToolHelper;
class QKeySequence;
class QCursor;
/**
* This class serves as a QAction-like control object for activation of a tool.
*
* It allows to implement a custom UI to control the activation of tools.
* See KoToolBox & KoModeBox in the kowidgets library.
*
* KoToolAction objects are indirectly owned by the KoToolManager singleton
* and live until the end of its lifetime.
*/
class KRITAFLAKE_EXPORT KoToolAction : public QObject
{
Q_OBJECT
public:
// toolHelper takes over ownership, and those live till the end of KoToolManager.
explicit KoToolAction(ToolHelper *toolHelper);
~KoToolAction() override;
public:
QString id() const; ///< The id of the tool
QString iconText() const; ///< The icontext of the tool
QString toolTip() const; ///< The tooltip of the tool
QString iconName() const; ///< The icon name of the tool
QKeySequence shortcut() const; ///< The shortcut to activate the tool
QString section() const; ///< The section the tool wants to be in.
int priority() const; ///< Lower number (higher priority) means coming first in the section.
int buttonGroupId() const; ///< A unique ID for this tool as passed by changedTool(), >= 0
QString visibilityCode() const; ///< This tool should become visible when we emit this string in toolCodesSelected()
public Q_SLOTS:
void trigger(); ///< Request the activation of the tool
Q_SIGNALS:
void changed(); ///< Emitted when a property changes (shortcut ATM)
private:
friend class ToolHelper;
class Private;
Private *const d;
};
/**
* This class manages the activation and deactivation of tools for
* each input device.
*
* Managing the active tool and switching tool based on various variables.
*
* The state of the toolbox will be the same for all views in the process so practically
* you can say we have one toolbox per application instance (process). Implementation
* does not allow one widget to be in more then one view, so we just make sure the toolbox
* is hidden in not-in-focus views.
*
* The ToolManager is a singleton and will manage all views in all applications that
* are loaded in this process. This means you will have to register and unregister your view.
* When creating your new view you should use a KoCanvasController() and register that
* with the ToolManager like this:
@code
MyGuiWidget::MyGuiWidget() {
m_canvasController = new KoCanvasController(this);
m_canvasController->setCanvas(m_canvas);
KoToolManager::instance()->addControllers(m_canvasController));
}
MyGuiWidget::~MyGuiWidget() {
KoToolManager::instance()->removeCanvasController(m_canvasController);
}
@endcode
*
* For a new view that extends KoView all you need to do is implement KoView::createToolBox()
*
* KoToolManager also keeps track of the current tool based on a
complex set of conditions and heuristics:
- there is one active tool per KoCanvasController (and there is one KoCanvasController
per view, because this is a class with scrollbars and a zoomlevel and so on)
- for every pointing device (determined by the unique id of tablet,
or 0 for mice -- you may have more than one mouse attached, but
Qt cannot distinquish between them, there is an associated tool.
- depending on things like tablet leave/enter proximity, incoming
mouse or tablet events and a little timer (that gets stopped when
we know what is what), the active pointing device is determined,
and the active tool is set accordingly.
Nota bene: if you use KoToolManager and register your canvases with
it you no longer have to manually implement methods to route mouse,
tablet, key or wheel events to the active tool. In fact, it's no
longer interesting to you which tool is active; you can safely
route the paint event through KoToolProxy::paint().
(The reason the input events are handled completely by the
toolmanager and the paint events not is that, generally speaking,
it's okay if the tools get the input events first, but you want to
paint your shapes or other canvas stuff first and only then paint
the tool stuff.)
*/
class KRITAFLAKE_EXPORT KoToolManager : public QObject
{
Q_OBJECT
public:
KoToolManager();
/// Return the toolmanager singleton
static KoToolManager* instance();
~KoToolManager() override;
/**
* Register actions for switching to tools at the actionCollection parameter.
* The actions will have the text / shortcut as stated by the toolFactory.
* If the application calls this in their KoView extending class they will have all the benefits
* from allowing this in the menus and to allow the use to configure the shortcuts used.
* @param ac the actionCollection that will be the parent of the actions.
* @param controller tools registered with this controller will have all their actions added as well.
*/
void registerToolActions(KActionCollection *ac, KoCanvasController *controller);
/**
* Register a new canvas controller
* @param controller the view controller that this toolmanager will manage the tools for
*/
void addController(KoCanvasController *controller);
/**
* Remove a set of controllers
* When the controller is no longer used it should be removed so all tools can be
* deleted and stop eating memory.
* @param controller the controller that is removed
*/
void removeCanvasController(KoCanvasController *controller);
/**
* Attempt to remove a controller.
* This is automatically called when a controller's proxy object is deleted, and
* it ensures that the controller is, in fact, removed, even if the creator forgot
* to do so.
* @param controller the proxy object of the controller to be removed
*/
Q_SLOT void attemptCanvasControllerRemoval(QObject *controller);
/// @return the active canvas controller
KoCanvasController *activeCanvasController() const;
- /**
- * Connect all the tools for the given canvas to the new shape controller.
- *
- * @param shapecontroller the new shape controller
- * @param canvasController the canvas
- */
- void updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController);
-
/**
* Return the tool that is able to create shapes for this param canvas.
* This is typically used by the KoShapeSelector to set which shape to create next.
* @param canvas the canvas that is a child of a previously registered controller
* who's tool you want.
* @see addController()
*/
KoCreateShapesTool *shapeCreatorTool(KoCanvasBase *canvas) const;
/**
* Returns the tool for the given tool id.
* @param canvas the canvas that is a child of a previously registered controller
* who's tool you want.
* @see addController()
*/
KoToolBase *toolById(KoCanvasBase *canvas, const QString &id) const;
/// @return the currently active pointing device
KoInputDevice currentInputDevice() const;
/**
* For the list of shapes find out which tool is the highest priorty tool that can handle it.
* @returns the toolId for the shapes.
* @param shapes a list of shapes, a selection for example, that is used to look for the tool.
*/
QString preferredToolForSelection(const QList &shapes);
/**
* Returns the list of toolActions for the current tools.
* @returns lists of toolActions for the current tools.
*/
QList toolActionList() const;
/// Update the internal shortcuts of each tool. (Activation shortcuts are exposed already.)
void updateToolShortcuts();
/// Request tool activation for the given canvas controller
void requestToolActivation(KoCanvasController *controller);
/// Returns the toolId of the currently active tool
QString activeToolId() const;
void initializeCurrentToolForCanvas();
class Private;
/**
* \internal return the private object for the toolmanager.
*/
KoToolManager::Private *priv();
public Q_SLOTS:
/**
* Request switching tool
* @param id the id of the tool
*/
void switchToolRequested(const QString &id);
/**
* Request change input device
* @param id the id of the input device
*/
void switchInputDeviceRequested(const KoInputDevice &id);
- /**
- * a new tool has become known to mankind
- */
- void addDeferredToolFactory(KoToolFactoryBase *toolFactory);
-
/**
* Request for temporary switching the tools.
* This switch can be later reverted with switchBackRequested().
* @param id the id of the tool
*
* @see switchBackRequested()
*/
void switchToolTemporaryRequested(const QString &id);
/**
* Switches back to the original tool after the temporary switch
* has been done. It the user changed the tool manually on the way,
* then it switches to the interaction tool
*/
void switchBackRequested();
Q_SIGNALS:
/**
* Emitted when a new tool is going to override the current tool
* @param canvas the currently active canvas.
*/
void aboutToChangeTool(KoCanvasController *canvas);
/**
* Emitted when a new tool was selected or became active.
* @param canvas the currently active canvas.
* @param uniqueToolId a random but unique code for the new tool.
*/
void changedTool(KoCanvasController *canvas, int uniqueToolId);
/**
* Emitted after the selection changed to state which unique shape-types are now
* in the selection.
* @param canvas the currently active canvas.
* @param types a list of string that are the shape types of the selected objects.
*/
void toolCodesSelected(const QList &types);
/**
* Emitted after the current layer changed either its properties or to a new layer.
* @param canvas the currently active canvas.
* @param layer the layer that is selected.
*/
void currentLayerChanged(const KoCanvasController *canvas, const KoShapeLayer *layer);
/**
* Every time a new input device gets used by a tool, this event is emitted.
* @param device the new input device that the user picked up.
*/
void inputDeviceChanged(const KoInputDevice &device);
/**
* Emitted whenever the active canvas changed.
* @param canvas the new activated canvas (might be 0)
*/
void changedCanvas(const KoCanvasBase *canvas);
/**
* Emitted whenever the active tool changes the status text.
* @param statusText the new status text
*/
void changedStatusText(const QString &statusText);
/**
* emitted whenever a new tool is dynamically added for the given canvas
*/
void addedTool(KoToolAction *toolAction, KoCanvasController *canvas);
/**
* Emit the new tool option widgets to be used with this canvas.
*/
void toolOptionWidgetsChanged(KoCanvasController *controller, const QList > &widgets);
private:
KoToolManager(const KoToolManager&);
KoToolManager operator=(const KoToolManager&);
Q_PRIVATE_SLOT(d, void toolActivated(ToolHelper *tool))
Q_PRIVATE_SLOT(d, void detachCanvas(KoCanvasController *controller))
Q_PRIVATE_SLOT(d, void attachCanvas(KoCanvasController *controller))
Q_PRIVATE_SLOT(d, void movedFocus(QWidget *from, QWidget *to))
Q_PRIVATE_SLOT(d, void updateCursor(const QCursor &cursor))
Q_PRIVATE_SLOT(d, void selectionChanged(const QList &shapes))
Q_PRIVATE_SLOT(d, void currentLayerChanged(const KoShapeLayer *layer))
QPair createTools(KoCanvasController *controller, ToolHelper *tool);
Private *const d;
};
#endif
diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp
index fcdb2f3b82..e2772e324c 100644
--- a/libs/flake/KoToolProxy.cpp
+++ b/libs/flake/KoToolProxy.cpp
@@ -1,589 +1,475 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander
* Copyright (c) 2006-2011 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoToolProxy.h"
#include "KoToolProxy_p.h"
#include
#include
#include
-#include
-#include
#include
+#include
#include
#include
#include
#include
#include "KoToolBase.h"
#include "KoPointerEvent.h"
#include "KoInputDevice.h"
#include "KoToolManager_p.h"
#include "KoToolSelection.h"
#include "KoCanvasBase.h"
#include "KoCanvasController.h"
#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoShapeLayer.h"
#include "KoShapeRegistry.h"
#include "KoShapeController.h"
#include "KoOdf.h"
#include "KoViewConverter.h"
#include "KoShapeFactoryBase.h"
KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p)
: activeTool(0),
- tabletPressed(false),
- hasSelection(false),
- controller(0),
- parent(p)
+ tabletPressed(false),
+ hasSelection(false),
+ controller(0),
+ parent(p)
{
scrollTimer.setInterval(100);
mouseLeaveWorkaround = false;
- multiClickCount = 0;
}
void KoToolProxyPrivate::timeout() // Auto scroll the canvas
{
Q_ASSERT(controller);
QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY());
QPoint origin = controller->canvas()->documentOrigin();
QPoint viewPoint = widgetScrollPoint - origin - offset;
QRectF mouseArea(viewPoint, QSizeF(10, 10));
mouseArea.setTopLeft(mouseArea.center());
controller->ensureVisible(mouseArea, true);
QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY());
QPoint moved = offset - newOffset;
if (moved.isNull())
return;
widgetScrollPoint += moved;
QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint);
QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0);
KoPointerEvent ev(&event, documentPoint);
activeTool->mouseMoveEvent(&ev);
}
void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event)
{
if (controller == 0) return;
if (!activeTool) return;
if (!activeTool->wantsAutoScroll()) return;
if (!event.isAccepted()) return;
if (event.buttons() != Qt::LeftButton) return;
widgetScrollPoint = event.pos();
if (! scrollTimer.isActive())
scrollTimer.start();
}
void KoToolProxyPrivate::selectionChanged(bool newSelection)
{
if (hasSelection == newSelection)
return;
hasSelection = newSelection;
emit parent->selectionChanged(hasSelection);
}
bool KoToolProxyPrivate::isActiveLayerEditable()
{
if (!activeTool)
return false;
KoShapeManager * shapeManager = activeTool->canvas()->shapeManager();
KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer();
if (activeLayer && !activeLayer->isEditable())
return false;
return true;
}
KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent)
- : QObject(parent),
- d(new KoToolProxyPrivate(this))
+ : QObject(parent),
+ d(new KoToolProxyPrivate(this))
{
KoToolManager::instance()->priv()->registerToolProxy(this, canvas);
connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout()));
}
KoToolProxy::~KoToolProxy()
{
delete d;
}
void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter)
{
if (d->activeTool) d->activeTool->paint(painter, converter);
}
void KoToolProxy::repaintDecorations()
{
if (d->activeTool) d->activeTool->repaintDecorations();
}
QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const
{
QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY());
QPoint origin = d->controller->canvas()->documentOrigin();
QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset);
return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint);
}
KoCanvasBase* KoToolProxy::canvas() const
{
return d->controller->canvas();
}
-void KoToolProxy::touchEvent(QTouchEvent *event)
-{
- QPointF point;
- QList touchPoints;
-
- bool isPrimary = true;
- Q_FOREACH (QTouchEvent::TouchPoint p, event->touchPoints()) {
- QPointF docPoint = widgetToDocument(p.screenPos());
- if (isPrimary) {
- point = docPoint;
- isPrimary = false;
- }
- KoTouchPoint touchPoint;
- touchPoint.touchPoint = p;
- touchPoint.point = point;
- touchPoint.lastPoint = widgetToDocument(p.lastNormalizedPos());
- touchPoints << touchPoint;
- }
-
- KoPointerEvent ev(event, point, touchPoints);
-
- KoInputDevice id;
- KoToolManager::instance()->priv()->switchInputDevice(id);
-
- switch (event->type()) {
- case QEvent::TouchBegin:
- ev.setTabletButton(Qt::LeftButton);
- if (d->activeTool) {
- if( d->activeTool->wantsTouch() )
- d->activeTool->touchEvent(event);
- else
- d->activeTool->mousePressEvent(&ev);
- }
- break;
- case QEvent::TouchUpdate:
- ev.setTabletButton(Qt::LeftButton);
- if (d->activeTool) {
- if( d->activeTool->wantsTouch() )
- d->activeTool->touchEvent(event);
- else
- d->activeTool->mouseMoveEvent(&ev);
- }
- break;
- case QEvent::TouchEnd:
- ev.setTabletButton(Qt::LeftButton);
- if (d->activeTool) {
- if( d->activeTool->wantsTouch() )
- d->activeTool->touchEvent(event);
- else
- d->activeTool->mouseReleaseEvent(&ev);
- }
- break;
- default:
- ; // ingore the rest
- }
- d->mouseLeaveWorkaround = true;
-
-}
-
void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point)
{
// We get these events exclusively from KisToolProxy - accept them
event->accept();
KoInputDevice id(event->device(), event->pointerType(), event->uniqueId());
KoToolManager::instance()->priv()->switchInputDevice(id);
KoPointerEvent ev(event, point);
switch (event->type()) {
case QEvent::TabletPress:
ev.setTabletButton(Qt::LeftButton);
if (!d->tabletPressed && d->activeTool)
d->activeTool->mousePressEvent(&ev);
d->tabletPressed = true;
break;
case QEvent::TabletRelease:
ev.setTabletButton(Qt::LeftButton);
d->tabletPressed = false;
d->scrollTimer.stop();
if (d->activeTool)
d->activeTool->mouseReleaseEvent(&ev);
break;
case QEvent::TabletMove:
if (d->tabletPressed)
ev.setTabletButton(Qt::LeftButton);
if (d->activeTool)
d->activeTool->mouseMoveEvent(&ev);
d->checkAutoScroll(ev);
default:
; // ignore the rest.
}
d->mouseLeaveWorkaround = true;
}
void KoToolProxy::mousePressEvent(KoPointerEvent *ev)
{
d->mouseLeaveWorkaround = false;
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
d->mouseDownPoint = ev->pos();
- if (d->tabletPressed) // refuse to send a press unless there was a release first.
+ if (d->tabletPressed) { // refuse to send a press unless there was a release first.
return;
-
- QPointF globalPoint = ev->globalPos();
- if (d->multiClickGlobalPoint != globalPoint) {
- if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5||
- qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) {
- d->multiClickCount = 0;
- }
- d->multiClickGlobalPoint = globalPoint;
- }
-
- if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) {
- // One more multiclick;
- d->multiClickCount++;
- } else {
- d->multiClickTimeStamp.start();
- d->multiClickCount = 1;
- }
-
- if (d->activeTool) {
- switch (d->multiClickCount) {
- case 0:
- case 1:
- d->activeTool->mousePressEvent(ev);
- break;
- case 2:
- d->activeTool->mouseDoubleClickEvent(ev);
- break;
- case 3:
- default:
- d->activeTool->mouseTripleClickEvent(ev);
- break;
- }
- } else {
- d->multiClickCount = 0;
- ev->ignore();
}
}
void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
mousePressEvent(&ev);
}
void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
mouseDoubleClickEvent(&ev);
}
void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event)
{
- // let us handle it as any other mousepress (where we then detect multi clicks
- mousePressEvent(event);
+ d->activeTool->mouseDoubleClickEvent(event);
}
void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
mouseMoveEvent(&ev);
}
void KoToolProxy::mouseMoveEvent(KoPointerEvent *event)
{
if (d->mouseLeaveWorkaround) {
d->mouseLeaveWorkaround = false;
return;
}
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
if (d->activeTool == 0) {
event->ignore();
return;
}
d->activeTool->mouseMoveEvent(event);
d->checkAutoScroll(*event);
}
void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
mouseReleaseEvent(&ev);
}
void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event)
{
d->mouseLeaveWorkaround = false;
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
d->scrollTimer.stop();
if (d->activeTool) {
d->activeTool->mouseReleaseEvent(event);
} else {
event->ignore();
}
}
void KoToolProxy::keyPressEvent(QKeyEvent *event)
{
if (d->activeTool)
d->activeTool->keyPressEvent(event);
else
event->ignore();
}
void KoToolProxy::keyReleaseEvent(QKeyEvent *event)
{
if (d->activeTool)
d->activeTool->keyReleaseEvent(event);
else
event->ignore();
}
-void KoToolProxy::wheelEvent(QWheelEvent *event, const QPointF &point)
-{
- KoPointerEvent ev(event, point);
- if (d->activeTool)
- d->activeTool->wheelEvent(&ev);
- else
- event->ignore();
-}
-
-void KoToolProxy::wheelEvent(KoPointerEvent *event)
-{
- if (d->activeTool)
- d->activeTool->wheelEvent(event);
- else
- event->ignore();
-}
-
void KoToolProxy::explicitUserStrokeEndRequest()
{
if (d->activeTool) {
d->activeTool->explicitUserStrokeEndRequest();
}
}
QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
{
if (d->activeTool)
return d->activeTool->inputMethodQuery(query, converter);
return QVariant();
}
void KoToolProxy::inputMethodEvent(QInputMethodEvent *event)
{
if (d->activeTool) d->activeTool->inputMethodEvent(event);
}
QMenu *KoToolProxy::popupActionsMenu()
{
return d->activeTool ? d->activeTool->popupActionsMenu() : 0;
}
void KoToolProxy::setActiveTool(KoToolBase *tool)
{
if (d->activeTool)
disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool)));
d->activeTool = tool;
if (tool) {
connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool)));
d->selectionChanged(hasSelection());
emit toolChanged(tool->toolId());
}
}
void KoToolProxyPrivate::setCanvasController(KoCanvasController *c)
{
controller = c;
}
QHash KoToolProxy::actions() const
{
return d->activeTool ? d->activeTool->actions() : QHash();
}
bool KoToolProxy::hasSelection() const
{
return d->activeTool ? d->activeTool->hasSelection() : false;
}
void KoToolProxy::cut()
{
if (d->activeTool && d->isActiveLayerEditable())
d->activeTool->cut();
}
void KoToolProxy::copy() const
{
if (d->activeTool)
d->activeTool->copy();
}
bool KoToolProxy::paste()
{
bool success = false;
KoCanvasBase *canvas = d->controller->canvas();
if (d->activeTool && d->isActiveLayerEditable()) {
success = d->activeTool->paste();
}
if (!success) {
const QMimeData *data = QApplication::clipboard()->mimeData();
QList imageList;
QImage image = QApplication::clipboard()->image();
if (!image.isNull()) {
imageList << image;
}
// QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks.
else if (data->hasUrls()) {
QList urls = QApplication::clipboard()->mimeData()->urls();
foreach (const QUrl &url, urls) {
QImage image;
image.load(url.toLocalFile());
if (!image.isNull()) {
imageList << image;
}
}
}
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape");
QWidget *canvasWidget = canvas->canvasWidget();
const KoViewConverter *converter = canvas->viewConverter();
if (imageList.length() > 0 && factory && canvasWidget) {
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image"));
QList pastedShapes;
Q_FOREACH (const QImage &image, imageList) {
if (!image.isNull()) {
QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos());
KoProperties params;
params.setProperty("qimage", image);
KoShape *shape = factory->createShape(¶ms, canvas->shapeController()->resourceManager());
shape->setPosition(p);
pastedShapes << shape;
success = true;
}
}
if (!pastedShapes.isEmpty()) {
// add shape to the document
canvas->shapeController()->addShapesDirect(pastedShapes, cmd);
canvas->addCommand(cmd);
}
}
}
return success;
}
void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point)
{
if (d->activeTool)
d->activeTool->dragMoveEvent(event, point);
}
void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event)
{
if (d->activeTool)
d->activeTool->dragLeaveEvent(event);
}
void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point)
{
if (d->activeTool)
d->activeTool->dropEvent(event, point);
}
void KoToolProxy::deleteSelection()
{
if (d->activeTool)
d->activeTool->deleteSelection();
}
void KoToolProxy::processEvent(QEvent *e) const
{
if(e->type()==QEvent::ShortcutOverride
- && d->activeTool
- && d->activeTool->isInTextMode()
- && (static_cast(e)->modifiers()==Qt::NoModifier ||
- static_cast(e)->modifiers()==Qt::ShiftModifier)) {
+ && d->activeTool
+ && d->activeTool->isInTextMode()
+ && (static_cast(e)->modifiers()==Qt::NoModifier ||
+ static_cast(e)->modifiers()==Qt::ShiftModifier)) {
e->accept();
}
}
void KoToolProxy::requestUndoDuringStroke()
{
if (d->activeTool) {
d->activeTool->requestUndoDuringStroke();
}
}
void KoToolProxy::requestStrokeCancellation()
{
if (d->activeTool) {
d->activeTool->requestStrokeCancellation();
}
}
void KoToolProxy::requestStrokeEnd()
{
if (d->activeTool) {
d->activeTool->requestStrokeEnd();
}
}
KoToolProxyPrivate *KoToolProxy::priv()
{
return d;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoToolProxy.cpp"
diff --git a/libs/flake/KoToolProxy.h b/libs/flake/KoToolProxy.h
index 2c281ce0c2..2e87461f74 100644
--- a/libs/flake/KoToolProxy.h
+++ b/libs/flake/KoToolProxy.h
@@ -1,201 +1,190 @@
/* This file is part of the KDE project
*
* Copyright (c) 2006, 2010 Boudewijn Rempt
* Copyright (C) 2006-2010 Thomas Zander
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_TOOL_PROXY_H_
#define _KO_TOOL_PROXY_H_
#include "kritaflake_export.h"
#include
#include
class QAction;
class QAction;
class QMouseEvent;
class QKeyEvent;
class QWheelEvent;
class QTabletEvent;
class KoCanvasBase;
class KoViewConverter;
class KoToolBase;
class KoToolProxyPrivate;
class QInputMethodEvent;
class KoPointerEvent;
class QDragMoveEvent;
class QDragLeaveEvent;
class QDropEvent;
class QTouchEvent;
class QPainter;
class QPointF;
class QMenu;
/**
* Tool proxy object which allows an application to address the current tool.
*
* Applications typically have a canvas and a canvas requires a tool for
* the user to do anything. Since the flake system is responsible for handling
* tools and also to change the active tool when needed we provide one class
* that can be used by an application canvas to route all the native events too
* which will transparantly be routed to the active tool. Without the application
* having to bother about which tool is active.
*/
class KRITAFLAKE_EXPORT KoToolProxy : public QObject
{
Q_OBJECT
public:
/**
* Constructor
* @param canvas Each canvas has 1 toolProxy. Pass the parent here.
* @param parent a parent QObject for memory management purposes.
*/
explicit KoToolProxy(KoCanvasBase *canvas, QObject *parent = 0);
~KoToolProxy() override;
/// Forwarded to the current KoToolBase
void paint(QPainter &painter, const KoViewConverter &converter);
/// Forwarded to the current KoToolBase
void repaintDecorations();
- /**
- * Forward the given touch event to the current KoToolBase.
- * The viewconverter and document offset are necessary to convert all
- * the QTouchPoints to KoTouchPoints that work in document coordinates.
- */
- void touchEvent(QTouchEvent *event);
-
/// Forwarded to the current KoToolBase
void tabletEvent(QTabletEvent *event, const QPointF &point);
/// Forwarded to the current KoToolBase
void mousePressEvent(QMouseEvent *event, const QPointF &point);
void mousePressEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point);
void mouseDoubleClickEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void mouseMoveEvent(QMouseEvent *event, const QPointF &point);
void mouseMoveEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void mouseReleaseEvent(QMouseEvent *event, const QPointF &point);
void mouseReleaseEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void keyPressEvent(QKeyEvent *event);
/// Forwarded to the current KoToolBase
void keyReleaseEvent(QKeyEvent *event);
- /// Forwarded to the current KoToolBase
- void wheelEvent(QWheelEvent * event, const QPointF &point);
- void wheelEvent(KoPointerEvent *event);
-
/// Forwarded to the current KoToolBase
void explicitUserStrokeEndRequest();
/// Forwarded to the current KoToolBase
QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const;
/// Forwarded to the current KoToolBase
void inputMethodEvent(QInputMethodEvent *event);
/// Forwarded to the current KoToolBase
QMenu* popupActionsMenu();
/// Forwarded to the current KoToolBase
void deleteSelection();
/// This method gives the proxy a chance to do things. for example it is need to have working singlekey
/// shortcuts. call it from the canvas' event function and forward it to QWidget::event() later.
void processEvent(QEvent *) const;
/**
* Retrieves the entire collection of actions for the active tool
* or an empty hash if there is no active tool yet.
*/
QHash actions() const;
/// returns true if the current tool holds a selection
bool hasSelection() const;
/// Forwarded to the current KoToolBase
void cut();
/// Forwarded to the current KoToolBase
void copy() const;
/// Forwarded to the current KoToolBase
bool paste();
/// Forwarded to the current KoToolBase
void dragMoveEvent(QDragMoveEvent *event, const QPointF &point);
/// Forwarded to the current KoToolBase
void dragLeaveEvent(QDragLeaveEvent *event);
/// Forwarded to the current KoToolBase
void dropEvent(QDropEvent *event, const QPointF &point);
-
+
/// Set the new active tool.
virtual void setActiveTool(KoToolBase *tool);
/// \internal
KoToolProxyPrivate *priv();
protected Q_SLOTS:
/// Forwarded to the current KoToolBase
void requestUndoDuringStroke();
/// Forwarded to the current KoToolBase
void requestStrokeCancellation();
/// Forwarded to the current KoToolBase
void requestStrokeEnd();
Q_SIGNALS:
/**
* A tool can have a selection that is copy-able, this signal is emitted when that status changes.
* @param hasSelection is true when the tool holds selected data.
*/
void selectionChanged(bool hasSelection);
/**
* Emitted every time a tool is changed.
* @param toolId the id of the tool.
* @see KoToolBase::toolId()
*/
void toolChanged(const QString &toolId);
protected:
virtual QPointF widgetToDocument(const QPointF &widgetPoint) const;
KoCanvasBase* canvas() const;
private:
Q_PRIVATE_SLOT(d, void timeout())
Q_PRIVATE_SLOT(d, void selectionChanged(bool))
friend class KoToolProxyPrivate;
KoToolProxyPrivate * const d;
};
#endif // _KO_TOOL_PROXY_H_
diff --git a/libs/flake/KoToolProxy_p.h b/libs/flake/KoToolProxy_p.h
index 06eddf297d..3bf8db60d3 100644
--- a/libs/flake/KoToolProxy_p.h
+++ b/libs/flake/KoToolProxy_p.h
@@ -1,68 +1,63 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOTOOLPROXYPRIVATE_P
#define KOTOOLPROXYPRIVATE_P
#include
-#include
-#include
+#include
class KoPointerEvent;
class KoToolBase;
class KoCanvasController;
class KoToolProxy;
class KoToolProxyPrivate
{
public:
explicit KoToolProxyPrivate(KoToolProxy *p);
void timeout(); // Auto scroll the canvas
void checkAutoScroll(const KoPointerEvent &event);
void selectionChanged(bool newSelection);
bool isActiveLayerEditable();
/// the toolManager tells us which KoCanvasController this toolProxy is working for.
void setCanvasController(KoCanvasController *controller);
KoToolBase *activeTool;
bool tabletPressed;
bool hasSelection;
QTimer scrollTimer;
QPoint widgetScrollPoint;
KoCanvasController *controller;
KoToolProxy *parent;
// used to determine if the mouse-release is after a drag or a simple click
QPoint mouseDownPoint;
// up until at least 4.3.0 we get a mouse move event when the tablet leaves the canvas.
bool mouseLeaveWorkaround;
- // for multi clicking (double click or triple click) we need the following
- int multiClickCount;
- QPointF multiClickGlobalPoint;
- QTime multiClickTimeStamp;
};
#endif
diff --git a/libs/flake/KoToolRegistry.cpp b/libs/flake/KoToolRegistry.cpp
index 4e19141eac..2e7b2bcb1e 100644
--- a/libs/flake/KoToolRegistry.cpp
+++ b/libs/flake/KoToolRegistry.cpp
@@ -1,85 +1,79 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander
* Copyright (c) 2004 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoToolRegistry.h"
#include
#include
#include
#include "tools/KoCreateShapesToolFactory.h"
#include "tools/KoCreateShapesTool.h"
#include "tools/KoPathToolFactory.h"
#include "tools/KoZoomTool.h"
#include "tools/KoZoomToolFactory.h"
#include "KoToolManager.h"
#include
#include
Q_GLOBAL_STATIC(KoToolRegistry, s_instance)
KoToolRegistry::KoToolRegistry()
: d(0)
{
}
void KoToolRegistry::init()
{
KoPluginLoader::PluginsConfig config;
config.group = "calligra";
config.whiteList = "ToolPlugins";
config.blacklist = "ToolPluginsDisabled";
KoPluginLoader::instance()->load(QString::fromLatin1("Calligra/Tool"),
QString::fromLatin1("[X-Flake-PluginVersion] == 28"),
config);
// register generic tools
add(new KoCreateShapesToolFactory());
add(new KoPathToolFactory());
add(new KoZoomToolFactory());
KConfigGroup cfg = KSharedConfig::openConfig()->group("calligra");
QStringList toolsBlacklist = cfg.readEntry("ToolsBlacklist", QStringList());
foreach (const QString& toolID, toolsBlacklist) {
delete value(toolID);
remove(toolID);
}
}
KoToolRegistry::~KoToolRegistry()
{
qDeleteAll(doubleEntries());
qDeleteAll(values());
}
KoToolRegistry* KoToolRegistry::instance()
{
if (!s_instance.exists()) {
s_instance->init();
}
return s_instance;
}
-
-void KoToolRegistry::addDeferred(KoToolFactoryBase *toolFactory)
-{
- add(toolFactory);
- KoToolManager::instance()->addDeferredToolFactory(toolFactory);
-}
diff --git a/libs/flake/KoToolRegistry.h b/libs/flake/KoToolRegistry.h
index 44c1efffe1..91d2054876 100644
--- a/libs/flake/KoToolRegistry.h
+++ b/libs/flake/KoToolRegistry.h
@@ -1,62 +1,55 @@
/* This file is part of the KDE project
* Copyright (c) 2004 Boudewijn Rempt
* Copyright (C) 2006 Thomas Zander
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KO_TOOL_REGISTRY_H_
#define KO_TOOL_REGISTRY_H_
#include "KoGenericRegistry.h"
#include "kritaflake_export.h"
class KoToolFactoryBase;
/**
* This singleton class keeps a register of all available flake tools,
* or rather, of the factories that the KoToolBox (and KoToolManager) will use
* to create flake tools.
*/
class KRITAFLAKE_EXPORT KoToolRegistry : public KoGenericRegistry
{
public:
KoToolRegistry();
~KoToolRegistry() override;
/**
* Return an instance of the KoToolRegistry
* Create a new instance on first call and return the singleton.
*/
static KoToolRegistry *instance();
- /**
- * Add a toolfactory from a deferred plugin. This will cause the toolFactoryAdded signal
- * to be emitted, which is caught by the KoToolManager which then adds the tool to all
- * canvases.
- */
- void addDeferred(KoToolFactoryBase *toolFactory);
-
private:
KoToolRegistry(const KoToolRegistry&);
KoToolRegistry operator=(const KoToolRegistry&);
void init();
class Private;
Private * const d;
};
#endif
diff --git a/libs/flake/tools/KoZoomTool.cpp b/libs/flake/tools/KoZoomTool.cpp
index dbab472f0d..e651e3485d 100644
--- a/libs/flake/tools/KoZoomTool.cpp
+++ b/libs/flake/tools/KoZoomTool.cpp
@@ -1,135 +1,129 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007 Thomas Zander
* Copyright (C) 2006 Thorsten Zachmann
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoZoomTool.h"
#include
#include "KoZoomStrategy.h"
#include "KoZoomToolWidget.h"
#include "KoPointerEvent.h"
#include "KoCanvasBase.h"
#include "KoCanvasController.h"
#include
KoZoomTool::KoZoomTool(KoCanvasBase *canvas)
: KoInteractionTool(canvas),
m_temporary(false), m_zoomInMode(true)
{
QPixmap inPixmap, outPixmap;
inPixmap.load(":/zoom_in_cursor.png");
outPixmap.load(":/zoom_out_cursor.png");
m_inCursor = QCursor(inPixmap, 4, 4);
m_outCursor = QCursor(outPixmap, 4, 4);
}
-void KoZoomTool::wheelEvent(KoPointerEvent *event)
-{
- // Let KoCanvasController handle this
- event->ignore();
-}
-
void KoZoomTool::mouseReleaseEvent(KoPointerEvent *event)
{
KoInteractionTool::mouseReleaseEvent(event);
if (m_temporary) {
emit KoToolBase::done();
}
}
void KoZoomTool::mouseMoveEvent(KoPointerEvent *event)
{
updateCursor(event->modifiers() & Qt::ControlModifier);
if (currentStrategy()) {
currentStrategy()->handleMouseMove(event->point, event->modifiers());
}
}
void KoZoomTool::keyPressEvent(QKeyEvent *event)
{
event->ignore();
updateCursor(event->modifiers() & Qt::ControlModifier);
}
void KoZoomTool::keyReleaseEvent(QKeyEvent *event)
{
event->ignore();
updateCursor(event->modifiers() & Qt::ControlModifier);
KoInteractionTool::keyReleaseEvent(event);
}
void KoZoomTool::activate(ToolActivation toolActivation, const QSet &)
{
m_temporary = toolActivation == TemporaryActivation;
updateCursor(false);
}
void KoZoomTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
mousePressEvent(event);
}
KoInteractionStrategy *KoZoomTool::createStrategy(KoPointerEvent *event)
{
KoZoomStrategy *zs = new KoZoomStrategy(this, m_controller, event->point);
bool shouldZoomIn = m_zoomInMode;
if (event->button() == Qt::RightButton ||
event->modifiers() == Qt::ControlModifier) {
shouldZoomIn = !shouldZoomIn;
}
if (shouldZoomIn) {
zs->forceZoomIn();
} else {
zs->forceZoomOut();
}
return zs;
}
QWidget *KoZoomTool::createOptionWidget()
{
return new KoZoomToolWidget(this);
}
void KoZoomTool::setZoomInMode(bool zoomIn)
{
m_zoomInMode = zoomIn;
updateCursor(false);
}
void KoZoomTool::updateCursor(bool swap)
{
bool setZoomInCursor = m_zoomInMode;
if (swap) {
setZoomInCursor = !setZoomInCursor;
}
if (setZoomInCursor) {
useCursor(m_inCursor);
} else {
useCursor(m_outCursor);
}
}
diff --git a/libs/flake/tools/KoZoomTool.h b/libs/flake/tools/KoZoomTool.h
index 8a9a2a7bd9..848a652cea 100644
--- a/libs/flake/tools/KoZoomTool.h
+++ b/libs/flake/tools/KoZoomTool.h
@@ -1,77 +1,69 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007 Thomas Zander
* Copyright (C) 2006 Thorsten Zachmann
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOZOOMTOOL_H
#define KOZOOMTOOL_H
#include "KoInteractionTool.h"
#include
class KoCanvasBase;
class KoCanvasController;
/// \internal
class KoZoomTool : public KoInteractionTool
{
public:
/**
* Create a new tool; typically not called by applications, only by the KoToolManager
* @param canvas the canvas this tool works for.
*/
explicit KoZoomTool(KoCanvasBase *canvas);
- /// reimplemented method
- void wheelEvent(KoPointerEvent *event) override;
- /// reimplemented method
void mouseReleaseEvent(KoPointerEvent *event) override;
- /// reimplemented method
void mouseMoveEvent(KoPointerEvent *event) override;
- /// reimplemented method
void keyPressEvent(QKeyEvent *event) override;
- /// reimplemented method
void keyReleaseEvent(QKeyEvent *event) override;
- /// reimplemented method
void activate(ToolActivation toolActivation, const QSet &shapes) override;
- /// reimplemented method
void mouseDoubleClickEvent(KoPointerEvent *event) override;
void setCanvasController(KoCanvasController *controller) {
m_controller = controller;
}
void setZoomInMode(bool zoomIn);
protected:
QWidget *createOptionWidget() override;
private:
KoInteractionStrategy *createStrategy(KoPointerEvent *event) override;
void updateCursor(bool swap);
KoCanvasController *m_controller;
QCursor m_inCursor;
QCursor m_outCursor;
bool m_temporary;
bool m_zoomInMode;
};
#endif
diff --git a/libs/image/brushengine/kis_paint_information.cc b/libs/image/brushengine/kis_paint_information.cc
index 4e066320b6..2c08688b3f 100644
--- a/libs/image/brushengine/kis_paint_information.cc
+++ b/libs/image/brushengine/kis_paint_information.cc
@@ -1,589 +1,590 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include
#include
#include
#include "kis_paintop.h"
#include "kis_algebra_2d.h"
#include "kis_lod_transform.h"
+#include "kis_spacing_information.h"
#include
struct KisPaintInformation::Private {
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
Private(const QPointF & pos_,
qreal pressure_,
qreal xTilt_, qreal yTilt_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
qreal time_,
qreal speed_,
bool isHoveringMode_)
:
pos(pos_),
pressure(pressure_),
xTilt(xTilt_),
yTilt(yTilt_),
rotation(rotation_),
tangentialPressure(tangentialPressure_),
perspective(perspective_),
time(time_),
speed(speed_),
isHoveringMode(isHoveringMode_),
randomSource(0),
currentDistanceInfo(0),
levelOfDetail(0)
{
}
~Private() {
KIS_ASSERT_RECOVER_NOOP(!currentDistanceInfo);
}
Private(const Private &rhs) {
copy(rhs);
}
Private& operator=(const Private &rhs) {
copy(rhs);
return *this;
}
void copy(const Private &rhs) {
pos = rhs.pos;
pressure = rhs.pressure;
xTilt = rhs.xTilt;
yTilt = rhs.yTilt;
rotation = rhs.rotation;
tangentialPressure = rhs.tangentialPressure;
perspective = rhs.perspective;
time = rhs.time;
speed = rhs.speed;
isHoveringMode = rhs.isHoveringMode;
randomSource = rhs.randomSource;
currentDistanceInfo = rhs.currentDistanceInfo;
canvasRotation = rhs.canvasRotation;
canvasMirroredH = rhs.canvasMirroredH;
if (rhs.drawingAngleOverride) {
drawingAngleOverride.reset(new qreal(*rhs.drawingAngleOverride));
}
levelOfDetail = rhs.levelOfDetail;
}
QPointF pos;
qreal pressure;
qreal xTilt;
qreal yTilt;
qreal rotation;
qreal tangentialPressure;
qreal perspective;
qreal time;
qreal speed;
bool isHoveringMode;
KisRandomSourceSP randomSource;
int canvasRotation;
bool canvasMirroredH;
QScopedPointer drawingAngleOverride;
KisDistanceInformation *currentDistanceInfo;
int levelOfDetail;
void registerDistanceInfo(KisDistanceInformation *di) {
currentDistanceInfo = di;
}
void unregisterDistanceInfo() {
currentDistanceInfo = 0;
}
};
KisPaintInformation::DistanceInformationRegistrar::
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo)
: p(_p)
{
p->d->registerDistanceInfo(distanceInfo);
}
KisPaintInformation::DistanceInformationRegistrar::
~DistanceInformationRegistrar()
{
p->d->unregisterDistanceInfo();
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective,
time,
speed,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF &pos,
qreal pressure)
: d(new Private(pos,
pressure,
0.0, 0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs)
: d(new Private(*rhs.d))
{
}
void KisPaintInformation::operator=(const KisPaintInformation & rhs)
{
*d = *rhs.d;
}
KisPaintInformation::~KisPaintInformation()
{
delete d;
}
bool KisPaintInformation::isHoveringMode() const
{
return d->isHoveringMode;
}
KisPaintInformation
KisPaintInformation::createHoveringModeInfo(const QPointF &pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal speed,
int canvasrotation,
bool canvasMirroredH)
{
KisPaintInformation info(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective, 0, speed);
info.d->isHoveringMode = true;
info.d->canvasRotation = canvasrotation;
info.d->canvasMirroredH = canvasMirroredH;
return info;
}
int KisPaintInformation::canvasRotation() const
{
return d->canvasRotation;
}
void KisPaintInformation::setCanvasRotation(int rotation)
{
if (rotation < 0) {
d->canvasRotation= 360- abs(rotation % 360);
} else {
d->canvasRotation= rotation % 360;
}
}
bool KisPaintInformation::canvasMirroredH() const
{
return d->canvasMirroredH;
}
void KisPaintInformation::setCanvasHorizontalMirrorState(bool mir)
{
d->canvasMirroredH = mir;
}
void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
{
// hovering mode infos are not supposed to be saved
KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode);
e.setAttribute("pointX", QString::number(pos().x(), 'g', 15));
e.setAttribute("pointY", QString::number(pos().y(), 'g', 15));
e.setAttribute("pressure", QString::number(pressure(), 'g', 15));
e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15));
e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15));
e.setAttribute("rotation", QString::number(rotation(), 'g', 15));
e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
e.setAttribute("time", QString::number(d->time, 'g', 15));
e.setAttribute("speed", QString::number(d->speed, 'g', 15));
}
KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
{
qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0")));
qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0")));
qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0")));
qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0")));
qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0")));
qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0")));
qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0")));
qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0")));
qreal time = KisDomUtils::toDouble(e.attribute("time", "0"));
qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0"));
return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
rotation, tangentialPressure, perspective, time, speed);
}
const QPointF& KisPaintInformation::pos() const
{
return d->pos;
}
void KisPaintInformation::setPos(const QPointF& p)
{
d->pos = p;
}
qreal KisPaintInformation::pressure() const
{
return d->pressure;
}
void KisPaintInformation::setPressure(qreal p)
{
d->pressure = p;
}
qreal KisPaintInformation::xTilt() const
{
return d->xTilt;
}
qreal KisPaintInformation::yTilt() const
{
return d->yTilt;
}
void KisPaintInformation::overrideDrawingAngle(qreal angle)
{
d->drawingAngleOverride.reset(new qreal(angle));
}
qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const
{
if (d->drawingAngleOverride) return *d->drawingAngleOverride;
if (!distance.hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingAngleSafe()" << "Cannot access Distance Info last dab data";
return 0.0;
}
return distance.nextDrawingAngle(pos());
}
KisPaintInformation::DistanceInformationRegistrar
KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance)
{
return DistanceInformationRegistrar(this, distance);
}
qreal KisPaintInformation::drawingAngle() const
{
if (d->drawingAngleOverride) return *d->drawingAngleOverride;
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingAngle()" << "Cannot access Distance Info last dab data";
return 0.0;
}
return d->currentDistanceInfo->nextDrawingAngle(pos());
}
void KisPaintInformation::lockCurrentDrawingAngle(qreal alpha_unused) const
{
Q_UNUSED(alpha_unused);
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::lockCurrentDrawingAngle()" << "Cannot access Distance Info last dab data";
return;
}
qreal angle = d->currentDistanceInfo->nextDrawingAngle(pos(), false);
qreal newAngle = angle;
if (d->currentDistanceInfo->hasLockedDrawingAngle()) {
const qreal stabilizingCoeff = 20.0;
const qreal dist = stabilizingCoeff * d->currentDistanceInfo->currentSpacing().scalarApprox();
const qreal alpha = qMax(0.0, dist - d->currentDistanceInfo->scalarDistanceApprox()) / dist;
const qreal oldAngle = d->currentDistanceInfo->lockedDrawingAngle();
if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) {
newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle;
} else {
newAngle = oldAngle;
}
}
d->currentDistanceInfo->setLockedDrawingAngle(newAngle);
}
QPointF KisPaintInformation::drawingDirectionVector() const
{
if (d->drawingAngleOverride) {
qreal angle = *d->drawingAngleOverride;
return QPointF(cos(angle), sin(angle));
}
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingDirectionVector()" << "Cannot access Distance Info last dab data";
return QPointF(1.0, 0.0);
}
return d->currentDistanceInfo->nextDrawingDirectionVector(pos());
}
qreal KisPaintInformation::drawingDistance() const
{
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingDistance()" << "Cannot access Distance Info last dab data";
return 1.0;
}
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
qreal length = diff.length();
if (d->levelOfDetail) {
length *= KisLodTransform::lodToInvScale(d->levelOfDetail);
}
return length;
}
qreal KisPaintInformation::drawingSpeed() const
{
return d->speed;
}
qreal KisPaintInformation::rotation() const
{
return d->rotation;
}
qreal KisPaintInformation::tangentialPressure() const
{
return d->tangentialPressure;
}
qreal KisPaintInformation::perspective() const
{
return d->perspective;
}
qreal KisPaintInformation::currentTime() const
{
return d->time;
}
KisRandomSourceSP KisPaintInformation::randomSource() const
{
if (!d->randomSource) {
d->randomSource = new KisRandomSource();
}
return d->randomSource;
}
void KisPaintInformation::setRandomSource(KisRandomSourceSP value)
{
d->randomSource = value;
}
void KisPaintInformation::setLevelOfDetail(int levelOfDetail) const
{
d->levelOfDetail = levelOfDetail;
}
QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
{
#ifdef NDEBUG
Q_UNUSED(info);
#else
dbg.nospace() << "Position: " << info.pos();
dbg.nospace() << ", Pressure: " << info.pressure();
dbg.nospace() << ", X Tilt: " << info.xTilt();
dbg.nospace() << ", Y Tilt: " << info.yTilt();
dbg.nospace() << ", Rotation: " << info.rotation();
dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure();
dbg.nospace() << ", Perspective: " << info.perspective();
dbg.nospace() << ", Drawing Angle: " << info.drawingAngle();
dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed();
dbg.nospace() << ", Drawing Distance: " << info.drawingDistance();
dbg.nospace() << ", Time: " << info.currentTime();
#endif
return dbg.space();
}
KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi)
{
QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos();
return mixImpl(pt, t, mixedPi, basePi, true, false);
}
KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
return mix(pt, t, pi1, pi2);
}
KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
return mixImpl(p, t, pi1, pi2, false, true);
}
KisPaintInformation KisPaintInformation::mixWithoutTime(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
return mixWithoutTime(pt, t, pi1, pi2);
}
KisPaintInformation KisPaintInformation::mixWithoutTime(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
return mixImpl(p, t, pi1, pi2, false, false);
}
KisPaintInformation KisPaintInformation::mixImpl(const QPointF &p, qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2, bool posOnly, bool mixTime)
{
if (posOnly) {
KisPaintInformation result(p,
pi2.pressure(),
pi2.xTilt(),
pi2.yTilt(),
pi2.rotation(),
pi2.tangentialPressure(),
pi2.perspective(),
pi2.currentTime(),
pi2.drawingSpeed());
result.setRandomSource(pi2.randomSource());
return result;
}
else {
qreal pressure = (1 - t) * pi1.pressure() + t * pi2.pressure();
qreal xTilt = (1 - t) * pi1.xTilt() + t * pi2.xTilt();
qreal yTilt = (1 - t) * pi1.yTilt() + t * pi2.yTilt();
qreal rotation = pi1.rotation();
if (pi1.rotation() != pi2.rotation()) {
qreal a1 = kisDegreesToRadians(pi1.rotation());
qreal a2 = kisDegreesToRadians(pi2.rotation());
qreal distance = shortestAngularDistance(a2, a1);
rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2));
}
qreal tangentialPressure = (1 - t) * pi1.tangentialPressure() + t * pi2.tangentialPressure();
qreal perspective = (1 - t) * pi1.perspective() + t * pi2.perspective();
qreal time = mixTime ? ((1 - t) * pi1.currentTime() + t * pi2.currentTime()) : pi2.currentTime();
qreal speed = (1 - t) * pi1.drawingSpeed() + t * pi2.drawingSpeed();
KisPaintInformation result(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed);
KIS_ASSERT_RECOVER_NOOP(pi1.isHoveringMode() == pi2.isHoveringMode());
result.d->isHoveringMode = pi1.isHoveringMode();
result.d->levelOfDetail = pi1.d->levelOfDetail;
result.d->randomSource = pi1.d->randomSource;
result.d->canvasRotation = pi2.canvasRotation();
result.d->canvasMirroredH = pi2.canvasMirroredH();
return result;
}
}
qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize)
{
qreal xTilt = info.xTilt();
qreal yTilt = info.yTilt();
// radians -PI, PI
qreal tiltDirection = atan2(-xTilt, yTilt);
// if normalize is true map to 0.0..1.0
return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection;
}
qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize)
{
qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0));
qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0));
qreal e;
if (fabs(xTilt) > fabs(yTilt)) {
e = sqrt(qreal(1.0) + yTilt * yTilt);
} else {
e = sqrt(qreal(1.0) + xTilt * xTilt);
}
qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e;
qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI]
// mapping to 0.0..1.0 if normalize is true
return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation;
}
diff --git a/libs/image/brushengine/kis_paint_information.h b/libs/image/brushengine/kis_paint_information.h
index 0a08a9cbb7..395ca6e422 100644
--- a/libs/image/brushengine/kis_paint_information.h
+++ b/libs/image/brushengine/kis_paint_information.h
@@ -1,282 +1,285 @@
/*
* Copyright (c) 2004 Cyrille Berger
* Copyright (c) 2006 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PAINT_INFORMATION_
#define _KIS_PAINT_INFORMATION_
#include
#include
#include "kis_global.h"
#include "kritaimage_export.h"
#include
#include "kis_random_source.h"
+#include "kis_spacing_information.h"
+#include "kis_timing_information.h"
class QDomDocument;
class QDomElement;
class KisDistanceInformation;
/**
* KisPaintInformation contains information about the input event that
* causes the brush action to happen to the brush engine's paint
* methods.
*
* XXX: we directly pass the KoPointerEvent x and y tilt to
* KisPaintInformation, and their range is -60 to +60!
*
* @param pos: the position of the paint event in subpixel accuracy
* @param pressure: the pressure of the stylus
* @param xTilt: the angle between the device (a pen, for example) and
* the perpendicular in the direction of the x axis. Positive values
* are towards the bottom of the tablet. The angle is within the range
* 0 to 1
* @param yTilt: the angle between the device (a pen, for example) and
* the perpendicular in the direction of the y axis. Positive values
* are towards the bottom of the tablet. The angle is within the range
* 0 to .
* @param movement: current position minus the last position of the call to paintAt
* @param rotation
* @param tangentialPressure
* @param perspective
**/
class KRITAIMAGE_EXPORT KisPaintInformation
{
public:
/**
* Note, that this class is relied on the compiler optimization
* of the return value. So if it doesn't work for some reason,
* please implement a proper copy c-tor
*/
class KRITAIMAGE_EXPORT DistanceInformationRegistrar
{
public:
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo);
~DistanceInformationRegistrar();
private:
KisPaintInformation *p;
};
public:
/**
* Create a new KisPaintInformation object.
*/
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed);
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation);
KisPaintInformation(const QPointF & pos = QPointF(),
qreal pressure = PRESSURE_DEFAULT);
KisPaintInformation(const KisPaintInformation& rhs);
void operator=(const KisPaintInformation& rhs);
~KisPaintInformation();
template
void paintAt(PaintOp &op, KisDistanceInformation *distanceInfo) {
KisSpacingInformation spacingInfo;
-
+ KisTimingInformation timingInfo;
{
DistanceInformationRegistrar r = registerDistanceInformation(distanceInfo);
spacingInfo = op.paintAt(*this);
+ timingInfo = op.updateTimingImpl(*this);
}
- distanceInfo->registerPaintedDab(*this, spacingInfo);
+ distanceInfo->registerPaintedDab(*this, spacingInfo, timingInfo);
}
const QPointF& pos() const;
void setPos(const QPointF& p);
/// The pressure of the value (from 0.0 to 1.0)
qreal pressure() const;
/// Set the pressure
void setPressure(qreal p);
/// The tilt of the pen on the horizontal axis (from 0.0 to 1.0)
qreal xTilt() const;
/// The tilt of the pen on the vertical axis (from 0.0 to 1.0)
qreal yTilt() const;
/// XXX !!! :-| Please add dox!
void overrideDrawingAngle(qreal angle);
/// XXX !!! :-| Please add dox!
qreal drawingAngleSafe(const KisDistanceInformation &distance) const;
/**
* Causes the specified distance information to be temporarily registered with this
* KisPaintInformation object, so that the KisPaintInformation can compute certain values that
* may be needed at painting time, such as the drawing direction. When the returned object is
* destroyed, the KisDistanceInformation will be unregistered. At most one
* KisDistanceInformation can be registered with a given KisPaintInformation at a time.
*/
DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance);
/**
* Current brush direction computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingAngle() const;
/**
* Lock current drawing angle for the rest of the stroke. If some
* value has already been locked, \p alpha shown the coefficient
* with which the new velue should be blended in.
*/
void lockCurrentDrawingAngle(qreal alpha) const;
/**
* Current brush direction vector computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
QPointF drawingDirectionVector() const;
/**
* Current brush speed computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingSpeed() const;
/**
* Current distance from the previous dab
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingDistance() const;
/// rotation as given by the tablet event
qreal rotation() const;
/// tangential pressure (i.e., rate for an airbrush device)
qreal tangentialPressure() const;
/// reciprocal of distance on the perspective grid
qreal perspective() const;
/// Number of ms since the beginning of the stroke
qreal currentTime() const;
// random source for generating in-stroke effects
KisRandomSourceSP randomSource() const;
// the stroke should initialize random source of all the used
// paint info objects, otherwise it shows a warning
void setRandomSource(KisRandomSourceSP value);
// set level of detail which info object has been generated for
void setLevelOfDetail(int levelOfDetail) const;
/**
* The paint information may be generated not only during real
* stroke when the actual painting is happening, but also when the
* cursor is hovering the canvas. In this mode some of the sensors
* work a bit differently. The most outstanding example is Fuzzy
* sensor, which returns unit value in this mode, otherwise it is
* too irritating for a user.
*
* This value is true only for paint information objects created with
* createHoveringModeInfo() constructor.
*
* \see createHoveringModeInfo()
*/
bool isHoveringMode() const;
/**
* Create a fake info object with isHoveringMode() property set to
* true.
*
* \see isHoveringMode()
*/
static KisPaintInformation createHoveringModeInfo(const QPointF &pos,
qreal pressure = PRESSURE_DEFAULT,
qreal xTilt = 0.0, qreal yTilt = 0.0,
qreal rotation = 0.0,
qreal tangentialPressure = 0.0,
qreal perspective = 1.0,
qreal speed = 0.0,
int canvasrotation = 0,
bool canvasMirroredH = false);
/**
*Returns the canvas rotation if that has been given to the kispaintinformation.
*/
int canvasRotation() const;
/**
*set the canvas rotation.
*/
void setCanvasRotation(int rotation);
/*
*Whether the canvas is mirrored for the paint-operation.
*/
bool canvasMirroredH() const;
/*
*Set whether the canvas is mirrored for the paint-operation.
*/
void setCanvasHorizontalMirrorState(bool mir);
void toXML(QDomDocument&, QDomElement&) const;
static KisPaintInformation fromXML(const QDomElement&);
/// (1-t) * p1 + t * p2
static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi);
static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2);
static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2);
static KisPaintInformation mixWithoutTime(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2);
static KisPaintInformation mixWithoutTime(qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2);
static qreal tiltDirection(const KisPaintInformation& info, bool normalize = true);
static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX = 60.0, qreal maxTiltY = 60.0, bool normalize = true);
private:
static KisPaintInformation mixImpl(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2, bool posOnly, bool mixTime);
private:
struct Private;
Private* const d;
};
KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisPaintInformation& info);
#endif
diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc
index 7b6e6cc4b4..24b1df7d35 100644
--- a/libs/image/brushengine/kis_paintop.cc
+++ b/libs/image/brushengine/kis_paintop.cc
@@ -1,185 +1,205 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2004 Boudewijn Rempt
* Copyright (c) 2004 Clarence Dang
* Copyright (c) 2004 Adrian Page
* Copyright (c) 2004,2007,2010 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_paintop.h"
#include
#include
#include
#include
#include "kis_painter.h"
#include "kis_layer.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_global.h"
#include "kis_datamanager.h"
#include
#include
#include "kis_vec.h"
#include "kis_perspective_math.h"
#include "kis_fixed_paint_device.h"
#include "kis_paintop_utils.h"
#define BEZIER_FLATNESS_THRESHOLD 0.5
#include
#include
struct Q_DECL_HIDDEN KisPaintOp::Private {
Private(KisPaintOp *_q)
: q(_q), dab(0),
fanCornersEnabled(false),
fanCornersStep(1.0) {}
KisPaintOp *q;
KisFixedPaintDeviceSP dab;
KisPainter* painter;
bool fanCornersEnabled;
qreal fanCornersStep;
};
KisPaintOp::KisPaintOp(KisPainter * painter) : d(new Private(this))
{
d->painter = painter;
}
KisPaintOp::~KisPaintOp()
{
d->dab.clear();
delete d;
}
KisFixedPaintDeviceSP KisPaintOp::cachedDab()
{
return cachedDab(d->painter->device()->colorSpace());
}
KisFixedPaintDeviceSP KisPaintOp::cachedDab(const KoColorSpace *cs)
{
if (!d->dab || *d->dab->colorSpace() != *cs) {
d->dab = new KisFixedPaintDevice(cs);
}
return d->dab;
}
void KisPaintOp::setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep)
{
d->fanCornersEnabled = fanCornersEnabled;
d->fanCornersStep = fanCornersStep;
}
void KisPaintOp::splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction)
{
const qint32 i = std::floor(coordinate);
const qreal f = coordinate - i;
*whole = i;
*fraction = f;
}
static void paintBezierCurve(KisPaintOp *paintOp,
const KisPaintInformation &pi1,
const KisVector2D &control1,
const KisVector2D &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
LineEquation line = LineEquation::Through(toKisVector2D(pi1.pos()), toKisVector2D(pi2.pos()));
qreal d1 = line.absDistance(control1);
qreal d2 = line.absDistance(control2);
if ((d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD)
|| qIsNaN(d1) || qIsNaN(d2)) {
paintOp->paintLine(pi1, pi2, currentDistance);
} else {
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
KisVector2D l2 = (toKisVector2D(pi1.pos()) + control1) / 2;
KisVector2D h = (control1 + control2) / 2;
KisVector2D l3 = (l2 + h) / 2;
KisVector2D r3 = (control2 + toKisVector2D(pi2.pos())) / 2;
KisVector2D r2 = (h + r3) / 2;
KisVector2D l4 = (l3 + r2) / 2;
KisPaintInformation middlePI = KisPaintInformation::mix(toQPointF(l4), 0.5, pi1, pi2);
paintBezierCurve(paintOp, pi1, l2, l3, middlePI, currentDistance);
paintBezierCurve(paintOp, middlePI, r2, r3, pi2, currentDistance);
}
}
void KisPaintOp::paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
return ::paintBezierCurve(this, pi1, toKisVector2D(control1), toKisVector2D(control2), pi2, currentDistance);
}
void KisPaintOp::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance)
{
KisPaintOpUtils::paintLine(*this, pi1, pi2, currentDistance,
d->fanCornersEnabled,
d->fanCornersStep);
}
void KisPaintOp::paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance)
{
Q_ASSERT(currentDistance);
KisPaintInformation pi(info);
pi.paintAt(*this, currentDistance);
}
void KisPaintOp::updateSpacing(const KisPaintInformation &info,
KisDistanceInformation ¤tDistance) const
{
KisPaintInformation pi(info);
KisSpacingInformation spacingInfo;
{
KisPaintInformation::DistanceInformationRegistrar r
= pi.registerDistanceInformation(¤tDistance);
spacingInfo = updateSpacingImpl(pi);
}
- currentDistance.setSpacing(spacingInfo);
+ currentDistance.updateSpacing(spacingInfo);
+}
+
+void KisPaintOp::updateTiming(const KisPaintInformation &info,
+ KisDistanceInformation ¤tDistance) const
+{
+ KisPaintInformation pi(info);
+ KisTimingInformation timingInfo;
+ {
+ KisPaintInformation::DistanceInformationRegistrar r
+ = pi.registerDistanceInformation(¤tDistance);
+ timingInfo = updateTimingImpl(pi);
+ }
+
+ currentDistance.updateTiming(timingInfo);
+}
+
+KisTimingInformation KisPaintOp::updateTimingImpl(const KisPaintInformation &info) const
+{
+ Q_UNUSED(info);
+ return KisTimingInformation();
}
KisPainter* KisPaintOp::painter() const
{
return d->painter;
}
KisPaintDeviceSP KisPaintOp::source() const
{
return d->painter->device();
}
diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h
index b6bbee5cc2..e97639249b 100644
--- a/libs/image/brushengine/kis_paintop.h
+++ b/libs/image/brushengine/kis_paintop.h
@@ -1,142 +1,155 @@
/*
* Copyright (c) 2002 Patrick Julien
* Copyright (c) 2004 Boudewijn Rempt
* Copyright (c) 2004 Clarence Dang
* Copyright (c) 2004 Adrian Page
* Copyright (c) 2004,2010 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTOP_H_
#define KIS_PAINTOP_H_
#include
#include "kis_shared.h"
#include "kis_types.h"
#include
class QPointF;
class KoColorSpace;
class KisPainter;
class KisPaintInformation;
/**
* KisPaintOp are use by tools to draw on a paint device. A paintop takes settings
* and input information, like pressure, tilt or motion and uses that to draw pixels
*/
class KRITAIMAGE_EXPORT KisPaintOp : public KisShared
{
struct Private;
public:
KisPaintOp(KisPainter * painter);
virtual ~KisPaintOp();
/**
- * Paint at the subpixel point pos using the specified paint
- * information..
+ * Paint at the subpixel point pos using the specified paint information..
*
- * The distance between two calls of the paintAt is always
- * specified by spacing, which is automatically saved into the
- * current distance information object
+ * The distance/time between two calls of the paintAt is always specified by spacing and timing,
+ * which are automatically saved into the current distance information object.
*/
void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance);
/**
- * Updates the spacing in currentDistance based on the provided information. Note that the
- * spacing is updated automatically in the paintAt method, so there is no need to call this
+ * Updates the spacing settings in currentDistance based on the provided information. Note that
+ * the spacing is updated automatically in the paintAt method, so there is no need to call this
* method if paintAt has just been called.
*/
void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance)
const;
+ /**
+ * Updates the timing settings in currentDistance based on the provided information. Note that
+ * the timing is updated automatically in the paintAt method, so there is no need to call this
+ * method if paintAt has just been called.
+ */
+ void updateTiming(const KisPaintInformation &info, KisDistanceInformation ¤tDistance)
+ const;
+
/**
* Draw a line between pos1 and pos2 using the currently set brush and color.
* If savedDist is less than zero, the brush is painted at pos1 before being
* painted along the line using the spacing setting.
*
* @return the drag distance, that is the remains of the distance
* between p1 and p2 not covered because the currenlty set brush
* has a spacing greater than that distance.
*/
virtual void paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
* Draw a Bezier curve between pos1 and pos2 using control points 1 and 2.
* If savedDist is less than zero, the brush is painted at pos1 before being
* painted along the curve using the spacing setting.
* @return the drag distance, that is the remains of the distance between p1 and p2 not covered
* because the currenlty set brush has a spacing greater than that distance.
*/
virtual void paintBezierCurve(const KisPaintInformation &pi1,
const QPointF &control1,
const QPointF &control2,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance);
/**
* Whether this paintop can paint. Can be false in case that some setting isn't read correctly.
* @return if paintop is ready for painting, default is true
*/
virtual bool canPaint() const {
return true;
}
/**
* Split the coordinate into whole + fraction, where fraction is always >= 0.
*/
static void splitCoordinate(qreal coordinate, qint32 *whole, qreal *fraction);
protected:
friend class KisPaintInformation;
/**
- * The implementation of painting of a dab and updating spacing
+ * The implementation of painting of a dab and updating spacing. This does NOT need to update
+ * the timing information.
*/
virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0;
/**
* Implementation of a spacing update
*/
virtual KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const = 0;
+ /**
+ * Implementation of a timing update. The default implementation always disables timing. This is
+ * suitable for paintops that do not support airbrushing.
+ */
+ virtual KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const;
+
KisFixedPaintDeviceSP cachedDab();
KisFixedPaintDeviceSP cachedDab(const KoColorSpace *cs);
/**
* Return the painter this paintop is owned by
*/
KisPainter* painter() const;
/**
* Return the paintdevice the painter this paintop is owned by
*/
KisPaintDeviceSP source() const;
private:
friend class KisPressureRotationOption;
void setFanCornersInfo(bool fanCornersEnabled, qreal fanCornersStep);
private:
Private* const d;
};
#endif // KIS_PAINTOP_H_
diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp
index be3f1678e7..c6680e195a 100644
--- a/libs/image/brushengine/kis_paintop_settings.cpp
+++ b/libs/image/brushengine/kis_paintop_settings.cpp
@@ -1,450 +1,456 @@
/*
* Copyright (c) 2007 Boudewijn Rempt
* Copyright (c) 2008 Lukáš Tvrdý
* Copyright (c) 2014 Mohit Goyal
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_paint_layer.h"
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_paint_device.h"
#include "kis_paintop_registry.h"
+#include "kis_timing_information.h"
#include
#include "kis_paintop_config_widget.h"
#include
#include "kis_paintop_settings_update_proxy.h"
#include
#include
#include
#include
#include
struct Q_DECL_HIDDEN KisPaintOpSettings::Private {
Private()
: disableDirtyNotifications(false)
{}
QPointer settingsWidget;
QString modelName;
KisPaintOpPresetWSP preset;
QList uniformProperties;
bool disableDirtyNotifications;
class DirtyNotificationsLocker {
public:
DirtyNotificationsLocker(KisPaintOpSettings::Private *d)
: m_d(d),
m_oldNotificationsState(d->disableDirtyNotifications)
{
m_d->disableDirtyNotifications = true;
}
~DirtyNotificationsLocker() {
m_d->disableDirtyNotifications = m_oldNotificationsState;
}
private:
KisPaintOpSettings::Private *m_d;
bool m_oldNotificationsState;
Q_DISABLE_COPY(DirtyNotificationsLocker)
};
KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const {
auto presetSP = preset.toStrongRef();
return presetSP ? presetSP->updateProxyNoCreate() : 0;
}
KisPaintopSettingsUpdateProxy* updateProxyCreate() const {
auto presetSP = preset.toStrongRef();
return presetSP ? presetSP->updateProxy() : 0;
}
};
KisPaintOpSettings::KisPaintOpSettings()
: d(new Private)
{
d->preset = 0;
}
KisPaintOpSettings::~KisPaintOpSettings()
{
}
KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs)
: KisPropertiesConfiguration(rhs)
, d(new Private)
{
d->settingsWidget = 0;
d->preset = rhs.preset();
d->modelName = rhs.modelName();
}
void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget)
{
d->settingsWidget = widget;
}
void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset)
{
d->preset = preset;
}
KisPaintOpPresetWSP KisPaintOpSettings::preset() const
{
return d->preset;
}
bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode)
{
Q_UNUSED(modifiers);
Q_UNUSED(currentNode);
setRandomOffset(paintInformation);
return true; // ignore the event by default
}
void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation)
{
if (getBool("Texture/Pattern/Enabled")) {
if (getBool("Texture/Pattern/isRandomOffsetX")) {
setProperty("Texture/Pattern/OffsetX",
paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX")));
}
if (getBool("Texture/Pattern/isRandomOffsetY")) {
setProperty("Texture/Pattern/OffsetY",
paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY")));
}
}
}
KisPaintOpSettingsSP KisPaintOpSettings::clone() const
{
QString paintopID = getString("paintop");
if (paintopID.isEmpty())
return 0;
KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID, ""));
QMapIterator i(getProperties());
while (i.hasNext()) {
i.next();
settings->setProperty(i.key(), QVariant(i.value()));
}
settings->setPreset(this->preset());
return settings;
}
void KisPaintOpSettings::activate()
{
}
void KisPaintOpSettings::setPaintOpOpacity(qreal value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("OpacityValue", value);
}
void KisPaintOpSettings::setPaintOpFlow(qreal value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("FlowValue", value);
}
void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("CompositeOp", value);
}
qreal KisPaintOpSettings::paintOpOpacity()
{
KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this);
return proxy->getDouble("OpacityValue", 1.0);
}
qreal KisPaintOpSettings::paintOpFlow()
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
return proxy->getDouble("FlowValue", 1.0);
}
QString KisPaintOpSettings::paintOpCompositeOp()
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
return proxy->getString("CompositeOp", COMPOSITE_OVER);
}
void KisPaintOpSettings::setEraserMode(bool value)
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
proxy->setProperty("EraserMode", value);
}
bool KisPaintOpSettings::eraserMode()
{
KisLockedPropertiesProxySP proxy(
KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
return proxy->getBool("EraserMode", false);
}
QString KisPaintOpSettings::effectivePaintOpCompositeOp()
{
return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE;
}
qreal KisPaintOpSettings::savedEraserSize() const
{
return getDouble("SavedEraserSize", 0.0);
}
void KisPaintOpSettings::setSavedEraserSize(qreal value)
{
setProperty("SavedEraserSize", value);
setPropertyNotSaved("SavedEraserSize");
}
qreal KisPaintOpSettings::savedBrushSize() const
{
return getDouble("SavedBrushSize", 0.0);
}
void KisPaintOpSettings::setSavedBrushSize(qreal value)
{
setProperty("SavedBrushSize", value);
setPropertyNotSaved("SavedBrushSize");
}
qreal KisPaintOpSettings::savedEraserOpacity() const
{
return getDouble("SavedEraserOpacity", 0.0);
}
void KisPaintOpSettings::setSavedEraserOpacity(qreal value)
{
setProperty("SavedEraserOpacity", value);
setPropertyNotSaved("SavedEraserOpacity");
}
qreal KisPaintOpSettings::savedBrushOpacity() const
{
return getDouble("SavedBrushOpacity", 0.0);
}
void KisPaintOpSettings::setSavedBrushOpacity(qreal value)
{
setProperty("SavedBrushOpacity", value);
setPropertyNotSaved("SavedBrushOpacity");
}
QString KisPaintOpSettings::modelName() const
{
return d->modelName;
}
void KisPaintOpSettings::setModelName(const QString & modelName)
{
d->modelName = modelName;
}
KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const
{
if (d->settingsWidget.isNull())
return 0;
return d->settingsWidget.data();
}
bool KisPaintOpSettings::isValid() const
{
return true;
}
bool KisPaintOpSettings::isLoadable()
{
return isValid();
}
QString KisPaintOpSettings::indirectPaintingCompositeOp() const
{
return COMPOSITE_ALPHA_DARKEN;
}
bool KisPaintOpSettings::isAirbrushing() const
{
return getBool(AIRBRUSH_ENABLED, false);
}
qreal KisPaintOpSettings::airbrushInterval() const
{
qreal rate = getDouble(AIRBRUSH_RATE, 1.0);
if (rate == 0.0) {
- return 1000.0;
+ return LONG_TIME;
}
else {
return 1000.0 / rate;
}
}
+bool KisPaintOpSettings::useSpacingUpdates() const
+{
+ return getBool(SPACING_USE_UPDATES, false);
+}
+
QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode)
{
QPainterPath path;
if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) {
path = ellipseOutline(10, 10, 1.0, 0);
if (mode == CursorTiltOutline) {
path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0));
}
path.translate(info.pos());
}
return path;
}
QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation)
{
QPainterPath path;
QRectF ellipse(0, 0, width * scale, height * scale);
ellipse.translate(-ellipse.center());
path.addEllipse(ellipse);
QTransform m;
m.reset();
m.rotate(rotation);
path = m.map(path);
return path;
}
QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info,
QPointF const& start, qreal maxLength, qreal angle)
{
if (maxLength == 0.0) maxLength = 50.0;
maxLength = qMax(maxLength, 50.0);
qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true));
qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0);
QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle);
guideLine.translate(start);
QPainterPath ret;
ret.moveTo(guideLine.p1());
ret.lineTo(guideLine.p2());
guideLine.setAngle(baseAngle - angle);
ret.lineTo(guideLine.p2());
ret.lineTo(guideLine.p1());
return ret;
}
void KisPaintOpSettings::setCanvasRotation(qreal angle)
{
Private::DirtyNotificationsLocker locker(d.data());
setProperty("runtimeCanvasRotation", angle);
setPropertyNotSaved("runtimeCanvasRotation");
}
void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored)
{
Private::DirtyNotificationsLocker locker(d.data());
setProperty("runtimeCanvasMirroredX", xAxisMirrored);
setPropertyNotSaved("runtimeCanvasMirroredX");
setProperty("runtimeCanvasMirroredY", yAxisMirrored);
setPropertyNotSaved("runtimeCanvasMirroredY");
}
void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value)
{
if (value != KisPropertiesConfiguration::getProperty(name) &&
!d->disableDirtyNotifications) {
KisPaintOpPresetSP presetSP = preset().toStrongRef();
if (presetSP) {
presetSP->setPresetDirty(true);
}
}
KisPropertiesConfiguration::setProperty(name, value);
onPropertyChanged();
}
void KisPaintOpSettings::onPropertyChanged()
{
KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate();
if (proxy) {
proxy->notifySettingsChanged();
}
}
bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config)
{
return config->getBool("lodUserAllowed", true);
}
void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value)
{
config->setProperty("lodUserAllowed", value);
}
#include "kis_standard_uniform_properties_factory.h"
QList KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings)
{
QList props =
listWeakToStrong(d->uniformProperties);
if (props.isEmpty()) {
using namespace KisStandardUniformPropertiesFactory;
props.append(createProperty(opacity, settings, d->updateProxyCreate()));
props.append(createProperty(size, settings, d->updateProxyCreate()));
props.append(createProperty(flow, settings, d->updateProxyCreate()));
d->uniformProperties = listStrongToWeak(props);
}
return props;
}
diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h
index 1d9ac1a280..7ed5fb2e5c 100644
--- a/libs/image/brushengine/kis_paintop_settings.h
+++ b/libs/image/brushengine/kis_paintop_settings.h
@@ -1,303 +1,315 @@
/*
* Copyright (c) 2007 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PAINTOP_SETTINGS_H_
#define KIS_PAINTOP_SETTINGS_H_
#include "kis_types.h"
#include "kritaimage_export.h"
#include
#include
#include "kis_properties_configuration.h"
#include
#include
class KisPaintOpConfigWidget;
class KisPaintopSettingsUpdateProxy;
/**
* Configuration property used to control whether airbrushing is enabled.
*/
const QString AIRBRUSH_ENABLED = "PaintOpSettings/isAirbrushing";
/**
* Configuration property used to control airbrushing rate. The value should be in dabs per second.
*/
const QString AIRBRUSH_RATE = "PaintOpSettings/rate";
/**
* Configuration property used to control whether airbrushing is configured to ignore distance-based
* spacing.
*/
const QString AIRBRUSH_IGNORE_SPACING = "PaintOpSettings/ignoreSpacing";
+/**
+ * Configuration property used to control whether the spacing settings can be updated between
+ * painted dabs.
+ */
+const QString SPACING_USE_UPDATES = "PaintOpSettings/updateSpacingBetweenDabs";
+
/**
* This class is used to cache the settings for a paintop
* between two creations. There is one KisPaintOpSettings per input device (mouse, tablet,
* etc...).
*
* The settings may be stored in a preset or a recorded brush stroke. Note that if your
* paintop's settings subclass has data that is not stored as a property, that data is not
* saved and restored.
*
* The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset
* property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is
* set to dirty.
*/
class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration
{
public:
KisPaintOpSettings();
~KisPaintOpSettings() override;
KisPaintOpSettings(const KisPaintOpSettings &rhs);
/**
*
*/
virtual void setOptionsWidget(KisPaintOpConfigWidget* widget);
/**
* This function is called by a tool when the mouse is pressed. It's useful if
* the paintop needs mouse interaction for instance in the case of the clone op.
* If the tool is supposed to ignore the event, the paint op should return false
* and if the tool is supposed to use the event, return true.
*/
virtual bool mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode);
/**
* Clone the current settings object. Override this if your settings instance doesn't
* store everything as properties.
*/
virtual KisPaintOpSettingsSP clone() const;
/**
* @return the node the paintop is working on.
*/
KisNodeSP node() const;
/**
* Call this function when the paint op is selected or the tool is activated
*/
virtual void activate();
/**
* XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will
* be completely handled in the paintop, not in the tool. This is a filthy hack to move
* the option to the right place, at least.
* @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops
* do not support non-incremental.
*/
virtual bool paintIncremental() {
return true;
}
/**
* @return the composite op it to which the indirect painting device
* should be initialized to. This is used by clone op to reset
* the composite op to COMPOSITE_COPY
*/
virtual QString indirectPaintingCompositeOp() const;
/**
* Whether this paintop wants to deposit paint even when not moving, i.e. the tool needs to
* activate its timer. If this is true, painting updates need to be generated at regular
* intervals even in the absence of input device events, e.g. when the cursor is not moving.
*
* The default implementation checks the property AIRBRUSH_ENABLED, defaulting to false if the
* property is not found. This should be suitable for most paintops.
*/
virtual bool isAirbrushing() const;
/**
* Indicates the minimum time interval that might be needed between airbrush dabs, in
* milliseconds. A lower value means painting updates need to happen more frequently. This value
* should be ignored if isAirbrushing() is false.
*
* The default implementation uses the property AIRBRUSH_RATE, defaulting to an interval of
* one second if the property is not found. This should be suitable for most paintops.
*/
virtual qreal airbrushInterval() const;
+ /**
+ * Indicates whether this configuration allows spacing information to be updated between painted
+ * dabs during a stroke.
+ */
+ virtual bool useSpacingUpdates() const;
+
/**
* This enum defines the current mode for painting an outline.
*/
enum OutlineMode {
CursorIsOutline = 1, ///< When this mode is set, an outline is painted around the cursor
CursorIsCircleOutline,
CursorNoOutline,
CursorTiltOutline,
CursorColorOutline
};
/**
* Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates.
* Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline
* always like clone paintop indicating the duplicate position
*/
virtual QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode);
/**
* Helpers for drawing the brush outline
*/
static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation);
/**
* Helper for drawing a triangle representing the tilt of the stylus.
*
* @param start is the offset from the brush's outline's bounding box
* @param lengthScale is used for deciding the size of the triangle.
* Brush diameter or width are common choices for this.
* @param angle is the angle between the two sides of the triangle.
*/
static QPainterPath makeTiltIndicator(KisPaintInformation const& info,
QPointF const& start, qreal lengthScale, qreal angle);
/**
* Set paintop opacity directly in the properties
*/
void setPaintOpOpacity(qreal value);
/**
* Set paintop flow directly in the properties
*/
void setPaintOpFlow(qreal value);
/**
* Set paintop composite mode directly in the properties
*/
void setPaintOpCompositeOp(const QString &value);
/**
* @return opacity saved in the properties
*/
qreal paintOpOpacity();
/**
* @return flow saved in the properties
*/
qreal paintOpFlow();
/**
* @return composite mode saved in the properties
*/
QString paintOpCompositeOp();
/**
* Set paintop size directly in the properties
*/
virtual void setPaintOpSize(qreal value) = 0;
/**
* @return size saved in the properties
*/
virtual qreal paintOpSize() const = 0;
void setEraserMode(bool value);
bool eraserMode();
qreal savedEraserSize() const;
void setSavedEraserSize(qreal value);
qreal savedBrushSize() const;
void setSavedBrushSize(qreal value);
qreal savedEraserOpacity() const;
void setSavedEraserOpacity(qreal value);
qreal savedBrushOpacity() const;
void setSavedBrushOpacity(qreal value);
QString effectivePaintOpCompositeOp();
void setPreset(KisPaintOpPresetWSP preset);
KisPaintOpPresetWSP preset() const;
/**
* @return filename of the 3D brush model, empty if no brush is set
*/
virtual QString modelName() const;
/**
* Set filename of 3D brush model. By default no brush is set
*/
void setModelName(const QString & modelName);
/// Check if the settings are valid, setting might be invalid through missing brushes etc
/// Overwrite if the settings of a paintop can be invalid
/// @return state of the settings, default implementation is true
virtual bool isValid() const;
/// Check if the settings are loadable, that might the case if we can fallback to something
/// Overwrite if the settings can do some kind of fallback
/// @return loadable state of the settings, by default implementation return the same as isValid()
virtual bool isLoadable();
/**
* These methods are populating properties with runtime
* information about canvas rotation/mirroring. This information
* is set directly by KisToolFreehand. Later the data is accessed
* by the pressure options to make a final decision.
*/
void setCanvasRotation(qreal angle);
void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored);
/**
* Overrides the method in KisPropertiesCofiguration to allow
* onPropertyChanged() callback
*/
void setProperty(const QString & name, const QVariant & value) override;
virtual QList uniformProperties(KisPaintOpSettingsSP settings);
static bool isLodUserAllowed(const KisPropertiesConfigurationSP config);
static void setLodUserAllowed(KisPropertiesConfigurationSP config, bool value);
/**
* @return the option widget of the paintop (can be 0 is no option widgets is set)
*/
KisPaintOpConfigWidget* optionsWidget() const;
/**
* This function is called to set random offsets to the brush whenever the mouse is clicked. It is
* specific to when the pattern option is set.
*
*/
virtual void setRandomOffset(const KisPaintInformation &paintInformation);
protected:
/**
* The callback is called every time when a property changes
*/
virtual void onPropertyChanged();
private:
struct Private;
const QScopedPointer d;
};
#endif
diff --git a/libs/image/brushengine/kis_paintop_utils.h b/libs/image/brushengine/kis_paintop_utils.h
index 0c180ddc2b..ca53683483 100644
--- a/libs/image/brushengine/kis_paintop_utils.h
+++ b/libs/image/brushengine/kis_paintop_utils.h
@@ -1,215 +1,228 @@
/*
* Copyright (c) 2014 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PAINTOP_UTILS_H
#define __KIS_PAINTOP_UTILS_H
#include "kis_global.h"
#include "kis_paint_information.h"
#include "kis_distance_information.h"
+#include "kis_spacing_information.h"
+#include "kis_timing_information.h"
namespace KisPaintOpUtils {
template
bool paintFan(PaintOp &op,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance,
qreal fanCornersStep)
{
const qreal angleStep = fanCornersStep;
const qreal initialAngle = currentDistance->lastDrawingAngle();
const qreal finalAngle = pi2.drawingAngleSafe(*currentDistance);
const qreal fullDistance = shortestAngularDistance(initialAngle,
finalAngle);
qreal lastAngle = initialAngle;
int i = 0;
while (shortestAngularDistance(lastAngle, finalAngle) > angleStep) {
lastAngle = incrementInDirection(lastAngle, angleStep, finalAngle);
qreal t = angleStep * i++ / fullDistance;
QPointF pt = pi1.pos() + t * (pi2.pos() - pi1.pos());
KisPaintInformation pi = KisPaintInformation::mix(pt, t, pi1, pi2);
pi.overrideDrawingAngle(lastAngle);
pi.paintAt(op, currentDistance);
}
return i;
}
template
void paintLine(PaintOp &op,
const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
KisDistanceInformation *currentDistance,
bool fanCornersEnabled,
qreal fanCornersStep)
{
QPointF end = pi2.pos();
qreal endTime = pi2.currentTime();
KisPaintInformation pi = pi1;
qreal t = 0.0;
while ((t = currentDistance->getNextPointPosition(pi.pos(), end, pi.currentTime(), endTime)) >= 0.0) {
pi = KisPaintInformation::mix(t, pi, pi2);
if (fanCornersEnabled &&
currentDistance->hasLastPaintInformation()) {
paintFan(op,
currentDistance->lastPaintInformation(),
pi,
currentDistance,
fanCornersStep);
}
/**
* A bit complicated part to ensure the registration
* of the distance information is done in right order
*/
pi.paintAt(op, currentDistance);
}
/*
- * Perform a spacing update between dabs if appropriate. Typically, this will not happen if the
- * above loop actually painted anything. This is because the getNextPointPosition() call before
- * the paint operation will reset the accumulators in currentDistance and therefore make
- * needsSpacingUpdate() false. The temporal distance between pi1 and pi2 is typically too small
- * for the accumulators to build back up enough to require a spacing update after that.
- * (The accumulated time value is updated not during the paint operation, but during the call to
- * getNextPointPosition(), that is, updated during every paintLine() call.)
+ * Perform spacing and/or timing updates between dabs if appropriate. Typically, this will not
+ * happen if the above loop actually painted anything. This is because the
+ * getNextPointPosition() call before the paint operation will reset the accumulators in
+ * currentDistance and therefore make needsSpacingUpdate() and needsTimingUpdate() false. The
+ * temporal distance between pi1 and pi2 is typically too small for the accumulators to build
+ * back up enough to require a spacing or timing update after that. (The accumulated time values
+ * are updated not during the paint operation, but during the call to getNextPointPosition(),
+ * that is, updated during every paintLine() call.)
*/
if (currentDistance->needsSpacingUpdate()) {
op.updateSpacing(pi2, *currentDistance);
}
+ if (currentDistance->needsTimingUpdate()) {
+ op.updateTiming(pi2, *currentDistance);
+ }
}
/**
* A special class containing the previous position of the cursor for
* the sake of painting the outline of the paint op. The main purpose
* of this class is to ensure that the saved point does not equal to
* the current one, which would cause a outline flicker. To echieve
* this the class stores two previosly requested points instead of the
* last one.
*/
class PositionHistory
{
public:
/**
* \return the previously used point, which is guaranteed not to
* be equal to \p pt and updates the history if needed
*/
QPointF pushThroughHistory(const QPointF &pt) {
QPointF result;
const qreal pointSwapThreshold = 7.0;
/**
* We check x *and* y separately, because events generated by
* a mouse device tend to come separately for x and y offsets.
* Efficienty generating the 'stairs' pattern.
*/
if (qAbs(pt.x() - m_second.x()) > pointSwapThreshold &&
qAbs(pt.y() - m_second.y()) > pointSwapThreshold) {
result = m_second;
m_first = m_second;
m_second = pt;
} else {
result = m_first;
}
return result;
}
private:
QPointF m_first;
QPointF m_second;
};
bool checkSizeTooSmall(qreal scale, qreal width, qreal height)
{
return scale * width < 0.01 || scale * height < 0.01;
}
inline qreal calcAutoSpacing(qreal value, qreal coeff)
{
return coeff * (value < 1.0 ? value : sqrt(value));
}
QPointF calcAutoSpacing(const QPointF &pt, qreal coeff, qreal lodScale)
{
const qreal invLodScale = 1.0 / lodScale;
const QPointF lod0Point = invLodScale * pt;
return lodScale * QPointF(calcAutoSpacing(lod0Point.x(), coeff), calcAutoSpacing(lod0Point.y(), coeff));
}
KisSpacingInformation effectiveSpacing(qreal dabWidth,
qreal dabHeight,
qreal extraScale,
- qreal rateExtraScale,
bool distanceSpacingEnabled,
bool isotropicSpacing,
qreal rotation,
bool axesFlipped,
qreal spacingVal,
bool autoSpacingActive,
qreal autoSpacingCoeff,
- bool timedSpacingEnabled,
- qreal timedSpacingInterval,
qreal lodScale)
{
QPointF spacing;
if (!isotropicSpacing) {
if (autoSpacingActive) {
spacing = calcAutoSpacing(QPointF(dabWidth, dabHeight), autoSpacingCoeff, lodScale);
} else {
spacing = QPointF(dabWidth, dabHeight);
spacing *= spacingVal;
}
}
else {
qreal significantDimension = qMax(dabWidth, dabHeight);
if (autoSpacingActive) {
significantDimension = calcAutoSpacing(significantDimension, autoSpacingCoeff);
} else {
significantDimension *= spacingVal;
}
spacing = QPointF(significantDimension, significantDimension);
rotation = 0.0;
axesFlipped = false;
}
spacing *= extraScale;
- qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME :
- timedSpacingInterval / rateExtraScale;
+ return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped);
+}
+
+KisTimingInformation effectiveTiming(bool timingEnabled,
+ qreal timingInterval,
+ qreal rateExtraScale)
+{
- return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped,
- timedSpacingEnabled, scaledInterval);
+ if (!timingEnabled) {
+ return KisTimingInformation();
+ }
+ else {
+ qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : timingInterval / rateExtraScale;
+ return KisTimingInformation(scaledInterval);
+ }
}
}
#endif /* __KIS_PAINTOP_UTILS_H */
diff --git a/libs/image/kis_distance_information.cpp b/libs/image/kis_distance_information.cpp
index 20dba8196a..76643df404 100644
--- a/libs/image/kis_distance_information.cpp
+++ b/libs/image/kis_distance_information.cpp
@@ -1,557 +1,649 @@
/*
* Copyright (c) 2010 Cyrille Berger
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include
#include
+#include "kis_spacing_information.h"
+#include "kis_timing_information.h"
#include "kis_debug.h"
#include
#include
#include
#include "kis_algebra_2d.h"
#include "kis_dom_utils.h"
#include "kis_lod_transform.h"
const qreal MIN_DISTANCE_SPACING = 0.5;
// Smallest allowed interval when timed spacing is enabled, in milliseconds.
const qreal MIN_TIMED_INTERVAL = 0.5;
// Largest allowed interval when timed spacing is enabled, in milliseconds.
const qreal MAX_TIMED_INTERVAL = LONG_TIME;
struct Q_DECL_HIDDEN KisDistanceInformation::Private {
Private() :
accumDistance(),
accumTime(0.0),
spacingUpdateInterval(LONG_TIME),
+ timeSinceSpacingUpdate(0.0),
+ timingUpdateInterval(LONG_TIME),
+ timeSinceTimingUpdate(0.0),
lastDabInfoValid(false),
lastPaintInfoValid(false),
lockedDrawingAngle(0.0),
hasLockedDrawingAngle(false),
totalDistance(0.0) {}
+ // Accumulators of time/distance passed since the last painted dab
QPointF accumDistance;
qreal accumTime;
+
KisSpacingInformation spacing;
qreal spacingUpdateInterval;
+ // Accumulator of time passed since the last spacing update
+ qreal timeSinceSpacingUpdate;
+
+ KisTimingInformation timing;
+ qreal timingUpdateInterval;
+ // Accumulator of time passed since the last timing update
+ qreal timeSinceTimingUpdate;
+ // Information about the last position considered (not necessarily a painted dab)
QPointF lastPosition;
qreal lastTime;
qreal lastAngle;
bool lastDabInfoValid;
+ // Information about the last painted dab
KisPaintInformation lastPaintInformation;
bool lastPaintInfoValid;
qreal lockedDrawingAngle;
bool hasLockedDrawingAngle;
qreal totalDistance;
};
+struct Q_DECL_HIDDEN KisDistanceInitInfo::Private {
+ Private() :
+ hasLastInfo(false),
+ lastPosition(),
+ lastTime(0.0),
+ lastAngle(0.0),
+ spacingUpdateInterval(LONG_TIME),
+ timingUpdateInterval(LONG_TIME) {}
+
+
+ // Indicates whether lastPosition, lastTime, and lastAngle are valid or not.
+ bool hasLastInfo;
+
+ QPointF lastPosition;
+ qreal lastTime;
+ qreal lastAngle;
+
+ qreal spacingUpdateInterval;
+ qreal timingUpdateInterval;
+};
+
KisDistanceInitInfo::KisDistanceInitInfo()
- : m_hasLastInfo(false)
- , m_lastPosition()
- , m_lastTime(0.0)
- , m_lastAngle(0.0)
- , m_spacingUpdateInterval(LONG_TIME)
+ : m_d(new Private)
{
}
-KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval)
- : m_hasLastInfo(false)
- , m_lastPosition()
- , m_lastTime(0.0)
- , m_lastAngle(0.0)
- , m_spacingUpdateInterval(spacingUpdateInterval)
+KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval)
+ : m_d(new Private)
{
+ m_d->spacingUpdateInterval = spacingUpdateInterval;
+ m_d->timingUpdateInterval = timingUpdateInterval;
}
KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime,
qreal lastAngle)
- : m_hasLastInfo(true)
- , m_lastPosition(lastPosition)
- , m_lastTime(lastTime)
- , m_lastAngle(lastAngle)
- , m_spacingUpdateInterval(LONG_TIME)
+ : m_d(new Private)
{
+ m_d->hasLastInfo = true;
+ m_d->lastPosition = lastPosition;
+ m_d->lastTime = lastTime;
+ m_d->lastAngle = lastAngle;
}
KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime,
- qreal lastAngle, qreal spacingUpdateInterval)
- : m_hasLastInfo(true)
- , m_lastPosition(lastPosition)
- , m_lastTime(lastTime)
- , m_lastAngle(lastAngle)
- , m_spacingUpdateInterval(spacingUpdateInterval)
+ qreal lastAngle, qreal spacingUpdateInterval,
+ qreal timingUpdateInterval)
+ : m_d(new Private)
{
+ m_d->hasLastInfo = true;
+ m_d->lastPosition = lastPosition;
+ m_d->lastTime = lastTime;
+ m_d->lastAngle = lastAngle;
+ m_d->spacingUpdateInterval = spacingUpdateInterval;
+ m_d->timingUpdateInterval = timingUpdateInterval;
+}
+
+KisDistanceInitInfo::KisDistanceInitInfo(const KisDistanceInitInfo &rhs)
+ : m_d(new Private(*rhs.m_d))
+{
+}
+
+KisDistanceInitInfo::~KisDistanceInitInfo()
+{
+ delete m_d;
}
bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const
{
- if (m_spacingUpdateInterval != other.m_spacingUpdateInterval
- || m_hasLastInfo != other.m_hasLastInfo)
+ if (m_d->spacingUpdateInterval != other.m_d->spacingUpdateInterval
+ || m_d->timingUpdateInterval != other.m_d->timingUpdateInterval
+ || m_d->hasLastInfo != other.m_d->hasLastInfo)
{
return false;
}
- if (m_hasLastInfo) {
- if (m_lastPosition != other.m_lastPosition || m_lastTime != other.m_lastTime
- || m_lastAngle != other.m_lastAngle)
+ if (m_d->hasLastInfo) {
+ if (m_d->lastPosition != other.m_d->lastPosition || m_d->lastTime != other.m_d->lastTime
+ || m_d->lastAngle != other.m_d->lastAngle)
{
return false;
}
}
return true;
}
bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const
{
return !(*this == other);
}
+KisDistanceInitInfo &KisDistanceInitInfo::operator=(const KisDistanceInitInfo &rhs)
+{
+ *m_d = *rhs.m_d;
+ return *this;
+}
+
KisDistanceInformation KisDistanceInitInfo::makeDistInfo()
{
- if (m_hasLastInfo) {
- return KisDistanceInformation(m_lastPosition, m_lastTime, m_lastAngle,
- m_spacingUpdateInterval);
+ if (m_d->hasLastInfo) {
+ return KisDistanceInformation(m_d->lastPosition, m_d->lastTime, m_d->lastAngle,
+ m_d->spacingUpdateInterval, m_d->timingUpdateInterval);
}
else {
- return KisDistanceInformation(m_spacingUpdateInterval);
+ return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval);
}
}
void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const
{
- elt.setAttribute("spacingUpdateInterval", QString::number(m_spacingUpdateInterval, 'g', 15));
- if (m_hasLastInfo) {
+ elt.setAttribute("spacingUpdateInterval", QString::number(m_d->spacingUpdateInterval, 'g', 15));
+ elt.setAttribute("timingUpdateInterval", QString::number(m_d->timingUpdateInterval, 'g', 15));
+ if (m_d->hasLastInfo) {
QDomElement lastInfoElt = doc.createElement("LastInfo");
- lastInfoElt.setAttribute("lastPosX", QString::number(m_lastPosition.x(), 'g', 15));
- lastInfoElt.setAttribute("lastPosY", QString::number(m_lastPosition.y(), 'g', 15));
- lastInfoElt.setAttribute("lastTime", QString::number(m_lastTime, 'g', 15));
- lastInfoElt.setAttribute("lastAngle", QString::number(m_lastAngle, 'g', 15));
+ lastInfoElt.setAttribute("lastPosX", QString::number(m_d->lastPosition.x(), 'g', 15));
+ lastInfoElt.setAttribute("lastPosY", QString::number(m_d->lastPosition.y(), 'g', 15));
+ lastInfoElt.setAttribute("lastTime", QString::number(m_d->lastTime, 'g', 15));
+ lastInfoElt.setAttribute("lastAngle", QString::number(m_d->lastAngle, 'g', 15));
elt.appendChild(lastInfoElt);
}
}
KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt)
{
const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval",
QString::number(LONG_TIME, 'g', 15))));
+ const qreal timingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("timingUpdateInterval",
+ QString::number(LONG_TIME, 'g', 15))));
const QDomElement lastInfoElt = elt.firstChildElement("LastInfo");
const bool hasLastInfo = !lastInfoElt.isNull();
if (hasLastInfo) {
const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX",
"0.0")));
const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY",
"0.0")));
const qreal lastTime = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastTime",
"0.0")));
const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle",
"0.0")));
return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastTime, lastAngle,
- spacingUpdateInterval);
+ spacingUpdateInterval, timingUpdateInterval);
}
else {
- return KisDistanceInitInfo(spacingUpdateInterval);
+ return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval);
}
}
KisDistanceInformation::KisDistanceInformation()
: m_d(new Private)
{
}
-KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval)
+KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval,
+ qreal timingUpdateInterval)
: m_d(new Private)
{
m_d->spacingUpdateInterval = spacingUpdateInterval;
+ m_d->timingUpdateInterval = timingUpdateInterval;
}
KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
qreal lastTime,
qreal lastAngle)
: m_d(new Private)
{
m_d->lastPosition = lastPosition;
m_d->lastTime = lastTime;
m_d->lastAngle = lastAngle;
m_d->lastDabInfoValid = true;
}
KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
qreal lastTime,
qreal lastAngle,
- qreal spacingUpdateInterval)
+ qreal spacingUpdateInterval,
+ qreal timingUpdateInterval)
: KisDistanceInformation(lastPosition, lastTime, lastAngle)
{
m_d->spacingUpdateInterval = spacingUpdateInterval;
+ m_d->timingUpdateInterval = timingUpdateInterval;
}
KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs)
: m_d(new Private(*rhs.m_d))
{
}
KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail)
: m_d(new Private(*rhs.m_d))
{
KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid &&
"The distance information "
"should be cloned before the "
"actual painting is started");
KisLodTransform t(levelOfDetail);
m_d->lastPosition = t.map(m_d->lastPosition);
}
KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs)
{
*m_d = *rhs.m_d;
return *this;
}
void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition, qreal lastTime,
qreal lastAngle)
{
m_d->lastPosition = lastPosition;
m_d->lastTime = lastTime;
m_d->lastAngle = lastAngle;
m_d->lastDabInfoValid = true;
}
KisDistanceInformation::~KisDistanceInformation()
{
delete m_d;
}
const KisSpacingInformation& KisDistanceInformation::currentSpacing() const
{
return m_d->spacing;
}
-void KisDistanceInformation::setSpacing(const KisSpacingInformation &spacing)
+void KisDistanceInformation::updateSpacing(const KisSpacingInformation &spacing)
{
m_d->spacing = spacing;
+ m_d->timeSinceSpacingUpdate = 0.0;
}
bool KisDistanceInformation::needsSpacingUpdate() const
{
- // Only require spacing updates between dabs if timed spacing is enabled.
- return m_d->spacing.isTimedSpacingEnabled() && m_d->accumTime >= m_d->spacingUpdateInterval;
+ return m_d->timeSinceSpacingUpdate >= m_d->spacingUpdateInterval;
+}
+
+const KisTimingInformation &KisDistanceInformation::currentTiming() const
+{
+ return m_d->timing;
+}
+
+void KisDistanceInformation::updateTiming(const KisTimingInformation &timing)
+{
+ m_d->timing = timing;
+ m_d->timeSinceTimingUpdate = 0.0;
+}
+
+bool KisDistanceInformation::needsTimingUpdate() const
+{
+ return m_d->timeSinceTimingUpdate >= m_d->timingUpdateInterval;
}
bool KisDistanceInformation::hasLastDabInformation() const
{
return m_d->lastDabInfoValid;
}
QPointF KisDistanceInformation::lastPosition() const
{
return m_d->lastPosition;
}
qreal KisDistanceInformation::lastTime() const
{
return m_d->lastTime;
}
qreal KisDistanceInformation::lastDrawingAngle() const
{
return m_d->lastAngle;
}
bool KisDistanceInformation::hasLastPaintInformation() const
{
return m_d->lastPaintInfoValid;
}
const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const
{
return m_d->lastPaintInformation;
}
bool KisDistanceInformation::isStarted() const
{
return m_d->lastPaintInfoValid;
}
void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info,
- const KisSpacingInformation &spacing)
+ const KisSpacingInformation &spacing,
+ const KisTimingInformation &timing)
{
m_d->totalDistance += KisAlgebra2D::norm(info.pos() - m_d->lastPosition);
m_d->lastPaintInformation = info;
m_d->lastPaintInfoValid = true;
m_d->lastAngle = nextDrawingAngle(info.pos());
m_d->lastPosition = info.pos();
m_d->lastTime = info.currentTime();
m_d->lastDabInfoValid = true;
m_d->spacing = spacing;
+ m_d->timing = timing;
}
qreal KisDistanceInformation::getNextPointPosition(const QPointF &start,
const QPointF &end,
qreal startTime,
qreal endTime)
{
// Compute interpolation factor based on distance.
qreal distanceFactor = -1.0;
if (m_d->spacing.isDistanceSpacingEnabled()) {
distanceFactor = m_d->spacing.isIsotropic() ?
getNextPointPositionIsotropic(start, end) :
getNextPointPositionAnisotropic(start, end);
}
// Compute interpolation factor based on time.
qreal timeFactor = -1.0;
- if (m_d->spacing.isTimedSpacingEnabled()) {
+ if (m_d->timing.isTimedSpacingEnabled()) {
timeFactor = getNextPointPositionTimed(startTime, endTime);
}
// Return the distance-based or time-based factor, whichever is smallest.
+ qreal t = -1.0;
if (distanceFactor < 0.0) {
- return timeFactor;
+ t = timeFactor;
} else if (timeFactor < 0.0) {
- return distanceFactor;
+ t = distanceFactor;
} else {
- return qMin(distanceFactor, timeFactor);
+ t = qMin(distanceFactor, timeFactor);
}
+
+ // If we aren't ready to paint a dab, accumulate time for the spacing/timing updates that might
+ // be needed between dabs.
+ if (t < 0.0) {
+ m_d->timeSinceSpacingUpdate += endTime - startTime;
+ m_d->timeSinceTimingUpdate += endTime - startTime;
+ }
+
+ // If we are ready to paint a dab, reset the accumulated time for spacing/timing updates.
+ else {
+ m_d->timeSinceSpacingUpdate = 0.0;
+ m_d->timeSinceTimingUpdate = 0.0;
+ }
+
+ return t;
}
qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start,
const QPointF &end)
{
qreal distance = m_d->accumDistance.x();
qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
if (start == end) {
return -1;
}
qreal dragVecLength = QVector2D(end - start).length();
qreal nextPointDistance = spacing - distance;
qreal t;
// nextPointDistance can sometimes be negative if the spacing info has been modified since the
// last interpolation attempt. In that case, have a point painted immediately.
if (nextPointDistance <= 0.0) {
resetAccumulators();
t = 0.0;
}
else if (nextPointDistance <= dragVecLength) {
t = nextPointDistance / dragVecLength;
resetAccumulators();
} else {
t = -1;
m_d->accumDistance.rx() += dragVecLength;
}
return t;
}
qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start,
const QPointF &end)
{
if (start == end) {
return -1;
}
qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y());
qreal x = m_d->accumDistance.x();
qreal y = m_d->accumDistance.y();
qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1;
// If the distance accumulator is already past the spacing ellipse, have a point painted
// immediately. This can happen if the spacing info has been modified since the last
// interpolation attempt.
if (gamma >= 0.0) {
resetAccumulators();
return 0.0;
}
static const qreal eps = 2e-3; // < 0.2 deg
qreal currentRotation = m_d->spacing.rotation();
if (m_d->spacing.coordinateSystemFlipped()) {
currentRotation = 2 * M_PI - currentRotation;
}
QPointF diff = end - start;
if (currentRotation > eps) {
QTransform rot;
// since the ellipse is symmetrical, the sign
// of rotation doesn't matter
rot.rotateRadians(currentRotation);
diff = rot.map(diff);
}
qreal dx = qAbs(diff.x());
qreal dy = qAbs(diff.y());
qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev);
qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev;
qreal D_4 = pow2(beta) - alpha * gamma;
qreal t = -1.0;
if (D_4 >= 0) {
qreal k = (-beta + qSqrt(D_4)) / alpha;
if (k >= 0.0 && k <= 1.0) {
t = k;
resetAccumulators();
} else {
m_d->accumDistance += KisAlgebra2D::abs(diff);
}
} else {
warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened.";
}
return t;
}
qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime,
qreal endTime)
{
// If start time is not before end time, do not interpolate.
if (!(startTime < endTime)) {
return -1.0;
}
- qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->spacing.timedSpacingInterval(),
+ qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->timing.timedSpacingInterval(),
MAX_TIMED_INTERVAL);
qreal nextPointInterval = timedSpacingInterval - m_d->accumTime;
qreal t = -1.0;
// nextPointInterval can sometimes be negative if the spacing info has been modified since the
// last interpolation attempt. In that case, have a point painted immediately.
if (nextPointInterval <= 0.0) {
resetAccumulators();
t = 0.0;
}
else if (nextPointInterval <= endTime - startTime) {
resetAccumulators();
t = nextPointInterval / (endTime - startTime);
}
else {
m_d->accumTime += endTime - startTime;
t = -1.0;
}
return t;
}
void KisDistanceInformation::resetAccumulators()
{
m_d->accumDistance = QPointF();
m_d->accumTime = 0.0;
}
bool KisDistanceInformation::hasLockedDrawingAngle() const
{
return m_d->hasLockedDrawingAngle;
}
qreal KisDistanceInformation::lockedDrawingAngle() const
{
return m_d->lockedDrawingAngle;
}
void KisDistanceInformation::setLockedDrawingAngle(qreal angle)
{
m_d->hasLockedDrawingAngle = true;
m_d->lockedDrawingAngle = angle;
}
qreal KisDistanceInformation::nextDrawingAngle(const QPointF &nextPos,
bool considerLockedAngle) const
{
if (!m_d->lastDabInfoValid) {
warnKrita << "KisDistanceInformation::nextDrawingAngle()" << "No last dab data";
return 0.0;
}
// Compute the drawing angle. If the new position is the same as the previous position, an angle
// can't be computed. In that case, act as if the angle is the same as in the previous dab.
return drawingAngleImpl(m_d->lastPosition, nextPos, considerLockedAngle, m_d->lastAngle);
}
QPointF KisDistanceInformation::nextDrawingDirectionVector(const QPointF &nextPos,
bool considerLockedAngle) const
{
if (!m_d->lastDabInfoValid) {
warnKrita << "KisDistanceInformation::nextDrawingDirectionVector()" << "No last dab data";
return QPointF(1.0, 0.0);
}
// Compute the direction vector. If the new position is the same as the previous position, a
// direction can't be computed. In that case, act as if the direction is the same as in the
// previous dab.
return drawingDirectionVectorImpl(m_d->lastPosition, nextPos, considerLockedAngle,
m_d->lastAngle);
}
qreal KisDistanceInformation::scalarDistanceApprox() const
{
return m_d->totalDistance;
}
qreal KisDistanceInformation::drawingAngleImpl(const QPointF &start, const QPointF &end,
bool considerLockedAngle, qreal defaultAngle) const
{
if (m_d->hasLockedDrawingAngle && considerLockedAngle) {
return m_d->lockedDrawingAngle;
}
// If the start and end positions are the same, we can't compute an angle. In that case, use the
// provided default.
return KisAlgebra2D::directionBetweenPoints(start, end, defaultAngle);
}
QPointF KisDistanceInformation::drawingDirectionVectorImpl(const QPointF &start, const QPointF &end,
bool considerLockedAngle,
qreal defaultAngle) const
{
if (m_d->hasLockedDrawingAngle && considerLockedAngle) {
return QPointF(cos(m_d->lockedDrawingAngle), sin(m_d->lockedDrawingAngle));
}
// If the start and end positions are the same, we can't compute a drawing direction. In that
// case, use the provided default.
if (KisAlgebra2D::fuzzyPointCompare(start, end)) {
return QPointF(cos(defaultAngle), sin(defaultAngle));
}
const QPointF diff(end - start);
return KisAlgebra2D::normalize(diff);
}
diff --git a/libs/image/kis_distance_information.h b/libs/image/kis_distance_information.h
index 9a64340f86..5c8462e2a4 100644
--- a/libs/image/kis_distance_information.h
+++ b/libs/image/kis_distance_information.h
@@ -1,335 +1,203 @@
/*
* Copyright (c) 2010 Cyrille Berger
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DISTANCE_INFORMATION_H_
#define _KIS_DISTANCE_INFORMATION_H_
#include
#include
#include
#include
#include "kritaimage_export.h"
class KisPaintInformation;
+class KisSpacingInformation;
+class KisTimingInformation;
class KisDistanceInformation;
-/**
- * A time in milliseconds that is assumed to be longer than any stroke (or other paint operation)
- * will ever last. This is used instead of infinity to avoid potential errors. The value is
- * approximately ten years.
- */
-const qreal LONG_TIME = 320000000000.0;
-
-/**
- * This structure contains information about the desired spacing
- * requested by the paintAt call
- */
-class KisSpacingInformation {
-public:
- explicit KisSpacingInformation()
- : m_distanceSpacingEnabled(true)
- , m_distanceSpacing(0.0, 0.0)
- , m_timedSpacingEnabled(false)
- , m_timedSpacingInterval(0.0)
- , m_rotation(0.0)
- , m_coordinateSystemFlipped(false)
- {
- }
-
- explicit KisSpacingInformation(qreal isotropicSpacing)
- : m_distanceSpacingEnabled(true)
- , m_distanceSpacing(isotropicSpacing, isotropicSpacing)
- , m_timedSpacingEnabled(false)
- , m_timedSpacingInterval(0.0)
- , m_rotation(0.0)
- , m_coordinateSystemFlipped(false)
- {
- }
-
- explicit KisSpacingInformation(const QPointF &anisotropicSpacing, qreal rotation, bool coordinateSystemFlipped)
- : m_distanceSpacingEnabled(true)
- , m_distanceSpacing(anisotropicSpacing)
- , m_timedSpacingEnabled(false)
- , m_timedSpacingInterval(0.0)
- , m_rotation(rotation)
- , m_coordinateSystemFlipped(coordinateSystemFlipped)
- {
- }
-
- explicit KisSpacingInformation(qreal isotropicSpacing,
- qreal timedSpacingInterval)
- : m_distanceSpacingEnabled(true)
- , m_distanceSpacing(isotropicSpacing, isotropicSpacing)
- , m_timedSpacingEnabled(true)
- , m_timedSpacingInterval(timedSpacingInterval)
- , m_rotation(0.0)
- , m_coordinateSystemFlipped(false)
- {
- }
-
- explicit KisSpacingInformation(const QPointF &anisotropicSpacing,
- qreal rotation,
- bool coordinateSystemFlipped,
- qreal timedSpacingInterval)
- : m_distanceSpacingEnabled(true)
- , m_distanceSpacing(anisotropicSpacing)
- , m_timedSpacingEnabled(true)
- , m_timedSpacingInterval(timedSpacingInterval)
- , m_rotation(rotation)
- , m_coordinateSystemFlipped(coordinateSystemFlipped)
- {
- }
-
- explicit KisSpacingInformation(bool distanceSpacingEnabled,
- qreal isotropicSpacing,
- bool timedSpacingEnabled,
- qreal timedSpacingInterval)
- : m_distanceSpacingEnabled(distanceSpacingEnabled)
- , m_distanceSpacing(isotropicSpacing, isotropicSpacing)
- , m_timedSpacingEnabled(timedSpacingEnabled)
- , m_timedSpacingInterval(timedSpacingInterval)
- , m_rotation(0.0)
- , m_coordinateSystemFlipped(false)
- {
- }
-
- explicit KisSpacingInformation(bool distanceSpacingEnabled,
- const QPointF &anisotropicSpacing,
- qreal rotation,
- bool coordinateSystemFlipped,
- bool timedSpacingEnabled,
- qreal timedSpacingInterval)
- : m_distanceSpacingEnabled(distanceSpacingEnabled)
- , m_distanceSpacing(anisotropicSpacing)
- , m_timedSpacingEnabled(timedSpacingEnabled)
- , m_timedSpacingInterval(timedSpacingInterval)
- , m_rotation(rotation)
- , m_coordinateSystemFlipped(coordinateSystemFlipped)
- {
- }
-
- /**
- * @return True if and only if distance-based spacing is enabled.
- */
- inline bool isDistanceSpacingEnabled() const {
- return m_distanceSpacingEnabled;
- }
-
- inline QPointF distanceSpacing() const {
- return m_distanceSpacing;
- }
-
- /**
- * @return True if and only if time-based spacing is enabled.
- */
- inline bool isTimedSpacingEnabled() const {
- return m_timedSpacingEnabled;
- }
-
- /**
- * @return The desired maximum amount of time between dabs, in milliseconds. Returns LONG_TIME
- * if time-based spacing is disabled.
- */
- inline qreal timedSpacingInterval() const {
- return isTimedSpacingEnabled() ?
- m_timedSpacingInterval :
- LONG_TIME;
- }
-
- inline bool isIsotropic() const {
- return m_distanceSpacing.x() == m_distanceSpacing.y();
- }
-
- inline qreal scalarApprox() const {
- return isIsotropic() ? m_distanceSpacing.x() : QVector2D(m_distanceSpacing).length();
- }
-
- inline qreal rotation() const {
- return m_rotation;
- }
-
- bool coordinateSystemFlipped() const {
- return m_coordinateSystemFlipped;
- }
-
-private:
-
- // Distance-based spacing
- bool m_distanceSpacingEnabled;
- QPointF m_distanceSpacing;
-
- // Time-based spacing (interval is in milliseconds)
- bool m_timedSpacingEnabled;
- qreal m_timedSpacingInterval;
-
- qreal m_rotation;
- bool m_coordinateSystemFlipped;
-};
-
/**
* Represents some information that can be used to initialize a KisDistanceInformation object. The
* main purpose of this class is to allow serialization of KisDistanceInformation initial settings
* to XML.
*/
class KRITAIMAGE_EXPORT KisDistanceInitInfo {
public:
/**
- * Creates a KisDistanceInitInfo with no initial last dab information, and spacing update
- * interval set to LONG_TIME.
+ * Creates a KisDistanceInitInfo with no initial last dab information, and spacing and timing
+ * update intervals set to LONG_TIME.
*/
explicit KisDistanceInitInfo();
/**
* Creates a KisDistanceInitInfo with no initial last dab information, and the specified spacing
- * update interval.
+ * and timing update intervals.
*/
- explicit KisDistanceInitInfo(qreal spacingUpdateInterval);
+ explicit KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval);
/**
- * Creates a KisDistanceInitInfo with the specified last dab information, and spacing update
- * interval set to LONG_TIME.
+ * Creates a KisDistanceInitInfo with the specified last dab information, and spacing and timing
+ * update intervals set to LONG_TIME.
*/
explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle);
/**
- * Creates a KisDistanceInitInfo with the specified last dab information and spacing update
- * interval.
+ * Creates a KisDistanceInitInfo with the specified last dab information and spacing and timing
+ * update intervals.
*/
explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle,
- qreal spacingUpdateInterval);
+ qreal spacingUpdateInterval, qreal timingUpdateInterval);
+
+ KisDistanceInitInfo(const KisDistanceInitInfo &rhs);
+
+ ~KisDistanceInitInfo();
bool operator==(const KisDistanceInitInfo &other) const;
bool operator!=(const KisDistanceInitInfo &other) const;
+ KisDistanceInitInfo &operator=(const KisDistanceInitInfo &rhs);
+
/**
* Constructs a KisDistanceInformation with initial settings based on this object.
*/
KisDistanceInformation makeDistInfo();
void toXML(QDomDocument &doc, QDomElement &elt) const;
static KisDistanceInitInfo fromXML(const QDomElement &elt);
private:
- // Indicates whether lastPosition, lastTime, and lastAngle are valid or not.
- bool m_hasLastInfo;
-
- QPointF m_lastPosition;
- qreal m_lastTime;
- qreal m_lastAngle;
-
- qreal m_spacingUpdateInterval;
+ struct Private;
+ Private * const m_d;
};
/**
- * This structure is used as return value of paintLine to contain
- * information that is needed to be passed for the next call.
+ * This structure keeps track of distance and timing information during a stroke, e.g. the time or
+ * distance moved since the last dab.
*/
class KRITAIMAGE_EXPORT KisDistanceInformation {
public:
KisDistanceInformation();
- KisDistanceInformation(qreal spacingUpdateInterval);
+ KisDistanceInformation(qreal spacingUpdateInterval, qreal timingUpdateInterval);
KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle);
/**
* @param spacingUpdateInterval The amount of time allowed between spacing updates, in
- * milliseconds. Only used when timed spacing is enabled.
+ * milliseconds. Use LONG_TIME to only allow spacing updates when a
+ * dab is painted.
+ * @param timingUpdateInterval The amount of time allowed between time-based spacing updates, in
+ * milliseconds. Use LONG_TIME to only allow timing updates when a
+ * dab is painted.
*/
KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle,
- qreal spacingUpdateInterval);
+ qreal spacingUpdateInterval, qreal timingUpdateInterval);
KisDistanceInformation(const KisDistanceInformation &rhs);
KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail);
KisDistanceInformation& operator=(const KisDistanceInformation &rhs);
~KisDistanceInformation();
const KisSpacingInformation& currentSpacing() const;
- void setSpacing(const KisSpacingInformation &spacing);
+ void updateSpacing(const KisSpacingInformation &spacing);
/**
* Returns true if this KisDistanceInformation should have its spacing information updated
* immediately (regardless of whether a dab is ready to be painted).
*/
bool needsSpacingUpdate() const;
+ const KisTimingInformation ¤tTiming() const;
+ void updateTiming(const KisTimingInformation &timing);
+ /**
+ * Returns true if this KisDistanceInformation should have its timing information updated
+ * immediately (regardless of whether a dab is ready to be painted).
+ */
+ bool needsTimingUpdate() const;
+
bool hasLastDabInformation() const;
QPointF lastPosition() const;
qreal lastTime() const;
qreal lastDrawingAngle() const;
bool hasLastPaintInformation() const;
const KisPaintInformation& lastPaintInformation() const;
+ /**
+ * @param spacing The new effective spacing after the dab. (Painting a dab is always supposed to
+ * cause a spacing update.)
+ * @param timing The new effective timing after the dab. (Painting a dab is always supposed to
+ * cause a timing update.)
+ */
void registerPaintedDab(const KisPaintInformation &info,
- const KisSpacingInformation &spacing);
+ const KisSpacingInformation &spacing,
+ const KisTimingInformation &timing);
qreal getNextPointPosition(const QPointF &start,
const QPointF &end,
qreal startTime,
qreal endTime);
/**
* \return true if at least one dab has been painted with this
* distance information
*/
bool isStarted() const;
bool hasLockedDrawingAngle() const;
qreal lockedDrawingAngle() const;
void setLockedDrawingAngle(qreal angle);
/**
* Computes the next drawing angle assuming that the next painting position will be nextPos.
* This method should not be called when hasLastDabInformation() is false.
*/
qreal nextDrawingAngle(const QPointF &nextPos, bool considerLockedAngle = true) const;
/**
* Returns a unit vector pointing in the direction that would have been indicated by a call to
* nextDrawingAngle. This method should not be called when hasLastDabInformation() is false.
*/
QPointF nextDrawingDirectionVector(const QPointF &nextPos,
bool considerLockedAngle = true) const;
qreal scalarDistanceApprox() const;
void overrideLastValues(const QPointF &lastPosition, qreal lastTime, qreal lastAngle);
private:
qreal getNextPointPositionIsotropic(const QPointF &start,
const QPointF &end);
qreal getNextPointPositionAnisotropic(const QPointF &start,
const QPointF &end);
qreal getNextPointPositionTimed(qreal startTime,
qreal endTime);
void resetAccumulators();
qreal drawingAngleImpl(const QPointF &start, const QPointF &end,
bool considerLockedAngle = true, qreal defaultAngle = 0.0) const;
QPointF drawingDirectionVectorImpl(const QPointF &start, const QPointF &end,
bool considerLockedAngle = true,
qreal defaultAngle = 0.0) const;
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc
index 1a0c304bb7..0343f393fd 100644
--- a/libs/image/kis_painter.cc
+++ b/libs/image/kis_painter.cc
@@ -1,2911 +1,2915 @@
/*
* Copyright (c) 2002 Patrick Julien