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 | |
32 | FreeRegionGrabber::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 | |
43 | FreeRegionGrabber::~FreeRegionGrabber() |
44 | { |
45 | } |
46 | |
47 | void 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 | |
58 | static 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 | |
76 | void 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 | |
185 | void 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 | |
213 | void 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 | |
250 | void 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 | |
259 | void FreeRegionGrabber::mouseDoubleClickEvent( QMouseEvent* ) |
260 | { |
261 | grabRect(); |
262 | } |
263 | |
264 | void 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 | |
282 | void 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 | |