diff --git a/libs/ui/canvas/kis_coordinates_converter.cpp b/libs/ui/canvas/kis_coordinates_converter.cpp index 2479ee543a..5a34332399 100644 --- a/libs/ui/canvas/kis_coordinates_converter.cpp +++ b/libs/ui/canvas/kis_coordinates_converter.cpp @@ -1,428 +1,435 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * 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 "kis_coordinates_converter.h" #include #include #include #include struct KisCoordinatesConverter::Private { Private(): isXAxisMirrored(false), isYAxisMirrored(false), rotationAngle(0.0) { } KisImageWSP image; bool isXAxisMirrored; bool isYAxisMirrored; qreal rotationAngle; QSizeF canvasWidgetSize; QPointF documentOffset; QTransform flakeToWidget; QTransform imageToDocument; QTransform documentToFlake; QTransform widgetToViewport; }; /** * When vastScrolling value is less than 0.5 it is possible * that the whole scrolling area (viewport) will be smaller than * the size of the widget. In such cases the image should be * centered in the widget. Previously we used a special parameter * documentOrigin for this purpose, now the value for this * centering is calculated dynamically, helping the offset to * center the image inside the widget * * Note that the correction is null when the size of the document * plus vast scrolling reserve is larger than the widget. This * is always true for vastScrolling parameter > 0.5. */ QPointF KisCoordinatesConverter::centeringCorrection() const { KisConfig cfg; QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size(); QPointF dPoint(documentSize.width(), documentSize.height()); QPointF wPoint(m_d->canvasWidgetSize.width(), m_d->canvasWidgetSize.height()); QPointF minOffset = -cfg.vastScrolling() * wPoint; QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint; QPointF range = maxOffset - minOffset; range.rx() = qMin(range.x(), (qreal)0.0); range.ry() = qMin(range.y(), (qreal)0.0); range /= 2; return -range; } /** * The document offset and the position of the top left corner of the * image must always coincide, that is why we need to correct them to * and fro. * * When we change zoom level, the calculation of the new offset is * done by KoCanvasControllerWidget, that is why we just passively fix * the flakeToWidget transform to conform the offset and wait until * the canvas controller will recenter us. * * But when we do our own transformations of the canvas, like rotation * and mirroring, we cannot rely on the centering of the canvas * controller and we do it ourselves. Then we just set new offset and * return its value to be set in the canvas controller explicitly. */ void KisCoordinatesConverter::correctOffsetToTransformation() { m_d->documentOffset = -(imageRectInWidgetPixels().topLeft() - centeringCorrection()).toPoint(); } void KisCoordinatesConverter::correctTransformationToOffset() { QPointF topLeft = imageRectInWidgetPixels().topLeft(); QPointF diff = (-topLeft) - m_d->documentOffset; diff += centeringCorrection(); m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); } void KisCoordinatesConverter::recalculateTransformations() { if(!m_d->image) return; m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(), 1 / m_d->image->yRes()); qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY); correctTransformationToOffset(); QRectF irect = imageRectInWidgetPixels(); QRectF wrect = QRectF(QPoint(0,0), m_d->canvasWidgetSize); QRectF rrect = irect & wrect; QTransform reversedTransform = flakeToWidgetTransform().inverted(); QRectF canvasBounds = reversedTransform.mapRect(rrect); QPointF offset = canvasBounds.topLeft(); m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y()); } KisCoordinatesConverter::KisCoordinatesConverter() : m_d(new Private) { } KisCoordinatesConverter::~KisCoordinatesConverter() { delete m_d; } void KisCoordinatesConverter::setCanvasWidgetSize(QSize size) { m_d->canvasWidgetSize = size; recalculateTransformations(); } void KisCoordinatesConverter::setImage(KisImageWSP image) { m_d->image = image; recalculateTransformations(); } void KisCoordinatesConverter::setDocumentOffset(const QPoint& offset) { QPointF diff = m_d->documentOffset - offset; m_d->documentOffset = offset; m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); recalculateTransformations(); } QPoint KisCoordinatesConverter::documentOffset() const { return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y())); } qreal KisCoordinatesConverter::rotationAngle() const { return m_d->rotationAngle; } void KisCoordinatesConverter::setZoom(qreal zoom) { KoZoomHandler::setZoom(zoom); recalculateTransformations(); } qreal KisCoordinatesConverter::effectiveZoom() const { qreal scaleX, scaleY; this->imageScale(&scaleX, &scaleY); if (scaleX != scaleY) { qWarning() << "WARNING: Zoom is not isotropic!" << ppVar(scaleX) << ppVar(scaleY) << ppVar(qFuzzyCompare(scaleX, scaleY)); } // zoom by average of x and y return 0.5 * (scaleX + scaleY); } QPoint KisCoordinatesConverter::rotate(QPointF center, qreal angle) { QTransform rot; rot.rotate(angle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0); correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis, bool keepOrientation) { bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis; bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis; qreal scaleX = doXMirroring ? -1.0 : 1.0; qreal scaleY = doYMirroring ? -1.0 : 1.0; QTransform mirror = QTransform::fromScale(scaleX, scaleY); QTransform rot; rot.rotate(m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); if(keepOrientation) m_d->flakeToWidget *= rot.inverted(); m_d->flakeToWidget *= mirror; if(keepOrientation) m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(),center.y()); if(!keepOrientation && (doXMirroring ^ doYMirroring)) m_d->rotationAngle = -m_d->rotationAngle; m_d->isXAxisMirrored = mirrorXAxis; m_d->isYAxisMirrored = mirrorYAxis; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } bool KisCoordinatesConverter::xAxisMirrored() const { return m_d->isXAxisMirrored; } bool KisCoordinatesConverter::yAxisMirrored() const { return m_d->isYAxisMirrored; } QPoint KisCoordinatesConverter::resetRotation(QPointF center) { QTransform rot; rot.rotate(-m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = 0.0; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QTransform KisCoordinatesConverter::imageToWidgetTransform() const{ return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::imageToDocumentTransform() const { return m_d->imageToDocument; } QTransform KisCoordinatesConverter::documentToFlakeTransform() const { return m_d->documentToFlake; } QTransform KisCoordinatesConverter::flakeToWidgetTransform() const { return m_d->flakeToWidget; } QTransform KisCoordinatesConverter::documentToWidgetTransform() const { return m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::viewportToWidgetTransform() const { return m_d->widgetToViewport.inverted(); } QTransform KisCoordinatesConverter::imageToViewportTransform() const { return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget * m_d->widgetToViewport; } void KisCoordinatesConverter::getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *polygon) const { /** * Qt has different rounding for QPainter::drawRect/drawImage. * The image is rounded mathematically, while rect in aligned * to the next integer. That causes transparent line appear on * the canvas. * * See: https://bugreports.qt.nokia.com/browse/QTBUG-22827 */ QRectF imageRect = imageRectInViewportPixels(); imageRect.adjust(0,0,-0.5,-0.5); KisConfig cfg; if (cfg.scrollCheckers()) { *transform = viewportToWidgetTransform(); *polygon = imageRect; *brushOrigin = imageToViewport(QPointF(0,0)); } else { *transform = QTransform(); *polygon = viewportToWidgetTransform().map(imageRect); *brushOrigin = QPoint(0,0); } } void KisCoordinatesConverter::getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const { if(scrollCheckers) { *textureTransform = QTransform(); *textureRect = QRectF(0, 0, viewportRect.width(),viewportRect.height()); } else { *textureTransform = viewportToWidgetTransform(); *textureRect = viewportRect; } *modelTransform = viewportToWidgetTransform(); *modelRect = viewportRect; } QPointF KisCoordinatesConverter::imageCenterInWidgetPixel() const { if(!m_d->image) return QPointF(); QPolygonF poly = imageToWidget(QPolygon(m_d->image->bounds())); return (poly[0] + poly[1] + poly[2] + poly[3]) / 4.0; } // these functions return a bounding rect if the canvas is rotated QRectF KisCoordinatesConverter::imageRectInWidgetPixels() const { if(!m_d->image) return QRectF(); return imageToWidget(m_d->image->bounds()); } QRectF KisCoordinatesConverter::imageRectInViewportPixels() const { if(!m_d->image) return QRectF(); return imageToViewport(m_d->image->bounds()); } QRect KisCoordinatesConverter::imageRectInImagePixels() const { + if(!m_d->image) return QRect(); return m_d->image->bounds(); } +QRectF KisCoordinatesConverter::imageRectInDocumentPixels() const +{ + if(!m_d->image) return QRectF(); + return imageToDocument(m_d->image->bounds()); +} + QSizeF KisCoordinatesConverter::imageSizeInFlakePixels() const { if(!m_d->image) return QSizeF(); qreal scaleX, scaleY; imageScale(&scaleX, &scaleY); QSize imageSize = m_d->image->size(); return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY); } QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const { return widgetToFlake(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QPointF KisCoordinatesConverter::flakeCenterPoint() const { QRectF widgetRect = widgetRectInFlakePixels(); return QPointF(widgetRect.left() + widgetRect.width() / 2, widgetRect.top() + widgetRect.height() / 2); } QPointF KisCoordinatesConverter::widgetCenterPoint() const { return QPointF(m_d->canvasWidgetSize.width() / 2.0, m_d->canvasWidgetSize.height() / 2.0); } void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const { if(!m_d->image) { *scaleX = 1.0; *scaleY = 1.0; return; } // get the x and y zoom level of the canvas qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); // Get the KisImage resolution qreal resX = m_d->image->xRes(); qreal resY = m_d->image->yRes(); // Compute the scale factors *scaleX = zoomX / resX; *scaleY = zoomY / resY; } diff --git a/libs/ui/canvas/kis_coordinates_converter.h b/libs/ui/canvas/kis_coordinates_converter.h index 7358edbbd4..a016e37c57 100644 --- a/libs/ui/canvas/kis_coordinates_converter.h +++ b/libs/ui/canvas/kis_coordinates_converter.h @@ -1,161 +1,162 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * 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_COORDINATES_CONVERTER_H #define KIS_COORDINATES_CONVERTER_H #include #include #include "kritaui_export.h" #include "kis_types.h" #define EPSILON 1e-6 #define SCALE_LESS_THAN(scX, scY, value) \ (scX < (value) - EPSILON && scY < (value) - EPSILON) #define SCALE_MORE_OR_EQUAL_TO(scX, scY, value) \ (scX > (value) - EPSILON && scY > (value) - EPSILON) namespace _Private { template struct Traits { typedef T Result; static T map(const QTransform& transform, const T& obj) { return transform.map(obj); } }; template<> struct Traits { typedef QRectF Result; static QRectF map(const QTransform& transform, const QRectF& rc) { return transform.mapRect(rc); } }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; } class KRITAUI_EXPORT KisCoordinatesConverter: public KoZoomHandler { public: KisCoordinatesConverter(); ~KisCoordinatesConverter(); void setCanvasWidgetSize(QSize size); void setImage(KisImageWSP image); void setDocumentOffset(const QPoint &offset); QPoint documentOffset() const; qreal rotationAngle() const; QPoint rotate(QPointF center, qreal angle); QPoint mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis, bool keepOrientation=false); bool xAxisMirrored() const; bool yAxisMirrored() const; QPoint resetRotation(QPointF center); virtual void setZoom(qreal zoom); /** * A composition of to scale methods: zoom level + image resolution */ qreal effectiveZoom() const; template typename _Private::Traits::Result imageToViewport(const T& obj) const { return _Private::Traits::map(imageToViewportTransform(), obj); } template typename _Private::Traits::Result viewportToImage(const T& obj) const { return _Private::Traits::map(imageToViewportTransform().inverted(), obj); } template typename _Private::Traits::Result flakeToWidget(const T& obj) const { return _Private::Traits::map(flakeToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToFlake(const T& obj) const { return _Private::Traits::map(flakeToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result widgetToViewport(const T& obj) const { return _Private::Traits::map(viewportToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result viewportToWidget(const T& obj) const { return _Private::Traits::map(viewportToWidgetTransform(), obj); } template typename _Private::Traits::Result documentToWidget(const T& obj) const { return _Private::Traits::map(documentToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToDocument(const T& obj) const { return _Private::Traits::map(documentToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result imageToDocument(const T& obj) const { return _Private::Traits::map(imageToDocumentTransform(), obj); } template typename _Private::Traits::Result documentToImage(const T& obj) const { return _Private::Traits::map(imageToDocumentTransform().inverted(), obj); } template typename _Private::Traits::Result documentToFlake(const T& obj) const { return _Private::Traits::map(documentToFlakeTransform(), obj); } template typename _Private::Traits::Result flakeToDocument(const T& obj) const { return _Private::Traits::map(documentToFlakeTransform().inverted(), obj); } template typename _Private::Traits::Result imageToWidget(const T& obj) const { return _Private::Traits::map(imageToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToImage(const T& obj) const { return _Private::Traits::map(imageToWidgetTransform().inverted(), obj); } QTransform imageToWidgetTransform() const; QTransform imageToDocumentTransform() const; QTransform documentToFlakeTransform() const; QTransform imageToViewportTransform() const; QTransform viewportToWidgetTransform() const; QTransform flakeToWidgetTransform() const; QTransform documentToWidgetTransform() const; void getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *poligon) const; void getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, bool scrollCheckers) const; QPointF imageCenterInWidgetPixel() const; QRectF imageRectInWidgetPixels() const; QRectF imageRectInViewportPixels() const; QSizeF imageSizeInFlakePixels() const; QRectF widgetRectInFlakePixels() const; QRect imageRectInImagePixels() const; + QRectF imageRectInDocumentPixels() const; QPointF flakeCenterPoint() const; QPointF widgetCenterPoint() const; void imageScale(qreal *scaleX, qreal *scaleY) const; private: friend class KisZoomAndPanTest; QPointF centeringCorrection() const; void correctOffsetToTransformation(); void correctTransformationToOffset(); void recalculateTransformations(); private: struct Private; Private * const m_d; }; #endif /* KIS_COORDINATES_CONVERTER_H */ diff --git a/libs/ui/canvas/kis_guides_manager.cpp b/libs/ui/canvas/kis_guides_manager.cpp index b823407929..5cc30b4fa3 100644 --- a/libs/ui/canvas/kis_guides_manager.cpp +++ b/libs/ui/canvas/kis_guides_manager.cpp @@ -1,339 +1,438 @@ /* * Copyright (c) 2016 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 "kis_guides_manager.h" #include "kis_guides_decoration.h" +#include #include #include "kis_action_manager.h" #include "kis_action.h" #include "kis_signals_blocker.h" #include "kis_input_manager.h" #include "kis_coordinates_converter.h" +#include "kis_zoom_manager.h" +#include "kis_signal_auto_connection.h" +#include "KisViewManager.h" struct KisGuidesManager::Private { Private(KisGuidesManager *_q) : q(_q), decoration(0), invalidGuide(Qt::Horizontal, -1), currentGuide(invalidGuide), cursorSwitched(false), dragStartGuidePos(0) {} KisGuidesManager *q; KisGuidesDecoration *decoration; KoGuidesData guidesData; QPointer view; typedef QPair GuideHandle; GuideHandle findGuide(const QPointF &docPos); bool isGuideValid(const GuideHandle &h); qreal guideValue(const GuideHandle &h); void setGuideValue(const GuideHandle &h, qreal value); + void deleteGuide(const GuideHandle &h); const GuideHandle invalidGuide; bool updateCursor(const QPointF &docPos); + bool mouseMoveHandler(const QPointF &docPos); + bool mouseReleaseHandler(const QPointF &docPos); GuideHandle currentGuide; bool cursorSwitched; QCursor oldCursor; QPointF dragStartDoc; qreal dragStartGuidePos; + + KisSignalAutoConnectionsStore viewConnections; }; KisGuidesManager::KisGuidesManager(QObject *parent) : QObject(parent), m_d(new Private(this)) { - /* - m_d->guidesData.setShowGuides(true); - m_d->guidesData.addGuideLine(Qt::Horizontal, 200); - m_d->guidesData.addGuideLine(Qt::Horizontal, 300); - m_d->guidesData.addGuideLine(Qt::Horizontal, 400); - m_d->guidesData.addGuideLine(Qt::Vertical, 500); - m_d->guidesData.addGuideLine(Qt::Vertical, 300); - m_d->guidesData.addGuideLine(Qt::Vertical, 400); - setGuidesDataImpl(m_d->guidesData); - */ } KisGuidesManager::~KisGuidesManager() { } KisCanvasDecoration* KisGuidesManager::decoration() const { return m_d->decoration; } void KisGuidesManager::setGuidesDataImpl(const KoGuidesData &value) { if (m_d->decoration && value != m_d->decoration->guidesData()) { m_d->decoration->setVisible(value.showGuides()); m_d->decoration->setGuidesData(value); } const bool shouldFilterEvent = value.showGuides() && !value.lockGuides() && value.hasGuides(); attachEventFilterImpl(shouldFilterEvent); + syncActionsStatus(); } void KisGuidesManager::attachEventFilterImpl(bool value) { if (!m_d->view) return; KisInputManager *inputManager = m_d->view->globalInputManager(); if (inputManager) { if (value) { inputManager->attachPriorityEventFilter(this); } else { inputManager->detachPriorityEventFilter(this); } } } +void KisGuidesManager::syncActionsStatus() +{ + if (!m_d->view) return; + + KisActionManager *actionManager = m_d->view->viewManager()->actionManager(); + KisAction *showGuidesAction = actionManager->actionByName("new_show_guides"); + KisAction *lockGuidesAction = actionManager->actionByName("new_lock_guides"); + KisAction *snapToGuidesAction = actionManager->actionByName("new_snap_to_guides"); + + KisSignalsBlocker l(showGuidesAction, lockGuidesAction, snapToGuidesAction); + showGuidesAction->setChecked(m_d->guidesData.showGuides()); + lockGuidesAction->setChecked(m_d->guidesData.lockGuides()); + snapToGuidesAction->setChecked(m_d->guidesData.snapToGuides()); +} + bool KisGuidesManager::showGuides() const { return m_d->guidesData.showGuides(); } void KisGuidesManager::setShowGuides(bool value) { m_d->guidesData.setShowGuides(value); setGuidesDataImpl(m_d->guidesData); } bool KisGuidesManager::lockGuides() const { return m_d->guidesData.lockGuides(); } void KisGuidesManager::setLockGuides(bool value) { m_d->guidesData.setLockGuides(value); setGuidesDataImpl(m_d->guidesData); } bool KisGuidesManager::snapToGuides() const { return m_d->guidesData.snapToGuides(); } void KisGuidesManager::setSnapToGuides(bool value) { m_d->guidesData.setSnapToGuides(value); setGuidesDataImpl(m_d->guidesData); } void KisGuidesManager::setup(KisActionManager *actionManager) { KisAction *action = 0; - { - action = actionManager->createAction("new_show_guides"); - connect(action, SIGNAL(triggered(bool)), this, SLOT(setShowGuides(bool))); - KisSignalsBlocker l(action); - action->setChecked(m_d->guidesData.showGuides()); - } + action = actionManager->createAction("new_show_guides"); + connect(action, SIGNAL(triggered(bool)), this, SLOT(setShowGuides(bool))); - { - action = actionManager->createAction("new_lock_guides"); - connect(action, SIGNAL(triggered(bool)), this, SLOT(setLockGuides(bool))); - KisSignalsBlocker l(action); - action->setChecked(m_d->guidesData.lockGuides()); - } + action = actionManager->createAction("new_lock_guides"); + connect(action, SIGNAL(triggered(bool)), this, SLOT(setLockGuides(bool))); - { - action = actionManager->createAction("new_snap_to_guides"); - connect(action, SIGNAL(triggered(bool)), this, SLOT(setSnapToGuides(bool))); - KisSignalsBlocker l(action); - action->setChecked(m_d->guidesData.snapToGuides()); - } + action = actionManager->createAction("new_snap_to_guides"); + connect(action, SIGNAL(triggered(bool)), this, SLOT(setSnapToGuides(bool))); + + syncActionsStatus(); } void KisGuidesManager::setView(QPointer view) { if (m_d->view) { m_d->decoration = 0; + m_d->viewConnections.clear(); attachEventFilterImpl(false); } m_d->view = view; if (m_d->view) { KisGuidesDecoration* decoration = qobject_cast(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID)); if (!decoration) { decoration = new KisGuidesDecoration(m_d->view); m_d->view->canvasBase()->addDecoration(decoration); } m_d->decoration = decoration; setGuidesDataImpl(m_d->guidesData); + + m_d->viewConnections.addUniqueConnection( + m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)), + this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&))); + + m_d->viewConnections.addUniqueConnection( + m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)), + this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&))); + + m_d->viewConnections.addUniqueConnection( + m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)), + this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&))); + + m_d->viewConnections.addUniqueConnection( + m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)), + this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&))); } } KisGuidesManager::Private::GuideHandle KisGuidesManager::Private::findGuide(const QPointF &docPos) { const int snapRadius = 16; GuideHandle nearestGuide = invalidGuide; qreal nearestRadius = std::numeric_limits::max(); for (int i = 0; i < guidesData.horizontalGuideLines().size(); i++) { const qreal guide = guidesData.horizontalGuideLines()[i]; const qreal radius = qAbs(docPos.y() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Horizontal, i); nearestRadius = radius; } } for (int i = 0; i < guidesData.verticalGuideLines().size(); i++) { const qreal guide = guidesData.verticalGuideLines()[i]; const qreal radius = qAbs(docPos.x() - guide); if (radius < snapRadius && radius < nearestRadius) { nearestGuide = GuideHandle(Qt::Vertical, i); nearestRadius = radius; } } return nearestGuide; } bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h) { return h.second >= 0; } qreal KisGuidesManager::Private::guideValue(const GuideHandle &h) { return h.first == Qt::Horizontal ? guidesData.horizontalGuideLines()[h.second] : guidesData.verticalGuideLines()[h.second]; } void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value) { if (h.first == Qt::Horizontal) { QList guides = guidesData.horizontalGuideLines(); guides[h.second] = value; guidesData.setHorizontalGuideLines(guides); } else { QList guides = guidesData.verticalGuideLines(); guides[h.second] = value; guidesData.setVerticalGuideLines(guides); } } +void KisGuidesManager::Private::deleteGuide(const GuideHandle &h) +{ + if (h.first == Qt::Horizontal) { + QList guides = guidesData.horizontalGuideLines(); + guides.removeAt(h.second); + guidesData.setHorizontalGuideLines(guides); + } else { + QList guides = guidesData.verticalGuideLines(); + guides.removeAt(h.second); + guidesData.setVerticalGuideLines(guides); + } +} + bool KisGuidesManager::Private::updateCursor(const QPointF &docPos) { KisCanvas2 *canvas = view->canvasBase(); const GuideHandle guide = findGuide(docPos); const bool guideValid = isGuideValid(guide); if (guideValid && !cursorSwitched) { oldCursor = canvas->canvasWidget()->cursor(); } if (guideValid) { cursorSwitched = true; QCursor newCursor = guide.first == Qt::Horizontal ? Qt::SizeVerCursor : Qt::SizeHorCursor; canvas->canvasWidget()->setCursor(newCursor); } if (!guideValid && cursorSwitched) { canvas->canvasWidget()->setCursor(oldCursor); } return guideValid; } +bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos) +{ + if (isGuideValid(currentGuide)) { + const QPointF offset = docPos - dragStartDoc; + const qreal newValue = dragStartGuidePos + + (currentGuide.first == Qt::Horizontal ? + offset.y() : offset.x()); + + setGuideValue(currentGuide, newValue); + q->setGuidesDataImpl(guidesData); + + } + + return updateCursor(docPos); +} + +bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos) +{ + KisCanvas2 *canvas = view->canvasBase(); + const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); + + if (isGuideValid(currentGuide)) { + const QRectF docRect = converter->imageRectInDocumentPixels(); + if (!docRect.contains(docPos)) { + deleteGuide(currentGuide); + q->setGuidesDataImpl(guidesData); + } + + currentGuide = invalidGuide; + dragStartDoc = QPointF(); + dragStartGuidePos = 0; + } + + return updateCursor(docPos); +} + bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event) { if (obj != m_d->view->canvasBase()->canvasWidget()) return false; KisCanvas2 *canvas = m_d->view->canvasBase(); const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); bool retval = false; switch (event->type()) { case QEvent::Enter: case QEvent::Leave: case QEvent::MouseMove: { QMouseEvent *mouseEvent = static_cast(event); const QPointF docPos = converter->widgetToDocument(mouseEvent->pos()); - if (m_d->isGuideValid(m_d->currentGuide)) { - const QPointF offset = docPos - m_d->dragStartDoc; - const qreal newValue = m_d->dragStartGuidePos + - (m_d->currentGuide.first == Qt::Horizontal ? - offset.y() : offset.x()); - - m_d->setGuideValue(m_d->currentGuide, newValue); - setGuidesDataImpl(m_d->guidesData); - - } - - retval = m_d->updateCursor(docPos); + retval = m_d->mouseMoveHandler(docPos); break; } case QEvent::MouseButtonPress: { QMouseEvent *mouseEvent = static_cast(event); const QPointF docPos = converter->widgetToDocument(mouseEvent->pos()); const Private::GuideHandle guide = m_d->findGuide(docPos); const bool guideValid = m_d->isGuideValid(guide); if (guideValid) { m_d->currentGuide = guide; m_d->dragStartDoc = docPos; m_d->dragStartGuidePos = m_d->guideValue(guide); } retval = m_d->updateCursor(docPos); break; } case QEvent::MouseButtonRelease: { QMouseEvent *mouseEvent = static_cast(event); const QPointF docPos = converter->widgetToDocument(mouseEvent->pos()); - if (m_d->isGuideValid(m_d->currentGuide)) { - m_d->currentGuide = m_d->invalidGuide; - m_d->dragStartDoc = QPointF(); - m_d->dragStartGuidePos = 0; - } - - retval = m_d->updateCursor(docPos); + retval = m_d->mouseReleaseHandler(docPos); break; } default: break; } return !retval ? QObject::eventFilter(obj, event) : true; } + +void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos) +{ + if (m_d->guidesData.lockGuides()) return; + + KisCanvas2 *canvas = m_d->view->canvasBase(); + const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); + const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); + const QPointF docPos = converter->widgetToDocument(widgetPos); + + if (m_d->isGuideValid(m_d->currentGuide)) { + m_d->mouseMoveHandler(docPos); + } else { + m_d->guidesData.setShowGuides(true); + + if (orientation == Qt::Horizontal) { + QList guides = m_d->guidesData.horizontalGuideLines(); + guides.append(docPos.y()); + m_d->currentGuide.first = orientation; + m_d->currentGuide.second = guides.size() - 1; + m_d->guidesData.setHorizontalGuideLines(guides); + } else { + QList guides = m_d->guidesData.verticalGuideLines(); + guides.append(docPos.x()); + m_d->currentGuide.first = orientation; + m_d->currentGuide.second = guides.size() - 1; + m_d->guidesData.setVerticalGuideLines(guides); + } + + setGuidesDataImpl(m_d->guidesData); + } +} + +void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos) +{ + Q_UNUSED(orientation); + if (m_d->guidesData.lockGuides()) return; + + KisCanvas2 *canvas = m_d->view->canvasBase(); + const KisCoordinatesConverter *converter = canvas->coordinatesConverter(); + const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos); + const QPointF docPos = converter->widgetToDocument(widgetPos); + + m_d->mouseReleaseHandler(docPos); +} diff --git a/libs/ui/canvas/kis_guides_manager.h b/libs/ui/canvas/kis_guides_manager.h index 261a0dd123..575320e183 100644 --- a/libs/ui/canvas/kis_guides_manager.h +++ b/libs/ui/canvas/kis_guides_manager.h @@ -1,62 +1,67 @@ /* * Copyright (c) 2016 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_GUIDES_MANAGER_H #define __KIS_GUIDES_MANAGER_H #include #include class KisView; class KisActionManager; class KisCanvasDecoration; class KoGuidesData; class KisGuidesManager : public QObject { Q_OBJECT public: KisGuidesManager(QObject *parent = 0); ~KisGuidesManager(); void setup(KisActionManager *actionManager); void setView(QPointer view); KisCanvasDecoration* decoration() const; bool showGuides() const; bool lockGuides() const; bool snapToGuides() const; bool eventFilter(QObject *obj, QEvent *event); public Q_SLOTS: void setShowGuides(bool value); void setLockGuides(bool value); void setSnapToGuides(bool value); + void slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos); + void slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos); + private: void setGuidesDataImpl(const KoGuidesData &value); void attachEventFilterImpl(bool value); + void syncActionsStatus(); + private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_GUIDES_MANAGER_H */ diff --git a/libs/ui/kis_zoom_manager.cc b/libs/ui/kis_zoom_manager.cc index ab17c587ba..6d2a120541 100644 --- a/libs/ui/kis_zoom_manager.cc +++ b/libs/ui/kis_zoom_manager.cc @@ -1,297 +1,305 @@ /* * Copyright (C) 2006, 2010 Boudewijn Rempt * Copyright (C) 2009 Lukáš Tvrdý * * 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_zoom_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisDocument.h" #include "KisViewManager.h" #include "canvas/kis_canvas2.h" #include "kis_coordinates_converter.h" #include "kis_image.h" #include "kis_statusbar.h" #include "kis_config.h" #include "krita_utils.h" #include "kis_canvas_resource_provider.h" #include "kis_lod_transform.h" - class KisZoomController : public KoZoomController { public: KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KActionCollection *actionCollection, KoZoomAction::SpecialButtons specialButtons, QObject *parent) : KoZoomController(co, zh, actionCollection, false, specialButtons, parent), m_converter(zh) { } protected: QSize documentToViewport(const QSizeF &size) { QRectF docRect(QPointF(), size); return m_converter->documentToWidget(docRect).toRect().size(); } private: KisCoordinatesConverter *m_converter; }; KisZoomManager::KisZoomManager(QPointer view, KoZoomHandler * zoomHandler, KoCanvasController * canvasController) : m_view(view) , m_zoomHandler(zoomHandler) , m_canvasController(canvasController) , m_horizontalRuler(0) , m_verticalRuler(0) , m_zoomAction(0) , m_zoomActionWidget(0) { } KisZoomManager::~KisZoomManager() { KisConfig cfg; cfg.setShowRulers(m_horizontalRuler->isVisible()); if (m_zoomActionWidget && !m_zoomActionWidget->parent()) { delete m_zoomActionWidget; } } void KisZoomManager::setup(KActionCollection * actionCollection) { KisImageWSP image = m_view->image(); if (!image) return; connect(image, SIGNAL(sigSizeChanged(const QPointF &, const QPointF &)), this, SLOT(setMinMaxZoom())); KisCoordinatesConverter *converter = dynamic_cast(m_zoomHandler); m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, KoZoomAction::AspectMode, this); m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS); m_zoomHandler->setZoom(1.0); m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes())); m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true); m_zoomAction = m_zoomController->zoomAction(); setMinMaxZoom(); m_zoomActionWidget = m_zoomAction->createWidget(0); // Put the canvascontroller in a layout so it resizes with us QGridLayout * layout = new QGridLayout(m_view); layout->setSpacing(0); layout->setMargin(0); m_view->setLayout(layout); m_view->document()->setUnit(KoUnit(KoUnit::Pixel)); m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler); m_horizontalRuler->setShowMousePosition(true); m_horizontalRuler->createGuideToolConnection(m_view->canvasBase()); -// new KoRulerController(m_horizontalRuler, m_canvasController->canvas()->resourceManager()); m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler); m_verticalRuler->setShowMousePosition(true); m_verticalRuler->createGuideToolConnection(m_view->canvasBase()); QList unitActions = m_view->createChangeUnitActions(true); m_horizontalRuler->setPopupActionList(unitActions); m_verticalRuler->setPopupActionList(unitActions); connect(m_view->document(), SIGNAL(unitChanged(const KoUnit&)), SLOT(applyRulersUnit(const KoUnit&))); layout->addWidget(m_horizontalRuler, 0, 1); layout->addWidget(m_verticalRuler, 1, 0); layout->addWidget(static_cast(m_canvasController), 1, 1); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)), this, SLOT(pageOffsetChanged())); connect(m_canvasController->proxyObject, SIGNAL(canvasMousePositionChanged(const QPoint &)), SLOT(mousePositionChanged(const QPoint &))); connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode, qreal)), this, SLOT(slotZoomChanged(KoZoomMode::Mode, qreal))); connect(m_zoomController, SIGNAL(aspectModeChanged(bool)), this, SLOT(changeAspectMode(bool))); applyRulersUnit(m_view->document()->unit()); KisConfig cfg; toggleShowRulers(cfg.showRulers()); } +KoRuler* KisZoomManager::horizontalRuler() const +{ + return m_horizontalRuler; +} + +KoRuler* KisZoomManager::verticalRuler() const +{ + return m_verticalRuler; +} + void KisZoomManager::mousePositionChanged(const QPoint &viewPos) { QPoint pt = viewPos - m_rulersOffset; m_horizontalRuler->updateMouseCoordinate(pt.x()); m_verticalRuler->updateMouseCoordinate(pt.y()); } bool KisZoomManager::horizontalRulerVisible() const { return m_horizontalRuler->isVisible(); } bool KisZoomManager::verticalRulerVisible() const { return m_verticalRuler->isVisible(); } void KisZoomManager::toggleShowRulers(bool show) { m_horizontalRuler->setVisible(show); m_verticalRuler->setVisible(show); } void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit) { m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes())); m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes())); } void KisZoomManager::setMinMaxZoom() { KisImageWSP image = m_view->image(); if (!image) return; QSize imageSize = image->size(); qreal minDimension = qMin(imageSize.width(), imageSize.height()); qreal minZoom = qMin(100.0 / minDimension, 0.1); m_zoomAction->setMinimumZoom(minZoom); m_zoomAction->setMaximumZoom(90.0); } void KisZoomManager::updateGUI() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size(); m_horizontalRuler->setRulerLength(documentSize.width()); m_verticalRuler->setRulerLength(documentSize.height()); applyRulersUnit(m_horizontalRuler->unit()); } QWidget *KisZoomManager::zoomActionWidget() const { return m_zoomActionWidget; } void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom) { Q_UNUSED(mode); Q_UNUSED(zoom); m_view->canvasBase()->notifyZoomChanged(); qreal humanZoom = zoom * 100.0; // XXX: KOMVC -- this is very irritating in MDI mode if (m_view->viewManager()) { m_view->viewManager()-> showFloatingMessage( i18nc("floating message about zoom", "Zoom: %1 %", KritaUtils::prettyFormatReal(humanZoom)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } const qreal effectiveZoom = m_view->canvasBase()->coordinatesConverter()->effectiveZoom(); m_view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::EffectiveZoom, effectiveZoom); } void KisZoomManager::slotScrollAreaSizeChanged() { pageOffsetChanged(); updateGUI(); } void KisZoomManager::changeAspectMode(bool aspectMode) { KisImageWSP image = m_view->image(); KoZoomMode::Mode newMode = KoZoomMode::ZOOM_CONSTANT; qreal newZoom = m_zoomHandler->zoom(); qreal resolutionX = aspectMode ? image->xRes() : POINT_TO_INCH(static_cast(KoDpi::dpiX())); qreal resolutionY = aspectMode ? image->yRes() : POINT_TO_INCH(static_cast(KoDpi::dpiY())); m_zoomController->setZoom(newMode, newZoom, resolutionX, resolutionY); m_view->canvasBase()->notifyZoomChanged(); } void KisZoomManager::pageOffsetChanged() { QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels(); m_rulersOffset = widgetRect.topLeft().toPoint(); m_horizontalRuler->setOffset(m_rulersOffset.x()); m_verticalRuler->setOffset(m_rulersOffset.y()); } void KisZoomManager::zoomTo100() { m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0); m_view->canvasBase()->notifyZoomChanged(); } void KisZoomManager::showGuides(bool toggle) { m_view->document()->guidesData()->setShowGuideLines(toggle); m_view->canvasBase()->canvasWidget()->update(); } diff --git a/libs/ui/kis_zoom_manager.h b/libs/ui/kis_zoom_manager.h index ad0927d26b..871f8d6127 100644 --- a/libs/ui/kis_zoom_manager.h +++ b/libs/ui/kis_zoom_manager.h @@ -1,93 +1,96 @@ /* * 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_ZOOM_MANAGER #define KIS_ZOOM_MANAGER #include #include #include #include #include #include #include #include "KisView.h" class KoZoomHandler; class KoZoomAction; class KoRuler; class KoUnit; class KoCanvasController; class QPoint; /** The zoom manager handles all user actions related to zooming and unzooming. The actual computation of zoom levels and things are the job of KoZoomHandler or its descendants */ class KisZoomManager : public QObject { Q_OBJECT public: KisZoomManager(QPointer view, KoZoomHandler*, KoCanvasController *); ~KisZoomManager(); void setup(KActionCollection * actionCollection); void updateGUI(); KoZoomController * zoomController() const { return m_zoomController; } QWidget *zoomActionWidget() const; + KoRuler *horizontalRuler() const; + KoRuler *verticalRuler() const; + public Q_SLOTS: void slotZoomChanged(KoZoomMode::Mode mode, qreal zoom); void slotScrollAreaSizeChanged(); void toggleShowRulers(bool show); void mousePositionChanged(const QPoint &viewPos); void changeAspectMode(bool aspectMode); void pageOffsetChanged(); void zoomTo100(); void showGuides(bool toggle); void applyRulersUnit(const KoUnit &baseUnit); void setMinMaxZoom(); public: bool horizontalRulerVisible() const; bool verticalRulerVisible() const; private: QPointer m_view; KoZoomHandler * m_zoomHandler; KoCanvasController *m_canvasController; KoZoomController *m_zoomController; KoRuler * m_horizontalRuler; KoRuler * m_verticalRuler; KoZoomAction * m_zoomAction; QPointer m_zoomActionWidget; QPoint m_rulersOffset; }; #endif diff --git a/libs/widgets/KoRuler.cpp b/libs/widgets/KoRuler.cpp index 22638e81f1..64faa9014a 100644 --- a/libs/widgets/KoRuler.cpp +++ b/libs/widgets/KoRuler.cpp @@ -1,1337 +1,1357 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007-2008 Jan Hambrecht Copyright (C) 2007 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. */ #include "KoRuler.h" #include "KoRuler_p.h" #include #include #include #include #include #include #include #include #include #include // the distance in pixels of a mouse position considered outside the rule static const int OutsideRulerThreshold = 20; // static const int fullStepMarkerLength = 6; static const int halfStepMarkerLength = 6; static const int quarterStepMarkerLength = 3; static const int measurementTextAboveBelowMargin = 1; void RulerTabChooser::mousePressEvent(QMouseEvent *) { if (! m_showTabs) { return; } switch(m_type) { case QTextOption::LeftTab: m_type = QTextOption::RightTab; break; case QTextOption::RightTab: m_type = QTextOption::CenterTab; break; case QTextOption::CenterTab: m_type = QTextOption::DelimiterTab; break; case QTextOption::DelimiterTab: m_type = QTextOption::LeftTab; break; } update(); } void RulerTabChooser::paintEvent(QPaintEvent *) { if (! m_showTabs) { return; } QPainter painter(this); QPolygonF polygon; painter.setPen(palette().color(QPalette::Text)); painter.setBrush(palette().color(QPalette::Text)); painter.setRenderHint( QPainter::Antialiasing ); qreal x= width()/2; painter.translate(0,-height()/2+5); switch (m_type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x+6.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+0.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, height() - 8.5) << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, height() - 2.5) << QPointF(x+6.5, height() - 2.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, height() - 2.5) << QPointF(x+0.5, height() - 8.5); painter.drawPolyline(polygon); break; default: break; } } static int compareTabs(KoRuler::Tab &tab1, KoRuler::Tab &tab2) { return tab1.position < tab2.position; } QRectF HorizontalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewX(d->rulerLength); QRectF rectangle; rectangle.setX(qMax(0, d->offset)); rectangle.setY(0); rectangle.setWidth(qMin(qreal(d->ruler->width() - 1.0 - rectangle.x()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); rectangle.setHeight(d->ruler->height() - 1); QRectF activeRangeRectangle; activeRangeRectangle.setX(qMax(rectangle.x() + 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setY(rectangle.y() + 1); activeRangeRectangle.setRight(qMin(rectangle.right() - 1, d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd()) + d->offset)); activeRangeRectangle.setHeight(rectangle.height() - 2); painter.setPen(d->ruler->palette().color(QPalette::Mid)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewX(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(border, rectangle.y() + 1), QPointF(border, rectangle.bottom() - 1)); } } return rectangle; } void HorizontalPaintingStrategy::drawTabs(const KoRulerPrivate *d, QPainter &painter) { if (! d->showTabs) return; QPolygonF polygon; const QColor tabColor = d->ruler->palette().color(QPalette::Text); painter.setPen(tabColor); painter.setBrush(tabColor); painter.setRenderHint( QPainter::Antialiasing ); qreal position = -10000; foreach (const KoRuler::Tab & t, d->tabs) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } position = qMax(position, t.position); polygon.clear(); switch (t.type) { case QTextOption::LeftTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x+6.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::RightTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::CenterTab: polygon << QPointF(x+0.5, d->ruler->height() - 6.5) << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); break; case QTextOption::DelimiterTab: polygon << QPointF(x-5.5, d->ruler->height() - 0.5) << QPointF(x+6.5, d->ruler->height() - 0.5); painter.drawPolyline(polygon); polygon << QPointF(x+0.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 6.5); painter.drawPolyline(polygon); break; default: break; } } // and also draw the regular interval tab that are non editable if (d->tabDistance > 0.0) { // first possible position position = qMax(position, d->relativeTabs ? 0 : d->paragraphIndent); if (position < 0) { position = int(position / d->tabDistance) * d->tabDistance; } else { position = (int(position / d->tabDistance) + 1) * d->tabDistance; } while (position < d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart() - d->endIndent) { qreal x; if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + position) + d->offset; } polygon.clear(); polygon << QPointF(x+0.5, d->ruler->height() - 3.5) << QPointF(x+4.5, d->ruler->height() - 0.5) << QPointF(x+0.5, d->ruler->height() - 0.5); painter.drawPolygon(polygon); position += d->tabDistance; } } } void HorizontalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit // QRectF activeRangeRectangle; int numberStepPixel = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue(numberStep))); // const bool adjustMillimeters = (d->unit.type() == KoUnit::Millimeter); const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); if (numberStepPixel == 0 || numberStep == 0) return; // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding // Change number step so all digits fits while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } int start=0; // Calc the first number step if(d->offset < 0) start = qAbs(d->offset); // make a little hack so rulers shows correctly inversed number aligned const qreal lengthInUnit = d->unit.toUserValue(d->rulerLength); const qreal hackyLength = lengthInUnit - fmod(lengthInUnit, numberStep); if(d->rightToLeft) { start -= int(d->viewConverter->documentToViewX(fmod(d->rulerLength, d->unit.fromUserValue(numberStep)))); } int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; int pos = 0; const QPen numberPen(d->ruler->palette().color(QPalette::Text)); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text)); painter.setPen(markerPen); if(d->offset > 0) painter.translate(d->offset, 0); const int len = qRound(rectangle.width()) + start; int nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - fullStepMarkerLength)); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); int x = pos; if (d->rightToLeft) { // this is done in a hacky way with the fine tuning done above numberText = QString::number(hackyLength - stepCount * numberStep); } painter.setPen(numberPen); painter.drawText(QPointF(x-fontMetrics.width(numberText)/2.0, rectangle.bottom() -fullStepMarkerLength -measurementTextAboveBelowMargin), numberText); painter.setPen(markerPen); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewX( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - halfStepMarkerLength)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(pos, rectangle.bottom()-1), QPointF(pos, rectangle.bottom() - quarterStepMarkerLength)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewX(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal top = rectangle.y() + 1; const qreal bottom = rectangle.bottom() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.width() ) painter.drawLine(QPointF(mouseCoord, top), QPointF(mouseCoord, bottom)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal x = d->viewConverter->documentToViewX(hp.position) + d->offset; painter.drawLine(QPointF(x, top), QPointF(x, bottom)); } } } void HorizontalPaintingStrategy::drawIndents(const KoRulerPrivate *d, QPainter &painter) { QPolygonF polygon; painter.setBrush(d->ruler->palette().brush(QPalette::Base)); painter.setRenderHint( QPainter::Antialiasing ); qreal x; // Draw first line start indent if (d->rightToLeft) x = d->effectiveActiveRangeEnd() - d->firstLineIndent - d->paragraphIndent; else x = d->effectiveActiveRangeStart() + d->firstLineIndent + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); polygon << QPointF(x+6.5, 0.5) << QPointF(x+0.5, 8.5) << QPointF(x-5.5, 0.5) << QPointF(x+5.5, 0.5); painter.drawPolygon(polygon); // draw the hanging indent. if (d->rightToLeft) x = d->effectiveActiveRangeStart() + d->endIndent; else x = d->effectiveActiveRangeStart() + d->paragraphIndent; // convert and use the +0.5 to go to nearest integer so that the 0.5 added below ensures sharp lines x = int(d->viewConverter->documentToViewX(x) + d->offset + 0.5); const int bottom = d->ruler->height(); polygon.clear(); polygon << QPointF(x+6.5, bottom - 0.5) << QPointF(x+0.5, bottom - 8.5) << QPointF(x-5.5, bottom - 0.5) << QPointF(x+5.5, bottom - 0.5); painter.drawPolygon(polygon); // Draw end-indent or paragraph indent if mode is rightToLeft qreal diff; if (d->rightToLeft) diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->paragraphIndent) + d->offset - x; else diff = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - d->endIndent) + d->offset - x; polygon.translate(diff, 0); painter.drawPolygon(polygon); } QSize HorizontalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(0, minimum); } QRectF VerticalPaintingStrategy::drawBackground(const KoRulerPrivate *d, QPainter &painter) { lengthInPixel = d->viewConverter->documentToViewY(d->rulerLength); QRectF rectangle; rectangle.setX(0); rectangle.setY(qMax(0, d->offset)); rectangle.setWidth(d->ruler->width() - 1.0); rectangle.setHeight(qMin(qreal(d->ruler->height() - 1.0 - rectangle.y()), (d->offset >= 0) ? lengthInPixel : lengthInPixel + d->offset)); QRectF activeRangeRectangle; activeRangeRectangle.setX(rectangle.x() + 1); activeRangeRectangle.setY(qMax(rectangle.y() + 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeStart()) + d->offset)); activeRangeRectangle.setWidth(rectangle.width() - 2); activeRangeRectangle.setBottom(qMin(rectangle.bottom() - 1, d->viewConverter->documentToViewY(d->effectiveActiveRangeEnd()) + d->offset)); painter.setPen(d->ruler->palette().color(QPalette::Mid)); painter.drawRect(rectangle); if(d->effectiveActiveRangeStart() != d->effectiveActiveRangeEnd()) painter.fillRect(activeRangeRectangle, d->ruler->palette().brush(QPalette::Base)); if(d->showSelectionBorders) { // Draw first selection border if(d->firstSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->firstSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } // Draw second selection border if(d->secondSelectionBorder > 0) { qreal border = d->viewConverter->documentToViewY(d->secondSelectionBorder) + d->offset; painter.drawLine(QPointF(rectangle.x() + 1, border), QPointF(rectangle.right() - 1, border)); } } return rectangle; } void VerticalPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF &rectangle) { qreal numberStep = d->numberStepForUnit(); // number step in unit int numberStepPixel = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep))); if (numberStepPixel <= 0) return; const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); painter.setFont(font); // Calc the longest text length int textLength = 0; for(int i = 0; i < lengthInPixel; i += numberStepPixel) { int number = qRound((i / numberStepPixel) * numberStep); textLength = qMax(textLength, fontMetrics.width(QString::number(number))); } textLength += 4; // Add some padding if (numberStepPixel == 0 || numberStep == 0) return; // Change number step so all digits will fit while(textLength > numberStepPixel) { numberStepPixel += numberStepPixel; numberStep += numberStep; } // Calc the first number step const int start = d->offset < 0 ? qAbs(d->offset) : 0; // make a little hack so rulers shows correctly inversed number aligned int stepCount = (start / numberStepPixel) + 1; int halfStepCount = (start / qRound(numberStepPixel * 0.5)) + 1; int quarterStepCount = (start / qRound(numberStepPixel * 0.25)) + 1; const QPen numberPen(d->ruler->palette().color(QPalette::Text)); const QPen markerPen(d->ruler->palette().color(QPalette::Inactive, QPalette::Text)); painter.setPen(markerPen); if(d->offset > 0) painter.translate(0, d->offset); const int len = qRound(rectangle.height()) + start; int nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); int nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); int nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); int pos = 0; for(int i = start; i < len; ++i) { pos = i - start; if(i == nextStep) { painter.save(); painter.translate(rectangle.right()-fullStepMarkerLength, pos); if(pos != 0) painter.drawLine(QPointF(0, 0), QPointF(fullStepMarkerLength-1, 0)); painter.rotate(-90); int number = qRound(stepCount * numberStep); QString numberText = QString::number(number); painter.setPen(numberPen); painter.drawText(QPointF(-fontMetrics.width(numberText) / 2.0, -measurementTextAboveBelowMargin), numberText); painter.restore(); ++stepCount; nextStep = qRound(d->viewConverter->documentToViewY( d->unit.fromUserValue(numberStep * stepCount))); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextHalfStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - halfStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++halfStepCount; nextHalfStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.5 * halfStepCount))); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } else if(i == nextQuarterStep) { if(pos != 0) painter.drawLine(QPointF(rectangle.right() - quarterStepMarkerLength, pos), QPointF(rectangle.right() - 1, pos)); ++quarterStepCount; nextQuarterStep = qRound(d->viewConverter->documentToViewY(d->unit.fromUserValue( numberStep * 0.25 * quarterStepCount))); } } // Draw the mouse indicator const int mouseCoord = d->mouseCoordinate - start; if (d->selected == KoRulerPrivate::None || d->selected == KoRulerPrivate::HotSpot) { const qreal left = rectangle.left() + 1; const qreal right = rectangle.right() -1; if (d->selected == KoRulerPrivate::None && d->showMousePosition && mouseCoord > 0 && mouseCoord < rectangle.height() ) painter.drawLine(QPointF(left, mouseCoord), QPointF(right, mouseCoord)); foreach (const KoRulerPrivate::HotSpotData & hp, d->hotspots) { const qreal y = d->viewConverter->documentToViewY(hp.position) + d->offset; painter.drawLine(QPointF(left, y), QPointF(right, y)); } } } QSize VerticalPaintingStrategy::sizeHint() { // assumes that digits for the number only use glyphs which do not go below the baseline const QFontMetrics fm(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); const int digitsHeight = fm.ascent() + 1; // +1 for baseline const int minimum = digitsHeight + fullStepMarkerLength + 2*measurementTextAboveBelowMargin; return QSize(minimum, 0); } void HorizontalDistancesPaintingStrategy::drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end) { // Don't draw too short lines if (qMax(start, end) - qMin(start, end) < 1) return; painter.save(); painter.translate(d->offset, d->ruler->height() / 2); painter.setPen(d->ruler->palette().color(QPalette::Text)); painter.setBrush(d->ruler->palette().color(QPalette::Text)); QLineF line(QPointF(d->viewConverter->documentToViewX(start), 0), QPointF(d->viewConverter->documentToViewX(end), 0)); QPointF midPoint = line.pointAt(0.5); // Draw the label text const QFont font = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont); const QFontMetrics fontMetrics(font); QString label = d->unit.toUserStringValue( d->viewConverter->viewToDocumentX(line.length())) + ' ' + d->unit.symbol(); QPointF labelPosition = QPointF(midPoint.x() - fontMetrics.width(label)/2, midPoint.y() + fontMetrics.ascent()/2); painter.setFont(font); painter.drawText(labelPosition, label); // Draw the arrow lines qreal arrowLength = (line.length() - fontMetrics.width(label)) / 2 - 2; arrowLength = qMax(qreal(0.0), arrowLength); QLineF startArrow(line.p1(), line.pointAt(arrowLength / line.length())); QLineF endArrow(line.p2(), line.pointAt(1.0 - arrowLength / line.length())); painter.drawLine(startArrow); painter.drawLine(endArrow); // Draw the arrow heads QPolygonF arrowHead; arrowHead << line.p1() << QPointF(line.x1()+3, line.y1()-3) << QPointF(line.x1()+3, line.y1()+3); painter.drawPolygon(arrowHead); arrowHead.clear(); arrowHead << line.p2() << QPointF(line.x2()-3, line.y2()-3) << QPointF(line.x2()-3, line.y2()+3); painter.drawPolygon(arrowHead); painter.restore(); } void HorizontalDistancesPaintingStrategy::drawMeasurements(const KoRulerPrivate *d, QPainter &painter, const QRectF&) { QList points; points << 0.0; points << d->effectiveActiveRangeStart() + d->paragraphIndent + d->firstLineIndent; points << d->effectiveActiveRangeStart() + d->paragraphIndent; points << d->effectiveActiveRangeEnd() - d->endIndent; points << d->effectiveActiveRangeStart(); points << d->effectiveActiveRangeEnd(); points << d->rulerLength; qSort(points.begin(), points.end()); QListIterator i(points); i.next(); while (i.hasNext() && i.hasPrevious()) { drawDistanceLine(d, painter, i.peekPrevious(), i.peekNext()); i.next(); } } KoRulerPrivate::KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation o) : unit(KoUnit(KoUnit::Point)), orientation(o), viewConverter(vc), offset(0), rulerLength(0), activeRangeStart(0), activeRangeEnd(0), activeOverrideRangeStart(0), activeOverrideRangeEnd(0), mouseCoordinate(-1), showMousePosition(0), showSelectionBorders(false), firstSelectionBorder(0), secondSelectionBorder(0), showIndents(false), firstLineIndent(0), paragraphIndent(0), endIndent(0), showTabs(false), relativeTabs(false), tabMoved(false), originalIndex(-1), currentIndex(0), rightToLeft(false), selected(None), selectOffset(0), tabChooser(0), normalPaintingStrategy(o == Qt::Horizontal ? (PaintingStrategy*)new HorizontalPaintingStrategy() : (PaintingStrategy*)new VerticalPaintingStrategy()), distancesPaintingStrategy((PaintingStrategy*)new HorizontalDistancesPaintingStrategy()), paintingStrategy(normalPaintingStrategy), - ruler(parent) + ruler(parent), + guideCreationStarted(false) { } KoRulerPrivate::~KoRulerPrivate() { delete normalPaintingStrategy; delete distancesPaintingStrategy; } qreal KoRulerPrivate::numberStepForUnit() const { switch(unit.type()) { case KoUnit::Inch: case KoUnit::Centimeter: case KoUnit::Decimeter: case KoUnit::Millimeter: return 1.0; case KoUnit::Pica: case KoUnit::Cicero: return 10.0; case KoUnit::Point: default: return 100.0; } } qreal KoRulerPrivate::doSnapping(const qreal value) const { qreal numberStep = unit.fromUserValue(numberStepForUnit()/4.0); return numberStep * qRound(value / numberStep); } KoRulerPrivate::Selection KoRulerPrivate::selectionAtPosition(const QPoint & pos, int *selectOffset ) { const int height = ruler->height(); if (rightToLeft) { int x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - firstLineIndent - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x +8 && pos.y() > height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } else { int x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + firstLineIndent + paragraphIndent) + offset); if (pos.x() >= x -8 && pos.x() <= x + 8 && pos.y() < height / 2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::FirstLineIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeStart() + paragraphIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8 && pos.y() > height/2) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::ParagraphIndent; } x = int(viewConverter->documentToViewX(effectiveActiveRangeEnd() - endIndent) + offset); if (pos.x() >= x - 8 && pos.x() <= x + 8) { if (selectOffset) *selectOffset = x - pos.x(); return KoRulerPrivate::EndIndent; } } return KoRulerPrivate::None; } int KoRulerPrivate::hotSpotIndex(const QPoint & pos) { for(int counter = 0; counter < hotspots.count(); counter++) { bool hit; if (orientation == Qt::Horizontal) hit = qAbs(viewConverter->documentToViewX(hotspots[counter].position) - pos.x() + offset) < 3; else hit = qAbs(viewConverter->documentToViewY(hotspots[counter].position) - pos.y() + offset) < 3; if (hit) return counter; } return -1; } qreal KoRulerPrivate::effectiveActiveRangeStart() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeStart; } else { return activeRangeStart; } } qreal KoRulerPrivate::effectiveActiveRangeEnd() const { if (activeOverrideRangeStart != activeOverrideRangeEnd) { return activeOverrideRangeEnd; } else { return activeRangeEnd; } } void KoRulerPrivate::emitTabChanged() { KoRuler::Tab tab; if (currentIndex >= 0) tab = tabs[currentIndex]; emit ruler->tabChanged(originalIndex, currentIndex >= 0 ? &tab : 0); } KoRuler::KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter) : QWidget(parent) , d( new KoRulerPrivate( this, viewConverter, orientation) ) { setMouseTracking( true ); } KoRuler::~KoRuler() { delete d; } KoUnit KoRuler::unit() const { return d->unit; } void KoRuler::setUnit(const KoUnit &unit) { d->unit = unit; update(); } qreal KoRuler::rulerLength() const { return d->rulerLength; } Qt::Orientation KoRuler::orientation() const { return d->orientation; } void KoRuler::setOffset(int offset) { d->offset = offset; update(); } void KoRuler::setRulerLength(qreal length) { d->rulerLength = length; update(); } void KoRuler::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setClipRegion(event->region()); painter.save(); QRectF rectangle = d->paintingStrategy->drawBackground(d, painter); painter.restore(); painter.save(); d->paintingStrategy->drawMeasurements(d, painter, rectangle); painter.restore(); if (d->showIndents) { painter.save(); d->paintingStrategy->drawIndents(d, painter); painter.restore(); } d->paintingStrategy->drawTabs(d, painter); } QSize KoRuler::minimumSizeHint() const { return d->paintingStrategy->sizeHint(); } QSize KoRuler::sizeHint() const { return d->paintingStrategy->sizeHint(); } void KoRuler::setActiveRange(qreal start, qreal end) { d->activeRangeStart = start; d->activeRangeEnd = end; update(); } void KoRuler::setOverrideActiveRange(qreal start, qreal end) { d->activeOverrideRangeStart = start; d->activeOverrideRangeEnd = end; update(); } void KoRuler::updateMouseCoordinate(int coordinate) { if(d->mouseCoordinate == coordinate) return; d->mouseCoordinate = coordinate; update(); } void KoRuler::setShowMousePosition(bool show) { d->showMousePosition = show; update(); } void KoRuler::setRightToLeft(bool isRightToLeft) { d->rightToLeft = isRightToLeft; update(); } void KoRuler::setShowIndents(bool show) { d->showIndents = show; update(); } void KoRuler::setFirstLineIndent(qreal indent) { d->firstLineIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setParagraphIndent(qreal indent) { d->paragraphIndent = indent; if (d->showIndents) { update(); } } void KoRuler::setEndIndent(qreal indent) { d->endIndent = indent; if (d->showIndents) { update(); } } qreal KoRuler::firstLineIndent() const { return d->firstLineIndent; } qreal KoRuler::paragraphIndent() const { return d->paragraphIndent; } qreal KoRuler::endIndent() const { return d->endIndent; } QWidget *KoRuler::tabChooser() { if ((d->tabChooser == 0) && (d->orientation == Qt::Horizontal)) { d->tabChooser = new RulerTabChooser(parentWidget()); d->tabChooser->setShowTabs(d->showTabs); } return d->tabChooser; } void KoRuler::setShowSelectionBorders(bool show) { d->showSelectionBorders = show; update(); } void KoRuler::updateSelectionBorders(qreal first, qreal second) { d->firstSelectionBorder = first; d->secondSelectionBorder = second; if(d->showSelectionBorders) update(); } void KoRuler::setShowTabs(bool show) { if (d->showTabs == show) { return; } d->showTabs = show; if (d->tabChooser) { d->tabChooser->setShowTabs(show); } update(); } void KoRuler::setRelativeTabs(bool relative) { d->relativeTabs = relative; if (d->showTabs) { update(); } } void KoRuler::updateTabs(const QList &tabs, qreal tabDistance) { d->tabs = tabs; d->tabDistance = tabDistance; if (d->showTabs) { update(); } } QList KoRuler::tabs() const { QList answer = d->tabs; qSort(answer.begin(), answer.end(), compareTabs); return answer; } void KoRuler::setPopupActionList(const QList &popupActionList) { d->popupActions = popupActionList; } QList KoRuler::popupActionList() const { return d->popupActions; } void KoRuler::mousePressEvent ( QMouseEvent* ev ) { d->tabMoved = false; d->selected = KoRulerPrivate::None; if (ev->button() == Qt::RightButton && !d->popupActions.isEmpty()) QMenu::exec(d->popupActions, ev->globalPos()); if (ev->button() != Qt::LeftButton) { ev->ignore(); return; } + /** + * HACK ALERT: We don't need all that indentation stuff in Krita. + * Just ensure the rulers are created correctly. + */ + if (d->selected == KoRulerPrivate::None) { + d->guideCreationStarted = true; + return; + } + QPoint pos = ev->pos(); if (d->showTabs) { int i = 0; int x; foreach (const Tab & t, d->tabs) { if (d->rightToLeft) { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeEnd() - (d->relativeTabs ? d->paragraphIndent : 0) - t.position) + d->offset; } else { x = d->viewConverter->documentToViewX(d->effectiveActiveRangeStart() + (d->relativeTabs ? d->paragraphIndent : 0) + t.position) + d->offset; } if (pos.x() >= x-6 && pos.x() <= x+6) { d->selected = KoRulerPrivate::Tab; d->selectOffset = x - pos.x(); d->currentIndex = i; break; } i++; } d->originalIndex = d->currentIndex; } if (d->selected == KoRulerPrivate::None) d->selected = d->selectionAtPosition(ev->pos(), &d->selectOffset); if (d->selected == KoRulerPrivate::None) { int hotSpotIndex = d->hotSpotIndex(ev->pos()); if (hotSpotIndex >= 0) { d->selected = KoRulerPrivate::HotSpot; update(); } } if (d->showTabs && d->selected == KoRulerPrivate::None) { // still haven't found something so let assume the user wants to add a tab qreal tabpos; if (d->rightToLeft) { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) + d->effectiveActiveRangeEnd() + (d->relativeTabs ? d->paragraphIndent : 0); } else { tabpos = d->viewConverter->viewToDocumentX(pos.x() - d->offset) - d->effectiveActiveRangeStart() - (d->relativeTabs ? d->paragraphIndent : 0); } Tab t = {tabpos, d->tabChooser ? d->tabChooser->type() : d->rightToLeft ? QTextOption::RightTab : QTextOption::LeftTab}; d->tabs.append(t); d->selectOffset = 0; d->selected = KoRulerPrivate::Tab; d->currentIndex = d->tabs.count() - 1; d->originalIndex = -1; // new! update(); } if (d->orientation == Qt::Horizontal && (ev->modifiers() & Qt::ShiftModifier) && (d->selected == KoRulerPrivate::FirstLineIndent || d->selected == KoRulerPrivate::ParagraphIndent || d->selected == KoRulerPrivate::Tab || d->selected == KoRulerPrivate::EndIndent)) d->paintingStrategy = d->distancesPaintingStrategy; if (d->selected != KoRulerPrivate::None) emit aboutToChange(); } void KoRuler::mouseReleaseEvent ( QMouseEvent* ev ) { ev->accept(); - if (d->selected == KoRulerPrivate::Tab) { + if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { + d->guideCreationStarted = false; + emit guideCreationFinished(d->orientation, ev->globalPos()); + } else if (d->selected == KoRulerPrivate::Tab) { if (d->originalIndex >= 0 && !d->tabMoved) { int type = d->tabs[d->currentIndex].type; type++; if (type > 3) type = 0; d->tabs[d->currentIndex].type = static_cast (type); update(); } d->emitTabChanged(); } else if( d->selected != KoRulerPrivate::None) emit indentsChanged(true); else ev->ignore(); d->paintingStrategy = d->normalPaintingStrategy; d->selected = KoRulerPrivate::None; } void KoRuler::mouseMoveEvent ( QMouseEvent* ev ) { QPoint pos = ev->pos(); + if (d->selected == KoRulerPrivate::None && d->guideCreationStarted) { + emit guideCreationInProgress(d->orientation, ev->globalPos()); + ev->accept(); + update(); + return; + } + qreal activeLength = d->effectiveActiveRangeEnd() - d->effectiveActiveRangeStart(); switch (d->selected) { case KoRulerPrivate::FirstLineIndent: if (d->rightToLeft) d->firstLineIndent = d->effectiveActiveRangeEnd() - d->paragraphIndent - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->firstLineIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart() - d->paragraphIndent; if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->firstLineIndent = d->doSnapping(d->firstLineIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } emit indentsChanged(false); break; case KoRulerPrivate::ParagraphIndent: if (d->rightToLeft) d->paragraphIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->paragraphIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if( ! (ev->modifiers() & Qt::ShiftModifier)) { d->paragraphIndent = d->doSnapping(d->paragraphIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->paragraphIndent = activeLength - d->endIndent; emit indentsChanged(false); break; case KoRulerPrivate::EndIndent: if (d->rightToLeft) d->endIndent = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); else d->endIndent = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); if (!(ev->modifiers() & Qt::ShiftModifier)) { d->endIndent = d->doSnapping(d->endIndent); d->paintingStrategy = d->normalPaintingStrategy; } else { if (d->orientation == Qt::Horizontal) d->paintingStrategy = d->distancesPaintingStrategy; } if (d->paragraphIndent + d->endIndent > activeLength) d->endIndent = activeLength - d->paragraphIndent; emit indentsChanged(false); break; case KoRulerPrivate::Tab: d->tabMoved = true; if (d->currentIndex < 0) { // tab is deleted. if (ev->pos().y() < height()) { // reinstante it. d->currentIndex = d->tabs.count(); d->tabs.append(d->deletedTab); } else { break; } } if (d->rightToLeft) d->tabs[d->currentIndex].position = d->effectiveActiveRangeEnd() - d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset); else d->tabs[d->currentIndex].position = d->viewConverter->viewToDocumentX(pos.x() + d->selectOffset - d->offset) - d->effectiveActiveRangeStart(); if (!(ev->modifiers() & Qt::ShiftModifier)) d->tabs[d->currentIndex].position = d->doSnapping(d->tabs[d->currentIndex].position); if (d->tabs[d->currentIndex].position < 0) d->tabs[d->currentIndex].position = 0; if (d->tabs[d->currentIndex].position > activeLength) d->tabs[d->currentIndex].position = activeLength; if (ev->pos().y() > height() + OutsideRulerThreshold ) { // moved out of the ruler, delete it. d->deletedTab = d->tabs.takeAt(d->currentIndex); d->currentIndex = -1; // was that a temporary added tab? if ( d->originalIndex == -1 ) emit guideLineCreated(d->orientation, d->orientation == Qt::Horizontal ? d->viewConverter->viewToDocumentY(ev->pos().y()) : d->viewConverter->viewToDocumentX(ev->pos().x())); } d->emitTabChanged(); break; case KoRulerPrivate::HotSpot: qreal newPos; if (d->orientation == Qt::Horizontal) newPos= d->viewConverter->viewToDocumentX(pos.x() - d->offset); else newPos= d->viewConverter->viewToDocumentY(pos.y() - d->offset); d->hotspots[d->currentIndex].position = newPos; emit hotSpotChanged(d->hotspots[d->currentIndex].id, newPos); break; case KoRulerPrivate::None: d->mouseCoordinate = (d->orientation == Qt::Horizontal ? pos.x() : pos.y()) - d->offset; int hotSpotIndex = d->hotSpotIndex(pos); if (hotSpotIndex >= 0) { setCursor(QCursor( d->orientation == Qt::Horizontal ? Qt::SplitHCursor : Qt::SplitVCursor )); break; } unsetCursor(); KoRulerPrivate::Selection selection = d->selectionAtPosition(pos); QString text; switch(selection) { case KoRulerPrivate::FirstLineIndent: text = i18n("First line indent"); break; case KoRulerPrivate::ParagraphIndent: text = i18n("Left indent"); break; case KoRulerPrivate::EndIndent: text = i18n("Right indent"); break; case KoRulerPrivate::None: if (ev->buttons() & Qt::LeftButton) { if (d->orientation == Qt::Horizontal && ev->pos().y() > height() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentY(ev->pos().y())); else if (d->orientation == Qt::Vertical && ev->pos().x() > width() + OutsideRulerThreshold) emit guideLineCreated(d->orientation, d->viewConverter->viewToDocumentX(ev->pos().x())); } break; default: break; } setToolTip(text); } update(); } void KoRuler::clearHotSpots() { if (d->hotspots.isEmpty()) return; d->hotspots.clear(); update(); } void KoRuler::setHotSpot(qreal position, int id) { uint hotspotCount = d->hotspots.count(); for( uint i = 0; i < hotspotCount; ++i ) { KoRulerPrivate::HotSpotData & hs = d->hotspots[i]; if (hs.id == id) { hs.position = position; update(); return; } } // not there yet, then insert it. KoRulerPrivate::HotSpotData hs; hs.position = position; hs.id = id; d->hotspots.append(hs); } bool KoRuler::removeHotSpot(int id) { QList::Iterator iter = d->hotspots.begin(); while(iter != d->hotspots.end()) { if (iter->id == id) { d->hotspots.erase(iter); update(); return true; } } return false; } void KoRuler::createGuideToolConnection(KoCanvasBase *canvas) { Q_ASSERT(canvas); KoToolBase *tool = KoToolManager::instance()->toolById(canvas, QLatin1String("GuidesTool")); if (!tool) return; // It's perfectly fine to have no guides tool, we don't have to warn the user about it connect(this, SIGNAL(guideLineCreated(Qt::Orientation,qreal)), tool, SLOT(createGuideLine(Qt::Orientation,qreal))); } diff --git a/libs/widgets/KoRuler.h b/libs/widgets/KoRuler.h index 5e30558503..7ce02f19ff 100644 --- a/libs/widgets/KoRuler.h +++ b/libs/widgets/KoRuler.h @@ -1,275 +1,278 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Reginald Stadlbauer Copyright (C) 2006 Peter Simonsson Copyright (C) 2007 C. Boemann Copyright (C) 2007 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 koRuler_h #define koRuler_h #include "kritawidgets_export.h" #include #include class QPaintEvent; class KoViewConverter; class KoCanvasBase; class KoRulerPrivate; class KoUnit; /** * Decorator widget to draw a single ruler around a canvas. */ class KRITAWIDGETS_EXPORT KoRuler : public QWidget { Q_OBJECT public: /** * Creates a ruler with the orientation @p orientation * @param parent parent widget * @param orientation the orientation of the ruler * @param viewConverter the view converter used to convert from point to pixel */ KoRuler(QWidget* parent, Qt::Orientation orientation, const KoViewConverter* viewConverter); ~KoRuler(); /// For paragraphs each tab definition is represented by this struct. struct Tab { qreal position; ///< distance in point from the start of the text-shape QTextOption::TabType type; ///< Determine which type is used. }; /// The ruler's unit KoUnit unit() const; /// The length of the ruler in points (pt) qreal rulerLength() const; /// The orientation of the ruler Qt::Orientation orientation() const; /// The start indent of the first line qreal firstLineIndent() const; /// The start indent of the rest of the lines qreal paragraphIndent() const; /// The end indent of all lines qreal endIndent() const; /// The tab chooser widget, which you must put into a layout along with the ruler. /// Returns 0 for vertical rulers, QWidget *tabChooser(); /** * set a list of actions that will be shown in a popup should the user right click on this ruler. * @param popupActionList the list of actions * @see popupActionList() */ void setPopupActionList(const QList &popupActionList); /** * Return the actions list. * @see setPopupActionList() */ QList popupActionList() const; /// reimplemented virtual QSize minimumSizeHint() const; /// reimplemented virtual QSize sizeHint() const; public Q_SLOTS: /// Set the unit of the ruler void setUnit(const KoUnit &unit); /** Set the offset. Use this function to sync the ruler with * the canvas' position on screen * @param offset The offset in pixels */ void setOffset(int offset); /// Sets the length of the ruler to @p length in points (pt) void setRulerLength(qreal length); /** Set the active range, ie the part of the ruler that is most likely used. * set to 0, 0 when there is no longer any active range * @param start the start of the range in pt * @param end the end of the range in pt */ void setActiveRange(qreal start, qreal end); /** Set the override active range, ie the part of the ruler that is most likely used. * set to 0, 0 when there is no longer any active range * The override, means that if set it takes precedence over the normal active range. * @param start the start of the range in pt * @param end the end of the range in pt */ void setOverrideActiveRange(qreal start, qreal end); /** Set the state of the ruler so that it shows everything in right to left mode. * @param isRightToLeft state of right to left mode. Default is false. */ void setRightToLeft(bool isRightToLeft); /** Set if the ruler should show indents as used in textditors. * Set the indents with setFirstLineIndent(), setParagraphIndent(), setEndIndent() . * @param show show indents if true. Default is false. */ void setShowIndents(bool show); /** Set the position of the first line start indent relative to the active range. * If Right To left is set the indent is relative to the right side of the active range . * @param indent the value relative to the active range. */ void setFirstLineIndent(qreal indent); /** Set the position of the rest of the lines start indent relative to the active range. * If Right To left is set the indent is relative to the right side of the active range . * @param indent the value relative to the active range. */ void setParagraphIndent(qreal indent); /** Set the position of the end indent relative to the active range. * If Right To left is set the indent is relative to the left side of the active range . * @param indent the value relative to the active range. */ void setEndIndent(qreal indent); /** Set whether the ruler should show the current mouse position. * Update the position with updateMouseCoordinate(). * @param show show mouse position if true. Default is false. */ void setShowMousePosition(bool show); /** Update the current position of the mouse pointer, repainting if changed. * The ruler offset will be applied before painting. * @param coordinate Either the x or y coordinate of the mouse depending * of the orientation of the ruler. */ void updateMouseCoordinate(int coordinate); /** * Set whether the ruler should show the selection borders * @param show show selection borders if true, default is false. */ void setShowSelectionBorders(bool show); /** * Update the selection borders * @param first the first selection border in points * @param second the other selection border in points */ void updateSelectionBorders(qreal first, qreal second); /** * Set whether the ruler should show tabs * @param show show selection borders if true, default is false. */ void setShowTabs(bool show); /** * Set whether the tabs is relative to the paragraph indent * @param relative tabs are relative to pragraph indent if true, default is false. */ void setRelativeTabs(bool relative); /** * Update the tabs * @param tabs a list of tabs that is shown on the ruler * @param tabDistance the distncte between regular interval tabs */ void updateTabs(const QList &tabs, qreal tabDistance); /*** * Return the list of tabs set on this ruler. */ QList tabs() const; /** * Clear all previously set hotspots. * A hotspot is a position on the ruler that the user can manipulate by dragging. */ void clearHotSpots(); /** * Add or set a hotspot. * A hotspot is a position on the ruler that the user can manipulate by dragging. * @param position the new position of the hotspot. * @param id the unique id for the hotspot. If the id has not been set before, it will be added. */ void setHotSpot(qreal position, int id = -1); /** * Remove a previously set hotspot, returning true if one is actually returned. * @param id the unique id for the hotspot. * A hotspot is a position on the ruler that the user can manipulate by dragging. */ bool removeHotSpot(int id); /** * Connect the ruler to a guides tool * This allows the user to drag a guide out of the ruler and get in one smooth operation * the guide tool to draw and position the guide line. * @param canvas the canvas that has had the KoToolManager create the tool for previously. */ void createGuideToolConnection(KoCanvasBase *canvas); Q_SIGNALS: /** * emitted when any of the indents is moved by the user. * @param final false until the user releases the mouse. So you can implement live update. */ void indentsChanged(bool final); /** * Emitted when any of the tabs are moved, deleted or inserted by the user. * @param originalTabIndex the index in the list of tabs before the user interaction * started, or -1 if this is a new tab * @param tab the new tab, or zero when the tab has been removed. */ void tabChanged(int originalTabIndex, KoRuler::Tab *tab); /// emitted when there the user is about to change a tab or hotspot void aboutToChange(); void hotSpotChanged(int id, qreal newPosition); /// emitted when the mouse is drag+released outside the ruler void guideLineCreated(Qt::Orientation orientation, qreal viewPosition); + void guideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos); + void guideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos); + protected: /// reimplemented virtual void paintEvent(QPaintEvent* event); /// reimplemented virtual void mousePressEvent(QMouseEvent *ev); /// reimplemented virtual void mouseReleaseEvent(QMouseEvent *ev); /// reimplemented virtual void mouseMoveEvent(QMouseEvent *ev); private: KoRulerPrivate * const d; friend class KoRulerPrivate; }; #endif diff --git a/libs/widgets/KoRuler_p.h b/libs/widgets/KoRuler_p.h index bf0c2ebe62..1ba8f84cd6 100644 --- a/libs/widgets/KoRuler_p.h +++ b/libs/widgets/KoRuler_p.h @@ -1,208 +1,210 @@ /* This file is part of the KDE project * Copyright (C) 2007 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 KORULER_P_H #define KORULER_P_H #include class RulerTabChooser : public QWidget { public: RulerTabChooser(QWidget *parent) : QWidget(parent), m_type(QTextOption::LeftTab), m_showTabs(false) {} virtual ~RulerTabChooser() {} inline QTextOption::TabType type() {return m_type;} void setShowTabs(bool showTabs) { if (m_showTabs == showTabs) return; m_showTabs = showTabs; update(); } void mousePressEvent(QMouseEvent *); void paintEvent(QPaintEvent *); private: QTextOption::TabType m_type; bool m_showTabs :1; }; class PaintingStrategy { public: /// constructor PaintingStrategy() {} /// destructor virtual ~PaintingStrategy() {} /** * Draw the background of the ruler. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** * Draw the indicators for text-tabs. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual void drawTabs(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** * Draw the indicators for the measurements which typically are drawn every [unit]. * @param ruler the ruler to draw on. * @param painter the painter we can paint with. * @param rectangle */ virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle) = 0; /** * Draw the indicators for the indents of a text paragraph * @param ruler the ruler to draw on. * @param painter the painter we can paint with. */ virtual void drawIndents(const KoRulerPrivate *ruler, QPainter &painter) = 0; /** *returns the size suggestion for a ruler with this strategy. */ virtual QSize sizeHint() = 0; }; class HorizontalPaintingStrategy : public PaintingStrategy { public: HorizontalPaintingStrategy() : lengthInPixel(1) {} virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter); virtual void drawTabs(const KoRulerPrivate *ruler, QPainter &painter); virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle); virtual void drawIndents(const KoRulerPrivate *ruler, QPainter &painter); virtual QSize sizeHint(); private: qreal lengthInPixel; }; class VerticalPaintingStrategy : public PaintingStrategy { public: VerticalPaintingStrategy() : lengthInPixel(1) {} virtual QRectF drawBackground(const KoRulerPrivate *ruler, QPainter &painter); virtual void drawTabs(const KoRulerPrivate *, QPainter &) {} virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle); virtual void drawIndents(const KoRulerPrivate *, QPainter &) { } virtual QSize sizeHint(); private: qreal lengthInPixel; }; class HorizontalDistancesPaintingStrategy : public HorizontalPaintingStrategy { public: HorizontalDistancesPaintingStrategy() {} virtual void drawMeasurements(const KoRulerPrivate *ruler, QPainter &painter, const QRectF &rectangle); private: void drawDistanceLine(const KoRulerPrivate *d, QPainter &painter, const qreal start, const qreal end); }; class KoRulerPrivate { public: KoRulerPrivate(KoRuler *parent, const KoViewConverter *vc, Qt::Orientation orientation); ~KoRulerPrivate(); void emitTabChanged(); KoUnit unit; const Qt::Orientation orientation; const KoViewConverter * const viewConverter; int offset; qreal rulerLength; qreal activeRangeStart; qreal activeRangeEnd; qreal activeOverrideRangeStart; qreal activeOverrideRangeEnd; int mouseCoordinate; int showMousePosition; bool showSelectionBorders; qreal firstSelectionBorder; qreal secondSelectionBorder; bool showIndents; qreal firstLineIndent; qreal paragraphIndent; qreal endIndent; bool showTabs; bool relativeTabs; bool tabMoved; // set to true on first move of a selected tab QList tabs; int originalIndex; //index of selected tab before we started dragging it. int currentIndex; //index of selected tab or selected HotSpot - only valid when selected indicates tab or hotspot KoRuler::Tab deletedTab; qreal tabDistance; struct HotSpotData { qreal position; int id; }; QList hotspots; bool rightToLeft; enum Selection { None, Tab, FirstLineIndent, ParagraphIndent, EndIndent, HotSpot }; Selection selected; int selectOffset; QList popupActions; RulerTabChooser *tabChooser; // Cached painting strategies PaintingStrategy * normalPaintingStrategy; PaintingStrategy * distancesPaintingStrategy; // Current painting strategy PaintingStrategy * paintingStrategy; KoRuler *ruler; + bool guideCreationStarted; + qreal numberStepForUnit() const; /// @return The rounding of value to the nearest multiple of stepValue qreal doSnapping(const qreal value) const; Selection selectionAtPosition(const QPoint & pos, int *selectOffset = 0); int hotSpotIndex(const QPoint & pos); qreal effectiveActiveRangeStart() const; qreal effectiveActiveRangeEnd() const; friend class VerticalPaintingStrategy; friend class HorizontalPaintingStrategy; }; #endif