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 | | ||||
38 | QuickEditor::QuickEditor(const QPixmap& pixmap, QObject* parent) : | 41 | QuickEditor::QuickEditor(const QPixmap& pixmap, QObject* parent) : | ||
39 | mMaskColour(QColor::fromRgbF(0, 0, 0, 0.15)), | 42 | mMaskColour(QColor::fromRgbF(0, 0, 0, 0.15)), | ||
40 | mStrokeColor(palette().highlight().color()), | 43 | mStrokeColour(palette().highlight().color()), | ||
44 | mCrossColour(QColor::fromRgbF(mStrokeColour.redF(), mStrokeColour.greenF(), mStrokeColour.blueF(), 0.7)), | ||||
41 | mLabelBackgroundColour(QColor::fromRgbF( | 45 | mLabelBackgroundColour(QColor::fromRgbF( | ||
42 | palette().light().color().redF(), | 46 | palette().light().color().redF(), | ||
43 | palette().light().color().greenF(), | 47 | palette().light().color().greenF(), | ||
44 | palette().light().color().blueF(), | 48 | palette().light().color().blueF(), | ||
45 | 0.85 | 49 | 0.85 | ||
46 | )), | 50 | )), | ||
47 | mLabelForegroundColour(palette().windowText().color()), | 51 | mLabelForegroundColour(palette().windowText().color()), | ||
48 | mMidHelpText(tr("Click and drag to draw a selection rectangle,\nor press Esc to quit")), | 52 | mMidHelpText(tr("Click and drag to draw a selection rectangle,\nor press Esc to quit")), | ||
49 | mMidHelpTextFont(font()), | 53 | mMidHelpTextFont(font()), | ||
50 | mBottomHelpText({ | 54 | mBottomHelpText({ | ||
51 | {QStaticText(tr("Enter, double-click:")), QStaticText(tr("Take screenshot"))}, | 55 | {QStaticText(tr("Enter, double-click:")), QStaticText(tr("Take screenshot"))}, | ||
52 | {QStaticText(tr("Shift:")), QStaticText(tr("Hold to toggle magnifier"))}, | 56 | {QStaticText(tr("Shift:")), QStaticText(tr("Hold to toggle magnifier"))}, | ||
53 | {QStaticText(tr("Right-click:")), QStaticText(tr("Reset selection"))}, | 57 | {QStaticText(tr("Right-click:")), QStaticText(tr("Reset selection"))}, | ||
54 | {QStaticText(tr("Esc:")), QStaticText(tr("Cancel"))}, | 58 | {QStaticText(tr("Esc:")), QStaticText(tr("Cancel"))}, | ||
55 | }), | 59 | }), | ||
56 | mBottomHelpTextFont(font()), | 60 | mBottomHelpTextFont(font()), | ||
57 | mBottomHelpGridLeftWidth(0), | 61 | mBottomHelpGridLeftWidth(0), | ||
58 | mMouseDragState(MouseState::None), | 62 | mMouseDragState(MouseState::None), | ||
59 | mPixmap(pixmap) | 63 | mPixmap(pixmap), | ||
64 | mMagnifierAllowed(false), | ||||
65 | mShowMagnifier(SpectacleConfig::instance()->showMagnifierChecked()), | ||||
66 | mToggleMagnifier(false) | ||||
60 | { | 67 | { | ||
61 | Q_UNUSED(parent); | 68 | Q_UNUSED(parent); | ||
62 | 69 | | |||
63 | SpectacleConfig *config = SpectacleConfig::instance(); | 70 | SpectacleConfig *config = SpectacleConfig::instance(); | ||
64 | if (config->useLightRegionMaskColour()) { | 71 | if (config->useLightRegionMaskColour()) { | ||
65 | mMaskColour = QColor(255, 255, 255, 100); | 72 | mMaskColour = QColor(255, 255, 255, 100); | ||
66 | } | 73 | } | ||
67 | 74 | | |||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Line(s) | 131 | case Qt::Key_Escape: | |||
126 | delete this; | 133 | delete this; | ||
127 | break; | 134 | break; | ||
128 | case Qt::Key_Return: | 135 | case Qt::Key_Return: | ||
129 | case Qt::Key_Enter: | 136 | case Qt::Key_Enter: | ||
130 | acceptSelection(); | 137 | acceptSelection(); | ||
131 | default: | 138 | default: | ||
132 | break; | 139 | break; | ||
133 | } | 140 | } | ||
141 | if (event->modifiers() & Qt::ShiftModifier) { | ||||
142 | mToggleMagnifier = true; | ||||
143 | update(); | ||||
144 | } | ||||
145 | event->accept(); | ||||
146 | } | ||||
147 | | ||||
148 | void QuickEditor::keyReleaseEvent(QKeyEvent* event) | ||||
149 | { | ||||
150 | if (mToggleMagnifier && !(event->modifiers() & Qt::ShiftModifier)) { | ||||
151 | mToggleMagnifier = false; | ||||
152 | update(); | ||||
153 | } | ||||
154 | event->accept(); | ||||
134 | } | 155 | } | ||
135 | 156 | | |||
136 | void QuickEditor::mousePressEvent(QMouseEvent* event) | 157 | void QuickEditor::mousePressEvent(QMouseEvent* event) | ||
137 | { | 158 | { | ||
138 | if (event->button() & Qt::LeftButton) { | 159 | if (event->button() & Qt::LeftButton) { | ||
139 | const QPointF& pos = event->screenPos(); | 160 | const QPointF& pos = event->screenPos(); | ||
161 | mMousePos = pos; | ||||
162 | mMagnifierAllowed = true; | ||||
140 | mMouseDragState = whereIsTheMouse(pos); | 163 | mMouseDragState = whereIsTheMouse(pos); | ||
141 | switch(mMouseDragState) { | 164 | switch(mMouseDragState) { | ||
142 | case MouseState::Outside: | 165 | case MouseState::Outside: | ||
143 | mStartPos = pos; | 166 | mStartPos = pos; | ||
144 | break; | 167 | break; | ||
145 | case MouseState::Inside: | 168 | case MouseState::Inside: | ||
146 | mStartPos = pos; | 169 | mStartPos = pos; | ||
170 | mMagnifierAllowed = false; | ||||
147 | mInitialTopLeft = mSelection.topLeft(); | 171 | mInitialTopLeft = mSelection.topLeft(); | ||
148 | setCursor(Qt::ClosedHandCursor); | 172 | setCursor(Qt::ClosedHandCursor); | ||
149 | break; | 173 | break; | ||
150 | case MouseState::Top: | 174 | case MouseState::Top: | ||
151 | case MouseState::Left: | 175 | case MouseState::Left: | ||
152 | case MouseState::TopLeft: | 176 | case MouseState::TopLeft: | ||
153 | mStartPos = mSelection.bottomRight(); | 177 | mStartPos = mSelection.bottomRight(); | ||
154 | break; | 178 | break; | ||
155 | case MouseState::Bottom: | 179 | case MouseState::Bottom: | ||
156 | case MouseState::Right: | 180 | case MouseState::Right: | ||
157 | case MouseState::BottomRight: | 181 | case MouseState::BottomRight: | ||
158 | mStartPos = mSelection.topLeft(); | 182 | mStartPos = mSelection.topLeft(); | ||
159 | break; | 183 | break; | ||
160 | case MouseState::TopRight: | 184 | case MouseState::TopRight: | ||
161 | mStartPos = mSelection.bottomLeft(); | 185 | mStartPos = mSelection.bottomLeft(); | ||
162 | break; | 186 | break; | ||
163 | case MouseState::BottomLeft: | 187 | case MouseState::BottomLeft: | ||
164 | mStartPos = mSelection.topRight(); | 188 | mStartPos = mSelection.topRight(); | ||
165 | default: | 189 | default: | ||
166 | break; | 190 | break; | ||
167 | } | 191 | } | ||
168 | } | 192 | } | ||
193 | if (mMagnifierAllowed) { | ||||
194 | update(); | ||||
195 | } | ||||
169 | event->accept(); | 196 | event->accept(); | ||
170 | } | 197 | } | ||
171 | 198 | | |||
172 | void QuickEditor::mouseMoveEvent(QMouseEvent* event) | 199 | void QuickEditor::mouseMoveEvent(QMouseEvent* event) | ||
173 | { | 200 | { | ||
174 | const QPointF& pos = event->screenPos(); | 201 | const QPointF& pos = event->screenPos(); | ||
202 | mMousePos = pos; | ||||
203 | mMagnifierAllowed = true; | ||||
175 | switch (mMouseDragState) { | 204 | switch (mMouseDragState) { | ||
176 | case MouseState::None: { | 205 | case MouseState::None: { | ||
177 | setMouseCursor(pos); | 206 | setMouseCursor(pos); | ||
207 | mMagnifierAllowed = false; | ||||
178 | break; | 208 | break; | ||
179 | } | 209 | } | ||
180 | case MouseState::TopLeft: | 210 | case MouseState::TopLeft: | ||
181 | case MouseState::TopRight: | 211 | case MouseState::TopRight: | ||
182 | case MouseState::BottomRight: | 212 | case MouseState::BottomRight: | ||
183 | case MouseState::BottomLeft: | 213 | case MouseState::BottomLeft: | ||
184 | case MouseState::Outside: { | 214 | case MouseState::Outside: { | ||
185 | const qreal dprI = 1.0 / devicePixelRatioF(); | 215 | const qreal dprI = 1.0 / devicePixelRatioF(); | ||
Show All 26 Lines | 240 | mSelection.setRect( | |||
212 | mSelection.y(), | 242 | mSelection.y(), | ||
213 | qAbs(pos.x() - mStartPos.x()) + dprI, | 243 | qAbs(pos.x() - mStartPos.x()) + dprI, | ||
214 | mSelection.height() | 244 | mSelection.height() | ||
215 | ); | 245 | ); | ||
216 | update(); | 246 | update(); | ||
217 | break; | 247 | break; | ||
218 | } | 248 | } | ||
219 | case MouseState::Inside: { | 249 | case MouseState::Inside: { | ||
250 | mMagnifierAllowed = false; | ||||
220 | // We use some math here to figure out if the diff with which we | 251 | // We use some math here to figure out if the diff with which we | ||
221 | // move the rectangle with moves it out of bounds, | 252 | // move the rectangle with moves it out of bounds, | ||
222 | // in which case we adjust the diff to not let that happen | 253 | // in which case we adjust the diff to not let that happen | ||
223 | 254 | | |||
224 | // new top left point of the rectangle | 255 | // new top left point of the rectangle | ||
225 | QPointF newTopLeft = pos - mStartPos + mInitialTopLeft; | 256 | QPointF newTopLeft = pos - mStartPos + mInitialTopLeft; | ||
226 | 257 | | |||
227 | const qreal dprI = 1.0 / devicePixelRatioF(); | 258 | const qreal dprI = 1.0 / devicePixelRatioF(); | ||
Show All 27 Lines | |||||
255 | if (newTopLeft != mSelection.topLeft()) { | 286 | if (newTopLeft != mSelection.topLeft()) { | ||
256 | mSelection.moveTo(newTopLeft); | 287 | mSelection.moveTo(newTopLeft); | ||
257 | update(); | 288 | update(); | ||
258 | } | 289 | } | ||
259 | } | 290 | } | ||
260 | default: | 291 | default: | ||
261 | break; | 292 | break; | ||
262 | } | 293 | } | ||
294 | | ||||
263 | event->accept(); | 295 | event->accept(); | ||
264 | } | 296 | } | ||
265 | 297 | | |||
266 | void QuickEditor::mouseReleaseEvent(QMouseEvent* event) | 298 | void QuickEditor::mouseReleaseEvent(QMouseEvent* event) | ||
267 | { | 299 | { | ||
268 | const auto button = event->button(); | 300 | const auto button = event->button(); | ||
269 | if (button == Qt::LeftButton && mMouseDragState == MouseState::Inside) { | 301 | if (button == Qt::LeftButton && mMouseDragState == MouseState::Inside) { | ||
270 | setCursor(Qt::OpenHandCursor); | 302 | setCursor(Qt::OpenHandCursor); | ||
Show All 14 Lines | 313 | { | |||
285 | } | 317 | } | ||
286 | } | 318 | } | ||
287 | 319 | | |||
288 | void QuickEditor::paintEvent(QPaintEvent*) | 320 | void QuickEditor::paintEvent(QPaintEvent*) | ||
289 | { | 321 | { | ||
290 | QPainter painter(this); | 322 | QPainter painter(this); | ||
291 | painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); | 323 | painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); | ||
292 | const QRect& fullRect = geometry(); | 324 | const QRect& fullRect = geometry(); | ||
293 | painter.drawPixmap(fullRect, mPixmap); | 325 | // painter.drawPixmap(fullRect, mPixmap); | ||
294 | if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { | 326 | if (!mSelection.size().isEmpty() || mMouseDragState != MouseState::None) { | ||
295 | painter.setPen(mStrokeColor); | 327 | painter.setPen(mStrokeColour); | ||
296 | painter.drawRect(mSelection); | 328 | painter.drawRect(mSelection); | ||
297 | 329 | | |||
298 | QRectF top(0, 0, fullRect.width(), mSelection.top()); | 330 | QRectF top(0, 0, fullRect.width(), mSelection.top()); | ||
299 | QRectF right(mSelection.right(), mSelection.top(), fullRect.width() - mSelection.right(), mSelection.height()); | 331 | QRectF right(mSelection.right(), mSelection.top(), fullRect.width() - mSelection.right(), mSelection.height()); | ||
300 | QRectF bottom(0, mSelection.bottom(), fullRect.width(), fullRect.height() - mSelection.bottom()); | 332 | QRectF bottom(0, mSelection.bottom(), fullRect.width(), fullRect.height() - mSelection.bottom()); | ||
301 | QRectF left(0, mSelection.top(), mSelection.left(), mSelection.height()); | 333 | QRectF left(0, mSelection.top(), mSelection.left(), mSelection.height()); | ||
302 | for (auto& rect : { top, right, bottom, left }) { | 334 | for (auto& rect : { top, right, bottom, left }) { | ||
303 | painter.fillRect(rect, mMaskColour); | 335 | painter.fillRect(rect, mMaskColour); | ||
304 | } | 336 | } | ||
305 | 337 | | |||
306 | drawSelectionSizeTooltip(painter); | 338 | drawSelectionSizeTooltip(painter); | ||
307 | if (mMouseDragState == MouseState::None) { // mouse is up | 339 | if (mMouseDragState == MouseState::None) { // mouse is up | ||
308 | if ((mSelection.width() > 20) && (mSelection.height() > 20)) { | 340 | if ((mSelection.width() > 20) && (mSelection.height() > 20)) { | ||
309 | drawDragHandles(painter); | 341 | drawDragHandles(painter); | ||
310 | } | 342 | } | ||
311 | 343 | | |||
312 | drawBottomHelpText(painter); | 344 | drawBottomHelpText(painter); | ||
345 | } else if (mMagnifierAllowed && (mShowMagnifier ^ mToggleMagnifier)) { | ||||
346 | drawMagnifier(painter); | ||||
313 | } | 347 | } | ||
314 | } else { | 348 | } else { | ||
315 | drawMidHelpText(painter); | 349 | drawMidHelpText(painter); | ||
316 | } | 350 | } | ||
317 | } | 351 | } | ||
318 | 352 | | |||
319 | inline void QuickEditor::layoutBottomHelpText() | 353 | inline void QuickEditor::layoutBottomHelpText() | ||
320 | { | 354 | { | ||
▲ Show 20 Lines • Show All 89 Lines • ▼ Show 20 Line(s) | 403 | { | |||
410 | path.moveTo(centerX, bottomY); | 444 | path.moveTo(centerX, bottomY); | ||
411 | path.arcTo(centerX - midHandleRadius, bottomY - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, 180); | 445 | path.arcTo(centerX - midHandleRadius, bottomY - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, 180); | ||
412 | 446 | | |||
413 | // left-center handle | 447 | // left-center handle | ||
414 | path.moveTo(leftX, centerY); | 448 | path.moveTo(leftX, centerY); | ||
415 | path.arcTo(leftX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, -180); | 449 | path.arcTo(leftX - midHandleRadius, centerY - midHandleRadius, midHandleDiameter, midHandleDiameter, 90, -180); | ||
416 | 450 | | |||
417 | // draw the path | 451 | // draw the path | ||
418 | painter.fillPath(path, mStrokeColor); | 452 | painter.fillPath(path, mStrokeColour); | ||
453 | } | ||||
454 | | ||||
455 | inline void QuickEditor::drawMagnifier(QPainter &painter) | ||||
456 | { | ||||
457 | const int pixels = 2 * magPixels + 1; | ||||
458 | int magX = mMousePos.x() * devicePixelRatioF() - magPixels; | ||||
459 | int offsetX = 0; | ||||
460 | if (magX < 0) { | ||||
461 | offsetX = magX; | ||||
462 | magX = 0; | ||||
463 | } else { | ||||
464 | const int maxX = mPixmap.width() - pixels; | ||||
465 | if (magX > maxX) { | ||||
466 | offsetX = magX - maxX; | ||||
467 | magX = maxX; | ||||
468 | } | ||||
469 | } | ||||
470 | int magY = mMousePos.y() * devicePixelRatioF() - magPixels; | ||||
471 | int offsetY = 0; | ||||
472 | if (magY < 0) { | ||||
473 | offsetY = magY; | ||||
474 | magY = 0; | ||||
475 | } else { | ||||
476 | const int maxY = mPixmap.height() - pixels; | ||||
477 | if (magY > maxY) { | ||||
478 | offsetY = magY - maxY; | ||||
479 | magY = maxY; | ||||
480 | } | ||||
481 | } | ||||
482 | QRectF magniRect(magX, magY, pixels, pixels); | ||||
483 | | ||||
484 | const qreal gg = 32; | ||||
485 | qreal drawPosX = mMousePos.x() + gg + pixels * magZoom / 2; | ||||
486 | if (drawPosX > width() - pixels * magZoom / 2) { | ||||
487 | drawPosX = mMousePos.x() - gg - pixels * magZoom / 2; | ||||
488 | } | ||||
489 | qreal drawPosY = mMousePos.y() + gg + pixels * magZoom / 2; | ||||
490 | if (drawPosY > height() - pixels * magZoom / 2) { | ||||
491 | drawPosY = mMousePos.y() - gg - pixels * magZoom / 2; | ||||
492 | } | ||||
493 | QPointF drawPos(drawPosX, drawPosY); | ||||
494 | QRectF crossHairTop(drawPos.x() + magZoom * (offsetX - 0.5), drawPos.y() - magZoom * (magPixels + 0.5), magZoom, magZoom * (magPixels + offsetY)); | ||||
495 | QRectF crossHairRight(drawPos.x() + magZoom * (0.5 + offsetX), drawPos.y() + magZoom * (offsetY - 0.5), magZoom * (magPixels - offsetX), magZoom); | ||||
496 | QRectF crossHairBottom(drawPos.x() + magZoom * (offsetX - 0.5), drawPos.y() + magZoom * (0.5 + offsetY), magZoom, magZoom * (magPixels - offsetY)); | ||||
497 | QRectF crossHairLeft(drawPos.x() - magZoom * (magPixels + 0.5), drawPos.y() + magZoom * (offsetY - 0.5), magZoom * (magPixels + offsetX), magZoom); | ||||
498 | const auto frag = QPainter::PixmapFragment::create(drawPos, magniRect, magZoom, magZoom); | ||||
499 | painter.drawPixmapFragments(&frag, 1, mPixmap, QPainter::OpaqueHint); | ||||
500 | painter.setCompositionMode(QPainter::CompositionMode_SourceOver); | ||||
501 | for (auto& rect : { crossHairTop, crossHairRight, crossHairBottom, crossHairLeft }) { | ||||
502 | painter.fillRect(rect, mCrossColour); | ||||
503 | } | ||||
419 | } | 504 | } | ||
420 | 505 | | |||
421 | inline void QuickEditor::drawMidHelpText(QPainter &painter) | 506 | inline void QuickEditor::drawMidHelpText(QPainter &painter) | ||
422 | { | 507 | { | ||
423 | const QRect& fullRect = geometry(); | 508 | const QRect& fullRect = geometry(); | ||
424 | painter.fillRect(fullRect, mMaskColour); | 509 | painter.fillRect(fullRect, mMaskColour); | ||
425 | painter.setFont(mMidHelpTextFont); | 510 | painter.setFont(mMidHelpTextFont); | ||
426 | QRect textSize = painter.boundingRect(fullRect, Qt::AlignCenter, mMidHelpText); | 511 | QRect textSize = painter.boundingRect(fullRect, Qt::AlignCenter, mMidHelpText); | ||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Line(s) | 568 | { | |||
495 | } else { | 580 | } else { | ||
496 | setCursor(Qt::OpenHandCursor); | 581 | setCursor(Qt::OpenHandCursor); | ||
497 | } | 582 | } | ||
498 | } | 583 | } | ||
499 | 584 | | |||
500 | QuickEditor::MouseState QuickEditor::whereIsTheMouse(const QPointF& pos) | 585 | QuickEditor::MouseState QuickEditor::whereIsTheMouse(const QPointF& pos) | ||
501 | { | 586 | { | ||
502 | if (mSelection.contains(pos)) { | 587 | if (mSelection.contains(pos)) { | ||
503 | const qreal verSize = qMin(mouseAreaSize, mSelection.height()); | 588 | const qreal verSize = qMin(mouseAreaSize, mSelection.height() / 2); | ||
504 | const qreal horSize = qMin(mouseAreaSize, mSelection.width()); | 589 | const qreal horSize = qMin(mouseAreaSize, mSelection.width() / 2); | ||
505 | 590 | | |||
506 | auto withinThreshold = [](const qreal offset, const qreal size) { | 591 | auto withinThreshold = [](const qreal offset, const qreal size) { | ||
507 | return offset <= size && offset >= 0; | 592 | return offset <= size && offset >= 0; | ||
508 | }; | 593 | }; | ||
509 | 594 | | |||
510 | const bool withinTopEdge = withinThreshold(pos.y() - mSelection.top(), verSize); | 595 | const bool withinTopEdge = withinThreshold(pos.y() - mSelection.top(), verSize); | ||
511 | const bool withinRightEdge = withinThreshold(mSelection.right() - pos.x(), horSize); | 596 | const bool withinRightEdge = withinThreshold(mSelection.right() - pos.x(), horSize); | ||
512 | const bool withinBottomEdge = !withinTopEdge && withinThreshold(mSelection.bottom() - pos.y(), verSize); | 597 | const bool withinBottomEdge = !withinTopEdge && withinThreshold(mSelection.bottom() - pos.y(), verSize); | ||
Show All 29 Lines |