1/*
2 * Copyright (C) 2010 Pau Garcia i Quiles <pgquiles@elpauer.org>,
3 * based on the region grabber code by Luca Gugelmann <lucag@student.ethz.ch>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Library General Public License version 2 as
7 * published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include "freeregiongrabber.h"
21
22#include <QPainter>
23#include <QPaintEngine>
24#include <QMouseEvent>
25#include <QApplication>
26#include <QDesktopWidget>
27#include <QToolTip>
28#include <QTimer>
29#include <klocale.h>
30#include <KWindowSystem>
31
32FreeRegionGrabber::FreeRegionGrabber( const QPolygon &startFreeRegion ) :
33 QWidget( 0, Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | Qt::Tool ),
34 selection( startFreeRegion ), mouseDown( false ), newSelection( false ),
35 handleSize( 10 ), mouseOverHandle( 0 ),
36 showHelp( true ), grabbing( false )
37{
38 setMouseTracking( true );
39 int timeout = KWindowSystem::compositingActive() ? 200 : 50;
40 QTimer::singleShot( timeout, this, SLOT(init()) );
41}
42
43FreeRegionGrabber::~FreeRegionGrabber()
44{
45}
46
47void FreeRegionGrabber::init()
48{
49 pixmap = QPixmap::grabWindow( QApplication::desktop()->winId() );
50 resize( pixmap.size() );
51 move( 0, 0 );
52 setCursor( Qt::CrossCursor );
53 show();
54 grabMouse();
55 grabKeyboard();
56}
57
58static void drawPolygon( QPainter *painter, const QPolygon &p, const QColor &outline, const QColor &fill = QColor() )
59{
60 QRegion clip( p );
61 clip = clip - p;
62 QPen pen(outline, 1, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);
63
64 painter->save();
65 painter->setClipRegion( clip );
66 painter->setPen(pen);
67 painter->drawPolygon( p );
68 if ( fill.isValid() ) {
69 painter->setClipping( false );
70 painter->setBrush( fill );
71 painter->drawPolygon( p /*.translated( 1, -1 ) */);
72 }
73 painter->restore();
74}
75
76void FreeRegionGrabber::paintEvent( QPaintEvent* e )
77{
78 Q_UNUSED( e );
79 if ( grabbing ) // grabWindow() should just get the background
80 return;
81
82 QPainter painter( this );
83
84 QPalette pal(QToolTip::palette());
85 QFont font = QToolTip::font();
86
87 QColor handleColor = pal.color( QPalette::Active, QPalette::Highlight );
88 handleColor.setAlpha( 160 );
89 QColor overlayColor( 0, 0, 0, 160 );
90 QColor textColor = pal.color( QPalette::Active, QPalette::Text );
91 QColor textBackgroundColor = pal.color( QPalette::Active, QPalette::Base );
92 painter.drawPixmap(0, 0, pixmap);
93 painter.setFont(font);
94
95 QPolygon pol = selection;
96 if ( !selection.boundingRect().isNull() )
97 {
98 // Draw outline around selection.
99 // Important: the 1px-wide outline is *also* part of the captured free-region because
100 // I found no way to draw the outline *around* the selection because I found no way
101 // to create a QPolygon which is smaller than the selection by 1px (QPolygon::translated
102 // is NOT equivalent to QRect::adjusted)
103 QPen pen(handleColor, 1, Qt::SolidLine, Qt::SquareCap, Qt::BevelJoin);
104 painter.setPen(pen);
105 painter.drawPolygon( pol );
106
107 // Draw the grey area around the selection
108 QRegion grey( rect() );
109 grey = grey - pol;
110 painter.setClipRegion( grey );
111 painter.setPen( Qt::NoPen );
112 painter.setBrush( overlayColor );
113 painter.drawRect( rect() );
114 painter.setClipRect( rect() );
115 drawPolygon( &painter, pol, handleColor);
116 }
117
118 if ( showHelp )
119 {
120 painter.setPen( textColor );
121 painter.setBrush( textBackgroundColor );
122 QString helpText = i18n( "Select a region using the mouse. To take the snapshot, press the Enter key or double click. Press Esc to quit." );
123 helpTextRect = painter.boundingRect( rect().adjusted( 2, 2, -2, -2 ), Qt::TextWordWrap, helpText );
124 helpTextRect.adjust( -2, -2, 4, 2 );
125 drawPolygon( &painter, helpTextRect, textColor, textBackgroundColor );
126 painter.drawText( helpTextRect.adjusted( 3, 3, -3, -3 ), helpText );
127 }
128
129 if ( selection.isEmpty() )
130 {
131 return;
132 }
133
134 // The grabbed region is everything which is covered by the drawn
135 // rectangles (border included). This means that there is no 0px
136 // selection, since a 0px wide rectangle will always be drawn as a line.
137 QString txt = QString( "%1x%2" ).arg( selection.boundingRect().width() )
138 .arg( selection.boundingRect().height() );
139 QRect textRect = painter.boundingRect( rect(), Qt::AlignLeft, txt );
140 QRect boundingRect = textRect.adjusted( -4, 0, 0, 0);
141
142 if ( textRect.width() < pol.boundingRect().width() - 2*handleSize &&
143 textRect.height() < pol.boundingRect().height() - 2*handleSize &&
144 ( pol.boundingRect().width() > 100 && pol.boundingRect().height() > 100 ) ) // center, unsuitable for small selections
145 {
146 boundingRect.moveCenter( pol.boundingRect().center() );
147 textRect.moveCenter( pol.boundingRect().center() );
148 }
149 else if ( pol.boundingRect().y() - 3 > textRect.height() &&
150 pol.boundingRect().x() + textRect.width() < rect().right() ) // on top, left aligned
151 {
152 boundingRect.moveBottomLeft( QPoint( pol.boundingRect().x(), pol.boundingRect().y() - 3 ) );
153 textRect.moveBottomLeft( QPoint( pol.boundingRect().x() + 2, pol.boundingRect().y() - 3 ) );
154 }
155 else if ( pol.boundingRect().x() - 3 > textRect.width() ) // left, top aligned
156 {
157 boundingRect.moveTopRight( QPoint( pol.boundingRect().x() - 3, pol.boundingRect().y() ) );
158 textRect.moveTopRight( QPoint( pol.boundingRect().x() - 5, pol.boundingRect().y() ) );
159 }
160 else if ( pol.boundingRect().bottom() + 3 + textRect.height() < rect().bottom() &&
161 pol.boundingRect().right() > textRect.width() ) // at bottom, right aligned
162 {
163 boundingRect.moveTopRight( QPoint( pol.boundingRect().right(), pol.boundingRect().bottom() + 3 ) );
164 textRect.moveTopRight( QPoint( pol.boundingRect().right() - 2, pol.boundingRect().bottom() + 3 ) );
165 }
166 else if ( pol.boundingRect().right() + textRect.width() + 3 < rect().width() ) // right, bottom aligned
167 {
168 boundingRect.moveBottomLeft( QPoint( pol.boundingRect().right() + 3, pol.boundingRect().bottom() ) );
169 textRect.moveBottomLeft( QPoint( pol.boundingRect().right() + 5, pol.boundingRect().bottom() ) );
170 }
171 // if the above didn't catch it, you are running on a very tiny screen...
172 drawPolygon( &painter, boundingRect, textColor, textBackgroundColor );
173
174 painter.drawText( textRect, txt );
175
176 if ( ( pol.boundingRect().height() > handleSize*2 && pol.boundingRect().width() > handleSize*2 )
177 || !mouseDown )
178 {
179 painter.setBrush(QBrush(Qt::transparent));
180 painter.setClipRegion( QRegion(pol));
181 painter.drawPolygon( rect() );
182 }
183}
184
185void FreeRegionGrabber::mousePressEvent( QMouseEvent* e )
186{
187 pBefore = e->pos();
188 showHelp = !helpTextRect.contains( e->pos() );
189 if ( e->button() == Qt::LeftButton )
190 {
191 mouseDown = true;
192 dragStartPoint = e->pos();
193 selectionBeforeDrag = selection;
194 if ( !selection.containsPoint( e->pos(), Qt::WindingFill ) )
195 {
196 newSelection = true;
197 selection = QPolygon();
198 }
199 else
200 {
201 setCursor( Qt::ClosedHandCursor );
202 }
203 }
204 else if ( e->button() == Qt::RightButton )
205 {
206 newSelection = false;
207 selection = QPolygon();
208 setCursor( Qt::CrossCursor );
209 }
210 update();
211}
212
213void FreeRegionGrabber::mouseMoveEvent( QMouseEvent* e )
214{
215 bool shouldShowHelp = !helpTextRect.contains( e->pos() );
216 if (shouldShowHelp != showHelp) {
217 showHelp = shouldShowHelp;
218 update();
219 }
220
221 if ( mouseDown )
222 {
223 if ( newSelection )
224 {
225 QPoint p = e->pos();
226 selection << p;
227 }
228 else // moving the whole selection
229 {
230 QPoint p = e->pos() - pBefore; // Offset
231 pBefore = e->pos(); // Save position for next iteration
232 selection.translate(p);
233 }
234
235 update();
236 }
237 else
238 {
239 if ( selection.boundingRect().isEmpty() )
240 return;
241
242 if ( selection.containsPoint( e->pos(), Qt::WindingFill ) )
243 setCursor( Qt::OpenHandCursor );
244 else
245 setCursor( Qt::CrossCursor );
246
247 }
248}
249
250void FreeRegionGrabber::mouseReleaseEvent( QMouseEvent* e )
251{
252 mouseDown = false;
253 newSelection = false;
254 if ( mouseOverHandle == 0 && selection.containsPoint( e->pos(), Qt::WindingFill ) )
255 setCursor( Qt::OpenHandCursor );
256 update();
257}
258
259void FreeRegionGrabber::mouseDoubleClickEvent( QMouseEvent* )
260{
261 grabRect();
262}
263
264void FreeRegionGrabber::keyPressEvent( QKeyEvent* e )
265{
266 QPolygon pol = selection;
267 if ( e->key() == Qt::Key_Escape )
268 {
269 emit freeRegionUpdated( pol );
270 emit freeRegionGrabbed( QPixmap() );
271 }
272 else if ( e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return )
273 {
274 grabRect();
275 }
276 else
277 {
278 e->ignore();
279 }
280}
281
282void FreeRegionGrabber::grabRect()
283{
284 QPolygon pol = selection;
285 if ( !pol.isEmpty() )
286 {
287 grabbing = true;
288
289 int xOffset = pixmap.rect().x() - pol.boundingRect().x();
290 int yOffset = pixmap.rect().y() - pol.boundingRect().y();
291 QPolygon translatedPol = pol.translated(xOffset, yOffset);
292
293 QPixmap pixmap2(pol.boundingRect().size());
294 pixmap2.fill(Qt::transparent);
295
296 QPainter pt;
297 pt.begin(&pixmap2);
298 if (pt.paintEngine()->hasFeature(QPaintEngine::PorterDuff)) {
299 pt.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform, true);
300 pt.setBrush(Qt::black);
301 pt.setPen(QPen(QBrush(Qt::black), 0.5));
302 pt.drawPolygon(translatedPol);
303 pt.setCompositionMode(QPainter::CompositionMode_SourceIn);
304 } else {
305 pt.setClipRegion(QRegion(translatedPol));
306 pt.setCompositionMode(QPainter::CompositionMode_Source);
307 }
308
309 pt.drawPixmap(pixmap2.rect(), pixmap, pol.boundingRect());
310 pt.end();
311
312 emit freeRegionUpdated(pol);
313 emit freeRegionGrabbed(pixmap2);
314 }
315}
316
317#include "freeregiongrabber.moc"
318