Changeset View
Standalone View
src/QuickEditor/QuickEditor.cpp
Show All 15 Lines | |||||
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
17 | * Boston, MA 02110-1301, USA. | 17 | * Boston, MA 02110-1301, USA. | ||
18 | */ | 18 | */ | ||
19 | 19 | | |||
20 | #include "QuickEditor.h" | 20 | #include "QuickEditor.h" | ||
21 | 21 | | |||
22 | #include "SpectacleConfig.h" | 22 | #include "SpectacleConfig.h" | ||
23 | 23 | | |||
24 | #include <KDeclarative/KDeclarative> | 24 | const int QuickEditor::mouseAreaSize = 20; | ||
25 | #include <kdeclarative_version.h> | 25 | const qreal QuickEditor::cornerHandleRadius = 8.0; | ||
26 | const qreal QuickEditor::midHandleRadius = 5.0; | ||||
27 | const int QuickEditor::selectionSizeThreshold = 100; | ||||
26 | 28 | | |||
27 | #include <QPixmap> | 29 | QuickEditor::QuickEditor(const QPixmap& pixmap, QObject* parent) : | ||
28 | #include <QQuickImageProvider> | 30 | mMaskColour(QColor::fromRgbF(0, 0, 0, 0.15)), | ||
29 | #include <QQuickItem> | 31 | mStrokeColor(QColor::fromRgbF(0.114, 0.6, 0.953, 1)), | ||
30 | #include <QQuickItemGrabResult> | 32 | mMidHelpText(tr("Click anywhere on the screen (including here) to start drawing a selection rectangle, or press Esc to quit")), | ||
31 | #include <QQuickView> | 33 | mMidHelpTextFont(font()), | ||
32 | #include <QSize> | 34 | mBottomHelpText(tr("To take the screenshot, double-click or press Enter. Right-click to reset the selection, or press Esc to quit")), | ||
33 | 35 | mBottomHelpTextFont(font()), | |||
34 | struct QuickEditor::ImageStore : public QQuickImageProvider | 36 | mMouseDragState(MouseState::None), | ||
35 | { | | |||
36 | ImageStore(const QPixmap &pixmap) : | | |||
37 | QQuickImageProvider(QQuickImageProvider::Pixmap), | | |||
38 | mPixmap(pixmap) | 37 | mPixmap(pixmap) | ||
ngraham: Hmm, we still can't find a better way to handle this? I'm not sure if this text is really… | |||||
@ngraham I'm working on a better way to do this abalaji: @ngraham I'm working on a better way to do this | |||||
39 | {} | | |||
40 | | ||||
41 | QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) Q_DECL_OVERRIDE | | |||
42 | { | 38 | { | ||
43 | Q_UNUSED(id); | 39 | Q_UNUSED(parent); | ||
Call QWidget constructor: QuickEditor::QuickEditor(const QPixmap &pixmap, QWidget *parent) : QWidget(parent) broulik: Call `QWidget` constructor:
```
QuickEditor::QuickEditor(const QPixmap &pixmap, QWidget… | |||||
44 | 40 | | |||
There's a special way we handle markup and line breaks in strings; change i18n("some\ntext") to xi18nc("@info", "some<nl/>text") For more info, see https://api.kde.org/frameworks/ki18n/html/prg_guide.html#kuit_markup ngraham: There's a special way we handle markup and line breaks in strings; change `i18n("some\ntext")`… | |||||
45 | if (size) { | 41 | SpectacleConfig *config = SpectacleConfig::instance(); | ||
46 | *size = mPixmap.size(); | 42 | if (config->useLightRegionMaskColour()) { | ||
43 | mMaskColour = QColor(255, 255, 255, 100); | ||||
44 | mStrokeColor = QColor(96, 96, 96, 255); | ||||
broulik: Use `i18n()` from `KLocalizedString` | |||||
47 | } | 45 | } | ||
48 | 46 | | |||
What are all these s for? This seems like the wrong way to do whatever you're trying to do here. ngraham: What are all these ` `s for? This seems like the wrong way to do whatever you're trying to… | |||||
I'm using QStaticText for performance so the text layout information is not recalculated with each paint. As it turns out, the only way to easily accomplish linebreaks with QStaticText is using rich-text and <br>, but QStaticText with rich-text has a weird issue where it considers the width of everything before the first <br> (the first line) to be the maximum width for the entire piece of text, and thus wraps the text on each subsequent line to fit this width. I'm using non-breaking spaces to force it to put them on a single line. Try getting rid of all the 's and you'll see what I mean. I'll try and figure out a different solution for this. abalaji: I'm using `QStaticText` for performance so the text layout information is not recalculated with… | |||||
49 | if (requestedSize.isEmpty()) { | 47 | setMouseTracking(true); | ||
50 | return mPixmap; | 48 | setAttribute(Qt::WA_StaticContents); | ||
If we can make the full-screen overlay window not always stay on top (i.e. you can alt-tab out of it or switch virtual desktops while it's open) then that would probably be sufficient to fully fix https://bugs.kde.org/show_bug.cgi?id=374009 in conjunction with existing improvements that this patch provides. ngraham: If we can make the full-screen overlay window not always stay on top (i.e. you can alt-tab out… | |||||
49 | setWindowFlags(Qt::WindowStaysOnTopHint); | ||||
50 | setAttribute(Qt::WA_TranslucentBackground); | ||||
51 | showFullScreen(); | ||||
ngraham: All of this text needs to be translated; use `i18n()` like Kai indicated. | |||||
52 | | ||||
53 | const qreal dpr = devicePixelRatioF(); | ||||
ngraham: Don't hardcode font sizes | |||||
54 | setGeometry(0, 0, mPixmap.width() / dpr, mPixmap.height() / dpr); | ||||
55 | | ||||
56 | if (config->rememberLastRectangularRegion()) { | ||||
57 | QRect cropRegion = config->cropRegion(); | ||||
58 | if (!cropRegion.isEmpty()) { | ||||
ngraham: This is missing everything about the arrow keys. | |||||
abalaji: Sure, on it | |||||
59 | mSelection = QRect( | ||||
60 | cropRegion.x() / dpr, | ||||
61 | cropRegion.y() / dpr, | ||||
62 | cropRegion.width() / dpr, | ||||
63 | cropRegion.height() / dpr | ||||
64 | ).intersected(rect()); | ||||
65 | } | ||||
66 | setMouseCursor(QCursor::pos()); | ||||
67 | } else { | ||||
68 | setCursor(Qt::CrossCursor); | ||||
51 | } | 69 | } | ||
52 | 70 | | |||
53 | return mPixmap.scaled(requestedSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); | 71 | mMidHelpTextFont.setPointSize(12); | ||
72 | mMidHelpText.prepare(QTransform(), mMidHelpTextFont); | ||||
73 | mMidHelpText.setPerformanceHint(QStaticText::AggressiveCaching); | ||||
74 | | ||||
75 | mBottomHelpTextFont.setPointSize(9); | ||||
76 | mBottomHelpText.prepare(QTransform(), mBottomHelpTextFont); | ||||
Can this be done by the user of this class? Self-deletion is typically not a good idea broulik: Can this be done by the user of this class? Self-deletion is typically not a good idea | |||||
abalaji: Yeah this is stupid, I'll fix it | |||||
77 | mBottomHelpText.setPerformanceHint(QStaticText::AggressiveCaching); | ||||
78 | | ||||
79 | QPalette pal = palette(); | ||||
80 | setAutoFillBackground(true); | ||||
81 | pal.setBrush(QPalette::Window, Qt::transparent); | ||||
82 | setPalette(pal); | ||||
83 | update(); | ||||
54 | } | 84 | } | ||
55 | 85 | | |||
56 | QPixmap mPixmap; | 86 | inline void QuickEditor::acceptSelection() { | ||
57 | }; | 87 | if (mSelection.isEmpty()) { | ||
88 | SpectacleConfig::instance()->setCropRegion(mSelection); | ||||
89 | emit grabCancelled(); | ||||
90 | } else { | ||||
91 | const qreal dpr = devicePixelRatioF(); | ||||
92 | QRect scaledCropRegion = QRect( | ||||
93 | mSelection.x() * dpr, | ||||
94 | mSelection.y() * dpr, | ||||
95 | mSelection.width() * dpr, | ||||
96 | mSelection.height() * dpr | ||||
97 | ); | ||||
98 | SpectacleConfig::instance()->setCropRegion(scaledCropRegion); | ||||
99 | emit grabDone(mPixmap.copy(scaledCropRegion)); | ||||
100 | } | ||||
101 | delete this; | ||||
102 | } | ||||
58 | 103 | | |||
59 | struct QuickEditor::QuickEditorPrivate | 104 | void QuickEditor::keyPressEvent(QKeyEvent* event) | ||
60 | { | 105 | { | ||
61 | KDeclarative::KDeclarative *mDecl; | 106 | switch(event->key()) { | ||
62 | QQuickView *mQuickView; | 107 | case Qt::Key_Escape: | ||
63 | QQmlEngine *mQmlEngine; | 108 | emit grabCancelled(); | ||
64 | QRect mGrabRect; | 109 | delete this; | ||
65 | QSharedPointer<QQuickItemGrabResult> mCurrentGrabResult; | 110 | break; | ||
66 | }; | 111 | case Qt::Key_Return: | ||
112 | case Qt::Key_Enter: | ||||
113 | acceptSelection(); | ||||
114 | default: | ||||
115 | break; | ||||
116 | } | ||||
117 | } | ||||
67 | 118 | | |||
68 | QuickEditor::QuickEditor(const QPixmap &pixmap, QObject *parent) : | 119 | void QuickEditor::mousePressEvent(QMouseEvent* event) | ||
69 | QObject(parent), | 120 | { | ||
70 | mImageStore(new ImageStore(pixmap)), | 121 | if (event->button() & Qt::LeftButton) { | ||
71 | d_ptr(new QuickEditorPrivate) | 122 | const QPoint& pos = event->pos(); | ||
72 | { | 123 | mMouseDragState = whereIsTheMouse(pos); | ||
73 | Q_D(QuickEditor); | 124 | switch(mMouseDragState) { | ||
74 | 125 | case MouseState::Outside: | |||
75 | d->mQmlEngine = new QQmlEngine(); | 126 | mStartPos = event->pos(); | ||
76 | d->mDecl = new KDeclarative::KDeclarative; | 127 | break; | ||
77 | d->mDecl->setDeclarativeEngine(d->mQmlEngine); | 128 | case MouseState::Inside: | ||
78 | 129 | mStartPos = event->pos(); | |||
79 | #if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 45, 0) | 130 | mInitialTopLeft = mSelection.topLeft(); | ||
80 | d->mDecl->setupEngine(d->mQmlEngine); | 131 | setCursor(Qt::ClosedHandCursor); | ||
81 | d->mDecl->setupContext(); | 132 | break; | ||
82 | #else | 133 | case MouseState::Top: | ||
83 | d->mDecl->setupBindings(); | 134 | case MouseState::Left: | ||
84 | #endif | 135 | case MouseState::TopLeft: | ||
85 | 136 | mStartPos = mSelection.bottomRight(); | |||
86 | d->mQmlEngine->addImageProvider(QStringLiteral("snapshot"), mImageStore); | 137 | break; | ||
87 | 138 | case MouseState::Bottom: | |||
88 | d->mQuickView = new QQuickView(d->mQmlEngine, 0); | 139 | case MouseState::Right: | ||
89 | d->mQuickView->setClearBeforeRendering(false); | 140 | case MouseState::BottomRight: | ||
90 | d->mQuickView->setSource(QUrl(QStringLiteral("qrc:///QuickEditor/EditorRoot.qml"))); | 141 | mStartPos = mSelection.topLeft(); | ||
91 | 142 | break; | |||
92 | d->mQuickView->setFlags(Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool); | 143 | case MouseState::TopRight: | ||
93 | d->mQuickView->setGeometry(0, 0, pixmap.width(), pixmap.height()); | 144 | mStartPos = mSelection.bottomLeft(); | ||
94 | d->mQuickView->showFullScreen(); | 145 | break; | ||
95 | 146 | case MouseState::BottomLeft: | |||
96 | // connect up the signals | 147 | mStartPos = mSelection.topRight(); | ||
97 | QQuickItem *rootItem = d->mQuickView->rootObject(); | 148 | default: | ||
98 | connect(rootItem, SIGNAL(acceptImage(int, int, int, int)), this, SLOT(acceptImageHandler(int, int, int, int))); | 149 | break; | ||
99 | connect(rootItem, SIGNAL(cancelImage()), this, SIGNAL(grabCancelled())); | 150 | } | ||
151 | } | ||||
152 | event->accept(); | ||||
153 | } | ||||
100 | 154 | | |||
101 | // set up initial config | 155 | void QuickEditor::mouseMoveEvent(QMouseEvent* event) | ||
102 | SpectacleConfig *config = SpectacleConfig::instance(); | 156 | { | ||
103 | if (config->rememberLastRectangularRegion()) { | 157 | const QPoint& pos = event->pos(); | ||
104 | auto pixelRatio = d->mQuickView->devicePixelRatio(); | 158 | switch (mMouseDragState) { | ||
105 | QRect cropRegion = config->cropRegion(); | 159 | case MouseState::None: { | ||
106 | if (!cropRegion.isEmpty()) { | 160 | setMouseCursor(pos); | ||
107 | QMetaObject::invokeMethod( | 161 | break; | ||
108 | rootItem, "setInitialSelection", | 162 | } | ||
109 | Q_ARG(QVariant, cropRegion.x() / pixelRatio), | 163 | case MouseState::TopLeft: | ||
110 | Q_ARG(QVariant, cropRegion.y() / pixelRatio), | 164 | case MouseState::TopRight: | ||
111 | Q_ARG(QVariant, cropRegion.width() / pixelRatio), | 165 | case MouseState::BottomRight: | ||
112 | Q_ARG(QVariant, cropRegion.height() / pixelRatio) | 166 | case MouseState::BottomLeft: | ||
167 | case MouseState::Outside: | ||||
168 | mSelection.setRect( | ||||
169 | qMin(pos.x(), mStartPos.x()), | ||||
170 | qMin(pos.y(), mStartPos.y()), | ||||
171 | qAbs(pos.x() - mStartPos.x()), | ||||
172 | qAbs(pos.y() - mStartPos.y()) | ||||
173 | ); | ||||
174 | update(); | ||||
175 | break; | ||||
176 | case MouseState::Top: | ||||
177 | case MouseState::Bottom: | ||||
178 | mSelection.setRect( | ||||
179 | mSelection.x(), | ||||
180 | qMin(pos.y(), mStartPos.y()), | ||||
181 | mSelection.width(), | ||||
182 | qAbs(pos.y() - mStartPos.y()) | ||||
183 | ); | ||||
184 | update(); | ||||
185 | break; | ||||
186 | case MouseState::Right: | ||||
187 | case MouseState::Left: | ||||
188 | mSelection.setRect( | ||||
189 | qMin(pos.x(), mStartPos.x()), | ||||
190 | mSelection.y(), | ||||
191 | qAbs(pos.x() - mStartPos.x()), | ||||
192 | mSelection.height() | ||||
113 | ); | 193 | ); | ||
194 | update(); | ||||
195 | break; | ||||
196 | case MouseState::Inside: { | ||||
197 | // We use some math here to figure out if the diff with which we | ||||
198 | // move the rectangle with moves it out of bounds, | ||||
199 | // in which case we adjust the diff to not let that happen | ||||
200 | | ||||
201 | // new top left point of the rectangle | ||||
202 | QPoint newTopLeft = pos - mStartPos + mInitialTopLeft; | ||||
203 | | ||||
204 | // the max coordinates of the top left point | ||||
205 | const int maxX = size().width() - mSelection.width(); | ||||
206 | const int maxY = size().height() - mSelection.height(); | ||||
207 | | ||||
208 | if (newTopLeft.x() < 0) { | ||||
209 | // tweak startPos to prevent rectangle from getting stuck | ||||
210 | mStartPos.setX(mStartPos.x() + newTopLeft.x()); | ||||
211 | newTopLeft.setX(0); | ||||
212 | } else { | ||||
213 | const int xOffset = newTopLeft.x() - maxX; | ||||
214 | if (xOffset > 0) { | ||||
215 | mStartPos.setX(mStartPos.x() + xOffset); | ||||
216 | newTopLeft.setX(maxX); | ||||
114 | } | 217 | } | ||
115 | } | 218 | } | ||
116 | 219 | | |||
117 | if (config->useLightRegionMaskColour()) { | 220 | if (newTopLeft.y() < 0) { | ||
118 | rootItem->setProperty("maskColour", QColor(255, 255, 255, 100)); | 221 | mStartPos.setY(mStartPos.y() + newTopLeft.y()); | ||
119 | rootItem->setProperty("strokeColour", QColor(96, 96, 96, 255)); | 222 | newTopLeft.setY(0); | ||
223 | } else { | ||||
224 | const int yOffset = newTopLeft.y() - maxY; | ||||
225 | if (yOffset > 0) { | ||||
226 | mStartPos.setY(mStartPos.y() + yOffset); | ||||
227 | newTopLeft.setY(maxY); | ||||
120 | } | 228 | } | ||
121 | } | 229 | } | ||
122 | 230 | | |||
123 | QuickEditor::~QuickEditor() | 231 | // only move if the newTopLeft is different from the old one | ||
124 | { | 232 | newTopLeft -= mSelection.topLeft(); | ||
125 | Q_D(QuickEditor); | 233 | if (!newTopLeft.isNull()) { | ||
126 | delete d->mQuickView; | 234 | mSelection.translate(newTopLeft); | ||
127 | delete d->mDecl; | 235 | update(); | ||
128 | delete d->mQmlEngine; | 236 | } | ||
237 | } | ||||
238 | default: | ||||
239 | break; | ||||
240 | } | ||||
241 | event->accept(); | ||||
242 | } | ||||
129 | 243 | | |||
130 | delete d_ptr; | 244 | void QuickEditor::mouseReleaseEvent(QMouseEvent* event) | ||
245 | { | ||||
broulik: Use braces even for single-line statements
```
if (...) {
return;
}
``` | |||||
246 | const auto button = event->button(); | ||||
247 | if (button == Qt::LeftButton && mMouseDragState == MouseState::Inside) { | ||||
248 | setCursor(Qt::OpenHandCursor); | ||||
249 | } else if (button == Qt::RightButton) { | ||||
250 | mSelection.setWidth(0); | ||||
251 | mSelection.setHeight(0); | ||||
252 | } | ||||
253 | event->accept(); | ||||
254 | mMouseDragState = MouseState::None; | ||||
255 | update(); | ||||
131 | } | 256 | } | ||
132 | 257 | | |||
broulik: `const auto &` | |||||
133 | void QuickEditor::acceptImageHandler(int x, int y, int width, int height) | 258 | void QuickEditor::mouseDoubleClickEvent(QMouseEvent* event) | ||
134 | { | 259 | { | ||
135 | Q_D(QuickEditor); | 260 | if (event->button() == Qt::LeftButton && !mSelection.isEmpty() && mSelection.contains(event->pos()), true) { | ||
261 | event->accept(); | ||||
262 | acceptSelection(); | ||||
263 | } | ||||
264 | } | ||||
136 | 265 | | |||
137 | if ((x == -1) && (y == -1) && (width == -1) && (height == -1)) { | 266 | void QuickEditor::paintEvent(QPaintEvent*) { | ||
138 | SpectacleConfig::instance()->setCropRegion(QRect()); | 267 | QPainter painter(this); | ||
139 | emit grabCancelled(); | 268 | painter.setRenderHint(QPainter::Antialiasing); | ||
140 | return; | 269 | const QRect& fullRect = geometry(); | ||
270 | painter.drawPixmap(fullRect, mPixmap); | ||||
271 | if (!mSelection.size().isNull()) { | ||||
272 | painter.setPen(mStrokeColor); | ||||
273 | painter.drawRect(mSelection); | ||||
274 | painter.setClipRegion(QRegion(fullRect).subtracted(mSelection)); | ||||
275 | painter.fillRect(fullRect, mMaskColour); | ||||
276 | painter.setClipping(false); | ||||
277 | | ||||
278 | if ((mSelection.width() > 20) && (mSelection.height() > 20)) { | ||||
279 | const qreal leftX = mSelection.x(); | ||||
280 | const qreal width = mSelection.width(); | ||||
281 | const qreal centerX = leftX + width / 2.0; | ||||
282 | const qreal rightX = leftX + width; | ||||
283 | | ||||
284 | const qreal topY = mSelection.y(); | ||||
285 | const qreal height = mSelection.height(); | ||||
286 | const qreal centerY = topY + height / 2.0; | ||||
287 | const qreal bottomY = topY + height; | ||||
288 | | ||||
289 | QPainterPath path; | ||||
290 | | ||||
291 | const qreal cornerHandleDiameter = 2 * cornerHandleRadius; | ||||
292 | // top-left handle | ||||
293 | path.moveTo(leftX, topY); | ||||
294 | path.arcTo(leftX - cornerHandleRadius, topY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 0, -90); | ||||
295 | painter.fillPath(path, mStrokeColor); | ||||
296 | | ||||
297 | // top-right handle | ||||
298 | path.moveTo(rightX, topY); | ||||
299 | path.arcTo(rightX - cornerHandleRadius, topY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 180, 90); | ||||
300 | painter.fillPath(path, mStrokeColor); | ||||
301 | | ||||
302 | // bottom-left handle | ||||
303 | path.moveTo(leftX, bottomY); | ||||
304 | path.arcTo(leftX - cornerHandleRadius, bottomY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 0, 90); | ||||
305 | painter.fillPath(path, mStrokeColor); | ||||
306 | | ||||
307 | // bottom-right handle | ||||
308 | path.moveTo(rightX, bottomY); | ||||
309 | path.arcTo(rightX - cornerHandleRadius, bottomY - cornerHandleRadius, cornerHandleDiameter, cornerHandleDiameter, 180, -90); | ||||
310 | painter.fillPath(path, mStrokeColor); | ||||
311 | | ||||
312 | const qreal midHandleDiameter = 2 * midHandleRadius; | ||||
313 | // top-center handle | ||||
314 | path.moveTo(centerX, topY); | ||||
315 | path.arcTo(centerX - midHandleRadius, topY - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, -180); | ||||
316 | painter.fillPath(path, mStrokeColor); | ||||
317 | | ||||
318 | // right-center handle | ||||
319 | path.moveTo(rightX, centerY); | ||||
320 | path.arcTo(rightX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, 180); | ||||
321 | painter.fillPath(path, mStrokeColor); | ||||
322 | | ||||
323 | // bottom-center handle | ||||
324 | path.moveTo(centerX, bottomY); | ||||
325 | path.arcTo(centerX - midHandleRadius, bottomY - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, 180); | ||||
326 | painter.fillPath(path, mStrokeColor); | ||||
327 | | ||||
328 | // left-center handle | ||||
329 | path.moveTo(leftX, centerY); | ||||
330 | path.arcTo(leftX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, -180); | ||||
331 | painter.fillPath(path, mStrokeColor); | ||||
332 | } | ||||
333 | // Set the selection size and finds the most appropriate position: | ||||
334 | // - vertically centered inside the selection if the box is not covering the a large part of selection | ||||
335 | // - on top of the selection if the selection x position fits the box height plus some margin | ||||
336 | // - at the bottom otherwise | ||||
337 | // Note that text is drawn starting from the left bottom! | ||||
338 | const qreal dpr = devicePixelRatioF(); | ||||
339 | QString selectionText = QString(tr("%1x%2")).arg(qRound(mSelection.width() * dpr)).arg(qRound(mSelection.height() * dpr)); | ||||
340 | const QRect selectionTextRect = painter.boundingRect(rect(), 0, selectionText); | ||||
341 | int selectionBoxX = qMax(0, mSelection.x() + (mSelection.width() - selectionTextRect.width()) / 2); | ||||
342 | int selectionBoxY; | ||||
343 | if ((mSelection.width() > selectionSizeThreshold) && (mSelection.height() > selectionSizeThreshold)) { | ||||
344 | // show inside the box | ||||
345 | selectionBoxY = mSelection.y() + (mSelection.height() + selectionTextRect.height()) / 2; | ||||
346 | } else { | ||||
347 | // show on top by default | ||||
348 | selectionBoxY = mSelection.y() - 8; | ||||
349 | if (selectionBoxY < selectionTextRect.height()) { | ||||
350 | // show at the bottom | ||||
351 | selectionBoxY = mSelection.y() + mSelection.height() + selectionTextRect.height() + 6; | ||||
352 | } | ||||
353 | } | ||||
354 | // Now do the actual box, border and text drawing | ||||
355 | QPalette pal; | ||||
356 | painter.setBrush(pal.window()); | ||||
357 | painter.setPen(pal.windowText().color()); | ||||
358 | painter.drawRect(selectionBoxX - 4, selectionBoxY - selectionTextRect.height() - 2, selectionTextRect.width() + 10, selectionTextRect.height() + 8); | ||||
359 | painter.drawText(selectionBoxX - 4, selectionBoxY - selectionTextRect.height() - 2, selectionTextRect.width() + 10, selectionTextRect.height() + 8, Qt::AlignCenter, selectionText); | ||||
360 | | ||||
361 | QSize textSize = mBottomHelpText.size().toSize(); | ||||
362 | QPoint pos((size().width() - textSize.width()) / 2, size().height() - textSize.height() - 8); | ||||
363 | QRect bottomHelp(pos.x() - 12, pos.y() - 8, textSize.width() + 24, textSize.height() + 16); | ||||
364 | | ||||
365 | // display bottom help text only if it does not intersect with the selection | ||||
366 | if (!mSelection.intersects(bottomHelp)) { | ||||
367 | painter.setPen(Qt::black); | ||||
368 | painter.setBrush(QColor::fromRgbF(1, 1, 1, 0.85)); | ||||
369 | painter.drawRect(bottomHelp); | ||||
370 | painter.setFont(mBottomHelpTextFont); | ||||
371 | painter.drawStaticText(pos, mBottomHelpText); | ||||
372 | } | ||||
373 | } else { | ||||
374 | painter.fillRect(fullRect, mMaskColour); | ||||
375 | QSize textSize = mMidHelpText.size().toSize(); | ||||
376 | QPoint pos((fullRect.width() - textSize.width()) / 2, (fullRect.height() - textSize.height()) / 2); | ||||
377 | painter.setBrush(QColor::fromRgbF(1, 1, 1, 0.85)); | ||||
378 | QPen rectBorder(Qt::black); | ||||
379 | rectBorder.setWidth(2); | ||||
380 | painter.setPen(rectBorder); | ||||
381 | painter.drawRoundedRect(QRect(pos.x() - 20, pos.y() - 20, textSize.width() + 40, textSize.height() + 40), 10, 10); | ||||
382 | painter.setPen(Qt::black); | ||||
383 | painter.setFont(mMidHelpTextFont); | ||||
384 | painter.drawStaticText(pos, mMidHelpText); | ||||
385 | } | ||||
141 | } | 386 | } | ||
142 | 387 | | |||
143 | auto pixelRatio = d->mQuickView->devicePixelRatio(); | 388 | inline void QuickEditor::setMouseCursor(const QPoint& pos) { | ||
144 | d->mGrabRect = QRect(x * pixelRatio, y * pixelRatio, width * pixelRatio, height * pixelRatio); | 389 | MouseState mouseState = whereIsTheMouse(pos); | ||
145 | SpectacleConfig::instance()->setCropRegion(d->mGrabRect); | 390 | if (mouseState == MouseState::Outside) { | ||
391 | setCursor(Qt::CrossCursor); | ||||
392 | } else if (MouseState::TopLeftOrBottomRight & mouseState) { | ||||
393 | setCursor(Qt::SizeFDiagCursor); | ||||
394 | } else if (MouseState::TopRightOrBottomLeft & mouseState) { | ||||
395 | setCursor(Qt::SizeBDiagCursor); | ||||
396 | } else if (MouseState::TopOrBottom & mouseState) { | ||||
397 | setCursor(Qt::SizeVerCursor); | ||||
398 | } else if (MouseState::RightOrLeft & mouseState) { | ||||
399 | setCursor(Qt::SizeHorCursor); | ||||
400 | } else { | ||||
401 | setCursor(Qt::OpenHandCursor); | ||||
402 | } | ||||
403 | } | ||||
146 | 404 | | |||
147 | d->mQuickView->hide(); | 405 | QuickEditor::MouseState QuickEditor::whereIsTheMouse(const QPoint& pos) | ||
148 | emit grabDone(mImageStore->mPixmap.copy(d->mGrabRect), d->mGrabRect); | 406 | { | ||
407 | if (mSelection.contains(pos, true)) { | ||||
408 | const int topEdgeOffset = pos.y() - mSelection.top(); | ||||
409 | const int rightEdgeOffset = mSelection.right() - pos.x(); | ||||
410 | const int bottomEdgeOffset = mSelection.bottom() - pos.y(); | ||||
411 | const int leftEdgeOffset = pos.x() - mSelection.left(); | ||||
412 | const int verSize = qMin(mouseAreaSize, mSelection.height()); | ||||
413 | const int horSize = qMin(mouseAreaSize, mSelection.width()); | ||||
414 | | ||||
415 | auto withinThreshold = [](const int offset, const int size) { | ||||
416 | return offset < size && offset > 0; | ||||
417 | }; | ||||
418 | | ||||
419 | const bool withinTopEdge = withinThreshold(topEdgeOffset, verSize); | ||||
420 | const bool withinRightEdge = withinThreshold(rightEdgeOffset, horSize); | ||||
421 | const bool withinBottomEdge = withinThreshold(bottomEdgeOffset, verSize); | ||||
422 | const bool withinLeftEdge = withinThreshold(leftEdgeOffset, horSize); | ||||
423 | | ||||
guotao: should divde by 3, not 2, when adjust very small rectangle | |||||
True, makes sense. Then we would actually be able to move the rectangle around when it's small. abalaji: True, makes sense. Then we would actually be able to move the rectangle around when it's small. | |||||
424 | if (withinTopEdge) { | ||||
425 | if (withinLeftEdge) { | ||||
426 | return MouseState::TopLeft; | ||||
427 | } else if (withinRightEdge) { | ||||
428 | return MouseState::TopRight; | ||||
429 | } else { | ||||
430 | return MouseState::Top; | ||||
431 | } | ||||
432 | } else if (withinBottomEdge) { | ||||
433 | if (withinRightEdge) { | ||||
434 | return MouseState::BottomRight; | ||||
435 | } else if (withinLeftEdge) { | ||||
436 | return MouseState::BottomLeft; | ||||
437 | } else { | ||||
438 | return MouseState::Bottom; | ||||
439 | } | ||||
440 | } else if (withinRightEdge) { | ||||
441 | return MouseState::Right; | ||||
442 | } else if (withinLeftEdge) { | ||||
443 | return MouseState::Left; | ||||
444 | } else { | ||||
445 | return MouseState::Inside; | ||||
446 | } | ||||
447 | } else { | ||||
448 | return MouseState::Outside; | ||||
449 | } | ||||
149 | } | 450 | } |
Hmm, we still can't find a better way to handle this? I'm not sure if this text is really translatable in its current state.