1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2 * (c)LGPL2+
3 *
4 * LXQt - a lightweight, Qt based, desktop toolset
5 * https://lxqt.org
6 *
7 * Copyright: 2010-2011 Razor team
8 * Authors:
9 * Alexander Sokoloff <sokoloff.a@gmail.com>
10 *
11 * This program or library is free software; you can redistribute it
12 * and/or modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
20
21 * You should have received a copy of the GNU Lesser General
22 * Public License along with this library; if not, write to the
23 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 * Boston, MA 02110-1301 USA
25 *
26 * END_COMMON_COPYRIGHT_HEADER */
27
28
29 #include "lxqtpanel.h"
30 #include "lxqtpanellimits.h"
31 #include "ilxqtpanelplugin.h"
32 #include "lxqtpanelapplication.h"
33 #include "lxqtpanellayout.h"
34 #include "config/configpaneldialog.h"
35 #include "popupmenu.h"
36 #include "plugin.h"
37 #include "panelpluginsmodel.h"
38 #include "windownotifier.h"
39 #include <LXQt/PluginInfo>
40
41 #include <QScreen>
42 #include <QWindow>
43 #include <QX11Info>
44 #include <QDebug>
45 #include <QString>
46 #include <QMenu>
47 #include <QMessageBox>
48 #include <QDropEvent>
49 #include <XdgIcon>
50 #include <XdgDirs>
51
52 #include <KWindowSystem/KWindowSystem>
53 #include <KWindowSystem/NETWM>
54
55 // Turn on this to show the time required to load each plugin during startup
56 // #define DEBUG_PLUGIN_LOADTIME
57 #ifdef DEBUG_PLUGIN_LOADTIME
58 #include <QElapsedTimer>
59 #endif
60
61 // Config keys and groups
62 #define CFG_KEY_SCREENNUM "desktop"
63 #define CFG_KEY_POSITION "position"
64 #define CFG_KEY_PANELSIZE "panelSize"
65 #define CFG_KEY_ICONSIZE "iconSize"
66 #define CFG_KEY_LINECNT "lineCount"
67 #define CFG_KEY_LENGTH "width"
68 #define CFG_KEY_PERCENT "width-percent"
69 #define CFG_KEY_ALIGNMENT "alignment"
70 #define CFG_KEY_FONTCOLOR "font-color"
71 #define CFG_KEY_BACKGROUNDCOLOR "background-color"
72 #define CFG_KEY_BACKGROUNDIMAGE "background-image"
73 #define CFG_KEY_OPACITY "opacity"
74 #define CFG_KEY_RESERVESPACE "reserve-space"
75 #define CFG_KEY_PLUGINS "plugins"
76 #define CFG_KEY_HIDABLE "hidable"
77 #define CFG_KEY_VISIBLE_MARGIN "visible-margin"
78 #define CFG_KEY_HIDE_ON_OVERLAP "hide-on-overlap"
79 #define CFG_KEY_ANIMATION "animation-duration"
80 #define CFG_KEY_SHOW_DELAY "show-delay"
81 #define CFG_KEY_LOCKPANEL "lockPanel"
82
83 /************************************************
84 Returns the Position by the string.
85 String is one of "Top", "Left", "Bottom", "Right", string is not case sensitive.
86 If the string is not correct, returns defaultValue.
87 ************************************************/
strToPosition(const QString & str,ILXQtPanel::Position defaultValue)88 ILXQtPanel::Position LXQtPanel::strToPosition(const QString& str, ILXQtPanel::Position defaultValue)
89 {
90 if (str.toUpper() == QLatin1String("TOP")) return LXQtPanel::PositionTop;
91 if (str.toUpper() == QLatin1String("LEFT")) return LXQtPanel::PositionLeft;
92 if (str.toUpper() == QLatin1String("RIGHT")) return LXQtPanel::PositionRight;
93 if (str.toUpper() == QLatin1String("BOTTOM")) return LXQtPanel::PositionBottom;
94 return defaultValue;
95 }
96
97
98 /************************************************
99 Return string representation of the position
100 ************************************************/
positionToStr(ILXQtPanel::Position position)101 QString LXQtPanel::positionToStr(ILXQtPanel::Position position)
102 {
103 switch (position)
104 {
105 case LXQtPanel::PositionTop:
106 return QStringLiteral("Top");
107 case LXQtPanel::PositionLeft:
108 return QStringLiteral("Left");
109 case LXQtPanel::PositionRight:
110 return QStringLiteral("Right");
111 case LXQtPanel::PositionBottom:
112 return QStringLiteral("Bottom");
113 }
114
115 return QString();
116 }
117
118
119 /************************************************
120
121 ************************************************/
LXQtPanel(const QString & configGroup,LXQt::Settings * settings,QWidget * parent)122 LXQtPanel::LXQtPanel(const QString &configGroup, LXQt::Settings *settings, QWidget *parent) :
123 QFrame(parent),
124 mSettings(settings),
125 mConfigGroup(configGroup),
126 mPlugins{nullptr},
127 mStandaloneWindows{new WindowNotifier},
128 mPanelSize(0),
129 mIconSize(0),
130 mLineCount(0),
131 mLength(0),
132 mAlignment(AlignmentLeft),
133 mPosition(ILXQtPanel::PositionBottom),
134 mScreenNum(0), //whatever (avoid conditional on uninitialized value)
135 mActualScreenNum(0),
136 mHidable(false),
137 mVisibleMargin(true),
138 mHideOnOverlap(false),
139 mHidden(false),
140 mAnimationTime(0),
141 mReserveSpace(true),
142 mAnimation(nullptr),
143 mLockPanel(false)
144 {
145 //You can find information about the flags and widget attributes in your
146 //Qt documentation or at https://doc.qt.io/qt-5/qt.html
147 //Qt::FramelessWindowHint = Produces a borderless window. The user cannot
148 //move or resize a borderless window via the window system. On X11, ...
149 //Qt::WindowStaysOnTopHint = Informs the window system that the window
150 //should stay on top of all other windows. Note that on ...
151 Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint;
152
153 // NOTE: by PCMan:
154 // In Qt 4, the window is not activated if it has Qt::WA_X11NetWmWindowTypeDock.
155 // Since Qt 5, the default behaviour is changed. A window is always activated on mouse click.
156 // Please see the source code of Qt5: src/plugins/platforms/xcb/qxcbwindow.cpp.
157 // void QXcbWindow::handleButtonPressEvent(const xcb_button_press_event_t *event)
158 // This new behaviour caused lxqt bug #161 - Cannot minimize windows from panel 1 when two task managers are open
159 // Besides, this breaks minimizing or restoring windows when clicking on the taskbar buttons.
160 // To workaround this regression bug, we need to add this window flag here.
161 // However, since the panel gets no keyboard focus, this may decrease accessibility since
162 // it's not possible to use the panel with keyboards. We need to find a better solution later.
163 flags |= Qt::WindowDoesNotAcceptFocus;
164
165 setWindowFlags(flags);
166 //Adds _NET_WM_WINDOW_TYPE_DOCK to the window's _NET_WM_WINDOW_TYPE X11 window property. See https://standards.freedesktop.org/wm-spec/ for more details.
167 setAttribute(Qt::WA_X11NetWmWindowTypeDock);
168 //Enables tooltips for inactive windows.
169 setAttribute(Qt::WA_AlwaysShowToolTips);
170 //Indicates that the widget should have a translucent background, i.e., any non-opaque regions of the widgets will be translucent because the widget will have an alpha channel. Setting this ...
171 setAttribute(Qt::WA_TranslucentBackground);
172 //Allows data from drag and drop operations to be dropped onto the widget (see QWidget::setAcceptDrops()).
173 setAttribute(Qt::WA_AcceptDrops);
174
175 setWindowTitle(QStringLiteral("LXQt Panel"));
176 setObjectName(QStringLiteral("LXQtPanel %1").arg(configGroup));
177
178 //LXQtPanel (inherits QFrame) -> lav (QGridLayout) -> LXQtPanelWidget (QFrame) -> LXQtPanelLayout
179 LXQtPanelWidget = new QFrame(this);
180 LXQtPanelWidget->setObjectName(QStringLiteral("BackgroundWidget"));
181 QGridLayout* lav = new QGridLayout();
182 lav->setContentsMargins(0, 0, 0, 0);
183 setLayout(lav);
184 this->layout()->addWidget(LXQtPanelWidget);
185
186 mLayout = new LXQtPanelLayout(LXQtPanelWidget);
187 connect(mLayout, &LXQtPanelLayout::pluginMoved, this, &LXQtPanel::pluginMoved);
188 LXQtPanelWidget->setLayout(mLayout);
189 mLayout->setLineCount(mLineCount);
190
191 mDelaySave.setSingleShot(true);
192 mDelaySave.setInterval(SETTINGS_SAVE_DELAY);
__anon908f4ab10102null193 connect(&mDelaySave, &QTimer::timeout, this, [this] { saveSettings(); } );
194
195 mHideTimer.setSingleShot(true);
196 mHideTimer.setInterval(PANEL_HIDE_DELAY);
197 connect(&mHideTimer, &QTimer::timeout, this, &LXQtPanel::hidePanelWork);
198
199 mShowDelayTimer.setSingleShot(true);
200 mShowDelayTimer.setInterval(PANEL_SHOW_DELAY);
__anon908f4ab10202null201 connect(&mShowDelayTimer, &QTimer::timeout, this, [this] { showPanel(mAnimationTime > 0); });
202
203 // screen updates
__anon908f4ab10302(QScreen* newScreen) 204 connect(qApp, &QApplication::screenAdded, this, [this] (QScreen* newScreen) {
205 connect(newScreen, &QScreen::virtualGeometryChanged, this, &LXQtPanel::ensureVisible);
206 connect(newScreen, &QScreen::geometryChanged, this, &LXQtPanel::ensureVisible);
207 ensureVisible();
208 });
__anon908f4ab10402(QScreen* oldScreen) 209 connect(qApp, &QApplication::screenRemoved, this, [this] (QScreen* oldScreen) {
210 disconnect(oldScreen, &QScreen::virtualGeometryChanged, this, &LXQtPanel::ensureVisible);
211 disconnect(oldScreen, &QScreen::geometryChanged, this, &LXQtPanel::ensureVisible);
212 // wait until the screen is really removed because it may contain the panel
213 QTimer::singleShot(0, this, &LXQtPanel::ensureVisible);
214 });
215 const auto screens = QApplication::screens();
216 for(const auto& screen : screens)
217 {
218 connect(screen, &QScreen::virtualGeometryChanged, this, &LXQtPanel::ensureVisible);
219 connect(screen, &QScreen::geometryChanged, this, &LXQtPanel::ensureVisible);
220 }
221
__anon908f4ab10502null222 connect(LXQt::Settings::globalSettings(), &LXQt::GlobalSettings::settingsChanged, this, [this] { update(); } );
223 connect(lxqtApp, &LXQt::Application::themeChanged, this, &LXQtPanel::realign);
224
__anon908f4ab10602null225 connect(mStandaloneWindows.data(), &WindowNotifier::firstShown, this, [this] { showPanel(true); });
226 connect(mStandaloneWindows.data(), &WindowNotifier::lastHidden, this, &LXQtPanel::hidePanel);
227
228 readSettings();
229
230 ensureVisible();
231
232 loadPlugins();
233
234 // NOTE: Some (X11) WMs may need the geometry to be set before QWidget::show().
235 setPanelGeometry();
236
237 show();
238
239 // show it the first time, despite setting
240 if (mHidable)
241 {
242 showPanel(false);
243 QTimer::singleShot(PANEL_HIDE_FIRST_TIME, this, SLOT(hidePanel()));
244 }
245
__anon908f4ab10702null246 connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, [this] {
247 if (mHidable && mHideOnOverlap && !mHidden)
248 {
249 mShowDelayTimer.stop();
250 hidePanel();
251 }
252 });
__anon908f4ab10802null253 connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, [this] {
254 if (mHidable && mHideOnOverlap && mHidden && !isPanelOverlapped())
255 mShowDelayTimer.start();
256 });
__anon908f4ab10902null257 connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, [this] {
258 if (mHidable && mHideOnOverlap)
259 {
260 if (!mHidden)
261 {
262 mShowDelayTimer.stop();
263 hidePanel();
264 }
265 else if (!isPanelOverlapped())
266 mShowDelayTimer.start();
267 }
268 });
269 connect(KWindowSystem::self(),
270 static_cast<void (KWindowSystem::*)(WId, NET::Properties, NET::Properties2)>(&KWindowSystem::windowChanged),
__anon908f4ab10a02(WId , NET::Properties prop, NET::Properties2) 271 this, [this] (WId /* id */, NET::Properties prop, NET::Properties2) {
272 if (mHidable && mHideOnOverlap
273 // when a window is moved, resized, shaded, or minimized
274 && (prop.testFlag(NET::WMGeometry) || prop.testFlag(NET::WMState)))
275 {
276 if (!mHidden)
277 {
278 mShowDelayTimer.stop();
279 hidePanel();
280 }
281 else if (!isPanelOverlapped())
282 mShowDelayTimer.start();
283 }
284 });
285 }
286
287 /************************************************
288
289 ************************************************/
readSettings()290 void LXQtPanel::readSettings()
291 {
292 // Read settings ......................................
293 mSettings->beginGroup(mConfigGroup);
294
295 // Let Hidability be the first thing we read
296 // so that every call to realign() is without side-effect
297 mHidable = mSettings->value(QStringLiteral(CFG_KEY_HIDABLE), mHidable).toBool();
298 mHidden = mHidable;
299
300 mVisibleMargin = mSettings->value(QStringLiteral(CFG_KEY_VISIBLE_MARGIN), mVisibleMargin).toBool();
301
302 mHideOnOverlap = mSettings->value(QStringLiteral(CFG_KEY_HIDE_ON_OVERLAP), mHideOnOverlap).toBool();
303
304 mAnimationTime = mSettings->value(QStringLiteral(CFG_KEY_ANIMATION), mAnimationTime).toInt();
305 mShowDelayTimer.setInterval(mSettings->value(QStringLiteral(CFG_KEY_SHOW_DELAY), mShowDelayTimer.interval()).toInt());
306
307 // By default we are using size & count from theme.
308 setPanelSize(mSettings->value(QStringLiteral(CFG_KEY_PANELSIZE), PANEL_DEFAULT_SIZE).toInt(), false);
309 setIconSize(mSettings->value(QStringLiteral(CFG_KEY_ICONSIZE), PANEL_DEFAULT_ICON_SIZE).toInt(), false);
310 setLineCount(mSettings->value(QStringLiteral(CFG_KEY_LINECNT), PANEL_DEFAULT_LINE_COUNT).toInt(), false);
311
312 setLength(mSettings->value(QStringLiteral(CFG_KEY_LENGTH), 100).toInt(),
313 mSettings->value(QStringLiteral(CFG_KEY_PERCENT), true).toBool(),
314 false);
315
316 mScreenNum = mSettings->value(QStringLiteral(CFG_KEY_SCREENNUM), 0).toInt();
317 setPosition(mScreenNum,
318 strToPosition(mSettings->value(QStringLiteral(CFG_KEY_POSITION)).toString(), PositionBottom),
319 false);
320
321 setAlignment(Alignment(mSettings->value(QStringLiteral(CFG_KEY_ALIGNMENT), mAlignment).toInt()), false);
322
323 QColor color = mSettings->value(QStringLiteral(CFG_KEY_FONTCOLOR), QString()).value<QColor>();
324 if (color.isValid())
325 setFontColor(color, true);
326
327 setOpacity(mSettings->value(QStringLiteral(CFG_KEY_OPACITY), 100).toInt(), true);
328 mReserveSpace = mSettings->value(QStringLiteral(CFG_KEY_RESERVESPACE), true).toBool();
329 color = mSettings->value(QStringLiteral(CFG_KEY_BACKGROUNDCOLOR), QString()).value<QColor>();
330 if (color.isValid())
331 setBackgroundColor(color, true);
332
333 QString image = mSettings->value(QStringLiteral(CFG_KEY_BACKGROUNDIMAGE), QString()).toString();
334 if (!image.isEmpty())
335 setBackgroundImage(image, false);
336
337 mLockPanel = mSettings->value(QStringLiteral(CFG_KEY_LOCKPANEL), false).toBool();
338
339 mSettings->endGroup();
340 }
341
342
343 /************************************************
344
345 ************************************************/
saveSettings(bool later)346 void LXQtPanel::saveSettings(bool later)
347 {
348 mDelaySave.stop();
349 if (later)
350 {
351 mDelaySave.start();
352 return;
353 }
354
355 mSettings->beginGroup(mConfigGroup);
356
357 //Note: save/load of plugin names is completely handled by mPlugins object
358 //mSettings->setValue(CFG_KEY_PLUGINS, mPlugins->pluginNames());
359
360 mSettings->setValue(QStringLiteral(CFG_KEY_PANELSIZE), mPanelSize);
361 mSettings->setValue(QStringLiteral(CFG_KEY_ICONSIZE), mIconSize);
362 mSettings->setValue(QStringLiteral(CFG_KEY_LINECNT), mLineCount);
363
364 mSettings->setValue(QStringLiteral(CFG_KEY_LENGTH), mLength);
365 mSettings->setValue(QStringLiteral(CFG_KEY_PERCENT), mLengthInPercents);
366
367 mSettings->setValue(QStringLiteral(CFG_KEY_SCREENNUM), mScreenNum);
368 mSettings->setValue(QStringLiteral(CFG_KEY_POSITION), positionToStr(mPosition));
369
370 mSettings->setValue(QStringLiteral(CFG_KEY_ALIGNMENT), mAlignment);
371
372 mSettings->setValue(QStringLiteral(CFG_KEY_FONTCOLOR), mFontColor.isValid() ? mFontColor : QColor());
373 mSettings->setValue(QStringLiteral(CFG_KEY_BACKGROUNDCOLOR), mBackgroundColor.isValid() ? mBackgroundColor : QColor());
374 mSettings->setValue(QStringLiteral(CFG_KEY_BACKGROUNDIMAGE), QFileInfo::exists(mBackgroundImage) ? mBackgroundImage : QString());
375 mSettings->setValue(QStringLiteral(CFG_KEY_OPACITY), mOpacity);
376 mSettings->setValue(QStringLiteral(CFG_KEY_RESERVESPACE), mReserveSpace);
377
378 mSettings->setValue(QStringLiteral(CFG_KEY_HIDABLE), mHidable);
379 mSettings->setValue(QStringLiteral(CFG_KEY_VISIBLE_MARGIN), mVisibleMargin);
380 mSettings->setValue(QStringLiteral(CFG_KEY_HIDE_ON_OVERLAP), mHideOnOverlap);
381 mSettings->setValue(QStringLiteral(CFG_KEY_ANIMATION), mAnimationTime);
382 mSettings->setValue(QStringLiteral(CFG_KEY_SHOW_DELAY), mShowDelayTimer.interval());
383
384 mSettings->setValue(QStringLiteral(CFG_KEY_LOCKPANEL), mLockPanel);
385
386 mSettings->endGroup();
387 }
388
389
390 /************************************************
391
392 ************************************************/
ensureVisible()393 void LXQtPanel::ensureVisible()
394 {
395 if (!canPlacedOn(mScreenNum, mPosition))
396 setPosition(findAvailableScreen(mPosition), mPosition, false);
397 else
398 mActualScreenNum = mScreenNum;
399
400 // the screen size might be changed
401 realign();
402 }
403
404
405 /************************************************
406
407 ************************************************/
~LXQtPanel()408 LXQtPanel::~LXQtPanel()
409 {
410 mLayout->setEnabled(false);
411 delete mAnimation;
412 delete mConfigDialog.data();
413 // do not save settings because of "user deleted panel" functionality saveSettings();
414 }
415
416
417 /************************************************
418
419 ************************************************/
show()420 void LXQtPanel::show()
421 {
422 QWidget::show();
423 KWindowSystem::setOnDesktop(effectiveWinId(), NET::OnAllDesktops);
424 }
425
426
427 /************************************************
428
429 ************************************************/
pluginDesktopDirs()430 QStringList pluginDesktopDirs()
431 {
432 QStringList dirs;
433 dirs << QString::fromLocal8Bit(qgetenv("LXQT_PANEL_PLUGINS_DIR")).split(QLatin1Char(':'), Qt::SkipEmptyParts);
434 dirs << QStringLiteral("%1/%2").arg(XdgDirs::dataHome(), QStringLiteral("/lxqt/lxqt-panel"));
435 dirs << QStringLiteral(PLUGIN_DESKTOPS_DIR);
436 return dirs;
437 }
438
439
440 /************************************************
441
442 ************************************************/
loadPlugins()443 void LXQtPanel::loadPlugins()
444 {
445 QString names_key(mConfigGroup);
446 names_key += QLatin1Char('/');
447 names_key += QLatin1String(CFG_KEY_PLUGINS);
448 mPlugins.reset(new PanelPluginsModel(this, names_key, pluginDesktopDirs()));
449
450 connect(mPlugins.data(), &PanelPluginsModel::pluginAdded, mLayout, &LXQtPanelLayout::addPlugin);
451 connect(mPlugins.data(), &PanelPluginsModel::pluginMovedUp, mLayout, &LXQtPanelLayout::moveUpPlugin);
452 //reemit signals
453 connect(mPlugins.data(), &PanelPluginsModel::pluginAdded, this, &LXQtPanel::pluginAdded);
454 connect(mPlugins.data(), &PanelPluginsModel::pluginRemoved, this, &LXQtPanel::pluginRemoved);
455
456 const auto plugins = mPlugins->plugins();
457 for (auto const & plugin : plugins)
458 {
459 mLayout->addPlugin(plugin);
460 connect(plugin, &Plugin::dragLeft, this, [this] {
461 mShowDelayTimer.stop();
462 hidePanel();
463 });
464 }
465 }
466
467 /************************************************
468
469 ************************************************/
getReserveDimension()470 int LXQtPanel::getReserveDimension()
471 {
472 return mHidable ? PANEL_HIDE_SIZE : qMax(PANEL_MINIMUM_SIZE, mPanelSize);
473 }
474
setPanelGeometry(bool animate)475 void LXQtPanel::setPanelGeometry(bool animate)
476 {
477 const auto screens = QApplication::screens();
478 if (mActualScreenNum >= screens.size())
479 return;
480 const QRect currentScreen = screens.at(mActualScreenNum)->geometry();
481
482 QRect rect;
483
484 if (isHorizontal())
485 {
486 // Horiz panel ***************************
487 rect.setHeight(qMax(PANEL_MINIMUM_SIZE, mPanelSize));
488 if (mLengthInPercents)
489 rect.setWidth(currentScreen.width() * mLength / 100.0);
490 else
491 {
492 if (mLength <= 0)
493 rect.setWidth(currentScreen.width() + mLength);
494 else
495 rect.setWidth(mLength);
496 }
497
498 rect.setWidth(qMax(rect.size().width(), mLayout->minimumSize().width()));
499
500 // Horiz ......................
501 switch (mAlignment)
502 {
503 case LXQtPanel::AlignmentLeft:
504 rect.moveLeft(currentScreen.left());
505 break;
506
507 case LXQtPanel::AlignmentCenter:
508 rect.moveCenter(currentScreen.center());
509 break;
510
511 case LXQtPanel::AlignmentRight:
512 rect.moveRight(currentScreen.right());
513 break;
514 }
515
516 // Vert .......................
517 if (mPosition == ILXQtPanel::PositionTop)
518 {
519 if (mHidden)
520 rect.moveBottom(currentScreen.top() + PANEL_HIDE_SIZE - 1);
521 else
522 rect.moveTop(currentScreen.top());
523 }
524 else
525 {
526 if (mHidden)
527 rect.moveTop(currentScreen.bottom() - PANEL_HIDE_SIZE + 1);
528 else
529 rect.moveBottom(currentScreen.bottom());
530 }
531 }
532 else
533 {
534 // Vert panel ***************************
535 rect.setWidth(qMax(PANEL_MINIMUM_SIZE, mPanelSize));
536 if (mLengthInPercents)
537 rect.setHeight(currentScreen.height() * mLength / 100.0);
538 else
539 {
540 if (mLength <= 0)
541 rect.setHeight(currentScreen.height() + mLength);
542 else
543 rect.setHeight(mLength);
544 }
545
546 rect.setHeight(qMax(rect.size().height(), mLayout->minimumSize().height()));
547
548 // Vert .......................
549 switch (mAlignment)
550 {
551 case LXQtPanel::AlignmentLeft:
552 rect.moveTop(currentScreen.top());
553 break;
554
555 case LXQtPanel::AlignmentCenter:
556 rect.moveCenter(currentScreen.center());
557 break;
558
559 case LXQtPanel::AlignmentRight:
560 rect.moveBottom(currentScreen.bottom());
561 break;
562 }
563
564 // Horiz ......................
565 if (mPosition == ILXQtPanel::PositionLeft)
566 {
567 if (mHidden)
568 rect.moveRight(currentScreen.left() + PANEL_HIDE_SIZE - 1);
569 else
570 rect.moveLeft(currentScreen.left());
571 }
572 else
573 {
574 if (mHidden)
575 rect.moveLeft(currentScreen.right() - PANEL_HIDE_SIZE + 1);
576 else
577 rect.moveRight(currentScreen.right());
578 }
579 }
580 if (!mHidden || !mGeometry.isValid()) mGeometry = rect;
581 if (rect != geometry())
582 {
583 setFixedSize(rect.size());
584 if (animate)
585 {
586 if (mAnimation == nullptr)
587 {
588 mAnimation = new QPropertyAnimation(this, "geometry");
589 mAnimation->setEasingCurve(QEasingCurve::Linear);
590 //Note: for hiding, the margins are set after animation is finished
591 connect(mAnimation, &QAbstractAnimation::finished, this, [this] { if (mHidden) setMargins(); });
592 }
593 mAnimation->setDuration(mAnimationTime);
594 mAnimation->setStartValue(geometry());
595 mAnimation->setEndValue(rect);
596 //Note: for showing-up, the margins are removed instantly
597 if (!mHidden)
598 setMargins();
599 mAnimation->start();
600 }
601 else
602 {
603 setMargins();
604 setGeometry(rect);
605 }
606 }
607 }
608
setMargins()609 void LXQtPanel::setMargins()
610 {
611 if (mHidden)
612 {
613 if (isHorizontal())
614 {
615 if (mPosition == ILXQtPanel::PositionTop)
616 mLayout->setContentsMargins(0, 0, 0, PANEL_HIDE_SIZE);
617 else
618 mLayout->setContentsMargins(0, PANEL_HIDE_SIZE, 0, 0);
619 }
620 else
621 {
622 if (mPosition == ILXQtPanel::PositionLeft)
623 mLayout->setContentsMargins(0, 0, PANEL_HIDE_SIZE, 0);
624 else
625 mLayout->setContentsMargins(PANEL_HIDE_SIZE, 0, 0, 0);
626 }
627 if (!mVisibleMargin)
628 setWindowOpacity(0.0);
629 }
630 else {
631 mLayout->setContentsMargins(0, 0, 0, 0);
632 if (!mVisibleMargin)
633 setWindowOpacity(1.0);
634 }
635 }
636
realign()637 void LXQtPanel::realign()
638 {
639 if (!isVisible())
640 return;
641 #if 0
642 qDebug() << "** Realign *********************";
643 qDebug() << "PanelSize: " << mPanelSize;
644 qDebug() << "IconSize: " << mIconSize;
645 qDebug() << "LineCount: " << mLineCount;
646 qDebug() << "Length: " << mLength << (mLengthInPercents ? "%" : "px");
647 qDebug() << "Alignment: " << (mAlignment == 0 ? "center" : (mAlignment < 0 ? "left" : "right"));
648 qDebug() << "Position: " << positionToStr(mPosition) << "on" << mScreenNum;
649 qDebug() << "Plugins count: " << mPlugins.count();
650 #endif
651
652 setPanelGeometry();
653
654 // Reserve our space on the screen ..........
655 // It's possible that our geometry is not changed, but screen resolution is changed,
656 // so resetting WM_STRUT is still needed. To make it simple, we always do it.
657 updateWmStrut();
658 }
659
660
661 // Update the _NET_WM_PARTIAL_STRUT and _NET_WM_STRUT properties for the window
updateWmStrut()662 void LXQtPanel::updateWmStrut()
663 {
664 WId wid = effectiveWinId();
665 if(wid == 0 || !isVisible())
666 return;
667
668 if (mReserveSpace && QApplication::primaryScreen())
669 {
670 const QRect wholeScreen = QApplication::primaryScreen()->virtualGeometry();
671 const QRect rect = geometry();
672 // NOTE: https://standards.freedesktop.org/wm-spec/wm-spec-latest.html
673 // Quote from the EWMH spec: " Note that the strut is relative to the screen edge, and not the edge of the xinerama monitor."
674 // So, we use the geometry of the whole screen to calculate the strut rather than using the geometry of individual monitors.
675 // Though the spec only mention Xinerama and did not mention XRandR, the rule should still be applied.
676 // At least openbox is implemented like this.
677 switch (mPosition)
678 {
679 case LXQtPanel::PositionTop:
680 KWindowSystem::setExtendedStrut(wid,
681 /* Left */ 0, 0, 0,
682 /* Right */ 0, 0, 0,
683 /* Top */ rect.top() + getReserveDimension(), rect.left(), rect.right(),
684 /* Bottom */ 0, 0, 0
685 );
686 break;
687
688 case LXQtPanel::PositionBottom:
689 KWindowSystem::setExtendedStrut(wid,
690 /* Left */ 0, 0, 0,
691 /* Right */ 0, 0, 0,
692 /* Top */ 0, 0, 0,
693 /* Bottom */ wholeScreen.bottom() - rect.bottom() + getReserveDimension(), rect.left(), rect.right()
694 );
695 break;
696
697 case LXQtPanel::PositionLeft:
698 KWindowSystem::setExtendedStrut(wid,
699 /* Left */ rect.left() + getReserveDimension(), rect.top(), rect.bottom(),
700 /* Right */ 0, 0, 0,
701 /* Top */ 0, 0, 0,
702 /* Bottom */ 0, 0, 0
703 );
704
705 break;
706
707 case LXQtPanel::PositionRight:
708 KWindowSystem::setExtendedStrut(wid,
709 /* Left */ 0, 0, 0,
710 /* Right */ wholeScreen.right() - rect.right() + getReserveDimension(), rect.top(), rect.bottom(),
711 /* Top */ 0, 0, 0,
712 /* Bottom */ 0, 0, 0
713 );
714 break;
715 }
716 } else
717 {
718 KWindowSystem::setExtendedStrut(wid,
719 /* Left */ 0, 0, 0,
720 /* Right */ 0, 0, 0,
721 /* Top */ 0, 0, 0,
722 /* Bottom */ 0, 0, 0
723 );
724 }
725 }
726
727
728 /************************************************
729 This function checks if the panel can be placed on
730 the display @screenNum at @position.
731 NOTE: The panel can be placed only at screen edges
732 but no part of it should be between two screens.
733 ************************************************/
canPlacedOn(int screenNum,LXQtPanel::Position position)734 bool LXQtPanel::canPlacedOn(int screenNum, LXQtPanel::Position position)
735 {
736 const auto screens = QApplication::screens();
737 if (screens.size() > screenNum)
738 {
739 const QRect screenGeometry = screens.at(screenNum)->geometry();
740 switch (position)
741 {
742 case LXQtPanel::PositionTop:
743 for (const auto& screen : screens)
744 {
745 if (screen->geometry().top() < screenGeometry.top())
746 {
747 QRect r = screenGeometry.adjusted(0, screen->geometry().top() - screenGeometry.top(), 0, 0);
748 if (screen->geometry().intersects(r))
749 return false;
750 }
751 }
752 return true;
753
754 case LXQtPanel::PositionBottom:
755 for (const auto& screen : screens)
756 {
757 if (screen->geometry().bottom() > screenGeometry.bottom())
758 {
759 QRect r = screenGeometry.adjusted(0, 0, 0, screen->geometry().bottom() - screenGeometry.bottom());
760 if (screen->geometry().intersects(r))
761 return false;
762 }
763 }
764 return true;
765
766 case LXQtPanel::PositionLeft:
767 for (const auto& screen : screens)
768 {
769 if (screen->geometry().left() < screenGeometry.left())
770 {
771 QRect r = screenGeometry.adjusted(screen->geometry().left() - screenGeometry.left(), 0, 0, 0);
772 if (screen->geometry().intersects(r))
773 return false;
774 }
775 }
776 return true;
777
778 case LXQtPanel::PositionRight:
779 for (const auto& screen : screens)
780 {
781 if (screen->geometry().right() > screenGeometry.right())
782 {
783 QRect r = screenGeometry.adjusted(0, 0, screen->geometry().right() - screenGeometry.right(), 0);
784 if (screen->geometry().intersects(r))
785 return false;
786 }
787 }
788 return true;
789 }
790 }
791
792 return false;
793 }
794
795
796 /************************************************
797
798 ************************************************/
findAvailableScreen(LXQtPanel::Position position)799 int LXQtPanel::findAvailableScreen(LXQtPanel::Position position)
800 {
801 int current = mScreenNum;
802
803 for (int i = current; i < QApplication::screens().size(); ++i)
804 if (canPlacedOn(i, position))
805 return i;
806
807 for (int i = 0; i < current; ++i)
808 if (canPlacedOn(i, position))
809 return i;
810
811 return 0;
812 }
813
814
815 /************************************************
816
817 ************************************************/
showConfigDialog()818 void LXQtPanel::showConfigDialog()
819 {
820 if (mConfigDialog.isNull())
821 mConfigDialog = new ConfigPanelDialog(this, nullptr /*make it top level window*/);
822
823 mConfigDialog->showConfigPanelPage();
824 mStandaloneWindows->observeWindow(mConfigDialog.data());
825 mConfigDialog->show();
826 mConfigDialog->raise();
827 mConfigDialog->activateWindow();
828 WId wid = mConfigDialog->windowHandle()->winId();
829
830 KWindowSystem::activateWindow(wid);
831 KWindowSystem::setOnDesktop(wid, KWindowSystem::currentDesktop());
832 }
833
834
835 /************************************************
836
837 ************************************************/
showAddPluginDialog()838 void LXQtPanel::showAddPluginDialog()
839 {
840 if (mConfigDialog.isNull())
841 mConfigDialog = new ConfigPanelDialog(this, nullptr /*make it top level window*/);
842
843 mConfigDialog->showConfigPluginsPage();
844 mStandaloneWindows->observeWindow(mConfigDialog.data());
845 mConfigDialog->show();
846 mConfigDialog->raise();
847 mConfigDialog->activateWindow();
848 WId wid = mConfigDialog->windowHandle()->winId();
849
850 KWindowSystem::activateWindow(wid);
851 KWindowSystem::setOnDesktop(wid, KWindowSystem::currentDesktop());
852 }
853
854
855 /************************************************
856
857 ************************************************/
updateStyleSheet()858 void LXQtPanel::updateStyleSheet()
859 {
860 // NOTE: This is a workaround for Qt >= 5.13, which might not completely
861 // update the style sheet (especially positioned backgrounds of plugins
862 // with NeedsHandle="true") if it is not reset first.
863 setStyleSheet(QString());
864
865 QStringList sheet;
866 sheet << QStringLiteral("Plugin > QAbstractButton, LXQtTray { qproperty-iconSize: %1px %1px; }").arg(mIconSize);
867 sheet << QStringLiteral("Plugin > * > QAbstractButton, TrayIcon { qproperty-iconSize: %1px %1px; }").arg(mIconSize);
868
869 if (mFontColor.isValid())
870 sheet << QString(QStringLiteral("Plugin * { color: ") + mFontColor.name() + QStringLiteral("; }"));
871
872 if (mBackgroundColor.isValid())
873 {
874 QString color = QStringLiteral("%1, %2, %3, %4")
875 .arg(mBackgroundColor.red())
876 .arg(mBackgroundColor.green())
877 .arg(mBackgroundColor.blue())
878 .arg((float) mOpacity / 100);
879 sheet << QString(QStringLiteral("LXQtPanel #BackgroundWidget { background-color: rgba(") + color + QStringLiteral("); }"));
880 }
881
882 if (QFileInfo::exists(mBackgroundImage))
883 sheet << QString(QStringLiteral("LXQtPanel #BackgroundWidget { background-image: url('") + mBackgroundImage + QStringLiteral("');}"));
884
885 setStyleSheet(sheet.join(QStringLiteral("\n")));
886 }
887
888
889
890 /************************************************
891
892 ************************************************/
setPanelSize(int value,bool save)893 void LXQtPanel::setPanelSize(int value, bool save)
894 {
895 if (mPanelSize != value)
896 {
897 mPanelSize = value;
898 realign();
899
900 if (save)
901 saveSettings(true);
902 }
903 }
904
905
906
907 /************************************************
908
909 ************************************************/
setIconSize(int value,bool save)910 void LXQtPanel::setIconSize(int value, bool save)
911 {
912 if (mIconSize != value)
913 {
914 mIconSize = value;
915 updateStyleSheet();
916 mLayout->setLineSize(mIconSize);
917
918 if (save)
919 saveSettings(true);
920
921 realign();
922 }
923 }
924
925
926 /************************************************
927
928 ************************************************/
setLineCount(int value,bool save)929 void LXQtPanel::setLineCount(int value, bool save)
930 {
931 if (mLineCount != value)
932 {
933 mLineCount = value;
934 mLayout->setEnabled(false);
935 mLayout->setLineCount(mLineCount);
936 mLayout->setEnabled(true);
937
938 if (save)
939 saveSettings(true);
940
941 realign();
942 }
943 }
944
945
946 /************************************************
947
948 ************************************************/
setLength(int length,bool inPercents,bool save)949 void LXQtPanel::setLength(int length, bool inPercents, bool save)
950 {
951 if (mLength == length &&
952 mLengthInPercents == inPercents)
953 return;
954
955 mLength = length;
956 mLengthInPercents = inPercents;
957
958 if (save)
959 saveSettings(true);
960
961 realign();
962 }
963
964
965 /************************************************
966
967 ************************************************/
setPosition(int screen,ILXQtPanel::Position position,bool save)968 void LXQtPanel::setPosition(int screen, ILXQtPanel::Position position, bool save)
969 {
970 if (mScreenNum == screen &&
971 mPosition == position)
972 return;
973
974 mActualScreenNum = screen;
975 mPosition = position;
976 mLayout->setPosition(mPosition);
977
978 if (save)
979 {
980 mScreenNum = screen;
981 saveSettings(true);
982 }
983
984 // Qt 5 adds a new class QScreen and add API for setting the screen of a QWindow.
985 // so we had better use it. However, without this, our program should still work
986 // as long as XRandR is used. Since XRandR combined all screens into a large virtual desktop
987 // every screen and their virtual siblings are actually on the same virtual desktop.
988 // So things still work if we don't set the screen correctly, but this is not the case
989 // for other backends, such as the upcoming wayland support. Hence it's better to set it.
990 if(windowHandle())
991 {
992 // QScreen* newScreen = qApp->screens().at(screen);
993 // QScreen* oldScreen = windowHandle()->screen();
994 // const bool shouldRecreate = windowHandle()->handle() && !(oldScreen && oldScreen->virtualSiblings().contains(newScreen));
995 // Q_ASSERT(shouldRecreate == false);
996
997 // NOTE: When you move a window to another screen, Qt 5 might recreate the window as needed
998 // But luckily, this never happen in XRandR, so Qt bug #40681 is not triggered here.
999 // (The only exception is when the old screen is destroyed, Qt always re-create the window and
1000 // this corner case triggers #40681.)
1001 // When using other kind of multihead settings, such as Xinerama, this might be different and
1002 // unless Qt developers can fix their bug, we have no way to workaround that.
1003 windowHandle()->setScreen(qApp->screens().at(screen));
1004 }
1005
1006 realign();
1007 }
1008
1009 /************************************************
1010 *
1011 ************************************************/
setAlignment(Alignment value,bool save)1012 void LXQtPanel::setAlignment(Alignment value, bool save)
1013 {
1014 if (mAlignment == value)
1015 return;
1016
1017 mAlignment = value;
1018
1019 if (save)
1020 saveSettings(true);
1021
1022 realign();
1023 }
1024
1025 /************************************************
1026 *
1027 ************************************************/
setFontColor(QColor color,bool save)1028 void LXQtPanel::setFontColor(QColor color, bool save)
1029 {
1030 mFontColor = color;
1031 updateStyleSheet();
1032
1033 if (save)
1034 saveSettings(true);
1035 }
1036
1037 /************************************************
1038
1039 ************************************************/
setBackgroundColor(QColor color,bool save)1040 void LXQtPanel::setBackgroundColor(QColor color, bool save)
1041 {
1042 mBackgroundColor = color;
1043 updateStyleSheet();
1044
1045 if (save)
1046 saveSettings(true);
1047 }
1048
1049 /************************************************
1050
1051 ************************************************/
setBackgroundImage(QString path,bool save)1052 void LXQtPanel::setBackgroundImage(QString path, bool save)
1053 {
1054 mBackgroundImage = path;
1055 updateStyleSheet();
1056
1057 if (save)
1058 saveSettings(true);
1059 }
1060
1061
1062 /************************************************
1063 *
1064 ************************************************/
setOpacity(int opacity,bool save)1065 void LXQtPanel::setOpacity(int opacity, bool save)
1066 {
1067 mOpacity = opacity;
1068 updateStyleSheet();
1069
1070 if (save)
1071 saveSettings(true);
1072 }
1073
1074
1075 /************************************************
1076 *
1077 ************************************************/
setReserveSpace(bool reserveSpace,bool save)1078 void LXQtPanel::setReserveSpace(bool reserveSpace, bool save)
1079 {
1080 if (mReserveSpace == reserveSpace)
1081 return;
1082
1083 mReserveSpace = reserveSpace;
1084
1085 if (save)
1086 saveSettings(true);
1087
1088 updateWmStrut();
1089 }
1090
1091
1092 /************************************************
1093
1094 ************************************************/
globalGeometry() const1095 QRect LXQtPanel::globalGeometry() const
1096 {
1097 // panel is the the top-most widget/window, no calculation needed
1098 return geometry();
1099 }
1100
1101
1102 /************************************************
1103
1104 ************************************************/
event(QEvent * event)1105 bool LXQtPanel::event(QEvent *event)
1106 {
1107 switch (event->type())
1108 {
1109 case QEvent::ContextMenu:
1110 showPopupMenu();
1111 break;
1112
1113 case QEvent::LayoutRequest:
1114 emit realigned();
1115 break;
1116
1117 case QEvent::WinIdChange:
1118 {
1119 // qDebug() << "WinIdChange" << hex << effectiveWinId();
1120 if(effectiveWinId() == 0)
1121 break;
1122
1123 // Sometimes Qt needs to re-create the underlying window of the widget and
1124 // the winId() may be changed at runtime. So we need to reset all X11 properties
1125 // when this happens.
1126 qDebug() << "WinIdChange" << Qt::hex << effectiveWinId() << "handle" << windowHandle() << windowHandle()->screen();
1127
1128 // Qt::WA_X11NetWmWindowTypeDock becomes ineffective in Qt 5
1129 // See QTBUG-39887: https://bugreports.qt-project.org/browse/QTBUG-39887
1130 // Let's use KWindowSystem for that
1131 KWindowSystem::setType(effectiveWinId(), NET::Dock);
1132
1133 updateWmStrut(); // reserve screen space for the panel
1134 KWindowSystem::setOnAllDesktops(effectiveWinId(), true);
1135 break;
1136 }
1137 case QEvent::DragEnter:
1138 dynamic_cast<QDropEvent *>(event)->setDropAction(Qt::IgnoreAction);
1139 event->accept();
1140 #if __cplusplus >= 201703L
1141 [[fallthrough]];
1142 #endif
1143 // fall through
1144 case QEvent::Enter:
1145 mShowDelayTimer.start();
1146 break;
1147
1148 case QEvent::Leave:
1149 case QEvent::DragLeave:
1150 mShowDelayTimer.stop();
1151 hidePanel();
1152 break;
1153
1154 default:
1155 break;
1156 }
1157
1158 return QFrame::event(event);
1159 }
1160
1161 /************************************************
1162
1163 ************************************************/
1164
showEvent(QShowEvent * event)1165 void LXQtPanel::showEvent(QShowEvent *event)
1166 {
1167 QFrame::showEvent(event);
1168 realign();
1169 }
1170
1171
1172 /************************************************
1173
1174 ************************************************/
showPopupMenu(Plugin * plugin)1175 void LXQtPanel::showPopupMenu(Plugin *plugin)
1176 {
1177 PopupMenu * menu = new PopupMenu(tr("Panel"), this);
1178 menu->setAttribute(Qt::WA_DeleteOnClose);
1179
1180 menu->setIcon(XdgIcon::fromTheme(QStringLiteral("configure-toolbars")));
1181
1182 // Plugin Menu ..............................
1183 if (plugin)
1184 {
1185 QMenu *m = plugin->popupMenu();
1186
1187 if (m)
1188 {
1189 menu->addTitle(plugin->windowTitle());
1190 const auto actions = m->actions();
1191 for (auto const & action : actions)
1192 {
1193 action->setParent(menu);
1194 action->setDisabled(mLockPanel);
1195 menu->addAction(action);
1196 }
1197 delete m;
1198 }
1199 }
1200
1201 // Panel menu ...............................
1202
1203 menu->addTitle(QIcon(), tr("Panel"));
1204
1205 menu->addAction(XdgIcon::fromTheme(QLatin1String("configure")),
1206 tr("Configure Panel"),
1207 this, &LXQtPanel::showConfigDialog
1208 )->setDisabled(mLockPanel);
1209
1210 menu->addAction(XdgIcon::fromTheme(QStringLiteral("preferences-plugin")),
1211 tr("Manage Widgets"),
1212 this, &LXQtPanel::showAddPluginDialog
1213 )->setDisabled(mLockPanel);
1214
1215 LXQtPanelApplication *a = reinterpret_cast<LXQtPanelApplication*>(qApp);
1216 menu->addAction(XdgIcon::fromTheme(QLatin1String("list-add")),
1217 tr("Add New Panel"),
1218 a, &LXQtPanelApplication::addNewPanel
1219 );
1220
1221 if (a->count() > 1)
1222 {
1223 menu->addAction(XdgIcon::fromTheme(QLatin1String("list-remove")),
1224 tr("Remove Panel", "Menu Item"),
1225 this, &LXQtPanel::userRequestForDeletion
1226 )->setDisabled(mLockPanel);
1227 }
1228
1229 QAction * act_lock = menu->addAction(tr("Lock This Panel"));
1230 act_lock->setCheckable(true);
1231 act_lock->setChecked(mLockPanel);
1232 connect(act_lock, &QAction::triggered, this, [this] { mLockPanel = !mLockPanel; saveSettings(false); });
1233
1234 #ifdef DEBUG
1235 menu->addSeparator();
1236 menu->addAction("Exit (debug only)", qApp, &QApplication::quit);
1237 #endif
1238
1239 /* Note: in multihead & multipanel setup the QMenu::popup/exec places the window
1240 * sometimes wrongly (it seems that this bug is somehow connected to misinterpretation
1241 * of QDesktopWidget::availableGeometry)
1242 */
1243 menu->setGeometry(calculatePopupWindowPos(QCursor::pos(), menu->sizeHint()));
1244 willShowWindow(menu);
1245 menu->show();
1246 }
1247
findPlugin(const ILXQtPanelPlugin * iPlugin) const1248 Plugin* LXQtPanel::findPlugin(const ILXQtPanelPlugin* iPlugin) const
1249 {
1250 const auto plugins = mPlugins->plugins();
1251 for (auto const & plug : plugins)
1252 if (plug->iPlugin() == iPlugin)
1253 return plug;
1254 return nullptr;
1255 }
1256
1257 /************************************************
1258
1259 ************************************************/
calculatePopupWindowPos(QPoint const & absolutePos,QSize const & windowSize) const1260 QRect LXQtPanel::calculatePopupWindowPos(QPoint const & absolutePos, QSize const & windowSize) const
1261 {
1262 int x = absolutePos.x(), y = absolutePos.y();
1263
1264 switch (position())
1265 {
1266 case ILXQtPanel::PositionTop:
1267 y = mGeometry.bottom();
1268 break;
1269
1270 case ILXQtPanel::PositionBottom:
1271 y = mGeometry.top() - windowSize.height();
1272 break;
1273
1274 case ILXQtPanel::PositionLeft:
1275 x = mGeometry.right();
1276 break;
1277
1278 case ILXQtPanel::PositionRight:
1279 x = mGeometry.left() - windowSize.width();
1280 break;
1281 }
1282
1283 QRect res(QPoint(x, y), windowSize);
1284
1285 QRect panelScreen;
1286 const auto screens = QApplication::screens();
1287 if (mActualScreenNum < screens.size())
1288 panelScreen = screens.at(mActualScreenNum)->geometry();
1289 // NOTE: We cannot use AvailableGeometry() which returns the work area here because when in a
1290 // multihead setup with different resolutions. In this case, the size of the work area is limited
1291 // by the smallest monitor and may be much smaller than the current screen and we will place the
1292 // menu at the wrong place. This is very bad for UX. So let's use the full size of the screen.
1293 if (res.right() > panelScreen.right())
1294 res.moveRight(panelScreen.right());
1295
1296 if (res.bottom() > panelScreen.bottom())
1297 res.moveBottom(panelScreen.bottom());
1298
1299 if (res.left() < panelScreen.left())
1300 res.moveLeft(panelScreen.left());
1301
1302 if (res.top() < panelScreen.top())
1303 res.moveTop(panelScreen.top());
1304
1305 return res;
1306 }
1307
1308 /************************************************
1309
1310 ************************************************/
calculatePopupWindowPos(const ILXQtPanelPlugin * plugin,const QSize & windowSize) const1311 QRect LXQtPanel::calculatePopupWindowPos(const ILXQtPanelPlugin *plugin, const QSize &windowSize) const
1312 {
1313 Plugin *panel_plugin = findPlugin(plugin);
1314 if (nullptr == panel_plugin)
1315 {
1316 qWarning() << Q_FUNC_INFO << "Wrong logic? Unable to find Plugin* for" << plugin << "known plugins follow...";
1317 const auto plugins = mPlugins->plugins();
1318 for (auto const & plug : plugins)
1319 qWarning() << plug->iPlugin() << plug;
1320
1321 return QRect();
1322 }
1323
1324 // Note: assuming there are not contentMargins around the "BackgroundWidget" (LXQtPanelWidget)
1325 return calculatePopupWindowPos(mGeometry.topLeft() + panel_plugin->geometry().topLeft(), windowSize);
1326 }
1327
1328
1329 /************************************************
1330
1331 ************************************************/
willShowWindow(QWidget * w)1332 void LXQtPanel::willShowWindow(QWidget * w)
1333 {
1334 mStandaloneWindows->observeWindow(w);
1335 }
1336
1337 /************************************************
1338
1339 ************************************************/
pluginFlagsChanged(const ILXQtPanelPlugin *)1340 void LXQtPanel::pluginFlagsChanged(const ILXQtPanelPlugin * /*plugin*/)
1341 {
1342 mLayout->rebuild();
1343 }
1344
1345 /************************************************
1346
1347 ************************************************/
qssPosition() const1348 QString LXQtPanel::qssPosition() const
1349 {
1350 return positionToStr(position());
1351 }
1352
1353 /************************************************
1354
1355 ************************************************/
pluginMoved(Plugin * plug)1356 void LXQtPanel::pluginMoved(Plugin * plug)
1357 {
1358 //get new position of the moved plugin
1359 bool found{false};
1360 QString plug_is_before;
1361 for (int i=0; i<mLayout->count(); ++i)
1362 {
1363 Plugin *plugin = qobject_cast<Plugin*>(mLayout->itemAt(i)->widget());
1364 if (plugin)
1365 {
1366 if (found)
1367 {
1368 //we found our plugin in previous cycle -> is before this (or empty as last)
1369 plug_is_before = plugin->settingsGroup();
1370 break;
1371 } else
1372 found = (plug == plugin);
1373 }
1374 }
1375 mPlugins->movePlugin(plug, plug_is_before);
1376 }
1377
1378
1379 /************************************************
1380
1381 ************************************************/
userRequestForDeletion()1382 void LXQtPanel::userRequestForDeletion()
1383 {
1384 const QMessageBox::StandardButton ret
1385 = QMessageBox::warning(this, tr("Remove Panel", "Dialog Title") ,
1386 tr("Removing a panel can not be undone.\nDo you want to remove this panel?"),
1387 QMessageBox::Yes | QMessageBox::No);
1388
1389 if (ret != QMessageBox::Yes) {
1390 return;
1391 }
1392
1393 mSettings->beginGroup(mConfigGroup);
1394 const QStringList plugins = mSettings->value(QStringLiteral("plugins")).toStringList();
1395 mSettings->endGroup();
1396
1397 for(const QString& i : plugins)
1398 if (!i.isEmpty())
1399 mSettings->remove(i);
1400
1401 mSettings->remove(mConfigGroup);
1402
1403 emit deletedByUser(this);
1404 }
1405
isPanelOverlapped() const1406 bool LXQtPanel::isPanelOverlapped() const
1407 {
1408 QFlags<NET::WindowTypeMask> ignoreList;
1409 ignoreList |= NET::DesktopMask;
1410 ignoreList |= NET::DockMask;
1411 ignoreList |= NET::SplashMask;
1412 ignoreList |= NET::MenuMask;
1413 ignoreList |= NET::PopupMenuMask;
1414 ignoreList |= NET::DropdownMenuMask;
1415 ignoreList |= NET::TopMenuMask;
1416 ignoreList |= NET::NotificationMask;
1417
1418 const auto wIds = KWindowSystem::stackingOrder();
1419 for (auto const wId : wIds)
1420 {
1421 KWindowInfo info(wId, NET::WMWindowType | NET::WMState | NET::WMFrameExtents | NET::WMDesktop);
1422 if (info.valid()
1423 // skip windows that are on other desktops
1424 && info.isOnCurrentDesktop()
1425 // skip shaded, minimized or hidden windows
1426 && !(info.state() & (NET::Shaded | NET::Hidden))
1427 // check against the list of ignored types
1428 && !NET::typeMatchesMask(info.windowType(NET::AllTypesMask), ignoreList))
1429 {
1430 if (info.frameGeometry().intersects(mGeometry))
1431 return true;
1432 }
1433 }
1434 return false;
1435 }
1436
showPanel(bool animate)1437 void LXQtPanel::showPanel(bool animate)
1438 {
1439 if (mHidable)
1440 {
1441 mHideTimer.stop();
1442 if (mHidden)
1443 {
1444 mHidden = false;
1445 setPanelGeometry(mAnimationTime > 0 && animate);
1446 }
1447 }
1448 }
1449
hidePanel()1450 void LXQtPanel::hidePanel()
1451 {
1452 if (mHidable && !mHidden
1453 && !mStandaloneWindows->isAnyWindowShown())
1454 {
1455 mHideTimer.start();
1456 }
1457 }
1458
hidePanelWork()1459 void LXQtPanel::hidePanelWork()
1460 {
1461 if (!testAttribute(Qt::WA_UnderMouse))
1462 {
1463 if (!mStandaloneWindows->isAnyWindowShown())
1464 {
1465 if (!mHideOnOverlap || isPanelOverlapped())
1466 {
1467 mHidden = true;
1468 setPanelGeometry(mAnimationTime > 0);
1469 }
1470 }
1471 else
1472 {
1473 mHideTimer.start();
1474 }
1475 }
1476 }
1477
setHidable(bool hidable,bool save)1478 void LXQtPanel::setHidable(bool hidable, bool save)
1479 {
1480 if (mHidable == hidable)
1481 return;
1482
1483 mHidable = hidable;
1484
1485 if (save)
1486 saveSettings(true);
1487
1488 realign();
1489 }
1490
setVisibleMargin(bool visibleMargin,bool save)1491 void LXQtPanel::setVisibleMargin(bool visibleMargin, bool save)
1492 {
1493 if (mVisibleMargin == visibleMargin)
1494 return;
1495
1496 mVisibleMargin = visibleMargin;
1497
1498 if (save)
1499 saveSettings(true);
1500
1501 realign();
1502 }
1503
setHideOnOverlap(bool hideOnOverlap,bool save)1504 void LXQtPanel::setHideOnOverlap(bool hideOnOverlap, bool save)
1505 {
1506 if (mHideOnOverlap == hideOnOverlap)
1507 return;
1508
1509 mHideOnOverlap = hideOnOverlap;
1510
1511 if (save)
1512 saveSettings(true);
1513
1514 realign();
1515 }
1516
setAnimationTime(int animationTime,bool save)1517 void LXQtPanel::setAnimationTime(int animationTime, bool save)
1518 {
1519 if (mAnimationTime == animationTime)
1520 return;
1521
1522 mAnimationTime = animationTime;
1523
1524 if (save)
1525 saveSettings(true);
1526 }
1527
setShowDelay(int showDelay,bool save)1528 void LXQtPanel::setShowDelay(int showDelay, bool save)
1529 {
1530 if (mShowDelayTimer.interval() == showDelay)
1531 return;
1532
1533 mShowDelayTimer.setInterval(showDelay);
1534
1535 if (save)
1536 saveSettings(true);
1537 }
1538
iconTheme() const1539 QString LXQtPanel::iconTheme() const
1540 {
1541 return mSettings->value(QStringLiteral("iconTheme")).toString();
1542 }
1543
setIconTheme(const QString & iconTheme)1544 void LXQtPanel::setIconTheme(const QString& iconTheme)
1545 {
1546 LXQtPanelApplication *a = reinterpret_cast<LXQtPanelApplication*>(qApp);
1547 a->setIconTheme(iconTheme);
1548 }
1549
updateConfigDialog() const1550 void LXQtPanel::updateConfigDialog() const
1551 {
1552 if (!mConfigDialog.isNull() && mConfigDialog->isVisible())
1553 {
1554 mConfigDialog->updateIconThemeSettings();
1555 const QList<QWidget*> widgets = mConfigDialog->findChildren<QWidget*>();
1556 for (QWidget *widget : widgets)
1557 widget->update();
1558 }
1559 }
1560
isPluginSingletonAndRunnig(QString const & pluginId) const1561 bool LXQtPanel::isPluginSingletonAndRunnig(QString const & pluginId) const
1562 {
1563 Plugin const * plugin = mPlugins->pluginByID(pluginId);
1564 if (nullptr == plugin)
1565 return false;
1566 else
1567 return plugin->iPlugin()->flags().testFlag(ILXQtPanelPlugin::SingleInstance);
1568 }
1569