Changeset View
Changeset View
Standalone View
Standalone View
src/QuickEditor/QuickEditor.cpp
Show All 29 Lines | |||||
30 | const int QuickEditor::selectionBoxPaddingY = 4; | 30 | const int QuickEditor::selectionBoxPaddingY = 4; | ||
31 | const int QuickEditor::selectionBoxMarginY = 2; | 31 | const int QuickEditor::selectionBoxMarginY = 2; | ||
32 | 32 | | |||
33 | const int QuickEditor::bottomHelpBoxPaddingX = 12; | 33 | const int QuickEditor::bottomHelpBoxPaddingX = 12; | ||
34 | const int QuickEditor::bottomHelpBoxPaddingY = 8; | 34 | const int QuickEditor::bottomHelpBoxPaddingY = 8; | ||
35 | const int QuickEditor::bottomHelpBoxPairSpacing = 6; | 35 | const int QuickEditor::bottomHelpBoxPairSpacing = 6; | ||
36 | const int QuickEditor::bottomHelpBoxLineHeight = 24; | 36 | const int QuickEditor::bottomHelpBoxLineHeight = 24; | ||
37 | 37 | | |||
38 | const int QuickEditor::magZoom = 5; | ||||
39 | const int QuickEditor::magPixels = 16; | ||||
40 | const int QuickEditor::magOffset = 32; | ||||
41 | | ||||
38 | QuickEditor::QuickEditor(const QPixmap& pixmap, QObject* parent) : | 42 | QuickEditor::QuickEditor(const QPixmap& pixmap, QObject* parent) : | ||
39 | mMaskColour(QColor::fromRgbF(0, 0, 0, 0.15)), | 43 | mMaskColour(QColor::fromRgbF(0, 0, 0, 0.15)), | ||
40 | mStrokeColour(palette().highlight().color()), | 44 | mStrokeColour(palette().highlight().color()), | ||
45 | mCrossColour(QColor::fromRgbF(mStrokeColour.redF(), mStrokeColour.greenF(), mStrokeColour.blueF(), 0.7)), | ||||
41 | mLabelBackgroundColour(QColor::fromRgbF( | 46 | mLabelBackgroundColour(QColor::fromRgbF( | ||
42 | palette().light().color().redF(), | 47 | palette().light().color().redF(), | ||
43 | palette().light().color().greenF(), | 48 | palette().light().color().greenF(), | ||
44 | palette().light().color().blueF(), | 49 | palette().light().color().blueF(), | ||
45 | 0.85 | 50 | 0.85 | ||
46 | )), | 51 | )), | ||
47 | mLabelForegroundColour(palette().windowText().color()), | 52 | mLabelForegroundColour(palette().windowText().color()), | ||
48 | mMidHelpText(tr("Click and drag to draw a selection rectangle,\nor press Esc to quit")), | 53 | mMidHelpText(tr("Click and drag to draw a selection rectangle,\nor press Esc to quit")), | ||
49 | mMidHelpTextFont(font()), | 54 | mMidHelpTextFont(font()), | ||
50 | mBottomHelpText({ | 55 | mBottomHelpText({ | ||
51 | {QStaticText(tr("Enter, double-click:")), QStaticText(tr("Take screenshot"))}, | 56 | {QStaticText(tr("Enter, double-click:")), QStaticText(tr("Take screenshot"))}, | ||
52 | {QStaticText(tr("Shift:")), QStaticText(tr("Hold to toggle magnifier"))}, | 57 | {QStaticText(tr("Shift:")), QStaticText(tr("Hold to toggle magnifier"))}, | ||
53 | {QStaticText(tr("Right-click:")), QStaticText(tr("Reset selection"))}, | 58 | {QStaticText(tr("Right-click:")), QStaticText(tr("Reset selection"))}, | ||
54 | {QStaticText(tr("Esc:")), QStaticText(tr("Cancel"))}, | 59 | {QStaticText(tr("Esc:")), QStaticText(tr("Cancel"))}, | ||
55 | }), | 60 | }), | ||
56 | mBottomHelpTextFont(font()), | 61 | mBottomHelpTextFont(font()), | ||
57 | mBottomHelpGridLeftWidth(0), | 62 | mBottomHelpGridLeftWidth(0), | ||
58 | mMouseDragState(MouseState::None), | 63 | mMouseDragState(MouseState::None), | ||
59 | mPixmap(pixmap) | 64 | mPixmap(pixmap), | ||
65 | mMagnifierAllowed(false), | ||||
66 | mShowMagnifier(SpectacleConfig::instance()->showMagnifierChecked()), | ||||
67 | mToggleMagnifier(false) | ||||
60 | { | 68 | { | ||
61 | Q_UNUSED(parent); | 69 | Q_UNUSED(parent); | ||
62 | 70 | | |||
63 | SpectacleConfig *config = SpectacleConfig::instance(); | 71 | SpectacleConfig *config = SpectacleConfig::instance(); | ||
64 | if (config->useLightRegionMaskColour()) { | 72 | if (config->useLightRegionMaskColour()) { | ||
65 | mMaskColour = QColor(255, 255, 255, 100); | 73 | mMaskColour = QColor(255, 255, 255, 100); | ||
66 | } | 74 | } | ||
67 | 75 | | |||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Line(s) | 132 | case Qt::Key_Escape: | |||
126 | delete this; | 134 | delete this; | ||
127 | break; | 135 | break; | ||
128 | case Qt::Key_Return: | 136 | case Qt::Key_Return: | ||
129 | case Qt::Key_Enter: | 137 | case Qt::Key_Enter: | ||
130 | acceptSelection(); | 138 | acceptSelection(); | ||
131 | default: | 139 | default: | ||
132 | break; | 140 | break; | ||
133 | } | 141 | } | ||
142 | if (event->modifiers() & Qt::ShiftModifier) { | ||||
143 | mToggleMagnifier = true; | ||||
144 | update(); | ||||
145 | } | ||||
146 | event->accept(); | ||||
147 | } | ||||
148 | | ||||
149 | void QuickEditor::keyReleaseEvent(QKeyEvent* event) | ||||
150 | { | ||||
151 | if (mToggleMagnifier && !(event->modifiers() & Qt::ShiftModifier)) { | ||||
152 | mToggleMagnifier = false; | ||||
153 | update(); | ||||
154 | } | ||||
155 | event->accept(); | ||||
134 | } | 156 | } | ||
135 | 157 | | |||
136 | void QuickEditor::mousePressEvent(QMouseEvent* event) | 158 | void QuickEditor::mousePressEvent(QMouseEvent* event) | ||
137 | { | 159 | { | ||
138 | if (event->button() & Qt::LeftButton) { | 160 | if (event->button() & Qt::LeftButton) { | ||
139 | const QPointF& pos = event->screenPos(); | 161 | const QPointF& pos = event->screenPos(); | ||
162 | mMousePos = pos; | ||||
163 | mMagnifierAllowed = true; | ||||
140 | mMouseDragState = whereIsTheMouse(pos); | 164 | mMouseDragState = whereIsTheMouse(pos); | ||
141 | switch(mMouseDragState) { | 165 | switch(mMouseDragState) { | ||
142 | case MouseState::Outside: | 166 | case MouseState::Outside: | ||
143 | mStartPos = pos; | 167 | mStartPos = pos; | ||
144 | break; | 168 | break; | ||
145 | case MouseState::Inside: | 169 | case MouseState::Inside: | ||
146 | mStartPos = pos; | 170 | mStartPos = pos; | ||
171 | mMagnifierAllowed = false; | ||||
147 | mInitialTopLeft = mSelection.topLeft(); | 172 | mInitialTopLeft = mSelection.topLeft(); | ||
148 | setCursor(Qt::ClosedHandCursor); | 173 | setCursor(Qt::ClosedHandCursor); | ||
149 | break; | 174 | break; | ||
150 | case MouseState::Top: | 175 | case MouseState::Top: | ||
151 | case MouseState::Left: | 176 | case MouseState::Left: | ||
152 | case MouseState::TopLeft: | 177 | case MouseState::TopLeft: | ||
153 | mStartPos = mSelection.bottomRight(); | 178 | mStartPos = mSelection.bottomRight(); | ||
154 | break; | 179 | break; | ||
155 | case MouseState::Bottom: | 180 | case MouseState::Bottom: | ||
156 | case MouseState::Right: | 181 | case MouseState::Right: | ||
157 | case MouseState::BottomRight: | 182 | case MouseState::BottomRight: | ||
158 | mStartPos = mSelection.topLeft(); | 183 | mStartPos = mSelection.topLeft(); | ||
159 | break; | 184 | break; | ||
160 | case MouseState::TopRight: | 185 | case MouseState::TopRight: | ||
161 | mStartPos = mSelection.bottomLeft(); | 186 | mStartPos = mSelection.bottomLeft(); | ||
162 | break; | 187 | break; | ||
163 | case MouseState::BottomLeft: | 188 | case MouseState::BottomLeft: | ||
164 | mStartPos = mSelection.topRight(); | 189 | mStartPos = mSelection.topRight(); | ||
165 | default: | 190 | default: | ||
166 | break; | 191 | break; | ||
167 | } | 192 | } | ||
168 | } | 193 | } | ||
194 | if (mMagnifierAllowed) { | ||||
195 | update(); | ||||
196 | } | ||||
169 | event->accept(); | 197 | event->accept(); | ||
170 | } | 198 | } | ||
171 | 199 | | |||
172 | void QuickEditor::mouseMoveEvent(QMouseEvent* event) | 200 | void QuickEditor::mouseMoveEvent(QMouseEvent* event) | ||
173 | { | 201 | { | ||
174 | const QPointF& pos = event->screenPos(); | 202 | const QPointF& pos = event->screenPos(); | ||
203 | mMousePos = pos; | ||||
204 | mMagnifierAllowed = true; | ||||
175 | switch (mMouseDragState) { | 205 | switch (mMouseDragState) { | ||
176 | case MouseState::None: { | 206 | case MouseState::None: { | ||
177 | setMouseCursor(pos); | 207 | setMouseCursor(pos); | ||
208 | mMagnifierAllowed = false; | ||||
178 | break; | 209 | break; | ||
179 | } | 210 | } | ||
180 | case MouseState::TopLeft: | 211 | case MouseState::TopLeft: | ||
181 | case MouseState::TopRight: | 212 | case MouseState::TopRight: | ||
182 | case MouseState::BottomRight: | 213 | case MouseState::BottomRight: | ||
183 | case MouseState::BottomLeft: | 214 | case MouseState::BottomLeft: | ||
184 | case MouseState::Outside: { | 215 | case MouseState::Outside: { | ||
185 | const qreal dprI = 1.0 / devicePixelRatioF(); | 216 | const qreal dprI = 1.0 / devicePixelRatioF(); | ||
Show All 26 Lines | 241 | mSelection.setRect( | |||
212 | mSelection.y(), | 243 | mSelection.y(), | ||
213 | qAbs(pos.x() - mStartPos.x()) + dprI, | 244 | qAbs(pos.x() - mStartPos.x()) + dprI, | ||
214 | mSelection.height() | 245 | mSelection.height() | ||
215 | ); | 246 | ); | ||
216 | update(); | 247 | update(); | ||
217 | break; | 248 | break; | ||
218 | } | 249 | } | ||
219 | case MouseState::Inside: { | 250 | case MouseState::Inside: { | ||
251 | mMagnifierAllowed = false; | ||||
220 | // We use some math here to figure out if the diff with which we | 252 | // We use some math here to figure out if the diff with which we | ||
221 | // move the rectangle with moves it out of bounds, | 253 | // move the rectangle with moves it out of bounds, | ||
222 | // in which case we adjust the diff to not let that happen | 254 | // in which case we adjust the diff to not let that happen | ||
223 | 255 | | |||
224 | // new top left point of the rectangle | 256 | // new top left point of the rectangle | ||
225 | QPointF newTopLeft = pos - mStartPos + mInitialTopLeft; | 257 | QPointF newTopLeft = pos - mStartPos + mInitialTopLeft; | ||
226 | 258 | | |||
227 | const qreal dprI = 1.0 / devicePixelRatioF(); | 259 | const qreal dprI = 1.0 / devicePixelRatioF(); | ||
▲ Show 20 Lines • Show All 56 Lines • ▼ Show 20 Line(s) | 314 | { | |||
284 | if (event->button() == Qt::LeftButton && mSelection.contains(event->pos())) { | 316 | if (event->button() == Qt::LeftButton && mSelection.contains(event->pos())) { | ||
285 | acceptSelection(); | 317 | acceptSelection(); | ||
286 | } | 318 | } | ||
287 | } | 319 | } | ||
288 | 320 | | |||
289 | void QuickEditor::paintEvent(QPaintEvent*) | 321 | void QuickEditor::paintEvent(QPaintEvent*) | ||
290 | { | 322 | { | ||
291 | QPainter painter(this); | 323 | QPainter painter(this); | ||
292 | painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); | | |||
293 | const QRect& fullRect = geometry(); | 324 | const QRect& fullRect = geometry(); | ||
294 | painter.drawPixmap(fullRect, mPixmap); | 325 | painter.drawPixmap(fullRect, mPixmap); | ||
295 | if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { | 326 | if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { | ||
296 | painter.setPen(mStrokeColour); | 327 | const qreal dprI = 1.0 / devicePixelRatioF(); | ||
297 | painter.drawRect(mSelection); | 328 | painter.setPen(QPen(mStrokeColour, dprI)); | ||
329 | | ||||
330 | // because of the way drawRect works, we need to adjust the bottom and left edges of the rectangle | ||||
331 | // by one "real" pixel, where the reciprocal of devicePixelRatioF comes handle | ||||
332 | // make sure that anti-aliasing is switched OFF while drawing the rectangle | ||||
333 | const QRectF adjustedSelectionRect = mSelection.adjusted(0, 0, -dprI, -dprI); | ||||
334 | painter.drawRect(adjustedSelectionRect); | ||||
298 | 335 | | |||
299 | QRectF top(0, 0, fullRect.width(), mSelection.top()); | 336 | QRectF top(0, 0, fullRect.width(), mSelection.top()); | ||
300 | QRectF right(mSelection.right(), mSelection.top(), fullRect.width() - mSelection.right(), mSelection.height()); | 337 | QRectF right(mSelection.right(), mSelection.top(), fullRect.width() - mSelection.right(), mSelection.height()); | ||
301 | QRectF bottom(0, mSelection.bottom(), fullRect.width(), fullRect.height() - mSelection.bottom()); | 338 | QRectF bottom(0, mSelection.bottom(), fullRect.width(), fullRect.height() - mSelection.bottom()); | ||
302 | QRectF left(0, mSelection.top(), mSelection.left(), mSelection.height()); | 339 | QRectF left(0, mSelection.top(), mSelection.left(), mSelection.height()); | ||
303 | for (auto& rect : { top, right, bottom, left }) { | 340 | for (auto& rect : { top, right, bottom, left }) { | ||
304 | painter.fillRect(rect, mMaskColour); | 341 | painter.fillRect(rect, mMaskColour); | ||
305 | } | 342 | } | ||
306 | 343 | | |||
307 | drawSelectionSizeTooltip(painter); | 344 | drawSelectionSizeTooltip(painter); | ||
308 | if (mMouseDragState == MouseState::None) { // mouse is up | 345 | if (mMouseDragState == MouseState::None) { // mouse is up | ||
309 | if ((mSelection.width() > 20) && (mSelection.height() > 20)) { | 346 | if ((mSelection.width() > 20) && (mSelection.height() > 20)) { | ||
310 | drawDragHandles(painter); | 347 | drawDragHandles(painter); | ||
311 | } | 348 | } | ||
312 | 349 | | |||
313 | drawBottomHelpText(painter); | 350 | drawBottomHelpText(painter); | ||
351 | } else if (mMagnifierAllowed && (mShowMagnifier ^ mToggleMagnifier)) { | ||||
352 | drawMagnifier(painter); | ||||
314 | } | 353 | } | ||
315 | } else { | 354 | } else { | ||
316 | drawMidHelpText(painter); | 355 | drawMidHelpText(painter); | ||
317 | } | 356 | } | ||
318 | } | 357 | } | ||
319 | 358 | | |||
320 | inline void QuickEditor::layoutBottomHelpText() | 359 | inline void QuickEditor::layoutBottomHelpText() | ||
321 | { | 360 | { | ||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Line(s) | 397 | for (auto& item : mBottomHelpText) { | |||
363 | painter.drawStaticText(mBottomHelpGridLeftWidth - leftSize.width(), drawY, left); | 402 | painter.drawStaticText(mBottomHelpGridLeftWidth - leftSize.width(), drawY, left); | ||
364 | painter.drawStaticText(mBottomHelpGridLeftWidth + bottomHelpBoxPairSpacing, drawY, right); | 403 | painter.drawStaticText(mBottomHelpGridLeftWidth + bottomHelpBoxPairSpacing, drawY, right); | ||
365 | topOffset += bottomHelpBoxLineHeight; | 404 | topOffset += bottomHelpBoxLineHeight; | ||
366 | } | 405 | } | ||
367 | } | 406 | } | ||
368 | 407 | | |||
369 | inline void QuickEditor::drawDragHandles(QPainter& painter) | 408 | inline void QuickEditor::drawDragHandles(QPainter& painter) | ||
370 | { | 409 | { | ||
410 | painter.setRenderHints(QPainter::Antialiasing); | ||||
371 | const qreal leftX = mSelection.x(); | 411 | const qreal leftX = mSelection.x(); | ||
372 | const qreal width = mSelection.width(); | 412 | const qreal width = mSelection.width(); | ||
373 | const qreal centerX = leftX + width / 2.0; | 413 | const qreal centerX = leftX + width / 2.0; | ||
374 | const qreal rightX = leftX + width; | 414 | const qreal rightX = leftX + width; | ||
375 | 415 | | |||
376 | const qreal topY = mSelection.y(); | 416 | const qreal topY = mSelection.y(); | ||
377 | const qreal height = mSelection.height(); | 417 | const qreal height = mSelection.height(); | ||
378 | const qreal centerY = topY + height / 2.0; | 418 | const qreal centerY = topY + height / 2.0; | ||
Show All 35 Lines | |||||
414 | // left-center handle | 454 | // left-center handle | ||
415 | path.moveTo(leftX, centerY); | 455 | path.moveTo(leftX, centerY); | ||
416 | path.arcTo(leftX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, -180); | 456 | path.arcTo(leftX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, -180); | ||
417 | 457 | | |||
418 | // draw the path | 458 | // draw the path | ||
419 | painter.fillPath(path, mStrokeColour); | 459 | painter.fillPath(path, mStrokeColour); | ||
420 | } | 460 | } | ||
421 | 461 | | |||
462 | inline void QuickEditor::drawMagnifier(QPainter &painter) | ||||
463 | { | ||||
464 | const int pixels = 2 * magPixels + 1; | ||||
465 | int magX = mMousePos.x() * devicePixelRatioF() - magPixels; | ||||
466 | int offsetX = 0; | ||||
467 | if (magX < 0) { | ||||
468 | offsetX = magX; | ||||
469 | magX = 0; | ||||
470 | } else { | ||||
471 | const int maxX = mPixmap.width() - pixels; | ||||
472 | if (magX > maxX) { | ||||
473 | offsetX = magX - maxX; | ||||
474 | magX = maxX; | ||||
475 | } | ||||
476 | } | ||||
477 | int magY = mMousePos.y() * devicePixelRatioF() - magPixels; | ||||
478 | int offsetY = 0; | ||||
479 | if (magY < 0) { | ||||
480 | offsetY = magY; | ||||
481 | magY = 0; | ||||
482 | } else { | ||||
483 | const int maxY = mPixmap.height() - pixels; | ||||
484 | if (magY > maxY) { | ||||
485 | offsetY = magY - maxY; | ||||
486 | magY = maxY; | ||||
487 | } | ||||
488 | } | ||||
489 | QRectF magniRect(magX, magY, pixels, pixels); | ||||
490 | | ||||
491 | qreal drawPosX = mMousePos.x() + magOffset + pixels * magZoom / 2; | ||||
492 | if (drawPosX > width() - pixels * magZoom / 2) { | ||||
493 | drawPosX = mMousePos.x() - magOffset - pixels * magZoom / 2; | ||||
494 | } | ||||
495 | qreal drawPosY = mMousePos.y() + magOffset + pixels * magZoom / 2; | ||||
496 | if (drawPosY > height() - pixels * magZoom / 2) { | ||||
497 | drawPosY = mMousePos.y() - magOffset - pixels * magZoom / 2; | ||||
498 | } | ||||
499 | QPointF drawPos(drawPosX, drawPosY); | ||||
500 | QRectF crossHairTop(drawPos.x() + magZoom * (offsetX - 0.5), drawPos.y() - magZoom * (magPixels + 0.5), magZoom, magZoom * (magPixels + offsetY)); | ||||
501 | QRectF crossHairRight(drawPos.x() + magZoom * (0.5 + offsetX), drawPos.y() + magZoom * (offsetY - 0.5), magZoom * (magPixels - offsetX), magZoom); | ||||
502 | QRectF crossHairBottom(drawPos.x() + magZoom * (offsetX - 0.5), drawPos.y() + magZoom * (0.5 + offsetY), magZoom, magZoom * (magPixels - offsetY)); | ||||
503 | QRectF crossHairLeft(drawPos.x() - magZoom * (magPixels + 0.5), drawPos.y() + magZoom * (offsetY - 0.5), magZoom * (magPixels + offsetX), magZoom); | ||||
504 | const auto frag = QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom); | ||||
505 | painter.drawPixmapFragments(&frag, 1, mPixmap, QPainter::OpaqueHint); | ||||
506 | painter.setCompositionMode(QPainter::CompositionMode_SourceOver); | ||||
507 | for (auto& rect : { crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) { | ||||
508 | painter.fillRect(rect, mCrossColour); | ||||
509 | } | ||||
510 | } | ||||
511 | | ||||
422 | inline void QuickEditor::drawMidHelpText(QPainter &painter) | 512 | inline void QuickEditor::drawMidHelpText(QPainter &painter) | ||
423 | { | 513 | { | ||
424 | const QRect& fullRect = geometry(); | 514 | const QRect& fullRect = geometry(); | ||
425 | painter.fillRect(fullRect, mMaskColour); | 515 | painter.fillRect(fullRect, mMaskColour); | ||
426 | painter.setFont(mMidHelpTextFont); | 516 | painter.setFont(mMidHelpTextFont); | ||
427 | QRect textSize = painter.boundingRect(fullRect, Qt::AlignCenter, mMidHelpText); | 517 | QRect textSize = painter.boundingRect(fullRect, Qt::AlignCenter, mMidHelpText); | ||
428 | QPoint pos((fullRect.width() - textSize.width()) / 2, (fullRect.height() - textSize.height()) / 2); | 518 | QPoint pos((fullRect.width() - textSize.width()) / 2, (fullRect.height() - textSize.height()) / 2); | ||
429 | 519 | | |||
▲ Show 20 Lines • Show All 113 Lines • Show Last 20 Lines |