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
31RegionGrabber::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
48RegionGrabber::~RegionGrabber()
49{
50}
51
52void 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
63static 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
81void 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
186void 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
200void 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
227void 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
321void 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
330void RegionGrabber::mouseDoubleClickEvent( QMouseEvent* )
331{
332 grabRect();
333}
334
335void 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
353void 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
364void 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
380QRegion 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
395QPoint 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
403QRect 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