1 | /* |
2 | * Copyright (C) 2006 Aaron Seigo <aseigo@kde.org> |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify |
5 | * it 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 "krunnerdialog.h" |
20 | |
21 | #include <QPainter> |
22 | #include <QSvgRenderer> |
23 | #include <QResizeEvent> |
24 | #include <QMouseEvent> |
25 | #ifdef Q_WS_X11 |
26 | #include <QX11Info> |
27 | #endif |
28 | #include <QBitmap> |
29 | #include <QTimer> |
30 | #include <QDesktopWidget> |
31 | |
32 | #include <KDebug> |
33 | #include <KWindowSystem> |
34 | #include <KPluginInfo> |
35 | #ifdef Q_WS_X11 |
36 | #include <NETRootInfo> |
37 | #endif |
38 | |
39 | #include <QtCore/QStringBuilder> // % operator for QString |
40 | |
41 | #include "kworkspace/kdisplaymanager.h" |
42 | |
43 | #include <Plasma/AbstractRunner> |
44 | #include <Plasma/FrameSvg> |
45 | #include <Plasma/RunnerManager> |
46 | #include <Plasma/Theme> |
47 | #include <Plasma/WindowEffects> |
48 | |
49 | #include "configdialog.h" |
50 | #include "krunnerapp.h" |
51 | #include "krunnersettings.h" |
52 | #include "panelshadows.h" |
53 | |
54 | #ifdef Q_WS_X11 |
55 | #include <X11/Xlib.h> |
56 | #endif |
57 | |
58 | KRunnerDialog::KRunnerDialog(Plasma::RunnerManager *runnerManager, QWidget *parent, Qt::WindowFlags f) |
59 | : QWidget(parent, f), |
60 | m_runnerManager(runnerManager), |
61 | m_configWidget(0), |
62 | m_shadows(new PanelShadows(this)), |
63 | m_background(new Plasma::FrameSvg(this)), |
64 | m_shownOnScreen(-1), |
65 | m_offset(.5), |
66 | m_floating(!KRunnerSettings::freeFloating()), |
67 | m_resizing(false), |
68 | m_rightResize(false), |
69 | m_vertResize(false), |
70 | m_runningTimer(false), |
71 | m_desktopWidget(qApp->desktop()) |
72 | { |
73 | setAttribute(Qt::WA_TranslucentBackground); |
74 | setMouseTracking(true); |
75 | //setButtons(0); |
76 | setWindowTitle(i18nc("@title:window" , "Run Command" )); |
77 | setWindowIcon(KIcon(QLatin1String("system-run" ))); |
78 | |
79 | QPalette pal = palette(); |
80 | pal.setColor(backgroundRole(), Qt::transparent); |
81 | setPalette(pal); |
82 | |
83 | m_iconSvg = new Plasma::Svg(this); |
84 | m_iconSvg->setImagePath(QLatin1String("widgets/configuration-icons" )); |
85 | |
86 | connect(m_background, SIGNAL(repaintNeeded()), this, SLOT(themeUpdated())); |
87 | |
88 | connect(m_desktopWidget, SIGNAL(resized(int)), this, SLOT(screenGeometryChanged(int))); |
89 | connect(m_desktopWidget, SIGNAL(screenCountChanged(int)), this, SLOT(screenGeometryChanged(int))); |
90 | |
91 | connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(resetScreenPos())); |
92 | connect(KWindowSystem::self(), SIGNAL(compositingChanged(bool)), this, SLOT(compositingChanged(bool))); |
93 | |
94 | setFreeFloating(KRunnerSettings::freeFloating()); |
95 | } |
96 | |
97 | KRunnerDialog::~KRunnerDialog() |
98 | { |
99 | //kDebug( )<< "!!!!!!!!!! deleting" << m_floating << m_screenPos.count(); |
100 | if (!m_floating) { |
101 | KConfigGroup cg(KGlobal::config(), "EdgePositions" ); |
102 | cg.writeEntry(QLatin1String("Offset" ), m_offset); |
103 | } |
104 | } |
105 | |
106 | void KRunnerDialog::screenResized(int screen) |
107 | { |
108 | if (isVisible() && screen == m_shownOnScreen) { |
109 | positionOnScreen(); |
110 | } |
111 | } |
112 | |
113 | void KRunnerDialog::screenGeometryChanged(int screenCount) |
114 | { |
115 | if (isVisible()) { |
116 | positionOnScreen(); |
117 | } |
118 | } |
119 | |
120 | void KRunnerDialog::resetScreenPos() |
121 | { |
122 | if (isVisible() && !m_floating) { |
123 | positionOnScreen(); |
124 | } |
125 | } |
126 | |
127 | void KRunnerDialog::positionOnScreen() |
128 | { |
129 | if (m_desktopWidget->screenCount() < 2) { |
130 | m_shownOnScreen = m_desktopWidget->primaryScreen(); |
131 | } else if (isVisible()) { |
132 | m_shownOnScreen = m_desktopWidget->screenNumber(geometry().center()); |
133 | } else { |
134 | m_shownOnScreen = m_desktopWidget->screenNumber(QCursor::pos()); |
135 | } |
136 | |
137 | const QRect r = m_desktopWidget->screenGeometry(m_shownOnScreen); |
138 | |
139 | if (m_floating && !m_customPos.isNull()) { |
140 | int x = qBound(r.left(), m_customPos.x(), r.right() - width()); |
141 | int y = qBound(r.top(), m_customPos.y(), r.bottom() - height()); |
142 | move(x, y); |
143 | show(); |
144 | return; |
145 | } |
146 | |
147 | const int w = width(); |
148 | int x = r.left() + (r.width() * m_offset) - (w / 2); |
149 | |
150 | int y = r.top(); |
151 | if (m_floating) { |
152 | y += r.height() / 3; |
153 | } |
154 | |
155 | x = qBound(r.left(), x, r.right() - width()); |
156 | y = qBound(r.top(), y, r.bottom() - height()); |
157 | |
158 | move(x, y); |
159 | |
160 | if (!m_floating) { |
161 | checkBorders(r); |
162 | } |
163 | |
164 | show(); |
165 | |
166 | if (m_floating) { |
167 | KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); |
168 | //Turn the sliding effect off |
169 | Plasma::WindowEffects::slideWindow(this, Plasma::Floating); |
170 | } else { |
171 | KWindowSystem::setOnAllDesktops(winId(), true); |
172 | Plasma::WindowEffects::slideWindow(this, Plasma::TopEdge); |
173 | } |
174 | |
175 | KWindowSystem::forceActiveWindow(winId()); |
176 | //kDebug() << "moving to" << m_screenPos[screen]; |
177 | } |
178 | |
179 | void KRunnerDialog::moveEvent(QMoveEvent *) |
180 | { |
181 | if (m_floating) { |
182 | m_customPos = pos(); |
183 | } else { |
184 | const QRect screen = m_desktopWidget->screenGeometry(m_shownOnScreen); |
185 | m_offset = qRound((geometry().center().x() - screen.x()) / qreal(screen.width()) * 100) / 100.0; |
186 | } |
187 | } |
188 | |
189 | void KRunnerDialog::setFreeFloating(bool floating) |
190 | { |
191 | if (m_floating == floating) { |
192 | return; |
193 | } |
194 | |
195 | m_floating = floating; |
196 | m_shownOnScreen = -1; |
197 | unsetCursor(); |
198 | updatePresentation(); |
199 | } |
200 | |
201 | void KRunnerDialog::updatePresentation() |
202 | { |
203 | if (m_floating) { |
204 | KWindowSystem::setType(winId(), NET::Normal); |
205 | |
206 | //m_shadows->setImagePath(QLatin1String("dialogs/krunner")); |
207 | m_background->setImagePath(QLatin1String("dialogs/krunner" )); |
208 | m_background->setElementPrefix(QString()); |
209 | |
210 | themeUpdated(); |
211 | } else { |
212 | //m_shadows->setImagePath(QLatin1String("widgets/panel-background")); |
213 | m_background->setImagePath(QLatin1String("widgets/panel-background" )); |
214 | m_background->resizeFrame(size()); |
215 | m_background->setElementPrefix("north-mini" ); |
216 | // load the positions for each screen from our config |
217 | KConfigGroup cg(KGlobal::config(), "EdgePositions" ); |
218 | m_offset = cg.readEntry(QLatin1String("Offset" ), m_offset); |
219 | const QRect r = m_desktopWidget->screenGeometry(m_shownOnScreen); |
220 | checkBorders(r); |
221 | KWindowSystem::setType(winId(), NET::Dock); |
222 | } |
223 | |
224 | if (isVisible()) { |
225 | positionOnScreen(); |
226 | } |
227 | } |
228 | |
229 | void KRunnerDialog::compositingChanged(bool) |
230 | { |
231 | updatePresentation(); |
232 | updateMask(); |
233 | adjustSize(); |
234 | } |
235 | |
236 | bool KRunnerDialog::freeFloating() const |
237 | { |
238 | return m_floating; |
239 | } |
240 | |
241 | KRunnerDialog::ResizeMode KRunnerDialog::manualResizing() const |
242 | { |
243 | if (!m_resizing) { |
244 | return NotResizing; |
245 | } else if (m_vertResize) { |
246 | return VerticalResizing; |
247 | } else { |
248 | return HorizontalResizing; |
249 | } |
250 | } |
251 | |
252 | void KRunnerDialog::setStaticQueryMode(bool staticQuery) |
253 | { |
254 | Q_UNUSED(staticQuery) |
255 | } |
256 | |
257 | void KRunnerDialog::toggleConfigDialog() |
258 | { |
259 | if (m_configWidget) { |
260 | delete m_configWidget; |
261 | m_configWidget = 0; |
262 | |
263 | if (!m_floating) { |
264 | KWindowSystem::setType(winId(), NET::Dock); |
265 | } |
266 | } else { |
267 | m_configWidget = new KRunnerConfigWidget(m_runnerManager, this); |
268 | connect(m_configWidget, SIGNAL(finished()), this, SLOT(configCompleted())); |
269 | setConfigWidget(m_configWidget); |
270 | KWindowSystem::setType(winId(), NET::Normal); |
271 | } |
272 | } |
273 | |
274 | void KRunnerDialog::configCompleted() |
275 | { |
276 | if (m_configWidget) { |
277 | m_configWidget->deleteLater(); |
278 | m_configWidget = 0; |
279 | } |
280 | |
281 | if (!m_floating) { |
282 | KWindowSystem::setType(winId(), NET::Dock); |
283 | } |
284 | } |
285 | |
286 | void KRunnerDialog::themeUpdated() |
287 | { |
288 | m_shadows->addWindow(this); |
289 | |
290 | bool useShadowsForMargins = false; |
291 | if (m_floating) { |
292 | // recalc the contents margins |
293 | m_background->blockSignals(true); |
294 | if (KWindowSystem::compositingActive()) { |
295 | m_background->setEnabledBorders(Plasma::FrameSvg::NoBorder); |
296 | useShadowsForMargins = true; |
297 | } else { |
298 | m_background->setEnabledBorders(Plasma::FrameSvg::AllBorders); |
299 | } |
300 | m_background->blockSignals(false); |
301 | } |
302 | |
303 | if (useShadowsForMargins) { |
304 | m_shadows->getMargins(m_topBorderHeight, m_rightBorderWidth, m_bottomBorderHeight, m_leftBorderWidth); |
305 | } else { |
306 | m_leftBorderWidth = qMax(0, int(m_background->marginSize(Plasma::LeftMargin))); |
307 | m_rightBorderWidth = qMax(0, int(m_background->marginSize(Plasma::RightMargin))); |
308 | m_bottomBorderHeight = qMax(0, int(m_background->marginSize(Plasma::BottomMargin))); |
309 | // the -1 in the non-floating case is not optimal, but it gives it a bit of a "more snug to the |
310 | // top" feel; best would be if we could tell exactly where the edge/shadow of the frame svg was |
311 | // but this works nicely |
312 | m_topBorderHeight = m_floating ? qMax(0, int(m_background->marginSize(Plasma::TopMargin))) |
313 | : Plasma::Theme::defaultTheme()->windowTranslucencyEnabled() |
314 | ? qMax(1, m_bottomBorderHeight / 2) |
315 | : qMax(1, m_bottomBorderHeight - 1); |
316 | } |
317 | |
318 | kDebug() << m_leftBorderWidth << m_topBorderHeight << m_rightBorderWidth << m_bottomBorderHeight; |
319 | // the +1 gives us the extra mouseMoveEvent needed to always reset the resize cursor |
320 | setContentsMargins(m_leftBorderWidth + 1, m_topBorderHeight, m_rightBorderWidth + 1, m_bottomBorderHeight + 1); |
321 | |
322 | update(); |
323 | } |
324 | |
325 | void KRunnerDialog::paintEvent(QPaintEvent *e) |
326 | { |
327 | QPainter p(this); |
328 | p.setCompositionMode(QPainter::CompositionMode_Source); |
329 | p.setClipRect(e->rect()); |
330 | //kDebug() << "clip rect set to: " << e->rect(); |
331 | |
332 | m_background->paintFrame(&p); |
333 | } |
334 | |
335 | void KRunnerDialog::showEvent(QShowEvent *) |
336 | { |
337 | m_shadows->addWindow(this); |
338 | unsigned long state = NET::SkipTaskbar | NET::KeepAbove | NET::StaysOnTop; |
339 | if (m_floating) { |
340 | KWindowSystem::clearState(winId(), state); |
341 | } else { |
342 | KWindowSystem::setState(winId(), state); |
343 | } |
344 | m_runnerManager->setupMatchSession(); |
345 | } |
346 | |
347 | void KRunnerDialog::hideEvent(QHideEvent *) |
348 | { |
349 | // We delay the call to matchSessionComplete until next event cycle |
350 | // This is necessary since we might hide the dialog right before running |
351 | // a match, and the runner might still need to be prepped to |
352 | // succesfully run a match |
353 | QTimer::singleShot(0, m_runnerManager, SLOT(matchSessionComplete())); |
354 | delete m_configWidget; |
355 | m_configWidget = 0; |
356 | } |
357 | |
358 | void KRunnerDialog::updateMask() |
359 | { |
360 | // Enable the mask only when compositing is disabled; |
361 | // As this operation is quite slow, it would be nice to find some |
362 | // way to workaround it for no-compositing users. |
363 | |
364 | if (KWindowSystem::compositingActive()) { |
365 | clearMask(); |
366 | const QRegion mask = m_background->mask(); |
367 | Plasma::WindowEffects::enableBlurBehind(winId(), true, mask); |
368 | Plasma::WindowEffects::overrideShadow(winId(), true); |
369 | } else { |
370 | setMask(m_background->mask()); |
371 | } |
372 | } |
373 | |
374 | void KRunnerDialog::resizeEvent(QResizeEvent *e) |
375 | { |
376 | m_background->resizeFrame(e->size()); |
377 | |
378 | bool maskDirty = true; |
379 | if (m_resizing && !m_vertResize) { |
380 | const QRect r = m_desktopWidget->screenGeometry(m_shownOnScreen); |
381 | //kDebug() << "if" << x() << ">" << r.left() << "&&" << r.right() << ">" << (x() + width()); |
382 | const Plasma::FrameSvg::EnabledBorders borders = m_background->enabledBorders(); |
383 | if (borders & Plasma::FrameSvg::LeftBorder) { |
384 | const int dx = x() + (e->oldSize().width() - width()) / 2 ; |
385 | const int dy = (m_floating ? pos().y() : r.top()); |
386 | move(qBound(r.left(), dx, r.right() - width() + 1), dy); |
387 | maskDirty = m_floating || !checkBorders(r); |
388 | } |
389 | } |
390 | |
391 | if (maskDirty) { |
392 | updateMask(); |
393 | } |
394 | } |
395 | |
396 | void KRunnerDialog::mousePressEvent(QMouseEvent *e) |
397 | { |
398 | if (e->button() == Qt::LeftButton) { |
399 | m_lastPressPos = e->globalPos(); |
400 | |
401 | const bool leftResize = e->x() < qMax(5, m_leftBorderWidth); |
402 | m_rightResize = e->x() > width() - qMax(5, m_rightBorderWidth); |
403 | m_vertResize = e->y() > height() - qMax(5, m_bottomBorderHeight); |
404 | kWarning() << "right:" << m_rightResize << "left:" << leftResize << "vert:" << m_vertResize; |
405 | if (m_rightResize || m_vertResize || leftResize) { |
406 | // let's do a resize! :) |
407 | grabMouse(); |
408 | m_resizing = true; |
409 | } else if (m_floating) { |
410 | #ifdef Q_WS_X11 |
411 | m_lastPressPos = QPoint(); |
412 | // We have to release the mouse grab before initiating the move operation. |
413 | // Ideally we would call releaseMouse() to do this, but when we only have an |
414 | // implicit passive grab, Qt is unaware of it, and will refuse to release it. |
415 | XUngrabPointer(x11Info().display(), CurrentTime); |
416 | |
417 | // Ask the window manager to start an interactive move operation. |
418 | NETRootInfo rootInfo(x11Info().display(), NET::WMMoveResize); |
419 | rootInfo.moveResizeRequest(winId(), e->globalX(), e->globalY(), NET::Move); |
420 | #endif |
421 | } |
422 | e->accept(); |
423 | } |
424 | } |
425 | |
426 | void KRunnerDialog::mouseReleaseEvent(QMouseEvent *) |
427 | { |
428 | if (!m_lastPressPos.isNull()) { |
429 | releaseMouse(); |
430 | unsetCursor(); |
431 | m_lastPressPos = QPoint(); |
432 | m_resizing = false; |
433 | } |
434 | } |
435 | |
436 | bool KRunnerDialog::checkBorders(const QRect &screenGeom) |
437 | { |
438 | Q_ASSERT(!m_floating); |
439 | Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::BottomBorder; |
440 | |
441 | if (x() > screenGeom.left()) { |
442 | borders |= Plasma::FrameSvg::LeftBorder; |
443 | } |
444 | |
445 | if (x() + width() < screenGeom.right()) { |
446 | borders |= Plasma::FrameSvg::RightBorder; |
447 | } |
448 | |
449 | if (borders != m_background->enabledBorders()) { |
450 | m_background->setEnabledBorders(borders); |
451 | themeUpdated(); |
452 | updateMask(); |
453 | update(); |
454 | return true; |
455 | } |
456 | |
457 | return false; |
458 | } |
459 | |
460 | void KRunnerDialog::leaveEvent(QEvent *) |
461 | { |
462 | unsetCursor(); |
463 | } |
464 | |
465 | void KRunnerDialog::mouseMoveEvent(QMouseEvent *e) |
466 | { |
467 | //kDebug() << e->x() << m_leftBorderWidth << width() << m_rightBorderWidth; |
468 | if (m_lastPressPos.isNull()) { |
469 | // not press positiong, so we aren't going to resize or move. |
470 | checkCursor(e->pos()); |
471 | } else if (m_resizing) { |
472 | // resizing |
473 | if (m_vertResize) { |
474 | const int deltaY = e->globalY() - m_lastPressPos.y(); |
475 | resize(width(), qMax(80, height() + deltaY)); |
476 | m_lastPressPos = e->globalPos(); |
477 | } else { |
478 | const QRect r = m_desktopWidget->availableGeometry(m_shownOnScreen); |
479 | const int deltaX = (m_rightResize ? -1 : 1) * (m_lastPressPos.x() - e->globalX()); |
480 | int newWidth = width() + deltaX; |
481 | |
482 | // don't let it grow beyond the opposite screen edge |
483 | if (m_rightResize) { |
484 | if (m_leftBorderWidth > 0) { |
485 | newWidth += qMin(deltaX, x() - r.left()); |
486 | } |
487 | } else if (m_rightBorderWidth > 0) { |
488 | newWidth += qMin(deltaX, r.right() - (x() + width() - 1)); |
489 | } else if (newWidth > minimumWidth() && newWidth < width()) { |
490 | move(r.right() - newWidth + 1, y()); |
491 | } |
492 | |
493 | if (newWidth > minimumWidth()) { |
494 | resize(newWidth, height()); |
495 | m_lastPressPos = e->globalPos(); |
496 | } |
497 | } |
498 | } else { |
499 | // moving |
500 | const QRect r = m_desktopWidget->availableGeometry(m_shownOnScreen); |
501 | int newX = qBound(r.left(), x() - (m_lastPressPos.x() - e->globalX()), r.right() - width() + 1); |
502 | if (abs(r.center().x() - (newX + (width() / 2))) < 20) { |
503 | newX = r.center().x() - (width() / 2); |
504 | } else { |
505 | m_lastPressPos = e->globalPos(); |
506 | } |
507 | |
508 | move(newX, y()); |
509 | checkBorders(r); |
510 | } |
511 | } |
512 | |
513 | void KRunnerDialog::timerEvent(QTimerEvent *event) |
514 | { |
515 | killTimer(event->timerId()); |
516 | if (checkCursor(mapFromGlobal(QCursor::pos()))) { |
517 | m_runningTimer = true; |
518 | startTimer(100); |
519 | } else { |
520 | m_runningTimer = false; |
521 | } |
522 | } |
523 | |
524 | bool KRunnerDialog::checkCursor(const QPoint &pos) |
525 | { |
526 | //Plasma::FrameSvg borders = m_background->enabledBoders(); |
527 | if ((m_leftBorderWidth > 0 && pos.x() < qMax(5, m_leftBorderWidth)) || |
528 | (m_rightBorderWidth > 0 && pos.x() > width() - qMax(5, m_rightBorderWidth))) { |
529 | if (cursor().shape() != Qt::SizeHorCursor) { |
530 | setCursor(Qt::SizeHorCursor); |
531 | if (!m_runningTimer) { |
532 | m_runningTimer = true; |
533 | startTimer(100); |
534 | } |
535 | return false; |
536 | } |
537 | |
538 | return true; |
539 | } else if ((pos.y() > height() - qMax(5, m_bottomBorderHeight)) && (pos.y() < height())) { |
540 | if (cursor().shape() != Qt::SizeVerCursor) { |
541 | setCursor(Qt::SizeVerCursor); |
542 | if (!m_runningTimer) { |
543 | m_runningTimer = true; |
544 | startTimer(100); |
545 | } |
546 | return false; |
547 | } |
548 | |
549 | return true; |
550 | } |
551 | |
552 | unsetCursor(); |
553 | return false; |
554 | } |
555 | |
556 | #include "krunnerdialog.moc" |
557 | |