1 | /* |
2 | * Copyright (C) 2007 Luca Gugelmann <lucag@student.ethz.ch> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify it |
5 | * under the terms of the GNU Library General Public License version 2 as |
6 | * published by the Free Software Foundation |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details |
12 | * |
13 | * You should have received a copy of the GNU Library General Public |
14 | * License along with this program; if not, write to the |
15 | * Free Software Foundation, Inc., |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | */ |
18 | |
19 | #include "regiongrabber.h" |
20 | |
21 | #include <QPainter> |
22 | #include <QMouseEvent> |
23 | #include <QApplication> |
24 | #include <QDesktopWidget> |
25 | #include <QToolTip> |
26 | #include <QTimer> |
27 | |
28 | #include <klocale.h> |
29 | #include <KWindowSystem> |
30 | |
31 | RegionGrabber::RegionGrabber( const QRect &startSelection ) : |
32 | QWidget( 0, Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool ), |
33 | selection( startSelection ), mouseDown( false ), newSelection( false ), |
34 | handleSize( 10 ), mouseOverHandle( 0 ), |
35 | showHelp( true ), grabbing( false ), |
36 | TLHandle(0,0,handleSize,handleSize), TRHandle(0,0,handleSize,handleSize), |
37 | BLHandle(0,0,handleSize,handleSize), BRHandle(0,0,handleSize,handleSize), |
38 | LHandle(0,0,handleSize,handleSize), THandle(0,0,handleSize,handleSize), |
39 | RHandle(0,0,handleSize,handleSize), BHandle(0,0,handleSize,handleSize) |
40 | { |
41 | handles << &TLHandle << &TRHandle << &BLHandle << &BRHandle |
42 | << &LHandle << &THandle << &RHandle << &BHandle; |
43 | setMouseTracking( true ); |
44 | int timeout = KWindowSystem::compositingActive() ? 200 : 50; |
45 | QTimer::singleShot( timeout, this, SLOT(init()) ); |
46 | } |
47 | |
48 | RegionGrabber::~RegionGrabber() |
49 | { |
50 | } |
51 | |
52 | void RegionGrabber::init() |
53 | { |
54 | pixmap = QPixmap::grabWindow( QApplication::desktop()->winId() ); |
55 | resize( pixmap.size() ); |
56 | move( 0, 0 ); |
57 | setCursor( Qt::CrossCursor ); |
58 | show(); |
59 | grabMouse(); |
60 | grabKeyboard(); |
61 | } |
62 | |
63 | static void drawRect( QPainter *painter, const QRect &r, const QColor &outline, const QColor &fill = QColor() ) |
64 | { |
65 | QRegion clip( r ); |
66 | clip = clip.subtracted( r.adjusted( 1, 1, -1, -1 ) ); |
67 | |
68 | painter->save(); |
69 | painter->setClipRegion( clip ); |
70 | painter->setPen( Qt::NoPen ); |
71 | painter->setBrush( outline ); |
72 | painter->drawRect( r ); |
73 | if ( fill.isValid() ) { |
74 | painter->setClipping( false ); |
75 | painter->setBrush( fill ); |
76 | painter->drawRect( r.adjusted( 1, 1, -1, -1 ) ); |
77 | } |
78 | painter->restore(); |
79 | } |
80 | |
81 | void RegionGrabber::paintEvent( QPaintEvent* e ) |
82 | { |
83 | Q_UNUSED( e ); |
84 | if ( grabbing ) // grabWindow() should just get the background |
85 | return; |
86 | |
87 | QPainter painter( this ); |
88 | |
89 | QPalette pal(QToolTip::palette()); |
90 | QFont font = QToolTip::font(); |
91 | |
92 | QColor handleColor = pal.color( QPalette::Active, QPalette::Highlight ); |
93 | handleColor.setAlpha( 160 ); |
94 | QColor overlayColor( 0, 0, 0, 160 ); |
95 | QColor textColor = pal.color( QPalette::Active, QPalette::Text ); |
96 | QColor textBackgroundColor = pal.color( QPalette::Active, QPalette::Base ); |
97 | painter.drawPixmap(0, 0, pixmap); |
98 | painter.setFont(font); |
99 | |
100 | QRect r = selection; |
101 | if ( !selection.isNull() ) |
102 | { |
103 | QRegion grey( rect() ); |
104 | grey = grey.subtracted( r ); |
105 | painter.setClipRegion( grey ); |
106 | painter.setPen( Qt::NoPen ); |
107 | painter.setBrush( overlayColor ); |
108 | painter.drawRect( rect() ); |
109 | painter.setClipRect( rect() ); |
110 | drawRect( &painter, r, handleColor ); |
111 | } |
112 | |
113 | if ( showHelp ) |
114 | { |
115 | painter.setPen( textColor ); |
116 | painter.setBrush( textBackgroundColor ); |
117 | QString helpText = i18n( "Select a region using the mouse. To take the snapshot, press the Enter key or double click. Press Esc to quit." ); |
118 | helpTextRect = painter.boundingRect( rect().adjusted( 2, 2, -2, -2 ), Qt::TextWordWrap, helpText ); |
119 | helpTextRect.adjust( -2, -2, 4, 2 ); |
120 | drawRect( &painter, helpTextRect, textColor, textBackgroundColor ); |
121 | painter.drawText( helpTextRect.adjusted( 3, 3, -3, -3 ), helpText ); |
122 | } |
123 | |
124 | if ( selection.isNull() ) |
125 | { |
126 | return; |
127 | } |
128 | |
129 | // The grabbed region is everything which is covered by the drawn |
130 | // rectangles (border included). This means that there is no 0px |
131 | // selection, since a 0px wide rectangle will always be drawn as a line. |
132 | QString txt = QString( "%1x%2" ).arg( selection.width() ) |
133 | .arg( selection.height() ); |
134 | QRect textRect = painter.boundingRect( rect(), Qt::AlignLeft, txt ); |
135 | QRect boundingRect = textRect.adjusted( -4, 0, 0, 0); |
136 | |
137 | if ( textRect.width() < r.width() - 2*handleSize && |
138 | textRect.height() < r.height() - 2*handleSize && |
139 | ( r.width() > 100 && r.height() > 100 ) ) // center, unsuitable for small selections |
140 | { |
141 | boundingRect.moveCenter( r.center() ); |
142 | textRect.moveCenter( r.center() ); |
143 | } |
144 | else if ( r.y() - 3 > textRect.height() && |
145 | r.x() + textRect.width() < rect().right() ) // on top, left aligned |
146 | { |
147 | boundingRect.moveBottomLeft( QPoint( r.x(), r.y() - 3 ) ); |
148 | textRect.moveBottomLeft( QPoint( r.x() + 2, r.y() - 3 ) ); |
149 | } |
150 | else if ( r.x() - 3 > textRect.width() ) // left, top aligned |
151 | { |
152 | boundingRect.moveTopRight( QPoint( r.x() - 3, r.y() ) ); |
153 | textRect.moveTopRight( QPoint( r.x() - 5, r.y() ) ); |
154 | } |
155 | else if ( r.bottom() + 3 + textRect.height() < rect().bottom() && |
156 | r.right() > textRect.width() ) // at bottom, right aligned |
157 | { |
158 | boundingRect.moveTopRight( QPoint( r.right(), r.bottom() + 3 ) ); |
159 | textRect.moveTopRight( QPoint( r.right() - 2, r.bottom() + 3 ) ); |
160 | } |
161 | else if ( r.right() + textRect.width() + 3 < rect().width() ) // right, bottom aligned |
162 | { |
163 | boundingRect.moveBottomLeft( QPoint( r.right() + 3, r.bottom() ) ); |
164 | textRect.moveBottomLeft( QPoint( r.right() + 5, r.bottom() ) ); |
165 | } |
166 | // if the above didn't catch it, you are running on a very tiny screen... |
167 | drawRect( &painter, boundingRect, textColor, textBackgroundColor ); |
168 | |
169 | painter.drawText( textRect, txt ); |
170 | |
171 | if ( ( r.height() > handleSize*2 && r.width() > handleSize*2 ) |
172 | || !mouseDown ) |
173 | { |
174 | updateHandles(); |
175 | painter.setPen( Qt::NoPen ); |
176 | painter.setBrush( handleColor ); |
177 | painter.setClipRegion( handleMask( StrokeMask ) ); |
178 | painter.drawRect( rect() ); |
179 | handleColor.setAlpha( 60 ); |
180 | painter.setBrush( handleColor ); |
181 | painter.setClipRegion( handleMask( FillMask ) ); |
182 | painter.drawRect( rect() ); |
183 | } |
184 | } |
185 | |
186 | void RegionGrabber::resizeEvent( QResizeEvent* e ) |
187 | { |
188 | Q_UNUSED( e ); |
189 | if ( selection.isNull() ) |
190 | return; |
191 | QRect r = selection; |
192 | r.setTopLeft( limitPointToRect( r.topLeft(), rect() ) ); |
193 | r.setBottomRight( limitPointToRect( r.bottomRight(), rect() ) ); |
194 | if ( r.width() <= 1 || r.height() <= 1 ) //this just results in ugly drawing... |
195 | selection = QRect(); |
196 | else |
197 | selection = normalizeSelection(r); |
198 | } |
199 | |
200 | void RegionGrabber::mousePressEvent( QMouseEvent* e ) |
201 | { |
202 | showHelp = !helpTextRect.contains( e->pos() ); |
203 | if ( e->button() == Qt::LeftButton ) |
204 | { |
205 | mouseDown = true; |
206 | dragStartPoint = e->pos(); |
207 | selectionBeforeDrag = selection; |
208 | if ( !selection.contains( e->pos() ) ) |
209 | { |
210 | newSelection = true; |
211 | selection = QRect(); |
212 | } |
213 | else |
214 | { |
215 | setCursor( Qt::ClosedHandCursor ); |
216 | } |
217 | } |
218 | else if ( e->button() == Qt::RightButton ) |
219 | { |
220 | newSelection = false; |
221 | selection = QRect(); |
222 | setCursor( Qt::CrossCursor ); |
223 | } |
224 | update(); |
225 | } |
226 | |
227 | void RegionGrabber::mouseMoveEvent( QMouseEvent* e ) |
228 | { |
229 | bool shouldShowHelp = !helpTextRect.contains( e->pos() ); |
230 | if (shouldShowHelp != showHelp) { |
231 | showHelp = shouldShowHelp; |
232 | update(); |
233 | } |
234 | |
235 | if ( mouseDown ) |
236 | { |
237 | if ( newSelection ) |
238 | { |
239 | QPoint p = e->pos(); |
240 | QRect r = rect(); |
241 | selection = normalizeSelection(QRect( dragStartPoint, limitPointToRect( p, r ) )); |
242 | } |
243 | else if ( mouseOverHandle == 0 ) // moving the whole selection |
244 | { |
245 | QRect r = rect().normalized(), s = selectionBeforeDrag.normalized(); |
246 | QPoint p = s.topLeft() + e->pos() - dragStartPoint; |
247 | r.setBottomRight( r.bottomRight() - QPoint( s.width(), s.height() ) + QPoint( 1, 1 ) ); |
248 | if ( !r.isNull() && r.isValid() ) |
249 | selection.moveTo( limitPointToRect( p, r ) ); |
250 | } |
251 | else // dragging a handle |
252 | { |
253 | QRect r = selectionBeforeDrag; |
254 | QPoint offset = e->pos() - dragStartPoint; |
255 | |
256 | if ( mouseOverHandle == &TLHandle || mouseOverHandle == &THandle |
257 | || mouseOverHandle == &TRHandle ) // dragging one of the top handles |
258 | { |
259 | r.setTop( r.top() + offset.y() ); |
260 | } |
261 | |
262 | if ( mouseOverHandle == &TLHandle || mouseOverHandle == &LHandle |
263 | || mouseOverHandle == &BLHandle ) // dragging one of the left handles |
264 | { |
265 | r.setLeft( r.left() + offset.x() ); |
266 | } |
267 | |
268 | if ( mouseOverHandle == &BLHandle || mouseOverHandle == &BHandle |
269 | || mouseOverHandle == &BRHandle ) // dragging one of the bottom handles |
270 | { |
271 | r.setBottom( r.bottom() + offset.y() ); |
272 | } |
273 | |
274 | if ( mouseOverHandle == &TRHandle || mouseOverHandle == &RHandle |
275 | || mouseOverHandle == &BRHandle ) // dragging one of the right handles |
276 | { |
277 | r.setRight( r.right() + offset.x() ); |
278 | } |
279 | r.setTopLeft( limitPointToRect( r.topLeft(), rect() ) ); |
280 | r.setBottomRight( limitPointToRect( r.bottomRight(), rect() ) ); |
281 | selection = normalizeSelection(r); |
282 | } |
283 | update(); |
284 | } |
285 | else |
286 | { |
287 | if ( selection.isNull() ) |
288 | return; |
289 | bool found = false; |
290 | foreach( QRect* r, handles ) |
291 | { |
292 | if ( r->contains( e->pos() ) ) |
293 | { |
294 | mouseOverHandle = r; |
295 | found = true; |
296 | break; |
297 | } |
298 | } |
299 | if ( !found ) |
300 | { |
301 | mouseOverHandle = 0; |
302 | if ( selection.contains( e->pos() ) ) |
303 | setCursor( Qt::OpenHandCursor ); |
304 | else |
305 | setCursor( Qt::CrossCursor ); |
306 | } |
307 | else |
308 | { |
309 | if ( mouseOverHandle == &TLHandle || mouseOverHandle == &BRHandle ) |
310 | setCursor( Qt::SizeFDiagCursor ); |
311 | if ( mouseOverHandle == &TRHandle || mouseOverHandle == &BLHandle ) |
312 | setCursor( Qt::SizeBDiagCursor ); |
313 | if ( mouseOverHandle == &LHandle || mouseOverHandle == &RHandle ) |
314 | setCursor( Qt::SizeHorCursor ); |
315 | if ( mouseOverHandle == &THandle || mouseOverHandle == &BHandle ) |
316 | setCursor( Qt::SizeVerCursor ); |
317 | } |
318 | } |
319 | } |
320 | |
321 | void RegionGrabber::mouseReleaseEvent( QMouseEvent* e ) |
322 | { |
323 | mouseDown = false; |
324 | newSelection = false; |
325 | if ( mouseOverHandle == 0 && selection.contains( e->pos() ) ) |
326 | setCursor( Qt::OpenHandCursor ); |
327 | update(); |
328 | } |
329 | |
330 | void RegionGrabber::mouseDoubleClickEvent( QMouseEvent* ) |
331 | { |
332 | grabRect(); |
333 | } |
334 | |
335 | void RegionGrabber::keyPressEvent( QKeyEvent* e ) |
336 | { |
337 | QRect r = selection; |
338 | if ( e->key() == Qt::Key_Escape ) |
339 | { |
340 | emit regionUpdated( r ); |
341 | emit regionGrabbed( QPixmap() ); |
342 | } |
343 | else if ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return ) |
344 | { |
345 | grabRect(); |
346 | } |
347 | else |
348 | { |
349 | e->ignore(); |
350 | } |
351 | } |
352 | |
353 | void RegionGrabber::grabRect() |
354 | { |
355 | QRect r = selection; |
356 | if ( !r.isNull() && r.isValid() ) |
357 | { |
358 | grabbing = true; |
359 | emit regionUpdated( r ); |
360 | emit regionGrabbed( pixmap.copy(r) ); |
361 | } |
362 | } |
363 | |
364 | void RegionGrabber::updateHandles() |
365 | { |
366 | QRect r = selection; |
367 | int s2 = handleSize / 2; |
368 | |
369 | TLHandle.moveTopLeft( r.topLeft() ); |
370 | TRHandle.moveTopRight( r.topRight() ); |
371 | BLHandle.moveBottomLeft( r.bottomLeft() ); |
372 | BRHandle.moveBottomRight( r.bottomRight() ); |
373 | |
374 | LHandle.moveTopLeft( QPoint( r.x(), r.y() + r.height() / 2 - s2) ); |
375 | THandle.moveTopLeft( QPoint( r.x() + r.width() / 2 - s2, r.y() ) ); |
376 | RHandle.moveTopRight( QPoint( r.right(), r.y() + r.height() / 2 - s2 ) ); |
377 | BHandle.moveBottomLeft( QPoint( r.x() + r.width() / 2 - s2, r.bottom() ) ); |
378 | } |
379 | |
380 | QRegion RegionGrabber::handleMask( MaskType type ) const |
381 | { |
382 | // note: not normalized QRects are bad here, since they will not be drawn |
383 | QRegion mask; |
384 | foreach( QRect* rect, handles ) { |
385 | if ( type == StrokeMask ) { |
386 | QRegion r( *rect ); |
387 | mask += r.subtracted( rect->adjusted( 1, 1, -1, -1 ) ); |
388 | } else { |
389 | mask += QRegion( rect->adjusted( 1, 1, -1, -1 ) ); |
390 | } |
391 | } |
392 | return mask; |
393 | } |
394 | |
395 | QPoint RegionGrabber::limitPointToRect( const QPoint &p, const QRect &r ) const |
396 | { |
397 | QPoint q; |
398 | q.setX( p.x() < r.x() ? r.x() : p.x() < r.right() ? p.x() : r.right() ); |
399 | q.setY( p.y() < r.y() ? r.y() : p.y() < r.bottom() ? p.y() : r.bottom() ); |
400 | return q; |
401 | } |
402 | |
403 | QRect RegionGrabber::normalizeSelection( const QRect &s ) const |
404 | { |
405 | QRect r = s; |
406 | if (r.width() <= 0) { |
407 | int l = r.left(); |
408 | int w = r.width(); |
409 | r.setLeft(l + w - 1); |
410 | r.setRight(l); |
411 | } |
412 | if (r.height() <= 0) { |
413 | int t = r.top(); |
414 | int h = r.height(); |
415 | r.setTop(t + h - 1); |
416 | r.setBottom(t); |
417 | } |
418 | return r; |
419 | } |
420 | |
421 | #include "regiongrabber.moc" |
422 | |