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