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
58KRunnerDialog::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
97KRunnerDialog::~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
106void KRunnerDialog::screenResized(int screen)
107{
108 if (isVisible() && screen == m_shownOnScreen) {
109 positionOnScreen();
110 }
111}
112
113void KRunnerDialog::screenGeometryChanged(int screenCount)
114{
115 if (isVisible()) {
116 positionOnScreen();
117 }
118}
119
120void KRunnerDialog::resetScreenPos()
121{
122 if (isVisible() && !m_floating) {
123 positionOnScreen();
124 }
125}
126
127void 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
179void 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
189void 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
201void 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
229void KRunnerDialog::compositingChanged(bool)
230{
231 updatePresentation();
232 updateMask();
233 adjustSize();
234}
235
236bool KRunnerDialog::freeFloating() const
237{
238 return m_floating;
239}
240
241KRunnerDialog::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
252void KRunnerDialog::setStaticQueryMode(bool staticQuery)
253{
254 Q_UNUSED(staticQuery)
255}
256
257void 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
274void 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
286void 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
325void 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
335void 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
347void 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
358void 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
374void 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
396void 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
426void KRunnerDialog::mouseReleaseEvent(QMouseEvent *)
427{
428 if (!m_lastPressPos.isNull()) {
429 releaseMouse();
430 unsetCursor();
431 m_lastPressPos = QPoint();
432 m_resizing = false;
433 }
434}
435
436bool 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
460void KRunnerDialog::leaveEvent(QEvent *)
461{
462 unsetCursor();
463}
464
465void 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
513void 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
524bool 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