aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/core/lumina-desktop-unified/src-DE
diff options
context:
space:
mode:
authorKen Moore <ken@ixsystems.com>2017-01-04 16:44:55 -0500
committerKen Moore <ken@ixsystems.com>2017-01-04 16:44:55 -0500
commit25b2e77aa2395ba9143683a5ce1a27b99ee7a211 (patch)
treebbd732bb72689b9b46dfc619d3d0e1748f7e435b /src-qt5/core/lumina-desktop-unified/src-DE
parentTag version 1.2.1 on the master branch in preparation for new changes from th... (diff)
downloadlumina-25b2e77aa2395ba9143683a5ce1a27b99ee7a211.tar.gz
lumina-25b2e77aa2395ba9143683a5ce1a27b99ee7a211.tar.bz2
lumina-25b2e77aa2395ba9143683a5ce1a27b99ee7a211.zip
Create a new "lumina-desktop-unified" core subproject (DO NOT USE)
This is just a staging area for the merging of the desktop, window manager, etc.. into a single unified application. It is highly fragmented right now and will not build *AT ALL* for a while.
Diffstat (limited to 'src-qt5/core/lumina-desktop-unified/src-DE')
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.cpp183
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.h58
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/JsonMenu.h79
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.cpp553
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.h107
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.cpp90
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.h27
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.cpp333
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.h303
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LPanel.cpp399
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LPanel.h81
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LSession.cpp798
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LSession.h193
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.cpp48
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.h50
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.cpp118
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.h104
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.cpp67
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.h28
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.cpp104
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.h46
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.ui194
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.cpp63
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.h156
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/NewDP.h63
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/SamplePlugin.h38
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.cpp145
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.h59
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/OutlineToolButton.h99
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.cpp271
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.h84
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.ui182
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/calendar/CalendarPlugin.h59
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktop-plugins.pri24
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.cpp214
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.h55
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/LXDG-DBusNotifier.h17
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.cpp90
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.h48
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.cpp330
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.h66
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/quickcontainer/QuickDPlugin.h51
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp363
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h72
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui552
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp287
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h105
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.cpp63
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.h62
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.ui143
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LPPlugin.h77
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LTBWidget.h71
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/NewPP.h82
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/RotateToolButton.h58
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.cpp77
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.h63
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.cpp142
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.h64
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.cpp30
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.h49
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.cpp258
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.h59
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.ui182
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.cpp115
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.h53
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/NOTES49
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.cpp230
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.h58
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.cpp207
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.h88
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.cpp142
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.h72
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.cpp35
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.h42
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/line/LLine.h40
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/panel-plugins.pri57
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/quickcontainer/QuickPPlugin.h43
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.cpp43
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.h62
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/spacer/LSpacer.h34
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.cpp91
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.h76
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.cpp211
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.h54
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.ui400
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.cpp279
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.h98
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.cpp137
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.h113
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.cpp720
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.h105
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.ui1148
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.cpp167
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.h74
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.cpp128
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.h55
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.cpp271
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.h73
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.cpp141
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.h71
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.cpp67
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.h75
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.cpp205
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.h72
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.cpp393
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.h101
-rw-r--r--src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.ui593
107 files changed, 16224 insertions, 0 deletions
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.cpp
new file mode 100644
index 00000000..798d8b6d
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.cpp
@@ -0,0 +1,183 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "AppMenu.h"
+#include "LSession.h"
+#include <LuminaOS.h>
+
+AppMenu::AppMenu(QWidget* parent) : QMenu(parent){
+ appstorelink = LOS::AppStoreShortcut(); //Default application "store" to display (AppCafe in TrueOS)
+ controlpanellink = LOS::ControlPanelShortcut(); //Default control panel
+ sysApps = new XDGDesktopList(this, true); //have this one automatically keep in sync
+ APPS.clear();
+ //watcher = new QFileSystemWatcher(this);
+ //connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherUpdate()) );
+ //QTimer::singleShot(200, this, SLOT(start()) ); //Now start filling the menu
+ start(); //do the initial run during session init so things are responsive immediately.
+ connect(QApplication::instance(), SIGNAL(LocaleChanged()), this, SLOT(watcherUpdate()) );
+ connect(QApplication::instance(), SIGNAL(IconThemeChanged()), this, SLOT(watcherUpdate()) );
+}
+
+AppMenu::~AppMenu(){
+
+}
+
+QHash<QString, QList<XDGDesktop*> >* AppMenu::currentAppHash(){
+ return &APPS;
+}
+
+//===========
+// PRIVATE
+//===========
+void AppMenu::updateAppList(){
+ //watcher->removePaths(watcher->directories());
+ //Make sure the title/icon are updated as well (in case of locale/icon change)
+ this->setTitle(tr("Applications"));
+ this->setIcon( LXDG::findIcon("system-run","") );
+ //Now update the lists
+ this->clear();
+ APPS.clear(); //NOTE: Don't delete these pointers - the pointers are managed by the sysApps class and these are just references to them
+ //qDebug() << "New Apps List:";
+ if(LSession::handle()->sessionSettings()->value("AutomaticDesktopAppLinks",true).toBool() && !lastHashUpdate.isNull() ){
+ QString desktop = QDir::homePath()+"/"+tr("Desktop")+"/"; //translated desktop folder
+ if(!QFile::exists(desktop)){
+ desktop = QDir::homePath()+"/Desktop/"; //desktop folder
+ if(!QFile::exists(desktop)){
+ desktop = QDir::homePath()+"/desktop/"; //lowercase desktop folder
+ if(!QFile::exists(desktop)){ desktop.clear(); }
+ }
+ }
+ //qDebug() << "Update Desktop Folder:" << desktop << sysApps->removedApps << sysApps->newApps;
+ QStringList tmp = sysApps->removedApps;
+ for(int i=0; i<tmp.length() && !desktop.isEmpty(); i++){
+ //Remove any old symlinks first
+ QString filename = tmp[i].section("/",-1);
+ //qDebug() << "Check for symlink:" << filename;
+ if( QFileInfo(desktop+filename).isSymLink() ){ QFile::remove(desktop+filename); }
+ }
+ tmp = sysApps->newApps;
+ for(int i=0; i<tmp.length() && !desktop.isEmpty(); i++){
+ XDGDesktop *desk = sysApps->files.value(tmp[i]);
+ if(desk->isHidden || !desk->isValid(false) ){ continue; } //skip this one
+ //qDebug() << "New App: " << tmp[i] << desk.filePath << "Hidden:" << desk.isHidden;
+ //Create a new symlink for this file if one does not exist
+ QString filename = tmp[i].section("/",-1);
+ //qDebug() << "Check for symlink:" << filename;
+ if(!QFile::exists(desktop+filename) ){ QFile::link(tmp[i], desktop+filename); }
+ }
+ }
+ QList<XDGDesktop*> allfiles = sysApps->apps(false,false); //only valid, non-hidden apps
+ APPS = LXDG::sortDesktopCats(allfiles);
+ APPS.insert("All", LXDG::sortDesktopNames(allfiles));
+ lastHashUpdate = QDateTime::currentDateTime();
+ //Now fill the menu
+ //Add link to the file manager
+ //this->addAction( LXDG::findIcon("user-home", ""), tr("Browse Files"), this, SLOT(launchFileManager()) );
+ //--Look for the app store
+ XDGDesktop store(appstorelink);
+ if(store.isValid()){
+ this->addAction( LXDG::findIcon(store.icon, ""), tr("Manage Applications"), this, SLOT(launchStore()) );
+ }
+ //--Look for the control panel
+ XDGDesktop controlp(controlpanellink);
+ if(controlp.isValid()){
+ this->addAction( LXDG::findIcon(controlp.icon, ""), tr("Control Panel"), this, SLOT(launchControlPanel()) );
+ }
+ this->addSeparator();
+ //--Now create the sub-menus
+ QStringList cats = APPS.keys();
+ cats.sort(); //make sure they are alphabetical
+ for(int i=0; i<cats.length(); i++){
+ //Make sure they are translated and have the right icons
+ QString name, icon;
+ if(cats[i]=="All"){continue; } //skip this listing for the menu
+ else if(cats[i] == "Multimedia"){ name = tr("Multimedia"); icon = "applications-multimedia"; }
+ else if(cats[i] == "Development"){ name = tr("Development"); icon = "applications-development"; }
+ else if(cats[i] == "Education"){ name = tr("Education"); icon = "applications-education"; }
+ else if(cats[i] == "Game"){ name = tr("Games"); icon = "applications-games"; }
+ else if(cats[i] == "Graphics"){ name = tr("Graphics"); icon = "applications-graphics"; }
+ else if(cats[i] == "Network"){ name = tr("Network"); icon = "applications-internet"; }
+ else if(cats[i] == "Office"){ name = tr("Office"); icon = "applications-office"; }
+ else if(cats[i] == "Science"){ name = tr("Science"); icon = "applications-science"; }
+ else if(cats[i] == "Settings"){ name = tr("Settings"); icon = "preferences-system"; }
+ else if(cats[i] == "System"){ name = tr("System"); icon = "applications-system"; }
+ else if(cats[i] == "Utility"){ name = tr("Utility"); icon = "applications-utilities"; }
+ else if(cats[i] == "Wine"){ name = tr("Wine"); icon = "wine"; }
+ else{ name = tr("Unsorted"); icon = "applications-other"; }
+
+ QMenu *menu = new QMenu(name, this);
+ menu->setIcon(LXDG::findIcon(icon,""));
+ connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(launchApp(QAction*)) );
+ QList<XDGDesktop*> appL = APPS.value(cats[i]);
+ for( int a=0; a<appL.length(); a++){
+ if(appL[a]->actions.isEmpty()){
+ //Just a single entry point - no extra actions
+ QAction *act = new QAction(LXDG::findIcon(appL[a]->icon, ""), appL[a]->name, this);
+ act->setToolTip(appL[a]->comment);
+ act->setWhatsThis(appL[a]->filePath);
+ menu->addAction(act);
+ }else{
+ //This app has additional actions - make this a sub menu
+ // - first the main menu/action
+ QMenu *submenu = new QMenu(appL[a]->name, this);
+ submenu->setIcon( LXDG::findIcon(appL[a]->icon,"") );
+ //This is the normal behavior - not a special sub-action (although it needs to be at the top of the new menu)
+ QAction *act = new QAction(LXDG::findIcon(appL[a]->icon, ""), appL[a]->name, this);
+ act->setToolTip(appL[a]->comment);
+ act->setWhatsThis(appL[a]->filePath);
+ submenu->addAction(act);
+ //Now add entries for every sub-action listed
+ for(int sa=0; sa<appL[a]->actions.length(); sa++){
+ QAction *sact = new QAction(LXDG::findIcon(appL[a]->actions[sa].icon, appL[a]->icon), appL[a]->actions[sa].name, this);
+ sact->setToolTip(appL[a]->comment);
+ sact->setWhatsThis("-action \""+appL[a]->actions[sa].ID+"\" \""+appL[a]->filePath+"\"");
+ submenu->addAction(sact);
+ }
+ menu->addMenu(submenu);
+ }
+ }
+ this->addMenu(menu);
+ }
+ // watcher->addPaths(LXDG::systemApplicationDirs());
+ emit AppMenuUpdated();
+}
+
+//=================
+// PRIVATE SLOTS
+//=================
+void AppMenu::start(){
+ //Setup the watcher
+ connect(sysApps, SIGNAL(appsUpdated()), this, SLOT(watcherUpdate()) );
+ sysApps->updateList();
+ //Now fill the menu the first time
+ updateAppList();
+}
+
+void AppMenu::watcherUpdate(){
+ updateAppList(); //Update the menu listings
+}
+
+void AppMenu::launchStore(){
+ LSession::LaunchApplication("lumina-open \""+appstorelink+"\"");
+}
+
+void AppMenu::launchControlPanel(){
+ LSession::LaunchApplication("lumina-open \""+controlpanellink+"\"");
+}
+
+void AppMenu::launchFileManager(){
+ QString fm = "lumina-open \""+QDir::homePath()+"\"";
+ LSession::LaunchApplication(fm);
+}
+
+void AppMenu::launchApp(QAction *act){
+ QString appFile = act->whatsThis();
+ if(appFile.startsWith("-action")){
+ LSession::LaunchApplication("lumina-open "+appFile); //already has quotes put in place properly
+ }else{
+ LSession::LaunchApplication("lumina-open \""+appFile+"\"");
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.h b/src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.h
new file mode 100644
index 00000000..5baaab7a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/AppMenu.h
@@ -0,0 +1,58 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This menu auto-updates to keep the list of available applications
+// current at all times - and can launch them as necessary
+//===========================================
+#ifndef _LUMINA_DESKTOP_APP_MENU_H
+#define _LUMINA_DESKTOP_APP_MENU_H
+
+// Qt includes
+#include <QMenu>
+#include <QFileSystemWatcher>
+#include <QString>
+#include <QList>
+#include <QTimer>
+#include <QDateTime>
+#include <QHash>
+#include <QAction>
+#include <QSettings>
+//#include <QProcess>
+
+// libLumina includes
+#include <LuminaXDG.h>
+
+class AppMenu : public QMenu{
+ Q_OBJECT
+public:
+ AppMenu(QWidget *parent = 0);
+ ~AppMenu();
+
+ QHash<QString, QList<XDGDesktop*> > *currentAppHash();
+ QDateTime lastHashUpdate;
+
+private:
+ //QFileSystemWatcher *watcher;
+ QString appstorelink, controlpanellink;
+ QList<QMenu> MLIST;
+ XDGDesktopList *sysApps;
+ QHash<QString, QList<XDGDesktop*> > APPS;
+
+ void updateAppList(); //completely update the menu lists
+
+private slots:
+ void start(); //This is called in a new thread after initialization
+ void watcherUpdate();
+ void launchStore();
+ void launchControlPanel();
+ void launchFileManager();
+ void launchApp(QAction *act);
+
+signals:
+ void AppMenuUpdated();
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/JsonMenu.h b/src-qt5/core/lumina-desktop-unified/src-DE/JsonMenu.h
new file mode 100644
index 00000000..5a6b2237
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/JsonMenu.h
@@ -0,0 +1,79 @@
+//===========================================
+// Lumina Desktop source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This menu is used to automatically generate menu contents
+// based on the JSON output of an external script/utility
+//===========================================
+#ifndef _LUMINA_DESKTOP_JSON_MENU_H
+#define _LUMINA_DESKTOP_JSON_MENU_H
+
+#include <QMenu>
+#include <QString>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+
+#include <LUtils.h>
+#include <LuminaXDG.h>
+#include "LSession.h"
+
+class JsonMenu : public QMenu{
+ Q_OBJECT
+private:
+ QString exec;
+
+public:
+ JsonMenu(QString execpath, QWidget *parent = 0) : QMenu(parent){
+ exec = execpath;
+ connect(this, SIGNAL(aboutToShow()), this, SLOT(updateMenu()) );
+ connect(this, SIGNAL(triggered(QAction*)), this, SLOT(itemTriggered(QAction*)) );
+ }
+
+private slots:
+ void parseObject(QString label, QJsonObject obj){
+ if( label.isEmpty() || !obj.contains("type") ){ return; }
+ QString type = obj.value("type").toString();
+ if(type.toLower()=="item"){
+ QAction *act = this->addAction(label);
+ if(obj.contains("icon")){ act->setIcon( LXDG::findIcon(obj.value("icon").toString(),"") ); }
+ if(obj.contains("action")){ act->setWhatsThis( obj.value("action").toString() ); }
+ else{ act->setEnabled(false); } //not interactive
+ }else if(type.toLower()=="menu"){
+
+ }else if(type.toLower()=="jsonmenu"){
+ //This is a recursive JSON menu object
+ if(!obj.contains("exec")){ return; }
+ JsonMenu *menu = new JsonMenu(obj.value("exec").toString(), this);
+ menu->setTitle(label);
+ if(obj.contains("icon")){ menu->setIcon(LXDG::findIcon(obj.value("icon").toString(),"") ); }
+ this->addMenu(menu);
+ }
+ }
+
+ void updateMenu(){
+ this->clear();
+ QJsonDocument doc = QJsonDocument::fromJson( LUtils::getCmdOutput(exec).join(" ").toLocal8Bit() );
+ if(doc.isNull() || !doc.isObject()){
+ this->addAction( QString(tr("Error parsing script output: %1")).arg("\n"+exec) )->setEnabled(false);
+ }else{
+ QStringList keys = doc.object().keys();
+ for(int i=0; i<keys.length(); i++){
+ if(doc.object().value(keys[i]).isObject()){
+ parseObject(keys[i], doc.object().value(keys[i]).toObject());
+ }
+ }
+ }
+ }
+
+ void itemTriggered(QAction *act){
+ if(act->parent()!=this || act->whatsThis().isEmpty() ){ return; } //only handle direct child actions - needed for recursive nature of menu
+ QString cmd = act->whatsThis();
+ QString bin = cmd.section(" ",0,0);
+ if( !LUtils::isValidBinary(bin) ){ cmd.prepend("lumina-open "); }
+ LSession::handle()->LaunchApplication(cmd);
+ }
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.cpp
new file mode 100644
index 00000000..de7d086e
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.cpp
@@ -0,0 +1,553 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LDesktop.h"
+#include "LSession.h"
+
+#include <LuminaOS.h>
+#include <LuminaX11.h>
+#include "LWinInfo.h"
+#include "JsonMenu.h"
+
+#include <QScreen>
+
+#define DEBUG 0
+
+LDesktop::LDesktop(int deskNum, bool setdefault) : QObject(){
+ screenID = QApplication::screens().at(deskNum)->name();
+ DPREFIX = "desktop-"+screenID+"/";
+ //desktopnumber = deskNum;
+ //desktop = QApplication::desktop();
+ defaultdesktop = setdefault; //(desktop->screenGeometry(desktopnumber).x()==0);
+ //desktoplocked = true;
+ issyncing = bgupdating = false;
+ usewinmenu=false;
+
+ //Setup the internal variables
+ settings = new QSettings(QSettings::UserScope, "lumina-desktop","desktopsettings", this);
+ //qDebug() << " - Desktop Settings File:" << settings->fileName();
+ if(!QFile::exists(settings->fileName())){ settings->setValue(DPREFIX+"background/filelist",QStringList()<<"default"); settings->sync(); }
+ //bgWindow = 0;
+ bgDesktop = 0;
+ QTimer::singleShot(1,this, SLOT(InitDesktop()) );
+
+}
+
+LDesktop::~LDesktop(){
+ delete deskMenu;
+ delete winMenu;
+ //delete bgWindow;
+ delete workspacelabel;
+ delete wkspaceact;
+}
+
+int LDesktop::Screen(){
+ QList<QScreen*> scrns = QApplication::screens();
+ for(int i=0; i<scrns.length(); i++){
+ if(scrns[i]->name()==screenID){ return i; }
+ }
+ return -1;
+}
+
+void LDesktop::show(){
+ //if(bgWindow!=0){ bgWindow->show(); }
+ if(bgDesktop!=0){ bgDesktop->show(); }
+ for(int i=0; i<PANELS.length(); i++){ PANELS[i]->show(); }
+}
+
+void LDesktop::hide(){
+ //if(bgWindow!=0){ bgWindow->hide(); }
+ if(bgDesktop!=0){ bgDesktop->hide(); }
+ for(int i=0; i<PANELS.length(); i++){ PANELS[i]->hide(); }
+}
+
+void LDesktop::prepareToClose(){
+ //Get any panels ready to close
+ issyncing = true; //Stop handling any watcher events
+ for(int i=0; i<PANELS.length(); i++){ PANELS[i]->prepareToClose(); PANELS.takeAt(i)->deleteLater(); i--; }
+ //Now close down any desktop plugins
+ //desktoplocked = true; //make sure that plugin settings are preserved during removal
+ //Remove all the current containers
+ bgDesktop->cleanup();
+}
+
+WId LDesktop::backgroundID(){
+ if(bgDesktop!=0){ return bgDesktop->winId(); }
+ else{ return QX11Info::appRootWindow(); }
+}
+
+QRect LDesktop::availableScreenGeom(){
+ //Return a QRect containing the (global) screen area that is available (not under any panels)
+ if(bgDesktop!=0){
+ return globalWorkRect; //saved from previous calculations
+ }else{
+ return LSession::handle()->screenGeom( Screen() );
+ }
+}
+
+void LDesktop::UpdateGeometry(){
+ //First make sure there is something different about the geometry
+ //if(desktop->screenGeometry(Screen())==bgWindow->geometry()){ return; }
+ //Now update the screen
+ // NOTE: This functionality is highly event-driven based on X changes - so we need to keep things in order (no signals/slots)
+ //qDebug() << "Changing Desktop Geom:" << Screen();
+ //bgWindow->setGeometry(desktop->screenGeometry(Screen()));
+ /*for(int i=0; i<PANELS.length(); i++){
+ PANELS[i]->UpdatePanel(true); //geom only updates - do this before adjusting the background
+ }*/
+ //qDebug() << " - Update Desktop Plugin Area";
+ UpdateDesktopPluginArea();
+ //qDebug() << " - Done With Desktop Geom Updates";
+ QTimer::singleShot(0, this, SLOT(UpdatePanels()));
+}
+
+void LDesktop::SystemLock(){
+ QProcess::startDetached("xscreensaver-command -lock");
+}
+
+void LDesktop::SystemLogout(){
+ LSession::handle()->systemWindow();
+}
+
+void LDesktop::SystemTerminal(){
+ LSession::handle()->sessionSettings()->sync(); //make sure it is up to date
+ QString term = LXDG::findDefaultAppForMime("application/terminal"); //LSession::handle()->sessionSettings()->value("default-terminal","xterm").toString();
+ if(term.isEmpty() ||(!term.endsWith(".desktop") && !LUtils::isValidBinary(term)) ){ term = "xterm"; }
+ LSession::LaunchApplication("lumina-open \""+term+"\"");
+}
+
+void LDesktop::SystemFileManager(){
+ //Just open the home directory
+ QString fm = "lumina-open \""+QDir::homePath()+"\"";
+ LSession::LaunchApplication(fm);
+}
+
+void LDesktop::SystemApplication(QAction* act){
+ if(!act->whatsThis().isEmpty() && act->parent()==deskMenu){
+ LSession::LaunchApplication("lumina-open \""+act->whatsThis()+"\"");
+ }
+}
+
+void LDesktop::checkResolution(){
+ //Compare the current screen resolution with the last one used/saved and adjust config values *only*
+ //NOTE: This is only run the first time this desktop is created (before loading all the interface) - not on each desktop change
+ int oldWidth = settings->value(DPREFIX+"screen/lastWidth",-1).toInt();
+ int oldHeight = settings->value(DPREFIX+"screen/lastHeight",-1).toInt();
+ QRect scrn = LSession::handle()->screenGeom( Screen() );
+ if(scrn.isNull()){ return; }
+ issyncing = true;
+ settings->setValue(DPREFIX+"screen/lastWidth",scrn.width());
+ settings->setValue(DPREFIX+"screen/lastHeight",scrn.height());
+
+ if(oldWidth<1 || oldHeight<1 || scrn.width()<1 || scrn.height()<1){
+ //nothing to do - something invalid
+ }else if(scrn.width()==oldWidth && scrn.height()==oldHeight){
+ //nothing to do - same as before
+ }else{
+ //Calculate the scale factor between the old/new sizes in each dimension
+ // and forward that on to all the interface elements
+ double xscale = scrn.width()/((double) oldWidth);
+ double yscale = scrn.height()/((double) oldHeight);
+ if(DEBUG){
+ qDebug() << "Screen Resolution Changed:" << screenID;
+ qDebug() << " - Old:" << QString::number(oldWidth)+"x"+QString::number(oldHeight);
+ qDebug() << " - New:" << QString::number(scrn.width())+"x"+QString::number(scrn.height());
+ qDebug() << " - Scale Factors:" << xscale << yscale;
+ }
+ //Update any panels in the config file
+ for(int i=0; i<4; i++){
+ QString PPREFIX = "panel"+QString::number(Screen())+"."+QString::number(i)+"/";
+ int ht = settings->value(PPREFIX+"height",-1).toInt();
+ if(ht<1){ continue; } //no panel height defined
+ QString loc = settings->value(PPREFIX+"location","top").toString().toLower();
+ if(loc=="top" || loc=="bottom"){
+ settings->setValue(PPREFIX+"height", (int) ht*yscale); //vertical dimension
+ }else{
+ settings->setValue(PPREFIX+"height", (int) ht*xscale); //horizontal dimension
+ }
+ }
+ //Update any desktop plugins
+ QStringList plugs = settings->value(DPREFIX+"pluginlist").toStringList();
+ QFileInfoList files = LSession::handle()->DesktopFiles();
+ for(int i=0; i<files.length(); i++){
+ plugs << "applauncher::"+files[i].absoluteFilePath()+"---"+DPREFIX;
+ }
+ //QString pspath = QDir::homePath()+"/.lumina/desktop-plugins/%1.conf";
+ QSettings *DP = LSession::handle()->DesktopPluginSettings();
+ QStringList keys = DP->allKeys();
+ for(int i=0; i<plugs.length(); i++){
+ QStringList filter = keys.filter(plugs[i]);
+ for(int j=0; j<filter.length(); j++){
+ //Has existing settings - need to adjust it
+ if(filter[j].endsWith("location/height")){ DP->setValue( filter[j], qRound(DP->value(filter[j]).toInt()*yscale) ); }
+ if(filter[j].endsWith("location/width")){ DP->setValue( filter[j], qRound(DP->value(filter[j]).toInt()*xscale) ); }
+ if(filter[j].endsWith("location/x")){ DP->setValue( filter[j], qRound(DP->value(filter[j]).toInt()*xscale) ); }
+ if(filter[j].endsWith("location/y")){ DP->setValue( filter[j], qRound(DP->value(filter[j]).toInt()*yscale) ); }
+ if(filter[j].endsWith("IconSize")){ DP->setValue( filter[j], qRound(DP->value(filter[j]).toInt()*yscale) ); }
+ if(filter[j].endsWith("iconsize")){ DP->setValue( filter[j], qRound(DP->value(filter[j]).toInt()*yscale) ); }
+ }
+ }
+ DP->sync(); //make sure it gets saved to disk right away
+
+ }
+ issyncing = false;
+}
+
+// =====================
+// PRIVATE SLOTS
+// =====================
+void LDesktop::InitDesktop(){
+ //This is called *once* during the main initialization routines
+ checkResolution(); //Adjust the desktop config file first (if necessary)
+ if(DEBUG){ qDebug() << "Init Desktop:" << Screen(); }
+ //connect(desktop, SIGNAL(resized(int)), this, SLOT(UpdateGeometry(int)));
+ if(DEBUG){ qDebug() << "Desktop #"<<Screen()<<" -> "<< LSession::desktop()->screenGeometry(Screen()) << LSession::handle()->screenGeom(Screen()); }
+ deskMenu = new QMenu(0);
+ connect(deskMenu, SIGNAL(triggered(QAction*)), this, SLOT(SystemApplication(QAction*)) );
+ winMenu = new QMenu(0);
+ winMenu->setTitle(tr("Window List"));
+ winMenu->setIcon( LXDG::findIcon("preferences-system-windows","") );
+ connect(winMenu, SIGNAL(triggered(QAction*)), this, SLOT(winClicked(QAction*)) );
+ workspacelabel = new QLabel(0);
+ workspacelabel->setAlignment(Qt::AlignCenter);
+ wkspaceact = new QWidgetAction(0);
+ wkspaceact->setDefaultWidget(workspacelabel);
+ bgtimer = new QTimer(this);
+ bgtimer->setSingleShot(true);
+ connect(bgtimer, SIGNAL(timeout()), this, SLOT(UpdateBackground()) );
+
+ connect(QApplication::instance(), SIGNAL(DesktopConfigChanged()), this, SLOT(SettingsChanged()) );
+ connect(QApplication::instance(), SIGNAL(DesktopFilesChanged()), this, SLOT(UpdateDesktop()) );
+ connect(QApplication::instance(), SIGNAL(LocaleChanged()), this, SLOT(LocaleChanged()) );
+ connect(QApplication::instance(), SIGNAL(WorkspaceChanged()), this, SLOT(UpdateBackground()) );
+ //if(DEBUG){ qDebug() << "Create bgWindow"; }
+ /*bgWindow = new QWidget(); //LDesktopBackground();
+ bgWindow->setObjectName("bgWindow");
+ bgWindow->setContextMenuPolicy(Qt::CustomContextMenu);
+ bgWindow->setFocusPolicy(Qt::StrongFocus);
+ bgWindow->setWindowFlags(Qt::WindowStaysOnBottomHint | Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
+ LSession::handle()->XCB->SetAsDesktop(bgWindow->winId());
+ bgWindow->setGeometry(LSession::handle()->screenGeom(Screen()));
+ bgWindow->setWindowOpacity(0.0);
+ connect(bgWindow, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowMenu()) );*/
+ if(DEBUG){ qDebug() << "Create bgDesktop"; }
+ bgDesktop = new LDesktopPluginSpace();
+ int grid = settings->value(DPREFIX+"GridSize",-1).toInt();
+ if(grid<0 && LSession::desktop()->screenGeometry(Screen()).height() > 2000){ grid = 200; }
+ else if(grid<0){ grid = 100; }
+ bgDesktop->SetIconSize( grid );
+ bgDesktop->setContextMenuPolicy(Qt::CustomContextMenu);
+ //LSession::handle()->XCB->SetAsDesktop(bgDesktop->winId());
+ connect(bgDesktop, SIGNAL(PluginRemovedByUser(QString)), this, SLOT(RemoveDeskPlugin(QString)) );
+ connect(bgDesktop, SIGNAL(IncreaseIcons()), this, SLOT(IncreaseDesktopPluginIcons()) );
+ connect(bgDesktop, SIGNAL(DecreaseIcons()), this, SLOT(DecreaseDesktopPluginIcons()) );
+ connect(bgDesktop, SIGNAL(HideDesktopMenu()), deskMenu, SLOT(hide()));
+ connect(bgDesktop, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowMenu()) );
+ if(DEBUG){ qDebug() << " - Desktop Init Done:" << screenID; }
+ //Start the update processes
+ QTimer::singleShot(10,this, SLOT(UpdateMenu()) );
+ QTimer::singleShot(0,this, SLOT(UpdateBackground()) );
+ QTimer::singleShot(1,this, SLOT(UpdateDesktop()) );
+ QTimer::singleShot(2,this, SLOT(UpdatePanels()) );
+}
+
+void LDesktop::SettingsChanged(){
+ if(issyncing){ return; } //don't refresh for internal modifications to the
+ issyncing = true;
+ qDebug() << "Found Settings Change:" << screenID;
+ settings->sync(); //make sure to sync with external settings changes
+ UpdateBackground();
+ UpdateDesktop();
+ UpdatePanels();
+ UpdateMenu();
+ issyncing = false;
+ QTimer::singleShot(100, this, SLOT(UnlockSettings()) ); //give it a few moments to settle before performing another sync
+}
+
+void LDesktop::LocaleChanged(){
+ //Update any elements which require a re-translation
+ UpdateMenu(false); //do the full menu refresh
+}
+
+void LDesktop::UpdateMenu(bool fast){
+ if(DEBUG){ qDebug() << " - Update Menu:" << screenID; }
+ //Put a label at the top
+ int num = LSession::handle()->XCB->CurrentWorkspace(); //LX11::GetCurrentDesktop();
+ if(DEBUG){ qDebug() << "Found workspace number:" << num; }
+ if(num < 0){ workspacelabel->setText( "<b>"+tr("Lumina Desktop")+"</b>"); }
+ else{ workspacelabel->setText( "<b>"+QString(tr("Workspace %1")).arg(QString::number(num+1))+"</b>"); }
+ if(fast && usewinmenu){ UpdateWinMenu(); }
+ if(fast){ return; } //already done
+ deskMenu->clear(); //clear it for refresh
+ deskMenu->addAction(wkspaceact);
+ deskMenu->addSeparator();
+ //Now load the user's menu setup and fill the menu
+ QStringList items = settings->value("menu/itemlist", QStringList()<< "terminal" << "filemanager" <<"applications" << "line" << "settings" ).toStringList();
+ usewinmenu=false;
+ for(int i=0; i<items.length(); i++){
+ if(items[i]=="terminal"){ deskMenu->addAction(LXDG::findIcon("utilities-terminal",""), tr("Terminal"), this, SLOT(SystemTerminal()) ); }
+ else if(items[i]=="lockdesktop"){ deskMenu->addAction(LXDG::findIcon("system-lock-screen",""), tr("Lock Session"), this, SLOT(SystemLock()) ); }
+ else if(items[i]=="filemanager"){ deskMenu->addAction( LXDG::findIcon("user-home",""), tr("Browse Files"), this, SLOT(SystemFileManager()) ); }
+ else if(items[i]=="applications"){ deskMenu->addMenu( LSession::handle()->applicationMenu() ); }
+ else if(items[i]=="line"){ deskMenu->addSeparator(); }
+ else if(items[i]=="settings"){ deskMenu->addMenu( LSession::handle()->settingsMenu() ); }
+ else if(items[i]=="windowlist"){ deskMenu->addMenu( winMenu); usewinmenu=true;}
+ else if(items[i].startsWith("app::::") && items[i].endsWith(".desktop")){
+ //Custom *.desktop application
+ QString file = items[i].section("::::",1,1).simplified();
+ XDGDesktop xdgf(file);// = LXDG::loadDesktopFile(file, ok);
+ if(xdgf.type!=XDGDesktop::BAD){
+ deskMenu->addAction( LXDG::findIcon(xdgf.icon,""), xdgf.name)->setWhatsThis(file);
+ }else{
+ qDebug() << "Could not load application file:" << file;
+ }
+ }else if(items[i].startsWith("jsonmenu::::")){
+ //Custom JSON menu system (populated on demand via external scripts/tools
+ QStringList info = items[i].split("::::"); //FORMAT:[ "jsonmenu",exec,name, icon(optional)]
+ if(info.length()>=3){
+ qDebug() << "Custom JSON Menu Loaded:" << info;
+ JsonMenu *tmp = new JsonMenu(info[1], deskMenu);
+ tmp->setTitle(info[2]);
+ connect(tmp, SIGNAL(triggered(QAction*)), this, SLOT(SystemApplication(QAction*)) );
+ if(info.length()>=4){ tmp->setIcon( LXDG::findIcon(info[3],"") ); }
+ deskMenu->addMenu(tmp);
+ }
+ }
+ }
+ //Now add the system quit options
+ deskMenu->addSeparator();
+ deskMenu->addAction(LXDG::findIcon("system-log-out",""), tr("Leave"), this, SLOT(SystemLogout()) );
+}
+
+void LDesktop::UpdateWinMenu(){
+ winMenu->clear();
+ //Get the current list of windows
+ QList<WId> wins = LSession::handle()->XCB->WindowList();
+ //Now add them to the menu
+ for(int i=0; i<wins.length(); i++){
+ LWinInfo info(wins[i]);
+ bool junk;
+ QAction *act = winMenu->addAction( info.icon(junk), info.text() );
+ act->setData( QString::number(wins[i]) );
+ }
+}
+
+void LDesktop::winClicked(QAction* act){
+ LSession::handle()->XCB->ActivateWindow( act->data().toString().toULong() );
+}
+
+void LDesktop::UpdateDesktop(){
+ if(DEBUG){ qDebug() << " - Update Desktop Plugins for screen:" << screenID; }
+ QStringList plugins = settings->value(DPREFIX+"pluginlist", QStringList()).toStringList();
+ if(defaultdesktop && plugins.isEmpty()){
+ //plugins << "sample" << "sample" << "sample";
+ }
+ bool changed=false; //in case the plugin list needs to be changed
+ //First make sure all the plugin names are unique
+ for(int i=0; i<plugins.length(); i++){
+ if(!plugins[i].contains("---") ){
+ int num=1;
+ while( plugins.contains(plugins[i]+"---"+QString::number(Screen())+"."+QString::number(num)) ){
+ num++;
+ }
+ plugins[i] = plugins[i]+"---"+screenID+"."+QString::number(num);
+ //plugins[i] = plugins[i]+"---"+QString::number(Screen())+"."+QString::number(num);
+ changed=true;
+ }
+ }
+ if(changed){
+ //save the modified plugin list to file (so per-plugin settings are preserved)
+ issyncing=true; //don't let the change cause a refresh
+ settings->setValue(DPREFIX+"pluginlist", plugins);
+ settings->sync();
+ QTimer::singleShot(200, this, SLOT(UnlockSettings()) );
+ }
+ //If generating desktop file launchers, add those in
+ QStringList filelist;
+ if(settings->value(DPREFIX+"generateDesktopIcons",false).toBool()){
+ QFileInfoList files = LSession::handle()->DesktopFiles();
+ for(int i=0; i<files.length(); i++){
+ filelist << files[i].absoluteFilePath();
+ }
+ }
+ UpdateDesktopPluginArea();
+ bgDesktop->LoadItems(plugins, filelist);
+}
+
+void LDesktop::RemoveDeskPlugin(QString ID){
+ //This is called after a plugin is manually removed by the user
+ // just need to ensure that the plugin is also removed from the settings file
+ QStringList plugs = settings->value(DPREFIX+"pluginlist", QStringList()).toStringList();
+ if(plugs.contains(ID)){
+ plugs.removeAll(ID);
+ issyncing=true; //don't let the change cause a refresh
+ settings->setValue(DPREFIX+"pluginlist", plugs);
+ settings->sync();
+ QTimer::singleShot(200, this, SLOT(UnlockSettings()) );
+ }
+}
+
+void LDesktop::IncreaseDesktopPluginIcons(){
+ int cur = settings->value(DPREFIX+"GridSize",-1).toInt();
+ if(cur<0 &&LSession::desktop()->screenGeometry(Screen()).height() > 2000){ cur = 200; }
+ else if(cur<0){ cur = 100; }
+ cur+=16;
+ issyncing=true; //don't let the change cause a refresh
+ settings->setValue(DPREFIX+"GridSize",cur);
+ settings->sync();
+ QTimer::singleShot(200, this, SLOT(UnlockSettings()) );
+ bgDesktop->SetIconSize(cur);
+}
+
+void LDesktop::DecreaseDesktopPluginIcons(){
+ int cur = settings->value(DPREFIX+"GridSize",-1).toInt();
+ if(cur<0 &&LSession::desktop()->screenGeometry(Screen()).height() > 2000){ cur = 200; }
+ else if(cur<0){ cur = 100; }
+ if(cur<32){ return; } //cannot get smaller than 16x16
+ cur-=16;
+ issyncing=true; //don't let the change cause a refresh
+ settings->setValue(DPREFIX+"GridSize",cur);
+ settings->sync();
+ QTimer::singleShot(200, this, SLOT(UnlockSettings()) );
+ bgDesktop->SetIconSize(cur);
+}
+
+void LDesktop::UpdatePanels(){
+ if(DEBUG){ qDebug() << " - Update Panels For Screen:" << Screen(); }
+ int panels = settings->value(DPREFIX+"panels", -1).toInt();
+ //if(panels==-1 && defaultdesktop){ panels=1; } //need at least 1 panel on the primary desktop
+ //Remove all extra panels
+ for(int i=0; i<PANELS.length(); i++){
+ if(panels <= PANELS[i]->number()){
+ if(DEBUG){ qDebug() << " -- Remove Panel:" << PANELS[i]->number(); }
+ PANELS[i]->prepareToClose();
+ PANELS.takeAt(i)->deleteLater();
+ i--;
+ }
+ }
+ for(int i=0; i<panels; i++){
+ //Check for a panel with this number
+ bool found = false;
+ for(int p=0; p<PANELS.length() && !found; p++){
+ if(PANELS[p]->number() == i){
+ found = true;
+ if(DEBUG){ qDebug() << " -- Update panel "<< i; }
+ //panel already exists - just update it
+ QTimer::singleShot(0, PANELS[p], SLOT(UpdatePanel()) );
+ }
+ }
+ if(!found){
+ if(DEBUG){ qDebug() << " -- Create panel "<< i; }
+ //New panel
+ LPanel *pan = new LPanel(settings, screenID, i, bgDesktop);
+ PANELS << pan;
+ pan->show();
+ }
+ }
+ //Give it a 1/2 second before ensuring that the visible desktop area is correct
+ QTimer::singleShot(500, this, SLOT(UpdateDesktopPluginArea()) );
+}
+
+void LDesktop::UpdateDesktopPluginArea(){
+ QRegion visReg(LSession::desktop()->screenGeometry(Screen()) ); //visible region (not hidden behind a panel)
+ QRect rawRect = visReg.boundingRect(); //initial value (screen size)
+ //qDebug() << "Update Desktop Plugin Area:" << bgWindow->geometry();
+ for(int i=0; i<PANELS.length(); i++){
+ QRegion shifted = visReg;
+ QString loc = settings->value(PANELS[i]->prefix()+"location","top").toString().toLower();
+ int vis = PANELS[i]->visibleWidth();
+ if(loc=="top"){
+ if(!shifted.contains(QRect(rawRect.x(), rawRect.y(), rawRect.width(), vis))){ continue; }
+ shifted.translate(0, (rawRect.top()+vis)-shifted.boundingRect().top() );
+ }else if(loc=="bottom"){
+ if(!shifted.contains(QRect(rawRect.x(), rawRect.bottom()-vis, rawRect.width(), vis))){ continue; }
+ shifted.translate(0, (rawRect.bottom()-vis)-shifted.boundingRect().bottom());
+ }else if(loc=="left"){
+ if( !shifted.contains(QRect(rawRect.x(), rawRect.y(), vis,rawRect.height())) ){ continue; }
+ shifted.translate((rawRect.left()+vis)-shifted.boundingRect().left() ,0);
+ }else{ //right
+ if(!shifted.contains(QRect(rawRect.right()-vis, rawRect.y(), vis,rawRect.height())) ){ continue; }
+ shifted.translate((rawRect.right()-vis)-shifted.boundingRect().right(),0);
+ }
+ visReg = visReg.intersected( shifted );
+ }
+ //Now make sure the desktop plugin area is only the visible area
+ QRect rec = visReg.boundingRect();
+ //qDebug() << " - DPArea: Panel-Adjusted rectangle:" << rec;
+ //LSession::handle()->XCB->SetScreenWorkArea((unsigned int) Screen(), rec);
+ //Now remove the X offset to place it on the current screen (needs widget-coords, not global)
+ globalWorkRect = rec; //save this for later
+ rec.moveTopLeft( QPoint( rec.x()-LSession::desktop()->screenGeometry(Screen()).x() , rec.y()-LSession::desktop()->screenGeometry(Screen()).y() ) );
+ //qDebug() << "DPlug Area:" << rec << bgDesktop->geometry() << LSession::handle()->desktop()->availableGeometry(bgDesktop);
+ if(rec.size().isNull() || rec == bgDesktop->geometry()){return; } //nothing changed
+ bgDesktop->setGeometry( LSession::desktop()->screenGeometry(Screen()));
+ bgDesktop->setDesktopArea( rec );
+ bgDesktop->UpdateGeom(); //just in case the plugin space itself needs to do anything
+ QTimer::singleShot(10, this, SLOT(UpdateBackground()) );
+ //Re-paint the panels (just in case a plugin was underneath it and the panel is transparent)
+ //for(int i=0; i<PANELS.length(); i++){ PANELS[i]->update(); }
+ //Make sure to re-disable any WM control flags
+ LSession::handle()->XCB->SetDisableWMActions(bgDesktop->winId());
+}
+
+void LDesktop::UpdateBackground(){
+ //Get the current Background
+ if(bgupdating || bgDesktop==0){ return; } //prevent multiple calls to this at the same time
+ bgupdating = true;
+ if(DEBUG){ qDebug() << " - Update Desktop Background for screen:" << Screen(); }
+ //Get the list of background(s) to show
+ QStringList bgL = settings->value(DPREFIX+"background/filelist-workspace-"+QString::number( LSession::handle()->XCB->CurrentWorkspace()), QStringList()).toStringList();
+ if(bgL.isEmpty()){ bgL = settings->value(DPREFIX+"background/filelist", QStringList()).toStringList(); }
+
+ //qDebug() << " - List:" << bgL << CBG;
+ //Remove any invalid files
+ for(int i=0; i<bgL.length(); i++){
+ if( (!QFile::exists(bgL[i]) && bgL[i]!="default" && !bgL[i].startsWith("rgb(") ) || bgL[i].isEmpty()){ bgL.removeAt(i); i--; }
+ }
+ if(bgL.isEmpty()){ bgL << "default"; } //always fall back on the default
+ //Determine if the background needs to be changed
+ //qDebug() << "BG List:" << bgL << oldBGL << CBG << bgtimer->isActive();
+ if(bgL==oldBGL && !CBG.isEmpty() && bgtimer->isActive()){
+ //No background change scheduled - just update the widget
+ bgDesktop->update();
+ bgupdating=false;
+ return;
+ }
+ oldBGL = bgL; //save this for later
+ //Determine which background to use next
+ int index ( qrand() % bgL.length() );
+ if(index== bgL.indexOf(CBG)){ //if the current wallpaper was selected by the randomization again
+ //Go to the next in the list
+ if(index < 0 || index >= bgL.length()-1){ index = 0; } //if invalid or last item in the list - go to first
+ else{ index++; } //go to next
+ }
+ QString bgFile = bgL[index];
+ //Save this file as the current background
+ CBG = bgFile;
+ //qDebug() << " - Set Background to:" << CBG << index << bgL;
+ if( (bgFile.toLower()=="default")){ bgFile = LOS::LuminaShare()+"desktop-background.jpg"; }
+ //Now set this file as the current background
+ QString format = settings->value(DPREFIX+"background/format","stretch").toString();
+ //bgWindow->setBackground(bgFile, format);
+ QPixmap backPix = LDesktopBackground::setBackground(bgFile, format, LSession::handle()->screenGeom(Screen()));
+ bgDesktop->setBackground(backPix);
+ //Now reset the timer for the next change (if appropriate)
+ if(bgtimer->isActive()){ bgtimer->stop(); }
+ if(bgL.length() > 1){
+ //get the length of the timer (in minutes)
+ int min = settings->value(DPREFIX+"background/minutesToChange",5).toInt();
+ //restart the internal timer
+ if(min > 0){
+ bgtimer->start(min*60000); //convert from minutes to milliseconds
+ }
+ }
+ //Now update the panel backgrounds
+ for(int i=0; i<PANELS.length(); i++){
+ PANELS[i]->update();
+ PANELS[i]->show();
+ }
+ bgupdating=false;
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.h b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.h
new file mode 100644
index 00000000..b6034c18
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktop.h
@@ -0,0 +1,107 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_LDESKTOP_H
+#define _LUMINA_DESKTOP_LDESKTOP_H
+
+#include <QCoreApplication>
+
+
+#include <QSettings>
+#include <QFile>
+#include <QList>
+#include <QDebug>
+#include <QTimer>
+#include <QFileSystemWatcher>
+#include <QLabel>
+#include <QWidgetAction>
+#include <QMdiArea>
+#include <QMdiSubWindow>
+#include <QRegion>
+
+
+#include <LuminaXDG.h>
+
+#include "LPanel.h"
+//#include "Globals.h"
+#include "AppMenu.h"
+#include "LDesktopPluginSpace.h"
+#include "desktop-plugins/LDPlugin.h"
+//#include "desktop-plugins/NewDP.h"
+#include "LDesktopBackground.h"
+
+class LDesktop : public QObject{
+ Q_OBJECT
+public:
+ LDesktop(int deskNum=0, bool setdefault = false);
+ ~LDesktop();
+
+ int Screen(); //return the screen number this object is managing
+ void show();
+ void hide();
+ void prepareToClose();
+
+ WId backgroundID();
+ QRect availableScreenGeom();
+
+ void UpdateGeometry();
+
+public slots:
+ void SystemLock();
+ void SystemLogout();
+ void SystemTerminal();
+ void SystemFileManager();
+ void SystemApplication(QAction*);
+
+ void checkResolution();
+
+private:
+ QSettings *settings;
+ QTimer *bgtimer;
+ //QDesktopWidget *desktop;
+ QString DPREFIX, screenID;
+ //int desktopnumber;
+ QRegion availDPArea;
+ bool defaultdesktop, issyncing, usewinmenu, bgupdating;
+ QStringList oldBGL;
+ QList<LPanel*> PANELS;
+ LDesktopPluginSpace *bgDesktop; //desktop plugin area
+ //QWidget *bgWindow; //full screen background
+ QMenu *deskMenu, *winMenu;
+ QLabel *workspacelabel;
+ QWidgetAction *wkspaceact;
+ QList<LDPlugin*> PLUGINS;
+ QString CBG; //current background
+ QRect globalWorkRect;
+
+private slots:
+ void InitDesktop();
+ void SettingsChanged();
+ void UnlockSettings(){ issyncing=false; }
+ void LocaleChanged();
+
+ //Menu functions
+ void UpdateMenu(bool fast = false);
+ void ShowMenu(){
+ UpdateMenu(true); //run the fast version
+ deskMenu->popup(QCursor::pos()); //}
+ }
+ void UpdateWinMenu();
+ void winClicked(QAction*);
+
+ //Desktop plugin system functions
+ void UpdateDesktop();
+ void RemoveDeskPlugin(QString);
+ void IncreaseDesktopPluginIcons();
+ void DecreaseDesktopPluginIcons();
+
+ void UpdatePanels();
+
+ void UpdateDesktopPluginArea(); //make sure the area is not underneath any panels
+
+ void UpdateBackground();
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.cpp
new file mode 100644
index 00000000..6b458c24
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.cpp
@@ -0,0 +1,90 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Henry Hu
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LDesktopBackground.h"
+
+#include <QPainter>
+#include <QPaintEvent>
+#include <QDebug>
+
+#include "LSession.h"
+
+void LDesktopBackground::paintEvent(QPaintEvent *ev) {
+ //return; //do nothing - always invisible
+ if (bgPixmap != NULL) {
+ //qDebug() << "Wallpaper paint Event:" << ev->rect();
+ //QPainter painter(this);
+ //painter.setBrush(*bgPixmap);
+ //painter.drawRect(ev->rect().adjusted(-1,-1,2,2));
+ }else{
+ QWidget::paintEvent(ev);
+ }
+}
+
+QPixmap LDesktopBackground::setBackground(const QString& bgFile, const QString& format, QRect geom) {
+ //if (bgPixmap != NULL) delete bgPixmap;
+ QPixmap bgPixmap(geom.size());// = new QPixmap(size());
+
+ if (bgFile.startsWith("rgb(")) {
+ QStringList colors = bgFile.section(")",0,0).section("(",1,1).split(",");
+ QColor color = QColor(colors[0].toInt(), colors[1].toInt(), colors[2].toInt());
+ bgPixmap.fill(color);
+ } else {
+ bgPixmap.fill(Qt::black);
+
+ // Load the background file and scale
+ QPixmap bgImage(bgFile);
+ if (format == "stretch" || format == "full" || format == "fit") {
+ Qt::AspectRatioMode mode;
+ if (format == "stretch") {
+ mode = Qt::IgnoreAspectRatio;
+ } else if (format == "full") {
+ mode = Qt::KeepAspectRatioByExpanding;
+ } else {
+ mode = Qt::KeepAspectRatio;
+ }
+ if(bgImage.height() != geom.height() && bgImage.width() != geom.width() ){ bgImage = bgImage.scaled(geom.size(), mode); }
+ //bgImage = bgImage.scaled(size(), mode);
+ }
+
+ // Calculate the offset
+ int dx = 0, dy = 0;
+ int drawWidth = bgImage.width(), drawHeight = bgImage.height();
+ if (format == "fit" || format == "center" || format == "full") {
+ dx = (geom.width() - bgImage.width()) / 2;
+ dy = (geom.height() - bgImage.height()) / 2;
+ } else if (format == "tile") {
+ drawWidth = geom.width();
+ drawHeight = geom.height();
+ } else {
+ if (format.endsWith("right")) {
+ dx = geom.width() - bgImage.width();
+ }
+ if (format.startsWith("bottom")) {
+ dy = geom.height() - bgImage.height();
+ }
+ }
+
+ // Draw the background image
+ QPainter painter(&bgPixmap);
+ painter.setBrush(bgImage);
+ painter.setBrushOrigin(dx, dy);
+ painter.drawRect(dx, dy, drawWidth, drawHeight);
+ }
+ //this->repaint(); //make sure the entire thing gets repainted right away
+ //LSession::handle()->XCB->paintRoot(geom, &bgPixmap);
+ return bgPixmap;
+ //show();
+}
+
+LDesktopBackground::LDesktopBackground() : QWidget() {
+ bgPixmap = NULL;
+ this->setWindowOpacity(0);
+}
+
+LDesktopBackground::~LDesktopBackground() {
+ if (bgPixmap != NULL) delete bgPixmap;
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.h b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.h
new file mode 100644
index 00000000..a2e46748
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopBackground.h
@@ -0,0 +1,27 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Henry Hu
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_LDESKTOPBACKGROUND_H_
+#define _LUMINA_DESKTOP_LDESKTOPBACKGROUND_H_
+
+#include <QString>
+#include <QWidget>
+#include <QPixmap>
+
+class LDesktopBackground: public QWidget {
+ Q_OBJECT
+public:
+ LDesktopBackground();
+ virtual ~LDesktopBackground();
+
+ virtual void paintEvent(QPaintEvent*);
+ static QPixmap setBackground(const QString&, const QString&, QRect geom);
+
+private:
+ QPixmap *bgPixmap;
+};
+
+#endif // _LUMINA_DESKTOP_LDESKTOPBACKGROUND_H_
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.cpp
new file mode 100644
index 00000000..18126dfa
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.cpp
@@ -0,0 +1,333 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LDesktopPluginSpace.h"
+#include "LSession.h"
+#include "desktop-plugins/NewDP.h"
+
+#include <LuminaXDG.h>
+#include <QDesktopWidget>
+
+#define DEBUG 0
+
+// ===================
+// PUBLIC
+// ===================
+LDesktopPluginSpace::LDesktopPluginSpace() : QWidget(){
+ this->setObjectName("LuminaDesktopPluginSpace");
+ this->setAttribute(Qt::WA_TranslucentBackground);
+ //this->setAttribute(Qt::WA_NoSystemBackground);
+ this->setAutoFillBackground(false);
+ this->setStyleSheet("QWidget#LuminaDesktopPluginSpace{ border: none; background: transparent; }");
+ this->setWindowFlags(Qt::WindowStaysOnBottomHint | Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
+ this->setAcceptDrops(true);
+ this->setContextMenuPolicy(Qt::NoContextMenu);
+ this->setMouseTracking(true);
+ TopToBottom = true;
+ GRIDSIZE = 100.0; //default value if not set
+ plugsettings = LSession::handle()->DesktopPluginSettings();
+ LSession::handle()->XCB->SetAsDesktop(this->winId());
+ //this->setWindowOpacity(0.0);
+}
+
+LDesktopPluginSpace::~LDesktopPluginSpace(){
+
+}
+
+void LDesktopPluginSpace::LoadItems(QStringList plugs, QStringList files){
+ if(DEBUG){ qDebug() << "Loading Desktop Items:" << plugs << files << "Area:" << this->size() << GRIDSIZE; }
+ bool changes = false;
+ if(plugs != plugins){ plugins = plugs; changes = true; }
+ if(files != deskitems){ deskitems = files; changes = true; }
+ if(changes){ QTimer::singleShot(0,this, SLOT(reloadPlugins())); }
+ this->show();
+}
+
+void LDesktopPluginSpace::SetIconSize(int size){
+ if(DEBUG){ qDebug() << "Set Desktop Icon Size:" << size; }
+ //QSize newsize = calculateItemSize(size);
+ int oldsize = GRIDSIZE;
+ GRIDSIZE = size; //turn the int into a float;
+ //itemSize = QSize(1,1); //save this for all the later icons which are generated (grid size)
+ UpdateGeom(oldsize);
+ //Now re-set the item icon size
+ //reloadPlugins(true);
+}
+
+void LDesktopPluginSpace::cleanup(){
+ //Perform any final cleanup actions here
+ for(int i=0; i<ITEMS.length(); i++){
+ ITEMS.takeAt(i)->deleteLater();
+ i--;
+ }
+ plugins.clear();
+ deskitems.clear();
+ this->hide();
+}
+
+void LDesktopPluginSpace::setBackground(QPixmap pix){
+ wallpaper = pix;
+ this->repaint();
+}
+
+void LDesktopPluginSpace::setDesktopArea(QRect area){
+ desktopRect = area;
+}
+
+// ===================
+// PUBLIC SLOTS
+// ===================
+void LDesktopPluginSpace::UpdateGeom(int oldgrid){
+ if(DEBUG){ qDebug() << "Updated Desktop Geom:" << desktopRect.size() << GRIDSIZE << desktopRect.size()/GRIDSIZE; }
+ //Go through and check the locations/sizes of all items (particularly the ones on the bottom/right edges)
+ //bool reload = false;
+ for(int i=0; i<ITEMS.length(); i++){
+ QRect grid = geomToGrid(ITEMS[i]->geometry(), oldgrid);
+ if(DEBUG){ qDebug() << " - Check Plugin:" << ITEMS[i]->whatsThis() << grid; }
+ if( !ValidGrid(grid) ){
+ //This plugin is too far out of the screen - find new location for it
+ if(DEBUG){ qDebug() << " -- Out of bounds - Find a new spot"; }
+ grid = findOpenSpot(grid, ITEMS[i]->whatsThis(), true); //Reverse lookup spot
+ }
+ if(!ValidGrid(grid)){
+ qDebug() << "No Place for plugin:" << ITEMS[i]->whatsThis();
+ qDebug() << " - Removing it for now...";
+ ITEMS.takeAt(i)->deleteLater();
+ i--;
+ }else{
+ //NOTE: We are not doing the ValidGeometry() checks because we are only resizing existing plugin with pre-set & valid grid positions
+ grid = gridToGeom(grid); //convert to pixels before saving/sizing
+ MovePlugin(ITEMS[i], grid);
+ /*ITEMS[i]->setGeometry( grid );
+ ITEMS[i]->setFixedSize(grid.size());
+ ITEMS[i]->savePluginGeometry(grid);*/
+ }
+ }
+ //if(reload){ QTimer::singleShot(0,this, SLOT(reloadPlugins())); }
+}
+
+// ===================
+// PRIVATE
+// ===================
+void LDesktopPluginSpace::addDesktopItem(QString filepath){
+ addDesktopPlugin("applauncher::"+filepath+"---dlink"+QString::number(LSession::handle()->desktop()->screenNumber(this)) );
+}
+
+void LDesktopPluginSpace::addDesktopPlugin(QString plugID){
+ //This is used for generic plugins (QWidget-based)
+ if(DEBUG){ qDebug() << "Adding Desktop Plugin:" << plugID; }
+ LDPlugin *plug = NewDP::createPlugin(plugID, this);
+ if(plug==0){ return; } //invalid plugin
+ //plug->setAttribute(Qt::WA_TranslucentBackground);
+ plug->setWhatsThis(plugID);
+ //Now get the geometry for the plugin
+ QRect geom = plug->loadPluginGeometry(); //in pixel coords
+ if(!geom.isNull()){ geom = geomToGrid(geom); } //convert to grid coordinates
+ if(geom.isNull()){
+ //No previous location - need to calculate initial geom
+ QSize sz = plug->defaultPluginSize(); //in grid coordinates
+ geom.setSize(sz);
+ //if an applauncher - add from top-left, otherwise add in from bottom-right
+ if(plugID.startsWith("applauncher")){ geom = findOpenSpot(geom.width(), geom.height() ); }
+ else{ geom = findOpenSpot(geom.width(), geom.height(), RoundUp(this->height()/GRIDSIZE), RoundUp(this->width()/GRIDSIZE), true); }
+ }else if(!ValidGeometry(plugID, gridToGeom(geom)) ){
+ //Find a new location for the plugin (saved location is invalid)
+ geom = findOpenSpot(geom.width(), geom.height(), geom.y(), geom.x(), false); //try to get it within the same general area first
+ }
+ if(geom.x() < 0 || geom.y() < 0){
+ qDebug() << "No available space for desktop plugin:" << plugID << " - IGNORING";
+ delete plug;
+ }else{
+ if(DEBUG){ qDebug() << " - New Plugin Geometry (grid):" << geom; }
+ //Now place the item in the proper spot/size
+ MovePlugin(plug, gridToGeom(geom));
+ //plug->setGeometry( gridToGeom(geom) );
+ plug->show();
+ if(DEBUG){ qDebug() << " - New Plugin Geometry (px):" << plug->geometry(); }
+ ITEMS << plug;
+ connect(plug, SIGNAL(StartMoving(QString)), this, SLOT(StartItemMove(QString)) );
+ connect(plug, SIGNAL(StartResizing(QString)), this, SLOT(StartItemResize(QString)) );
+ connect(plug, SIGNAL(RemovePlugin(QString)), this, SLOT(RemoveItem(QString)) );
+ connect(plug, SIGNAL(IncreaseIconSize()), this, SIGNAL(IncreaseIcons()) );
+ connect(plug, SIGNAL(DecreaseIconSize()), this, SIGNAL(DecreaseIcons()) );
+ connect(plug, SIGNAL(CloseDesktopMenu()), this, SIGNAL(HideDesktopMenu()) );
+ }
+}
+
+QRect LDesktopPluginSpace::findOpenSpot(int gridwidth, int gridheight, int startRow, int startCol, bool reversed, QString plugID){
+ //Note about the return QPoint: x() is the column number, y() is the row number
+ QPoint pt(0,0);
+ //qDebug() << "FIND OPEN SPOT:" << gridwidth << gridheight << startRow << startCol << reversed;
+ int row = startRow; int col = startCol;
+ if(row<0){ row = 0; } //just in case - since this can be recursively called
+ if(col<0){ col = 0; } //just in case - since this can be recursively called
+ bool found = false;
+ int rowCount, colCount;
+ rowCount = RoundUp(desktopRect.height()/GRIDSIZE);
+ colCount = RoundUp(desktopRect.width()/GRIDSIZE);
+ if( (row+gridheight)>rowCount){ row = rowCount-gridheight; startRow = row; }
+ if( (col+gridwidth)>colCount){ col = colCount-gridwidth; startCol = col; }
+ QRect geom(0, 0, gridwidth*GRIDSIZE, gridheight*GRIDSIZE); //origin point will be adjusted in a moment
+ if(DEBUG){ qDebug() << "Search for plugin space:" << rowCount << colCount << gridheight << gridwidth << this->size(); }
+ if(TopToBottom && reversed && (startRow>0 || startCol>0) ){
+ //Arrange Top->Bottom (work backwards)
+ //qDebug() << "Search backwards for space:" << rowCount << colCount << startRow << startCol << gridheight << gridwidth;
+ while(col>=0 && !found){
+ while(row>=0 && !found){
+ bool ok = true;
+ geom.moveTo(col*GRIDSIZE, row*GRIDSIZE);
+ //qDebug() << " - Check Geom:" << geom << col << row;
+ //Check all the existing items to ensure no overlap
+ for(int i=0; i<ITEMS.length() && ok; i++){
+ if(ITEMS[i]->whatsThis()==plugID){ continue; } //same plugin - this is not a conflict
+ if(geom.intersects(ITEMS[i]->geometry())){
+ //Collision - move the next searchable row/column index
+ ok = false;
+ //qDebug() << "Collision:" << col << row;
+ row = ((ITEMS[i]->geometry().y()-GRIDSIZE/2)/GRIDSIZE) -gridheight; //use top edge for next search (minus item height)
+ //qDebug() << " - new row:" << row;
+ }
+ }
+ if(ok){ pt = QPoint(col,row); found = true; } //found an open spot
+ }
+ if(!found){ col--; row=rowCount-gridheight; } //go to the previous column
+ }
+ }else if(TopToBottom){
+ //Arrange Top->Bottom
+ while(col<(colCount-gridwidth) && !found){
+ while(row<(rowCount-gridheight) && !found){
+ bool ok = true;
+ geom.moveTo(col*GRIDSIZE, row*GRIDSIZE);
+ //qDebug() << " - Check Geom:" << geom << col << row;
+ //Check all the existing items to ensure no overlap
+ for(int i=0; i<ITEMS.length() && ok; i++){
+ if(ITEMS[i]->whatsThis()==plugID){ continue; } //same plugin - this is not a conflict
+ if(geom.intersects(ITEMS[i]->geometry())){
+ //Collision - move the next searchable row/column index
+ ok = false;
+ row = posToGrid(ITEMS[i]->geometry().bottomLeft()).y(); //use bottom edge for next search
+ }
+ }
+ if(ok){ pt = QPoint(col,row); found = true; } //found an open spot
+ //else{ row++; }
+ }
+ if(!found){ col++; row=0; } //go to the next column
+ }
+ }else if(reversed && (startRow>0 || startCol>0) ){
+ //Arrange Left->Right (work backwards)
+ while(row>=0 && !found){
+ while(col>=0 && !found){
+ bool ok = true;
+ geom.moveTo(col*GRIDSIZE, row*GRIDSIZE);
+ //Check all the existing items to ensure no overlap
+ for(int i=0; i<ITEMS.length() && ok; i++){
+ if(ITEMS[i]->whatsThis()==plugID){ continue; } //same plugin - this is not a conflict
+ if(geom.intersects(ITEMS[i]->geometry())){
+ //Collision - move the next searchable row/column index
+ ok = false;
+ col = (ITEMS[i]->geometry().x()-GRIDSIZE/2)/GRIDSIZE - gridwidth; // Fill according to row/column
+ }
+ }
+ if(ok){ pt = QPoint(col,row); found = true; } //found an open spot
+ //else{ col++; }
+ }
+ if(!found){ row--; col=colCount-gridwidth;} //go to the previous row
+ }
+ }else{
+ //Arrange Left->Right
+ while(row<(rowCount-gridheight) && !found){
+ while(col<(colCount-gridwidth) && !found){
+ bool ok = true;
+ geom.moveTo(col*GRIDSIZE, row*GRIDSIZE);
+ //Check all the existing items to ensure no overlap
+ for(int i=0; i<ITEMS.length() && ok; i++){
+ if(ITEMS[i]->whatsThis()==plugID){ continue; } //same plugin - this is not a conflict
+ if(geom.intersects(ITEMS[i]->geometry())){
+ //Collision - move the next searchable row/column index
+ ok = false;
+ col = posToGrid(ITEMS[i]->geometry().topRight()).x(); // Fill according to row/column
+ }
+ }
+ if(ok){ pt = QPoint(col,row); found = true; } //found an open spot
+ //else{ col++; }
+ }
+ if(!found){ row++; col=0;} //go to the next row
+ }
+ }
+ if(!found){
+ //qDebug() << "Could not find a spot:" << startRow << startCol << gridheight << gridwidth;
+ if( (startRow!=0 || startCol!=0) && !reversed){
+ //Did not check the entire screen yet - gradually work it's way back to the top/left corner
+ //qDebug() << " - Start backwards search";
+ return findOpenSpot(gridwidth, gridheight,startRow,startCol, true); //reverse the scan
+ }else if(gridwidth>1 && gridheight>1){
+ //Decrease the size of the item by 1x1 grid points and try again
+ //qDebug() << " - Out of space: Decrease item size and try again...";
+ return findOpenSpot(gridwidth-1, gridheight-1, 0, 0);
+ }else{
+ //qDebug() << " - Could not find an open spot for a desktop plugin:" << gridwidth << gridheight << startRow << startCol;
+ return QRect(-1,-1,-1,-1);
+ }
+ }else{
+ return QRect(pt,QSize(gridwidth,gridheight));
+ }
+}
+
+QRect LDesktopPluginSpace::findOpenSpot(QRect grid, QString plugID, bool recursive){ //Reverse lookup spotc{
+ //This is just an overloaded simplification for checking currently existing plugins
+ return findOpenSpot(grid.width(), grid.height(), grid.y(), grid.x(), recursive, plugID);
+}
+
+// ===================
+// PRIVATE SLOTS
+// ===================
+void LDesktopPluginSpace::reloadPlugins(bool ForceIconUpdate ){
+ //Remove any plugins as necessary
+ QStringList plugs = plugins;
+ QStringList items = deskitems;
+ for(int i=0; i<ITEMS.length(); i++){
+
+ if( ITEMS[i]->whatsThis().startsWith("applauncher") && ForceIconUpdate){
+ //Change the size of the existing plugin - preserving the location if possible
+ /*QRect geom = ITEMS[i]->loadPluginGeometry(); //pixel coords
+ if(!geom.isNull()){
+ geom = geomToGrid(geom); //convert to grid coords
+ geom.setSize(itemSize); //Reset back to default size (does not change location)
+ ITEMS[i]->savePluginGeometry( gridToGeom(geom)); //save it back in pixel coords
+ }*/
+ //Now remove the plugin for the moment - run it through the re-creation routine below
+ ITEMS.takeAt(i)->deleteLater();
+ i--;
+ }
+ else if(plugs.contains(ITEMS[i]->whatsThis())){ plugs.removeAll(ITEMS[i]->whatsThis()); }
+ else if(items.contains(ITEMS[i]->whatsThis().section("---",0,0).section("::",1,50))){ items.removeAll(ITEMS[i]->whatsThis().section("---",0,0).section("::",1,50)); }
+ else{ ITEMS[i]->removeSettings(true); ITEMS.takeAt(i)->deleteLater(); i--; } //this is considered a permanent removal (cleans settings)
+ }
+
+ //Now create any new items
+ //First load the plugins (almost always have fixed locations)
+ for(int i=0; i<plugs.length(); i++){
+ addDesktopPlugin(plugs[i]);
+ }
+ //Now load the desktop shortcuts (fill in the gaps as needed)
+ for(int i=0; i<items.length(); i++){
+ addDesktopItem(items[i]);
+ }
+}
+
+
+//=================
+// PROTECTED
+//=================
+void LDesktopPluginSpace::paintEvent(QPaintEvent*ev){
+ if(!wallpaper.isNull()){
+ QPainter painter(this);
+ painter.setBrush(wallpaper);
+ painter.drawRect(ev->rect().adjusted(-1,-1,2,2));
+ }else{
+ QWidget::paintEvent(ev);
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.h b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.h
new file mode 100644
index 00000000..abc34878
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LDesktopPluginSpace.h
@@ -0,0 +1,303 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_LDESKTOP_PLUGIN_SPACE_H
+#define _LUMINA_DESKTOP_LDESKTOP_PLUGIN_SPACE_H
+
+#include <QListWidget>
+#include <QDropEvent>
+#include <QDrag> //includes all the QDrag*Event classes
+#include <QUrl>
+#include <QMimeData>
+#include <QSettings>
+#include <QDebug>
+#include <QFile>
+#include <QDir>
+#include <QFileInfo>
+#include <QProcess>
+
+#include "desktop-plugins/LDPlugin.h"
+
+#define MIMETYPE QString("x-special/lumina-desktop-plugin")
+
+class LDesktopPluginSpace : public QWidget{
+ Q_OBJECT
+
+signals:
+ void PluginRemovedByUser(QString ID);
+ void IncreaseIcons(); //increase default icon sizes
+ void DecreaseIcons(); //decrease default icon sizes
+ void HideDesktopMenu();
+
+public:
+ LDesktopPluginSpace();
+ ~LDesktopPluginSpace();
+
+ void LoadItems(QStringList plugs, QStringList files);
+ //void setShowGrid(bool show); This is already implemented in QTableView (inherited)
+ void SetIconSize(int size);
+ void ArrangeTopToBottom(bool ttb); //if false, will arrange left->right
+ void cleanup();
+
+ void setBackground(QPixmap pix); //should already be sized appropriately for this widget
+ void setDesktopArea(QRect area);
+
+public slots:
+ void UpdateGeom(int oldgrid = -1);
+
+private:
+ QSettings *plugsettings;
+ QStringList plugins, deskitems;
+ QList<LDPlugin*> ITEMS;
+ QPixmap wallpaper;
+ QRect desktopRect;
+ bool TopToBottom;
+ float GRIDSIZE;
+
+ int RoundUp(double num){
+ int out = num; //This will truncate the number
+ if(out < num){ out++; } //need to increase by 1
+ return out;
+ }
+
+ void addDesktopItem(QString filepath); //This will convert it into a valid Plugin ID automatically
+ void addDesktopPlugin(QString plugID);
+
+
+ QRect findOpenSpot(int gridwidth = 1, int gridheight = 1, int startRow = 0, int startCol = 0, bool reversed = false, QString plugID = "");
+ QRect findOpenSpot(QRect grid, QString plugID, bool recursive = false);
+
+ QPoint posToGrid(QPoint pos){
+ //This assumes a point in widget-relative coordinates
+ pos.setX( RoundUp(pos.x()/GRIDSIZE));
+ pos.setY( RoundUp(pos.y()/GRIDSIZE));
+ return pos;
+ }
+
+ QRect geomToGrid(QRect geom, int grid = -1){
+ if(grid<0){
+ //use the current grid size
+ return QRect( RoundUp(geom.x()/GRIDSIZE), RoundUp(geom.y()/GRIDSIZE), \
+ RoundUp(geom.width()/GRIDSIZE), RoundUp(geom.height()/GRIDSIZE) );
+ }else{
+ //use the input grid size
+ return QRect( RoundUp(geom.x()/((double) grid)), RoundUp(geom.y()/((double) grid)), \
+ RoundUp(geom.width()/((double) grid)), RoundUp(geom.height()/((double) grid)) );
+ }
+ }
+
+ QRect gridToGeom(QRect grid){
+ //This function incorporates the bottom/right edge matchins procedures (for incomplete last grid)
+ QRect geom(grid.x()*GRIDSIZE, grid.y()*GRIDSIZE, grid.width()*GRIDSIZE, grid.height()*GRIDSIZE);
+ //Now check the edge conditions (last right/bottom grid points might be smaller than GRIDSIZE)
+ QSize areaSize = desktopRect.size(); //use the size of the area instead of the geometry - because we need this in child coordinates like "geom" above
+ //qDebug() << "GridToGeom:" << grid << geom << "Area size:" << areaSize;
+ if(geom.right() > areaSize.width() && (geom.right()-areaSize.width())<GRIDSIZE ){
+ geom.setRight(areaSize.width()-1); //match up with the edge
+ }
+ if(geom.bottom() > areaSize.height() && (geom.bottom() -areaSize.height())<GRIDSIZE ){
+ geom.setBottom(areaSize.height()-1); //match up with the edge
+ }
+ //qDebug() << " - Adjusted:" << geom;
+ return geom;
+ }
+
+ //Internal simplification for setting up a drag event
+ void setupDrag(QString id, QString type){
+ QMimeData *mime = new QMimeData;
+ mime->setData(MIMETYPE, QString(type+"::::"+id).toLocal8Bit() );
+ //If this is a desktop file - also add it to the generic URI list mimetype
+ if(id.startsWith("applauncher::")){
+ QList<QUrl> urilist;
+ urilist << QUrl::fromLocalFile( id.section("---",0,0).section("::",1,50) );
+ mime->setUrls(urilist);
+ }
+ //Create the drag structure
+ QDrag *drag = new QDrag(this);
+ drag->setMimeData(mime);
+ drag->exec(Qt::CopyAction);
+ }
+
+ bool ValidGrid(QRect grid){
+ //qDebug() << "Check Valid Grid:" << grid << RoundUp(this->width()/GRIDSIZE) << RoundUp(this->height()/GRIDSIZE);
+ //This just checks that the grid coordinates are not out of bounds - should still run ValidGeometry() below with the actual pixel geom
+ if(grid.x()<0 || grid.y()<0 || grid.width()<0 || grid.height()<0){ return false; }
+ else if( (grid.x()+grid.width()) > RoundUp(desktopRect.width()/GRIDSIZE) ){ return false; }
+ else if( (grid.y()+grid.height()) > RoundUp(desktopRect.height()/GRIDSIZE) ){ return false; }
+ return true;
+ }
+
+ bool ValidGeometry(QString id, QRect geom){
+ //First check that it is within the desktop area completely
+ // Note that "this->geometry()" is not in the same coordinate space as the geometry inputs
+ if(!QRect(0,0,desktopRect.width(), desktopRect.height()).contains(geom)){ return false; }
+ //Now check that it does not collide with any other items
+ for(int i=0; i<ITEMS.length(); i++){
+ if(ITEMS[i]->whatsThis()==id){ continue; }
+ else if(geom.intersects(ITEMS[i]->geometry())){ return false; }
+ }
+ return true;
+ }
+
+ LDPlugin* ItemFromID(QString ID){
+ for(int i=0; i<ITEMS.length(); i++){
+ if(ITEMS[i]->whatsThis()==ID){ return ITEMS[i]; }
+ }
+ return 0;
+ }
+
+ void MovePlugin(LDPlugin* plug, QRect geom){
+ plug->setGeometry( geom );
+ plug->setFixedSize(geom.size()); //needed for some plugins
+ plug->savePluginGeometry(geom);
+ }
+
+private slots:
+ void reloadPlugins(bool ForceIconUpdate = false);
+
+ void StartItemMove(QString ID){
+ setupDrag(ID, "move");
+ }
+ void StartItemResize(QString ID){
+ setupDrag(ID, "resize");
+ }
+ void RemoveItem(QString ID){
+ //Special case - desktop file/dir link using the "applauncher" plugin
+ if(ID.startsWith("applauncher::")){
+ QFileInfo info(ID.section("---",0,0).section("::",1,50) );
+ if(info.exists() && info.absolutePath()==QDir::homePath()+"/Desktop"){
+ qDebug() << "Deleting Desktop Item:" << info.absoluteFilePath();
+ if(!info.isSymLink() && info.isDir()){ QProcess::startDetached("rm -r \""+info.absoluteFilePath()+"\""); }
+ else{ QFile::remove(info.absoluteFilePath()); } //just remove the file/symlink directly
+ emit PluginRemovedByUser(ID);
+ return;
+ }
+ }
+ //Any other type of plugin
+ for(int i=0; i<ITEMS.length(); i++){
+ if(ITEMS[i]->whatsThis()==ID){
+ ITEMS[i]->Cleanup();
+ ITEMS.takeAt(i)->deleteLater();
+ break;
+ }
+ }
+ emit PluginRemovedByUser(ID);
+ }
+
+protected:
+ void focusInEvent(QFocusEvent *ev){
+ this->lower(); //make sure we stay on the bottom of the window stack
+ QWidget::focusInEvent(ev); //do normal handling
+ }
+ void paintEvent(QPaintEvent*ev);
+
+ //Need Drag and Drop functionality (internal movement)
+ void dragEnterEvent(QDragEnterEvent *ev){
+ if(ev->mimeData()->hasFormat(MIMETYPE) ){
+ ev->acceptProposedAction(); //allow this to be dropped here
+ }else if(ev->mimeData()->hasUrls()){
+ ev->acceptProposedAction(); //allow this to be dropped here
+ }else{
+ ev->ignore();
+ }
+ }
+
+ void dragMoveEvent(QDragMoveEvent *ev){
+ if(ev->mimeData()->hasFormat(MIMETYPE) ){
+ //Internal move/resize - Check for validity
+ QString act = QString( ev->mimeData()->data(MIMETYPE) );
+ LDPlugin *item = ItemFromID(act.section("::::",1,50));
+ //qDebug() << "Internal Move Event:" << act << ev->pos();
+ if(item!=0){
+ QRect geom = item->geometry();
+ QPoint grid = posToGrid(ev->pos());
+ if(act.section("::::",0,0)=="move"){
+ QPoint diff = grid - posToGrid(geom.center()); //difference in grid coords
+ //qDebug() << "Move Event:" << "Diff:" << diff << "Geom:" << geom << grid << ev->pos();
+ geom = geomToGrid(geom); //convert to grid coords
+ //qDebug() << "Move Event:" << "Old geom (grid):" << geom;
+ geom.moveTo( (geom.topLeft()+diff) );
+ //qDebug() << " - After Move:" << geom;
+ bool valid = ValidGrid(geom);
+ if(valid){
+ //Convert to pixel coordinates and check validity again
+ geom = gridToGeom(geom); //convert back to px coords with edge matching
+ valid = ValidGeometry(act.section("::::",1,50), geom);
+ }
+ if(valid){
+ MovePlugin(item, geom);
+ //item->setGeometry(geom);
+ //item->setFixedSize(geom.size()); //needed due to resizing limitations and such for some plugins
+ ev->acceptProposedAction();
+ //item->savePluginGeometry(geom); //save in pixel coords
+ }else{ ev->ignore(); } //invalid location
+
+ }else{
+ //Resize operation
+ QPoint diff = ev->pos() - (geom.center()-QPoint(1,1)); //need difference from center (pixels)
+ //Note: Use the 1x1 pixel offset to ensure that the center point is not exactly on a grid point intersection (2x2, 4x4, etc)
+ //qDebug() << "Resize Plugin:" << geom << grid << posToGrid(geom.center()) << diff;
+ geom = geomToGrid(geom); //convert to grid coordinates now
+ //qDebug() << " - Grid Geom:" << geom;
+ if(diff.x()<0){ geom.setLeft(ev->pos().x()/GRIDSIZE); } //expanding to the left (round down)
+ else if(diff.x()>0){ geom.setRight( ev->pos().x()/GRIDSIZE); } //expanding to the right (round down)
+ if(diff.y()<0){ geom.setTop( ev->pos().y()/GRIDSIZE); } //expanding above (round down)
+ else if(diff.y()>0){ geom.setBottom( ev->pos().y()/GRIDSIZE); } //expanding below (round down)
+ //qDebug() << " - Adjusted:" << geom;
+ bool valid = ValidGrid(geom);
+ if(valid){
+ //Convert to pixel coordinates and check validity again
+ geom = gridToGeom(geom); //convert back to px coords with edge matching
+ valid = ValidGeometry(act.section("::::",1,50), geom);
+ }
+ if(valid){
+ MovePlugin(item, geom);
+ //item->setGeometry(geom);
+ //item->setFixedSize(geom.size()); //needed due to resizing limitations and such for some plugins
+ ev->acceptProposedAction();
+ //item->savePluginGeometry(geom); //save in pixel coords
+ }else{ ev->ignore(); } //invalid location
+ }
+ }
+ }else if(ev->mimeData()->hasUrls()){
+ ev->acceptProposedAction(); //allow this to be dropped here
+ }else{
+ ev->ignore();
+ }
+ }
+
+ void dropEvent(QDropEvent *ev){
+ //QPoint grid = posToGrid(ev->pos());
+ if(ev->mimeData()->hasFormat(MIMETYPE)){
+ //Desktop Items getting moved around - already performed in the dragMoveEvent
+ ev->accept();
+ }else if(ev->mimeData()->hasUrls()){
+ ev->accept();
+ //Files getting dropped here
+ QList<QUrl> urls = ev->mimeData()->urls();
+ qDebug() << "Desktop Drop Event:" << urls;
+ for(int i=0; i<urls.length(); i++){
+ //If this file is not in the desktop folder, move/copy it here
+ if(urls[i].isLocalFile()){
+ QFileInfo info(urls[i].toLocalFile());
+ if(info.exists() && !QFile::exists(QDir::homePath()+"/Desktop/"+info.fileName())){
+ //Make a link to the file here
+ QFile::link(info.absoluteFilePath(), QDir::homePath()+"/Desktop/"+info.fileName());
+ }else{
+ qWarning() << "Invalid desktop file drop (ignored):" << urls[i].toString();
+ }
+ }
+
+ }
+ }else{
+ //Ignore this event
+ ev->ignore();
+ }
+ }
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LPanel.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LPanel.cpp
new file mode 100644
index 00000000..5df1fcb6
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LPanel.cpp
@@ -0,0 +1,399 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LPanel.h"
+#include "LSession.h"
+#include <QScreen>
+
+#include "panel-plugins/systemtray/LSysTray.h"
+
+#define DEBUG 0
+
+LPanel::LPanel(QSettings *file, QString scr, int num, QWidget *parent) : QWidget(){
+ //Take care of inputs
+ this->setMouseTracking(true);
+ hascompositer = false; //LUtils::isValidBinary("xcompmgr"); //NOT WORKING YET - xcompmgr issue with special window flags?
+ if(DEBUG){ qDebug() << " - Creating Panel:" << scr << num; }
+ bgWindow = parent; //save for later
+ //Setup the widget overlay for the entire panel to provide transparency effects
+ panelArea = new QWidget(this);
+ //panelArea->setAttribute(Qt::WA_TranslucentBackground);
+ QBoxLayout *tmp = new QBoxLayout(QBoxLayout::LeftToRight);
+ tmp->setContentsMargins(0,0,0,0);
+ this->setLayout(tmp);
+ tmp->addWidget(panelArea);
+ settings = file;
+ screenID = scr;
+ panelnum = num; //save for later
+ screen = LSession::desktop();
+ QString screenID = QApplication::screens().at(Screen())->name();
+ PPREFIX = "panel_"+screenID+"."+QString::number(num)+"/";
+ defaultpanel = (LSession::handle()->screenGeom(Screen()).x()==0 && num==0);
+ horizontal=true; //use this by default initially
+ hidden = false; //use this by default
+ //Setup the panel
+ if(DEBUG){ qDebug() << " -- Setup Panel"; }
+ this->setContentsMargins(0,0,0,0);
+ this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ //panels cannot get keyboard focus otherwise it upsets the task manager window detection
+ //this->setAttribute(Qt::WA_X11DoNotAcceptFocus);
+ this->setAttribute(Qt::WA_X11NetWmWindowTypeDock);
+ this->setAttribute(Qt::WA_AlwaysShowToolTips);
+ this->setAttribute(Qt::WA_TranslucentBackground);
+ //this->setAttribute(Qt::WA_NoSystemBackground);
+ this->setAutoFillBackground(false);
+ this->setWindowFlags(Qt::FramelessWindowHint | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint);
+ //this->setWindowFlags(Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint);
+
+ this->setWindowTitle("LuminaPanel");
+ this->setObjectName("LuminaPanelBackgroundWidget");
+ this->setStyleSheet("QToolButton::menu-indicator{ image: none; } QWidget#LuminaPanelBackgroundWidget{ background: transparent; }");
+ panelArea->setObjectName("LuminaPanelColor");
+ layout = new QBoxLayout(QBoxLayout::LeftToRight);
+ layout->setContentsMargins(0,0,0,0);
+ layout->setSpacing(1);
+ //layout->setSizeConstraint(QLayout::SetFixedSize);
+ panelArea->setLayout(layout);
+ //Set special window flags on the panel for proper usage
+ this->show();
+ LSession::handle()->XCB->SetAsPanel(this->winId());
+ LSession::handle()->XCB->SetAsSticky(this->winId());
+ if(hascompositer){
+ //qDebug() << "Enable Panel compositing";
+ //this->setStyleSheet("QWidget#LuminaPanelBackgroundWidget{ background: transparent; }");
+ //this->setWindowOpacity(0.5); //fully transparent background for the main widget
+ //panelArea->setWindowOpacity(1.0); //fully opaque for the widget on top (apply stylesheet transparencies)
+ }
+ QTimer::singleShot(1,this, SLOT(UpdatePanel()) );
+ //connect(screen, SIGNAL(resized(int)), this, SLOT(UpdatePanel()) ); //in case the screen resolution changes
+}
+
+LPanel::~LPanel(){
+
+}
+
+int LPanel::Screen(){
+ // Find the screen number associated with this ID
+ QList<QScreen*> scrns = QApplication::screens();
+ for(int i=0; i<scrns.length(); i++){
+ if(scrns[i]->name() == screenID){ return i; }
+ }
+ return -1;
+}
+
+void LPanel::prepareToClose(){
+ //Go through and remove all the plugins
+ for(int i=0; i<PLUGINS.length(); i++){
+ PLUGINS[i]->AboutToClose(); //any last cleanup for this plugin
+ layout->takeAt(i); //remove from the layout
+ PLUGINS.takeAt(i)->deleteLater(); //delete the actual widget
+ LSession::processEvents();
+ i--; //need to back up one space to not miss another plugin
+ }
+ this->hide();
+}
+
+void LPanel::scalePanel(double xscale, double yscale){
+ int ht = settings->value(PPREFIX+"height", 30).toInt(); //this is technically the distance into the screen from the edge
+ QString loc = settings->value(PPREFIX+"location","").toString().toLower();
+ if(loc=="top" || loc=="bottom"){
+ ht = qRound(ht*yscale);
+ }else{
+ ht = qRound(ht*xscale);
+ }
+ settings->setValue(PPREFIX+"height", ht);
+ settings->sync();
+ QTimer::singleShot(0, this, SLOT(UpdatePanel()) );
+}
+
+//===========
+// PUBLIC SLOTS
+//===========
+void LPanel::UpdatePanel(bool geomonly){
+ //Create/Update the panel as designated in the Settings file
+ settings->sync(); //make sure to catch external settings changes
+ //First set the geometry of the panel and send the EWMH message to reserve that space
+ if(DEBUG){ qDebug() << "Update Panel: Geometry only=" << geomonly << "Screen Size:" << LSession::handle()->screenGeom(Screen()); }
+ hidden = settings->value(PPREFIX+"hidepanel",false).toBool();
+ QString loc = settings->value(PPREFIX+"location","").toString().toLower();
+ if(loc.isEmpty() && defaultpanel){ loc="top"; }
+ if(loc=="top" || loc=="bottom"){
+ horizontal=true;
+ layout->setAlignment(Qt::AlignLeft);
+ layout->setDirection(QBoxLayout::LeftToRight);
+ }else{
+ horizontal=false;
+ layout->setAlignment(Qt::AlignTop);
+ layout->setDirection(QBoxLayout::TopToBottom);
+ }
+ int ht = qRound(settings->value(PPREFIX+"height", 30).toDouble()); //this is technically the distance into the screen from the edge
+ fullwidth = ht; //save this for later
+ if(ht<=1){ ht = 30; } //some kind of error in the saved height - use the default value
+ int hidesize = qRound(ht*0.01); //use 1% of the panel size
+ if(hidesize<2){ hidesize=2; } //minimum of 2 pixels (need space for the mouse to go over it)
+ if(hidden){ viswidth = hidesize; }
+ else{ viswidth = ht; }
+ if(DEBUG){ qDebug() << "Hidden Panel size:" << hidesize << "pixels"; }
+ //qDebug() << " - set Geometry";
+ int xwid = LSession::handle()->screenGeom(Screen()).width();
+ int xhi = LSession::handle()->screenGeom(Screen()).height();
+ int xloc = LSession::handle()->screenGeom(Screen()).x();
+ int yloc = LSession::handle()->screenGeom(Screen()).y();
+ double panelPercent = settings->value(PPREFIX+"lengthPercent",100).toInt();
+ if(panelPercent<1 || panelPercent>100){ panelPercent = 100; }
+ panelPercent = panelPercent/100.0;
+ QString panelPinLoc = settings->value(PPREFIX+"pinLocation","center").toString().toLower(); //[left/right/center] possible values (assume center otherwise)
+ if(DEBUG){ qDebug() << " - Panel settings:" << QString::number(panelPercent)+QString("%") << panelPinLoc << loc; }
+ //xloc=xoffset;
+ if(loc=="top"){ //top of screen
+ QSize sz = QSize(xwid*panelPercent, ht);
+ if(panelPinLoc=="left"){} // no change to xloc
+ else if(panelPinLoc=="right"){ xloc = xloc+xwid-sz.width(); }
+ else{ xloc = xloc+((xwid-sz.width())/2) ; } //centered
+ //qDebug() << " - Panel Sizing:" << xloc << sz;
+ this->setMinimumSize(sz);
+ this->setMaximumSize(sz);
+ this->setGeometry(xloc,yloc,sz.width(), sz.height());
+ //qDebug() << " - Reserve Panel Localation";
+ if(!hidden){ LSession::handle()->XCB->ReserveLocation(this->winId(), this->geometry(), "top"); }
+ else{
+ LSession::handle()->XCB->ReserveLocation(this->winId(), QRect(xloc, yloc, this->width(), hidesize), "top");
+ hidepoint = QPoint(xloc, yloc);
+ showpoint = QPoint(xloc, yloc);
+ this->move(hidepoint);
+ this->resize( this->width(), viswidth);
+ }
+ }else if(loc=="bottom"){ //bottom of screen
+ QSize sz = QSize(xwid*panelPercent, ht);
+ if(panelPinLoc=="left"){} // no change to xloc
+ else if(panelPinLoc=="right"){ xloc = xloc+xwid-sz.width(); }
+ else{ xloc = xloc+((xwid-sz.width())/2) ; } //centered
+ this->setMinimumSize(sz);
+ this->setMaximumSize(sz);
+ this->setGeometry(xloc,yloc+xhi-ht,sz.width(), ht );
+ if(!hidden){ LSession::handle()->XCB->ReserveLocation(this->winId(), this->geometry(), "bottom"); }
+ else{
+ LSession::handle()->XCB->ReserveLocation(this->winId(), QRect(xloc,yloc+ xhi-hidesize, this->width(), hidesize), "bottom");
+ hidepoint = QPoint(xloc, yloc+xhi-hidesize);
+ showpoint = QPoint(xloc, yloc+xhi-ht);
+ this->move(hidepoint); //Could bleed over onto the screen below
+ this->resize( this->width(), viswidth);
+ }
+ }else if(loc=="left"){ //left side of screen
+ QSize sz = QSize(ht, xhi*panelPercent);
+ if(panelPinLoc=="left"){} //this is actually the top (left of center in length dimension)
+ else if(panelPinLoc=="right"){ yloc = yloc+xhi-sz.height(); }
+ else{ yloc = yloc+((xhi-sz.height())/2) ; } //centered
+ this->setMinimumSize(sz);
+ this->setMaximumSize(sz);
+ this->setGeometry(xloc,yloc, ht, sz.height());
+ if(!hidden){ LSession::handle()->XCB->ReserveLocation(this->winId(), this->geometry(), "left"); }
+ else{
+ LSession::handle()->XCB->ReserveLocation(this->winId(), QRect(xloc, yloc, hidesize, sz.height()), "left");
+ hidepoint = QPoint(xloc, yloc);
+ showpoint = QPoint(xloc, yloc);
+ this->move(hidepoint); //Could bleed over onto the screen left
+ this->resize( viswidth, this->height());
+ }
+ }else{ //right side of screen
+ QSize sz = QSize(ht, xhi*panelPercent);
+ if(panelPinLoc=="left"){} //this is actually the top (left of center in length dimension)
+ else if(panelPinLoc=="right"){ yloc = yloc+xhi-sz.height(); }
+ else{ yloc = yloc+((xhi-sz.height())/2) ; } //centered
+ this->setMinimumSize(sz);
+ this->setMaximumSize(sz);
+ this->setGeometry(xloc+xwid-ht,yloc,ht, sz.height());
+ if(!hidden){ LSession::handle()->XCB->ReserveLocation(this->winId(), this->geometry(), "right"); }
+ else{
+ LSession::handle()->XCB->ReserveLocation(this->winId(), QRect(xloc+xwid-hidesize, yloc, hidesize, sz.height()), "right");
+ hidepoint = QPoint(xloc+xwid-hidesize, yloc);
+ showpoint = QPoint(xloc+xwid-ht, yloc);
+ this->move(hidepoint); //Could bleed over onto the screen right
+ this->resize( viswidth, this->height());
+ }
+ }
+ if(DEBUG){ qDebug() << " - Done with panel geometry"; }
+ //Double check that the "sticky" bit is set on the window state
+ bool needsticky = !LSession::handle()->XCB->WM_Get_Window_States(this->winId()).contains(LXCB::S_STICKY);
+ if(needsticky){ LSession::handle()->XCB->SetAsSticky(this->winId()); }
+ if(geomonly){ return; }
+ //Now update the appearance of the toolbar
+ if(settings->value(PPREFIX+"customColor", false).toBool()){
+ QString color = settings->value(PPREFIX+"color", "rgba(255,255,255,160)").toString();
+ QString style = "QWidget#LuminaPanelColor{ background: %1; border-radius: 3px; border: 1px solid %1; }";
+ style = style.arg(color);
+ panelArea->setStyleSheet(style);
+ }else{
+ panelArea->setStyleSheet(""); //clear it and use the one from the theme
+ }
+
+ //Then go through the plugins and create them as necessary
+ QStringList plugins = settings->value(PPREFIX+"pluginlist", QStringList()).toStringList();
+ /*if(defaultpanel && plugins.isEmpty()){
+ plugins << "userbutton" << "taskmanager" << "spacer" << "systemtray" << "clock" << "systemdashboard";
+ }*/
+ if(DEBUG){ qDebug() << " - Initialize Plugins: " << plugins; }
+ for(int i=0; i<plugins.length(); i++){
+ //Ensure this plugin has a unique ID (NOTE: this numbering does not persist between sessions)
+ if(!plugins[i].contains("---")){
+ int num=1;
+ while( plugins.contains(plugins[i]+"---"+QString::number(Screen())+"."+QString::number(this->number())+"."+QString::number(num)) ){
+ num++;
+ }
+
+ plugins[i] = plugins[i]+"---"+QString::number(Screen())+"."+QString::number(this->number())+"."+QString::number(num);
+ //qDebug() << "Adjust Plugin ID:" << plugins[i];
+ }
+ //See if this plugin is already there or in a different spot
+ bool found = false;
+ for(int p=0; p<PLUGINS.length(); p++){
+ if(PLUGINS[p]->type()==plugins[i]){
+ found = true; //already exists
+ //Make sure the plugin layout has the correct orientation
+ if(horizontal){PLUGINS[p]->layout()->setDirection(QBoxLayout::LeftToRight); }
+ else{ PLUGINS[p]->layout()->setDirection(QBoxLayout::TopToBottom); }
+ PLUGINS[p]->OrientationChange();
+ //Now check the location of the plugin in the panel
+ if(p!=i){ //wrong place in the panel
+ layout->takeAt(p); //remove the item from the current location
+ layout->insertWidget(i, PLUGINS[p]); //add the item into the correct location
+ PLUGINS.move(p,i); //move the identifier in the list to match
+ }
+ break;
+ }
+ }
+ if(!found){
+ //New Plugin
+ if(DEBUG){ qDebug() << " -- New Plugin:" << plugins[i] << i; }
+ LPPlugin *plug = NewPP::createPlugin(plugins[i], panelArea, horizontal);
+ if(plug != 0){
+ PLUGINS.insert(i, plug);
+ layout->insertWidget(i, PLUGINS[i]);
+ connect(plug, SIGNAL(MenuClosed()), this, SLOT(checkPanelFocus()));
+ }else{
+ //invalid plugin type
+ plugins.removeAt(i); //remove this invalid plugin from the list
+ i--; //make sure we don't miss the next item with the re-order
+ }
+ }
+ //LSession::processEvents();
+ }
+ //Now remove any extra plugins from the end
+ //qDebug() << "plugins:" << plugins;
+ //qDebug() << "PLUGINS length:" << PLUGINS.length();
+ for(int i=0; i<PLUGINS.length(); i++){
+ if(plugins.contains(PLUGINS[i]->type())){ continue; } //good plugin - skip it
+ if(DEBUG){ qDebug() << " -- Remove Plugin: " << PLUGINS[i]->type(); }
+ //If this is the system tray - stop it first
+ if( PLUGINS[i]->type().startsWith("systemtray---") ){
+ static_cast<LSysTray*>(PLUGINS[i])->stop();
+ }
+ layout->takeAt(i); //remove from the layout
+ PLUGINS.takeAt(i)->deleteLater(); //delete the actual widget
+ //LSession::processEvents();
+ i--; //need to back up one space to not miss another plugin
+ }
+ this->update();
+ this->show(); //make sure the panel is visible now
+ if(hidden){ this->move(hidepoint); }
+ //Now go through and send the orientation update signal to each plugin
+ for(int i=0; i<PLUGINS.length(); i++){
+ QTimer::singleShot(0,PLUGINS[i], SLOT(OrientationChange()));
+ }
+ checkPanelFocus();
+ //LSession::processEvents();
+}
+
+void LPanel::UpdateLocale(){
+ //The panel itself has no text to translate, just forward the signal to all the plugins
+ for(int i=0; i<PLUGINS.length(); i++){
+ QTimer::singleShot(1,PLUGINS[i], SLOT(LocaleChange()));
+ }
+}
+
+void LPanel::UpdateTheme(){
+ //The panel itself has no theme-based icons, just forward the signal to all the plugins
+ for(int i=0; i<PLUGINS.length(); i++){
+ QTimer::singleShot(1,PLUGINS[i], SLOT(ThemeChange()));
+ }
+}
+
+// ===================
+// PRIVATE SLOTS
+// ===================
+void LPanel::checkPanelFocus(){
+ qDebug() << "Check Panel Focus:" << panelnum << viswidth << fullwidth << this->size();
+ if( !this->geometry().contains(QCursor::pos()) ){
+ //Move the panel back to it's "hiding" spot
+ if(hidden){
+ QSize sz(horizontal ? this->width() : viswidth, horizontal ? viswidth : this->height() );
+ this->setMinimumSize(sz);
+ this->setMaximumSize(sz);
+ this->setGeometry( QRect(hidepoint, sz) );
+ }
+ //Re-active the old window
+ if(LSession::handle()->activeWindow()!=0){
+ LSession::handle()->XCB->ActivateWindow(LSession::handle()->activeWindow());
+ }
+ }else if(hidden){
+ QSize sz(horizontal ? this->width() : fullwidth, horizontal ? fullwidth : this->height() );
+ this->setMinimumSize(sz);
+ this->setMaximumSize(sz);
+ this->setGeometry( QRect(showpoint, sz) );
+ }
+}
+
+//===========
+// PROTECTED
+//===========
+void LPanel::resizeEvent(QResizeEvent *event){
+ QWidget::resizeEvent(event);
+ for(int i=0; i<PLUGINS.length(); i++){ PLUGINS[i]->OrientationChange(); }
+}
+
+void LPanel::paintEvent(QPaintEvent *event){
+ if(!hascompositer){
+ QPainter *painter = new QPainter(this);
+ //qDebug() << "Paint Panel:" << PPREFIX;
+ //Make sure the base background of the event rectangle is the associated rectangle from the BGWindow
+ QRect rec = event->rect();//this->geometry(); //start with the global geometry of the panel
+ rec.adjust(-1,-1,2,2); //add 1 more pixel on each side
+ //Need to translate that rectangle to the background image coordinates
+ //qDebug() << " - Rec:" << rec << hidden << this->geometry() << bgWindow->geometry();
+ rec.moveTo( bgWindow->mapFromGlobal( this->mapToGlobal(rec.topLeft()) ) ); //(rec.x()-LSession::handle()->screenGeom(Screen()).x(), rec.y()-LSession::handle()->screenGeom(Screen()).y() );
+ //qDebug() << " - Adjusted Window Rec:" << rec;
+ painter->drawPixmap(event->rect().adjusted(-1,-1,2,2), bgWindow->grab(rec));
+ //painter->drawPixmap(event->rect().adjusted(-1,-1,2,2), QApplication::screens().at(Screen())->grabWindow(QX11Info::appRootWindow(), rec.x(), rec.y(), rec.width(), rec.height()) );
+ delete(painter);
+ }
+ QWidget::paintEvent(event); //now pass the event along to the normal painting event
+}
+
+void LPanel::enterEvent(QEvent *event){
+ //qDebug() << "Panel Enter Event:";
+ checkPanelFocus();
+ /*if(hidden){
+ //Move the panel out so it is fully available
+ this->move(showpoint);
+ this->resize( horizontal ? this->width() : fullwidth, horizontal ? fullwidth : this->height() );
+ this->update();
+ }*/
+ //this->activateWindow();
+ event->accept(); //just to quiet the compile warning
+}
+
+void LPanel::leaveEvent(QEvent *event){
+ /*qDebug() << "Panel Leave Event:";
+ qDebug() << "Panel Geom:" << this->geometry().x() << this->geometry().y() << this->geometry().width() << this->geometry().height() ;
+ QPoint pt = QCursor::pos();
+ qDebug() << "Mouse Point (global):" << pt.x() << pt.y();
+ //pt = this->mapFromGlobal(pt);
+ //qDebug() << "Mouse Point (local):" << pt.x() << pt.y();
+ qDebug() << "Contained:" << this->geometry().contains(pt);*/
+ checkPanelFocus();
+ QWidget::leaveEvent(event);
+ //event->accept(); //just to quiet the compile warning
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LPanel.h b/src-qt5/core/lumina-desktop-unified/src-DE/LPanel.h
new file mode 100644
index 00000000..bcea8eaf
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LPanel.h
@@ -0,0 +1,81 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This is the generic class for creating a full-width panel that stays
+// on top of all other windows (top or bottom of the screen only)
+//===========================================
+#ifndef _LUMINA_DESKTOP_PANEL_H
+#define _LUMINA_DESKTOP_PANEL_H
+
+#include <QWidget>
+#include <QBoxLayout>
+#include <QSettings>
+#include <QString>
+#include <QStringList>
+#include <QTimer>
+#include <QMoveEvent>
+#include <QDesktopWidget>
+#include <QPainter>
+#include <QPaintEvent>
+
+#include "panel-plugins/NewPP.h"
+#include "panel-plugins/LPPlugin.h"
+
+#include <LuminaX11.h>
+#include <LuminaOS.h>
+
+class LPanel : public QWidget{
+ Q_OBJECT
+private:
+ QBoxLayout *layout;
+ QSettings *settings;
+ QString PPREFIX; //internal prefix for all settings
+ QDesktopWidget *screen;
+ QWidget *bgWindow, *panelArea;
+ //QRect hidegeom, showgeom; //for hidden panels
+ QPoint hidepoint, showpoint; //for hidden panels: locations when hidden/visible
+ bool defaultpanel, horizontal, hidden, hascompositer;
+ QString screenID;
+ int panelnum;
+ int viswidth, fullwidth;
+ QList<LPPlugin*> PLUGINS;
+
+ int Screen(); //Turn the screenID into the appropriate number
+
+public:
+ LPanel(QSettings *file, QString scr = 0, int num =0, QWidget *parent=0); //settings file, screen number, panel number
+ ~LPanel();
+
+ int number(){
+ return panelnum;
+ }
+
+ QString prefix(){
+ return PPREFIX;
+ }
+
+ int visibleWidth(){
+ return viswidth;
+ }
+ void prepareToClose();
+ void scalePanel(double xscale, double yscale);
+
+public slots:
+ void UpdatePanel(bool geomonly = false); //Load the settings file and update the panel appropriately
+ void UpdateLocale(); //Locale Changed externally
+ void UpdateTheme(); //Theme Changed externally
+
+private slots:
+ void checkPanelFocus();
+
+protected:
+ void resizeEvent(QResizeEvent *event);
+ void paintEvent(QPaintEvent *event);
+ void enterEvent(QEvent *event);
+ void leaveEvent(QEvent *event);
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LSession.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LSession.cpp
new file mode 100644
index 00000000..0387555a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LSession.cpp
@@ -0,0 +1,798 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LSession.h"
+#include <LuminaOS.h>
+
+#include <QTime>
+#include <QScreen>
+#include <QtConcurrent>
+#include "LXcbEventFilter.h"
+#include "BootSplash.h"
+
+//LibLumina X11 class
+#include <LuminaX11.h>
+#include <LUtils.h>
+
+#include <unistd.h> //for usleep() usage
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+XCBEventFilter *evFilter = 0;
+
+LSession::LSession(int &argc, char ** argv) : LSingleApplication(argc, argv, "lumina-desktop"){
+ if(this->isPrimaryProcess()){
+ connect(this, SIGNAL(InputsAvailable(QStringList)), this, SLOT(NewCommunication(QStringList)) );
+ this->setApplicationName("Lumina Desktop Environment");
+ this->setApplicationVersion( LDesktopUtils::LuminaDesktopVersion() );
+ this->setOrganizationName("LuminaDesktopEnvironment");
+ this->setQuitOnLastWindowClosed(false); //since the LDesktop's are not necessarily "window"s
+ //Enabled a few of the simple effects by default
+ this->setEffectEnabled( Qt::UI_AnimateMenu, true);
+ this->setEffectEnabled( Qt::UI_AnimateCombo, true);
+ this->setEffectEnabled( Qt::UI_AnimateTooltip, true);
+ //this->setAttribute(Qt::AA_UseDesktopOpenGL);
+ //this->setAttribute(Qt::AA_UseHighDpiPixmaps); //allow pixmaps to be scaled up as well as down
+ //this->setStyle( new MenuProxyStyle); //QMenu icon size override
+ SystemTrayID = 0; VisualTrayID = 0;
+ sysWindow = 0;
+ TrayDmgEvent = 0;
+ TrayDmgError = 0;
+ lastActiveWin = 0;
+ cleansession = true;
+ TrayStopping = false;
+ screenTimer = new QTimer(this);
+ screenTimer->setSingleShot(true);
+ screenTimer->setInterval(50);
+ connect(screenTimer, SIGNAL(timeout()), this, SLOT(updateDesktops()) );
+ for(int i=1; i<argc; i++){
+ if( QString::fromLocal8Bit(argv[i]) == "--noclean" ){ cleansession = false; break; }
+ }
+ XCB = new LXCB(); //need access to XCB data/functions right away
+ //initialize the empty internal pointers to 0
+ appmenu = 0;
+ settingsmenu = 0;
+ currTranslator=0;
+ mediaObj=0;
+ sessionsettings=0;
+ //Setup the event filter for Qt5
+ evFilter = new XCBEventFilter(this);
+ this->installNativeEventFilter( evFilter );
+ connect(this, SIGNAL(screenAdded(QScreen*)), this, SLOT(screensChanged()) );
+ connect(this, SIGNAL(screenRemoved(QScreen*)), this, SLOT(screensChanged()) );
+ connect(this, SIGNAL(primaryScreenChanged(QScreen*)), this, SLOT(screensChanged()) );
+ } //end check for primary process
+}
+
+LSession::~LSession(){
+ if(this->isPrimaryProcess()){
+ //WM->stopWM();
+ for(int i=0; i<DESKTOPS.length(); i++){
+ DESKTOPS[i]->deleteLater();
+ }
+ //delete WM;
+ settingsmenu->deleteLater();
+ appmenu->deleteLater();
+ delete currTranslator;
+ if(mediaObj!=0){delete mediaObj;}
+ }
+}
+
+void LSession::setupSession(){
+ //Seed random number generator (if needed)
+ qsrand( QTime::currentTime().msec() );
+
+ BootSplash splash;
+ splash.showScreen("init");
+ qDebug() << "Initializing Session";
+ if(QFile::exists("/tmp/.luminastopping")){ QFile::remove("/tmp/.luminastopping"); }
+ QTime* timer = 0;
+ //if(DEBUG){ timer = new QTime(); timer->start(); qDebug() << " - Init srand:" << timer->elapsed();}
+
+ //Setup the QSettings default paths
+ splash.showScreen("settings");
+ if(DEBUG){ qDebug() << " - Init QSettings:" << timer->elapsed();}
+ sessionsettings = new QSettings("lumina-desktop", "sessionsettings");
+ DPlugSettings = new QSettings("lumina-desktop","pluginsettings/desktopsettings");
+ //Load the proper translation files
+ if(sessionsettings->value("ForceInitialLocale",false).toBool()){
+ //Some system locale override it in place - change the env first
+ LUtils::setLocaleEnv( sessionsettings->value("InitLocale/LANG","").toString(), \
+ sessionsettings->value("InitLocale/LC_MESSAGES","").toString(), \
+ sessionsettings->value("InitLocale/LC_TIME","").toString(), \
+ sessionsettings->value("InitLocale/LC_NUMERIC","").toString(), \
+ sessionsettings->value("InitLocale/LC_MONETARY","").toString(), \
+ sessionsettings->value("InitLocale/LC_COLLATE","").toString(), \
+ sessionsettings->value("InitLocale/LC_CTYPE","").toString() );
+ }
+ currTranslator = LUtils::LoadTranslation(this, "lumina-desktop");
+//use the system settings
+ //Setup the user's lumina settings directory as necessary
+ splash.showScreen("user");
+ if(DEBUG){ qDebug() << " - Init User Files:" << timer->elapsed();}
+ checkUserFiles(); //adds these files to the watcher as well
+
+ //Initialize the internal variables
+ DESKTOPS.clear();
+
+ //Start the background system tray
+ splash.showScreen("systray");
+ if(DEBUG){ qDebug() << " - Init System Tray:" << timer->elapsed();}
+ startSystemTray();
+
+ //Initialize the global menus
+ qDebug() << " - Initialize system menus";
+ splash.showScreen("apps");
+ if(DEBUG){ qDebug() << " - Init AppMenu:" << timer->elapsed();}
+ appmenu = new AppMenu();
+
+ splash.showScreen("menus");
+ if(DEBUG){ qDebug() << " - Init SettingsMenu:" << timer->elapsed();}
+ settingsmenu = new SettingsMenu();
+ if(DEBUG){ qDebug() << " - Init SystemWindow:" << timer->elapsed();}
+ sysWindow = new SystemWindow();
+
+ //Initialize the desktops
+ splash.showScreen("desktop");
+ if(DEBUG){ qDebug() << " - Init Desktops:" << timer->elapsed();}
+ desktopFiles = QDir(QDir::homePath()+"/Desktop").entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs, QDir::Name | QDir::IgnoreCase | QDir::DirsFirst);
+ updateDesktops();
+ for(int i=0; i<6; i++){ LSession::processEvents(); } //Run through this a few times so the interface systems get up and running
+
+ //Now setup the system watcher for changes
+ splash.showScreen("final");
+ qDebug() << " - Initialize file system watcher";
+ if(DEBUG){ qDebug() << " - Init QFileSystemWatcher:" << timer->elapsed();}
+ watcher = new QFileSystemWatcher(this);
+ QString confdir = sessionsettings->fileName().section("/",0,-2);
+ watcherChange(sessionsettings->fileName() );
+ watcherChange( confdir+"/desktopsettings.conf" );
+ watcherChange( confdir+"/fluxbox-init" );
+ watcherChange( confdir+"/fluxbox-keys" );
+ watcherChange( confdir+"/favorites.list" );
+ //Try to watch the localized desktop folder too
+ if(QFile::exists(QDir::homePath()+"/"+tr("Desktop"))){ watcherChange( QDir::homePath()+"/"+tr("Desktop") ); }
+ watcherChange( QDir::homePath()+"/Desktop" );
+
+ //connect internal signals/slots
+ connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(watcherChange(QString)) );
+ connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(watcherChange(QString)) );
+ connect(this, SIGNAL(aboutToQuit()), this, SLOT(SessionEnding()) );
+ if(DEBUG){ qDebug() << " - Init Finished:" << timer->elapsed(); delete timer;}
+ for(int i=0; i<4; i++){ LSession::processEvents(); } //Again, just a few event loops here so thing can settle before we close the splash screen
+ //launchStartupApps();
+ QTimer::singleShot(500, this, SLOT(launchStartupApps()) );
+ splash.hide();
+ LSession::processEvents();
+ splash.close();
+ LSession::processEvents();
+}
+
+void LSession::CleanupSession(){
+ //Close any running applications and tray utilities (Make sure to keep the UI interactive)
+ LSession::processEvents();
+ QDateTime time = QDateTime::currentDateTime();
+ qDebug() << "Start closing down the session: " << time.toString( Qt::SystemLocaleShortDate);
+ //Create a temporary flag to prevent crash dialogs from opening during cleanup
+ LUtils::writeFile("/tmp/.luminastopping",QStringList() << "yes", true);
+ //Start the logout chimes (if necessary)
+ LOS::setAudioVolume( LOS::audioVolume() ); //make sure the audio volume is saved in the backend for the next login
+ bool playaudio = sessionsettings->value("PlayLogoutAudio",true).toBool();
+ if( playaudio ){ playAudioFile(LOS::LuminaShare()+"Logout.ogg"); }
+ //Stop the background system tray (detaching/closing apps as necessary)
+ stopSystemTray(!cleansession);
+ //Now perform any other cleanup
+ if(cleansession){
+ //Close any open windows
+ //qDebug() << " - Closing any open windows";
+ QList<WId> WL = XCB->WindowList(true);
+ for(int i=0; i<WL.length(); i++){
+ qDebug() << " - Closing window:" << XCB->WindowClass(WL[i]) << WL[i];
+ XCB->CloseWindow(WL[i]);
+ LSession::processEvents();
+ }
+ //Now wait a moment for things to close down before quitting
+ for(int i=0; i<20; i++){ LSession::processEvents(); usleep(25); } //1/2 second pause
+ //Kill any remaining windows
+ WL = XCB->WindowList(true); //all workspaces
+ for(int i=0; i<WL.length(); i++){
+ qDebug() << " - Window did not close, killing application:" << XCB->WindowClass(WL[i]) << WL[i];
+ XCB->KillClient(WL[i]);
+ LSession::processEvents();
+ }
+ }
+ evFilter->StopEventHandling();
+ //Stop the window manager
+ //qDebug() << " - Stopping the window manager";
+ //WM->stopWM();
+ //Now close down the desktop
+ qDebug() << " - Closing down the desktop elements";
+ for(int i=0; i<DESKTOPS.length(); i++){
+ DESKTOPS[i]->prepareToClose();
+ //don't actually close them yet - that will happen when the session exits
+ // this will leave the wallpapers up for a few moments (preventing black screens)
+ }
+ //Now wait a moment for things to close down before quitting
+ if(playaudio){
+ //wait a max of 5 seconds for audio to finish
+ bool waitmore = true;
+ for(int i=0; i<100 && waitmore; i++){
+ usleep(50000); //50ms = 50000 us
+ waitmore = (mediaObj->state()==QMediaPlayer::PlayingState);
+ LSession::processEvents();
+ }
+ if(waitmore){ mediaObj->stop(); } //timed out
+ }else{
+ for(int i=0; i<20; i++){ LSession::processEvents(); usleep(25000); } //1/2 second pause
+ }
+ //Clean up the temporary flag
+ if(QFile::exists("/tmp/.luminastopping")){ QFile::remove("/tmp/.luminastopping"); }
+}
+
+int LSession::VersionStringToNumber(QString version){
+ version = version.section("-",0,0); //trim any extra labels off the end
+ int maj, mid, min; //major/middle/minor version numbers (<Major>.<Middle>.<Minor>)
+ maj = mid = min = 0;
+ bool ok = true;
+ maj = version.section(".",0,0).toInt(&ok);
+ if(ok){ mid = version.section(".",1,1).toInt(&ok); }else{ maj = 0; }
+ if(ok){ min = version.section(".",2,2).toInt(&ok); }else{ mid = 0; }
+ if(!ok){ min = 0; }
+ //Now assemble the number
+ //NOTE: This format allows numbers to be anywhere from 0->999 without conflict
+ return (maj*1000000 + mid*1000 + min);
+}
+
+void LSession::NewCommunication(QStringList list){
+ if(DEBUG){ qDebug() << "New Communications:" << list; }
+ for(int i=0; i<list.length(); i++){
+ if(list[i]=="--check-geoms"){
+ screensChanged();
+ }else if(list[i]=="--show-start"){
+ emit StartButtonActivated();
+ }
+ }
+}
+
+void LSession::launchStartupApps(){
+ //First start any system-defined startups, then do user defined
+ qDebug() << "Launching startup applications";
+
+ //Enable Numlock
+ if(LUtils::isValidBinary("numlockx")){ //make sure numlockx is installed
+ if(sessionsettings->value("EnableNumlock",false).toBool()){
+ QProcess::startDetached("numlockx on");
+ }else{
+ QProcess::startDetached("numlockx off");
+ }
+ }
+ int tmp = LOS::ScreenBrightness();
+ if(tmp>0){
+ LOS::setScreenBrightness( tmp );
+ qDebug() << " - - Screen Brightness:" << QString::number(tmp)+"%";
+ }
+ QProcess::startDetached("nice lumina-open -autostart-apps");
+
+ //Re-load the screen brightness and volume settings from the previous session
+ // Wait until after the XDG-autostart functions, since the audio system might be started that way
+ qDebug() << " - Loading previous settings";
+ tmp = LOS::audioVolume();
+ LOS::setAudioVolume(tmp);
+ qDebug() << " - - Audio Volume:" << QString::number(tmp)+"%";
+
+ //Now play the login music since we are finished
+ if(sessionsettings->value("PlayStartupAudio",true).toBool()){
+ //Make sure to re-set the system volume to the last-used value at outset
+ int vol = LOS::audioVolume();
+ if(vol>=0){ LOS::setAudioVolume(vol); }
+ LSession::playAudioFile(LOS::LuminaShare()+"Login.ogg");
+ }
+ qDebug() << " - Finished with startup routines";
+}
+
+void LSession::StartLogout(){
+ CleanupSession();
+ QCoreApplication::exit(0);
+}
+
+void LSession::StartShutdown(bool skipupdates){
+ CleanupSession();
+ LOS::systemShutdown(skipupdates);
+ QCoreApplication::exit(0);
+}
+
+void LSession::StartReboot(bool skipupdates){
+ CleanupSession();
+ LOS::systemRestart(skipupdates);
+ QCoreApplication::exit(0);
+}
+
+void LSession::reloadIconTheme(){
+ //Wait a moment for things to settle before sending out the signal to the interfaces
+ QApplication::processEvents();
+ QApplication::processEvents();
+ emit IconThemeChanged();
+}
+
+void LSession::watcherChange(QString changed){
+ if(DEBUG){ qDebug() << "Session Watcher Change:" << changed; }
+ //if(changed.endsWith("fluxbox-init") || changed.endsWith("fluxbox-keys")){ refreshWindowManager(); }
+ if(changed.endsWith("sessionsettings.conf") ){
+ sessionsettings->sync();
+ //qDebug() << "Session Settings Changed";
+ if(sessionsettings->contains("Qt5_theme_engine")){
+ QString engine = sessionsettings->value("Qt5_theme_engine","").toString();
+ //qDebug() << "Set Qt5 theme engine: " << engine;
+ if(engine.isEmpty()){ unsetenv("QT_QPA_PLATFORMTHEME"); }
+ else{ setenv("QT_QPA_PLATFORMTHEME", engine.toUtf8().data(),1); }
+ }
+ emit SessionConfigChanged();
+ }else if(changed.endsWith("desktopsettings.conf") ){ emit DesktopConfigChanged(); }
+ else if(changed == QDir::homePath()+"/Desktop" || changed == QDir::homePath()+"/"+tr("Desktop") ){
+ desktopFiles = QDir(changed).entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs ,QDir::Name | QDir::IgnoreCase | QDir::DirsFirst);
+ if(DEBUG){ qDebug() << "New Desktop Files:" << desktopFiles.length(); }
+ emit DesktopFilesChanged();
+ }else if(changed.endsWith("favorites.list")){ emit FavoritesChanged(); }
+ //Now ensure this file was not removed from the watcher
+ if(!watcher->files().contains(changed) && !watcher->directories().contains(changed)){
+ if(!QFile::exists(changed)){
+ //Create the file really quick to ensure it can be watched
+ //TODO
+ }
+ watcher->addPath(changed);
+ }
+}
+
+void LSession::screensChanged(){
+ qDebug() << "Screen Number Changed";
+ if(screenTimer->isActive()){ screenTimer->stop(); }
+ screenTimer->start();
+}
+
+void LSession::screenResized(int scrn){
+ qDebug() << "Screen Resized:" << scrn;
+ if(screenTimer->isActive()){ screenTimer->stop(); }
+ screenTimer->start();
+}
+
+void LSession::checkWindowGeoms(){
+ //Only do one window per run (this will be called once per new window - with time delays between)
+ if(checkWin.isEmpty()){ return; }
+ WId win = checkWin.takeFirst();
+ if(RunningApps.contains(win) ){ //just to make sure it did not close during the delay
+ adjustWindowGeom( win );
+ }
+}
+
+void LSession::checkUserFiles(){
+ //internal version conversion examples:
+ // [1.0.0 -> 1000000], [1.2.3 -> 1002003], [0.6.1 -> 6001]
+ QString OVS = sessionsettings->value("DesktopVersion","0").toString(); //Old Version String
+ bool changed = LDesktopUtils::checkUserFiles(OVS);
+ if(changed){
+ //Save the current version of the session to the settings file (for next time)
+ sessionsettings->setValue("DesktopVersion", this->applicationVersion());
+ }
+}
+
+void LSession::refreshWindowManager(){
+ LUtils::runCmd("touch \""+QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/fluxbox-init\"" );
+}
+
+void LSession::updateDesktops(){
+ qDebug() << " - Update Desktops";
+ QDesktopWidget *DW = this->desktop();
+ int sC = DW->screenCount();
+ qDebug() << " Screen Count:" << sC;
+ qDebug() << " DESKTOPS Length:" << DESKTOPS.length();
+ if(sC<1){ return; } //stop here - no screens available temporarily (displayport/4K issue)
+
+ for(int i=0; i<sC; i++){ qDebug() << " -- Screen["+QString::number(i)+"]:" << DW->screenGeometry(i); }
+
+ bool firstrun = (DESKTOPS.length()==0);
+ bool numchange = DESKTOPS.length()!=sC;
+
+ // If the screen count is changing on us
+ if ( sC != DW->screenCount() ) {
+ qDebug() << "Screen Count changed while running";
+ return;
+ }
+
+ //First clean up any current desktops
+ QList<int> dnums; //keep track of which screens are already managed
+ QList<QRect> geoms;
+ for(int i=0; i<DESKTOPS.length(); i++){
+ if ( DESKTOPS[i]->Screen() < 0 || DESKTOPS[i]->Screen() >= sC || geoms.contains(DW->screenGeometry(DESKTOPS[i]->Screen())) ) {
+ //qDebug() << " - Close desktop:" << i;
+ qDebug() << " - Close desktop on screen:" << DESKTOPS[i]->Screen();
+ DESKTOPS[i]->prepareToClose();
+ DESKTOPS.takeAt(i)->deleteLater();
+ i--;
+ } else {
+ //qDebug() << " - Show desktop:" << i;
+ qDebug() << " - Show desktop on screen:" << DESKTOPS[i]->Screen();
+ DESKTOPS[i]->UpdateGeometry();
+ DESKTOPS[i]->show();
+ dnums << DESKTOPS[i]->Screen();
+ geoms << DW->screenGeometry(DESKTOPS[i]->Screen());
+ }
+ }
+
+ //Now add any new desktops
+ for(int i=0; i<sC; i++){
+ if(!dnums.contains(i) && !geoms.contains(DW->screenGeometry(i)) ){
+ //Start the desktop on this screen
+ qDebug() << " - Start desktop on screen:" << i;
+ DESKTOPS << new LDesktop(i);
+ geoms << DW->screenGeometry(i);
+ }
+ }
+
+ //Make sure fluxbox also gets prompted to re-load screen config if the number of screens changes in the middle of a session
+ if(numchange && !firstrun) {
+ qDebug() << "Update WM";
+ refreshWindowManager();
+ }
+
+ //Make sure all the background windows are registered on the system as virtual roots
+ QTimer::singleShot(100,this, SLOT(registerDesktopWindows()));
+}
+
+void LSession::registerDesktopWindows(){
+ QList<WId> wins;
+ for(int i=0; i<DESKTOPS.length(); i++){
+ wins << DESKTOPS[i]->backgroundID();
+ }
+ XCB->RegisterVirtualRoots(wins);
+}
+
+void LSession::adjustWindowGeom(WId win, bool maximize){
+ //return; //temporary disable
+ if(DEBUG){ qDebug() << "AdjustWindowGeometry():" << win << maximize << XCB->WindowClass(win); }
+ if(XCB->WindowIsFullscreen(win) >=0 ){ return; } //don't touch it
+ //Quick hack for making sure that new windows are not located underneath any panels
+ // Get the window location
+ QRect geom = XCB->WindowGeometry(win, false);
+ //Get the frame size
+ QList<int> frame = XCB->WindowFrameGeometry(win); //[top,bottom,left,right] sizes of the frame
+ //Calculate the full geometry (window + frame)
+ QRect fgeom = QRect(geom.x()-frame[2], geom.y()-frame[0], geom.width()+frame[2]+frame[3], geom.height()+frame[0]+frame[1]);
+ if(DEBUG){
+ qDebug() << "Check Window Geometry:" << XCB->WindowClass(win) << !geom.isNull() << geom << fgeom;
+ }
+ if(geom.isNull()){ return; } //Could not get geometry for some reason
+ //Get the available geometry for the screen the window is on
+ QRect desk;
+ for(int i=0; i<DESKTOPS.length(); i++){
+ if( this->desktop()->screenGeometry(DESKTOPS[i]->Screen()).contains(geom.center()) ){
+ //Window is on this screen
+ if(DEBUG){ qDebug() << " - On Screen:" << DESKTOPS[i]->Screen(); }
+ desk = DESKTOPS[i]->availableScreenGeom();
+ if(DEBUG){ qDebug() << " - Screen Geom:" << desk; }
+ break;
+ }
+ }
+ if(desk.isNull()){ return; } //Unable to determine screen
+ //Adjust the window location if necessary
+ if(maximize){
+ if(DEBUG){ qDebug() << " - Maximizing New Window:" << desk.width() << desk.height(); }
+ geom = desk; //Use the full screen
+ XCB->MoveResizeWindow(win, geom);
+ XCB->MaximizeWindow(win, true); //directly set the appropriate "maximized" flags (bypassing WM)
+
+ }else if(!desk.contains(fgeom) ){
+ //Adjust origin point for left/top margins
+ if(fgeom.y() < desk.y()){ geom.moveTop(desk.y()+frame[0]); fgeom.moveTop(desk.y()); } //move down to the edge (top panel)
+ if(fgeom.x() < desk.x()){ geom.moveLeft(desk.x()+frame[2]); fgeom.moveLeft(desk.x()); } //move right to the edge (left panel)
+ //Adjust size for bottom margins (within reason, since window titles are on top normally)
+ // if(geom.right() > desk.right() && (geom.width() > 100)){ geom.setRight(desk.right()); }
+ if(fgeom.bottom() > desk.bottom() && geom.height() > 10){
+ if(DEBUG){ qDebug() << "Adjust Y:" << fgeom << geom << desk; }
+ int diff = fgeom.bottom()-desk.bottom(); //amount of overlap
+ if(DEBUG){ qDebug() << "Y-Diff:" << diff; }
+ if(diff < 0){ diff = -diff; } //need a positive value
+ if( (fgeom.height()+ diff)< desk.height()){
+ //just move the window - there is room for it above
+ geom.setBottom(desk.bottom()-frame[1]);
+ fgeom.setBottom(desk.bottom());
+ }else if(geom.height() > diff){ //window bigger than the difference
+ //Need to resize the window - keeping the origin point the same
+ geom.setHeight( geom.height()-diff-1 ); //shrink it by the difference (need an extra pixel somewhere)
+ fgeom.setHeight( fgeom.height()-diff );
+ }
+ }
+ //Now move/resize the window
+ if(DEBUG){
+ qDebug() << " - New Geom:" << geom << fgeom;
+ }
+ XCB->WM_Request_MoveResize_Window(win, geom);
+ }
+
+}
+
+void LSession::SessionEnding(){
+ stopSystemTray(); //just in case it was not stopped properly earlier
+}
+
+//===============
+// SYSTEM ACCESS
+//===============
+void LSession::LaunchApplication(QString cmd){
+ LSession::setOverrideCursor(QCursor(Qt::BusyCursor));
+ QProcess::startDetached(cmd);
+}
+
+QFileInfoList LSession::DesktopFiles(){
+ return desktopFiles;
+}
+
+QRect LSession::screenGeom(int num){
+ if(num < 0 || num >= this->desktop()->screenCount() ){ return QRect(); }
+ QRect geom = this->desktop()->screenGeometry(num);
+ return geom;
+}
+
+AppMenu* LSession::applicationMenu(){
+ return appmenu;
+}
+
+SettingsMenu* LSession::settingsMenu(){
+ return settingsmenu;
+}
+
+QSettings* LSession::sessionSettings(){
+ return sessionsettings;
+}
+
+QSettings* LSession::DesktopPluginSettings(){
+ return DPlugSettings;
+}
+
+WId LSession::activeWindow(){
+ //Check the last active window pointer first
+ WId active = XCB->ActiveWindow();
+ //qDebug() << "Check Active Window:" << active << lastActiveWin;
+ if(RunningApps.contains(active)){ lastActiveWin = active; }
+ else if(RunningApps.contains(lastActiveWin) && XCB->WindowState(lastActiveWin) >= LXCB::VISIBLE){} //no change needed
+ else if(RunningApps.contains(lastActiveWin) && RunningApps.length()>1){
+ int start = RunningApps.indexOf(lastActiveWin);
+ if(start<1){ lastActiveWin = RunningApps.length()-1; } //wrap around to the last item
+ else{ lastActiveWin = RunningApps[start-1]; }
+ }else{
+ //Need to change the last active window - find the first one which is visible
+ lastActiveWin = 0; //fallback value - nothing active
+ for(int i=0; i<RunningApps.length(); i++){
+ if(XCB->WindowState(RunningApps[i]) >= LXCB::VISIBLE){
+ lastActiveWin = RunningApps[i];
+ break;
+ }
+ }
+ //qDebug() << " -- New Last Active Window:" << lastActiveWin;
+ }
+ return lastActiveWin;
+}
+
+//Temporarily change the session locale (nothing saved between sessions)
+void LSession::switchLocale(QString localeCode){
+ currTranslator = LUtils::LoadTranslation(this, "lumina-desktop", localeCode, currTranslator);
+ if(currTranslator!=0 || localeCode=="en_US"){
+ LUtils::setLocaleEnv(localeCode); //will set everything to this locale (no custom settings)
+ }
+ emit LocaleChanged();
+}
+
+void LSession::systemWindow(){
+ if(sysWindow==0){ sysWindow = new SystemWindow(); }
+ else{ sysWindow->updateWindow(); }
+ sysWindow->show();
+ //LSession::processEvents();
+}
+
+//Play System Audio
+void LSession::playAudioFile(QString filepath){
+ if( !QFile::exists(filepath) ){ return; }
+ //Setup the audio output systems for the desktop
+ if(DEBUG){ qDebug() << "Play Audio File"; }
+ if(mediaObj==0){ qDebug() << " - Initialize media player"; mediaObj = new QMediaPlayer(); }
+ if(mediaObj !=0 ){
+ if(DEBUG){ qDebug() << " - starting playback:" << filepath; }
+ mediaObj->setVolume(100);
+ mediaObj->setMedia(QUrl::fromLocalFile(filepath));
+ mediaObj->play();
+ LSession::processEvents();
+ }
+ if(DEBUG){ qDebug() << " - Done with Audio File"; }
+}
+// =======================
+// XCB EVENT FILTER FUNCTIONS
+// =======================
+void LSession::RootSizeChange(){
+ qDebug() << "Got Root Size Change";
+ if(DESKTOPS.isEmpty()){ return; } //Initial setup not run yet
+ screenTimer->start();
+}
+
+void LSession::WindowPropertyEvent(){
+ if(DEBUG){ qDebug() << "Window Property Event"; }
+ QList<WId> newapps = XCB->WindowList();
+ if(RunningApps.length() < newapps.length()){
+ //New Window found
+ //qDebug() << "New window found";
+ LSession::restoreOverrideCursor(); //restore the mouse cursor back to normal (new window opened?)
+ //Perform sanity checks on any new window geometries
+ for(int i=0; i<newapps.length() && !TrayStopping; i++){
+ if(!RunningApps.contains(newapps[i])){
+ checkWin << newapps[i];
+ XCB->SelectInput(newapps[i]); //make sure we get property/focus events for this window
+ if(DEBUG){ qDebug() << "New Window - check geom in a moment:" << XCB->WindowClass(newapps[i]); }
+ QTimer::singleShot(50, this, SLOT(checkWindowGeoms()) );
+ }
+ }
+ }
+
+ //Now save the list and send out the event
+ RunningApps = newapps;
+ emit WindowListEvent();
+}
+
+void LSession::WindowPropertyEvent(WId win){
+ //Emit the single-app signal if the window in question is one used by the task manager
+ if(RunningApps.contains(win)){
+ if(DEBUG){ qDebug() << "Single-window property event"; }
+ //emit WindowListEvent();
+ WindowPropertyEvent(); //Run through the entire routine for window checks
+ }else if(RunningTrayApps.contains(win)){
+ emit TrayIconChanged(win);
+ }
+}
+
+void LSession::SysTrayDockRequest(WId win){
+ if(TrayStopping){ return; }
+ attachTrayWindow(win); //Check to see if the window is already registered
+}
+
+void LSession::WindowClosedEvent(WId win){
+ if(TrayStopping){ return; }
+ removeTrayWindow(win); //Check to see if the window is a tray app
+}
+
+void LSession::WindowConfigureEvent(WId win){
+ if(TrayStopping){ return; }
+ if(RunningTrayApps.contains(win)){
+ if(DEBUG){ qDebug() << "SysTray: Configure Event"; }
+ emit TrayIconChanged(win); //trigger a repaint event
+ }else if(RunningApps.contains(win)){
+ WindowPropertyEvent();
+ }
+}
+
+void LSession::WindowDamageEvent(WId win){
+ if(TrayStopping){ return; }
+ if(RunningTrayApps.contains(win)){
+ if(DEBUG){ qDebug() << "SysTray: Damage Event"; }
+ emit TrayIconChanged(win); //trigger a repaint event
+ }
+}
+
+void LSession::WindowSelectionClearEvent(WId win){
+ if(win==SystemTrayID && !TrayStopping){
+ qDebug() << "Stopping system tray";
+ stopSystemTray(true); //make sure to detach all windows
+ }
+}
+
+
+//======================
+// SYSTEM TRAY FUNCTIONS
+//======================
+bool LSession::registerVisualTray(WId visualTray){
+ //Only one visual tray can be registered at a time
+ // (limitation of how tray apps are embedded)
+ if(TrayStopping){ return false; }
+ else if(VisualTrayID==0){ VisualTrayID = visualTray; return true; }
+ else if(VisualTrayID==visualTray){ return true; }
+ else{ return false; }
+}
+
+void LSession::unregisterVisualTray(WId visualTray){
+ if(VisualTrayID==visualTray){
+ qDebug() << "Unregistered Visual Tray";
+ VisualTrayID=0;
+ if(!TrayStopping){ emit VisualTrayAvailable(); }
+ }
+}
+
+QList<WId> LSession::currentTrayApps(WId visualTray){
+ if(visualTray==VisualTrayID){
+ //Check the validity of all the current tray apps (make sure nothing closed erratically)
+ for(int i=0; i<RunningTrayApps.length(); i++){
+ if(XCB->WindowClass(RunningTrayApps[i]).isEmpty()){ RunningTrayApps.removeAt(i); i--; }
+ }
+ return RunningTrayApps;
+ }else if( registerVisualTray(visualTray) ){
+ return RunningTrayApps;
+ }else{
+ return QList<WId>();
+ }
+}
+
+void LSession::startSystemTray(){
+ if(SystemTrayID!=0){ return; }
+ RunningTrayApps.clear(); //nothing running yet
+ SystemTrayID = XCB->startSystemTray(0);
+ TrayStopping = false;
+ if(SystemTrayID!=0){
+ XCB->SelectInput(SystemTrayID); //make sure TrayID events get forwarded here
+ TrayDmgEvent = XCB->GenerateDamageID(SystemTrayID);
+ evFilter->setTrayDamageFlag(TrayDmgEvent);
+ qDebug() << "System Tray Started Successfully";
+ if(DEBUG){ qDebug() << " - System Tray Flags:" << TrayDmgEvent << TrayDmgError; }
+ }
+}
+
+void LSession::stopSystemTray(bool detachall){
+ if(TrayStopping){ return; } //already run
+ qDebug() << "Stopping system tray...";
+ TrayStopping = true; //make sure the internal list does not modified during this
+ //Close all the running Tray Apps
+ QList<WId> tmpApps = RunningTrayApps;
+ RunningTrayApps.clear(); //clear this ahead of time so tray's do not attempt to re-access the apps
+ if(!detachall){
+ for(int i=0; i<tmpApps.length(); i++){
+ qDebug() << " - Stopping tray app:" << XCB->WindowClass(tmpApps[i]);
+ //Tray apps are special and closing the window does not close the app
+ XCB->KillClient(tmpApps[i]);
+ LSession::processEvents();
+ }
+ }
+ //Now close down the tray backend
+ XCB->closeSystemTray(SystemTrayID);
+ SystemTrayID = 0;
+ TrayDmgEvent = 0;
+ TrayDmgError = 0;
+ evFilter->setTrayDamageFlag(0); //turn off tray event handling
+ emit TrayListChanged();
+ LSession::processEvents();
+}
+
+void LSession::attachTrayWindow(WId win){
+ //static int appnum = 0;
+ if(TrayStopping){ return; }
+ if(RunningTrayApps.contains(win)){ return; } //already managed
+ qDebug() << "Session Tray: Window Added";
+ RunningTrayApps << win;
+ LSession::restoreOverrideCursor();
+ if(DEBUG){ qDebug() << "Tray List Changed"; }
+ emit TrayListChanged();
+}
+
+void LSession::removeTrayWindow(WId win){
+ if(SystemTrayID==0){ return; }
+ for(int i=0; i<RunningTrayApps.length(); i++){
+ if(win==RunningTrayApps[i]){
+ qDebug() << "Session Tray: Window Removed";
+ RunningTrayApps.removeAt(i);
+ emit TrayListChanged();
+ break;
+ }
+ }
+}
+//=========================
+// START MENU FUNCTIONS
+//=========================
+bool LSession::registerStartButton(QString ID){
+ if(StartButtonID.isEmpty()){ StartButtonID = ID; }
+ return (StartButtonID==ID);
+}
+
+void LSession::unregisterStartButton(QString ID){
+ if(StartButtonID == ID){
+ StartButtonID.clear();
+ emit StartButtonAvailable();
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LSession.h b/src-qt5/core/lumina-desktop-unified/src-DE/LSession.h
new file mode 100644
index 00000000..bd93289a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LSession.h
@@ -0,0 +1,193 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_SESSION_H
+#define _LUMINA_DESKTOP_SESSION_H
+
+#include <QApplication>
+#include <QDebug>
+#include <QString>
+#include <QX11Info>
+#include <QEvent>
+#include <QTranslator>
+#include <QSettings>
+#include <QProxyStyle>
+#include <QDesktopWidget>
+#include <QList>
+#include <QThread>
+#include <QMediaPlayer>
+#include <QThread>
+#include <QUrl>
+
+#include "Globals.h"
+#include "AppMenu.h"
+#include "SettingsMenu.h"
+#include "SystemWindow.h"
+#include "LDesktop.h"
+//#include "WMProcess.h"
+//#include "BootSplash.h"
+
+#include <LuminaX11.h>
+#include <LuminaSingleApplication.h>
+
+//SYSTEM TRAY STANDARD DEFINITIONS
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+/*class MenuProxyStyle : public QProxyStyle{
+public:
+ int pixelMetric(PixelMetric metric, const QStyleOption *option=0, const QWidget *widget=0) const{
+ if(metric==PM_SmallIconSize){ return 22; } //override QMenu icon size (make it larger)
+ else{ return QProxyStyle::pixelMetric(metric, option, widget); } //use the current style for everything else
+ }
+};*/
+
+class LSession : public LSingleApplication{
+ Q_OBJECT
+public:
+ LSession(int &argc, char **argv);
+ ~LSession();
+ //Functions to be called during startup
+ void setupSession();
+
+ //Public System Tray Functions
+ QList<WId> currentTrayApps(WId visualTray);
+ bool registerVisualTray(WId);
+ void unregisterVisualTray(WId);
+
+ //Public start menu buttons
+ bool registerStartButton(QString ID);
+ void unregisterStartButton(QString ID);
+
+ //Special functions for XCB event filter parsing only
+ // (DO NOT USE MANUALLY)
+ void RootSizeChange();
+ void WindowPropertyEvent();
+ void WindowPropertyEvent(WId);
+ void SysTrayDockRequest(WId);
+ void WindowClosedEvent(WId);
+ void WindowConfigureEvent(WId);
+ void WindowDamageEvent(WId);
+ void WindowSelectionClearEvent(WId);
+
+ //System Access
+ //Return a pointer to the current session
+ static LSession* handle(){
+ return static_cast<LSession*>(LSession::instance());
+ }
+
+ static void LaunchApplication(QString cmd);
+ QFileInfoList DesktopFiles();
+
+ QRect screenGeom(int num);
+
+ AppMenu* applicationMenu();
+ void systemWindow();
+ SettingsMenu* settingsMenu();
+ LXCB *XCB; //class for XCB usage
+
+ QSettings* sessionSettings();
+ QSettings* DesktopPluginSettings();
+
+ //Keep track of which non-desktop window should be treated as active
+ WId activeWindow(); //This will return the last active window if a desktop element is currently active
+
+ //Temporarily change the session locale (nothing saved between sessions)
+ void switchLocale(QString localeCode);
+
+ //Play System Audio
+ void playAudioFile(QString filepath);
+ //Window Adjustment Routine (due to Fluxbox not respecting _NET_WM_STRUT)
+ void adjustWindowGeom(WId win, bool maximize = false);
+
+private:
+ //WMProcess *WM;
+ QList<LDesktop*> DESKTOPS;
+ QFileSystemWatcher *watcher;
+ QTimer *screenTimer;
+
+ //Internal variable for global usage
+ AppMenu *appmenu;
+ SettingsMenu *settingsmenu;
+ SystemWindow *sysWindow;
+ QTranslator *currTranslator;
+ QMediaPlayer *mediaObj;
+ QSettings *sessionsettings, *DPlugSettings;
+ bool cleansession;
+ //QList<QRect> savedScreens;
+
+ //System Tray Variables
+ WId SystemTrayID, VisualTrayID;
+ int TrayDmgEvent, TrayDmgError;
+ QList<WId> RunningTrayApps;
+ bool TrayStopping;
+ //Start Button Variables
+ QString StartButtonID;
+
+ //Task Manager Variables
+ WId lastActiveWin;
+ QList<WId> RunningApps;
+ QList<WId> checkWin;
+ QFileInfoList desktopFiles;
+
+ void CleanupSession();
+
+ int VersionStringToNumber(QString version);
+
+public slots:
+ void StartLogout();
+ void StartShutdown(bool skipupdates = false);
+ void StartReboot(bool skipupdates = false);
+
+ void reloadIconTheme();
+
+private slots:
+ void NewCommunication(QStringList);
+ void launchStartupApps(); //used during initialization
+ void watcherChange(QString);
+ void screensChanged();
+ void screenResized(int);
+ void checkWindowGeoms();
+
+ //System Tray Functions
+ void startSystemTray();
+ void stopSystemTray(bool detachall = false);
+ void attachTrayWindow(WId);
+ void removeTrayWindow(WId);
+
+ //Internal simplification functions
+ void checkUserFiles();
+ void refreshWindowManager();
+ void updateDesktops();
+ void registerDesktopWindows();
+
+
+ void SessionEnding();
+
+signals:
+ //System Tray Signals
+ void VisualTrayAvailable(); //new Visual Tray Plugin can be registered
+ void TrayListChanged(); //Item added/removed from the list
+ void TrayIconChanged(WId); //WinID of Tray App
+ //Start Button signals
+ void StartButtonAvailable();
+ void StartButtonActivated();
+ //Task Manager Signals
+ void WindowListEvent(WId);
+ void WindowListEvent();
+ //General Signals
+ void LocaleChanged();
+ void IconThemeChanged();
+ void DesktopConfigChanged();
+ void SessionConfigChanged();
+ void FavoritesChanged();
+ void DesktopFilesChanged();
+ void WorkspaceChanged();
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.cpp
new file mode 100644
index 00000000..6a6cea0b
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.cpp
@@ -0,0 +1,48 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LWinInfo.h"
+
+#include <LuminaX11.h>
+
+#include "LSession.h"
+
+//Information Retrieval
+ // Don't cache these results because they can change regularly
+QString LWinInfo::text(){
+ if(window==0){ return ""; }
+ QString nm = LSession::handle()->XCB->WindowVisibleIconName(window);
+ if(nm.simplified().isEmpty()){ nm = LSession::handle()->XCB->WindowIconName(window); }
+ if(nm.simplified().isEmpty()){ nm = LSession::handle()->XCB->WindowVisibleName(window); }
+ if(nm.simplified().isEmpty()){ nm = LSession::handle()->XCB->WindowName(window); }
+ if(nm.simplified().isEmpty()){ nm = LSession::handle()->XCB->OldWindowIconName(window); }
+ if(nm.simplified().isEmpty()){ nm = LSession::handle()->XCB->OldWindowName(window); }
+ //Make sure that the text is a reasonable size (40 char limit)
+ //if(nm.length()>40){ nm = nm.left(40)+"..."; }
+ return nm;
+}
+
+QIcon LWinInfo::icon(bool &noicon){
+ if(window==0){ noicon = true; return QIcon();}
+ noicon = false;
+ QIcon ico = LSession::handle()->XCB->WindowIcon(window);
+ //Check for a null icon, and supply one if necessary
+ if(ico.isNull()){ ico = LXDG::findIcon( this->Class().toLower(),""); }
+ if(ico.isNull()){ico = LXDG::findIcon("preferences-system-windows",""); noicon=true;}
+ return ico;
+}
+
+QString LWinInfo::Class(){
+ return LSession::handle()->XCB->WindowClass(window);
+}
+
+LXCB::WINDOWVISIBILITY LWinInfo::status(bool update){
+ if(window==0){ return LXCB::IGNORE; }
+ if(update || cstate == LXCB::IGNORE){
+ cstate = LSession::handle()->XCB->WindowState(window);
+ }
+ return cstate;
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.h b/src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.h
new file mode 100644
index 00000000..3d2ea65a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LWinInfo.h
@@ -0,0 +1,50 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_WINDOW_INFO_H
+#define _LUMINA_DESKTOP_WINDOW_INFO_H
+
+// Qt includes
+#include <QString>
+#include <QPixmap>
+#include <QIcon>
+#include <QPainter>
+
+// libLumina includes
+#include <LuminaX11.h>
+#include <LuminaXDG.h>
+
+// Local includes
+//#include "Globals.h" //For the STATES enumeration definition
+//#include "LSession.h"
+
+
+class LWinInfo{
+private:
+ WId window;
+ LXCB::WINDOWVISIBILITY cstate; //current window state
+
+public:
+ LWinInfo(WId id = 0){
+ window = id;
+ cstate = LXCB::IGNORE; //make sure this gets updates with the first "status" call
+ }
+ ~LWinInfo(){};
+
+ //The current window ID
+ WId windowID(){
+ return window;
+ }
+
+ //Information Retrieval
+ // Don't cache these results because they can change regularly
+ QString text();
+ QIcon icon(bool &noicon);
+ QString Class();
+ LXCB::WINDOWVISIBILITY status(bool update = false);
+};
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.cpp
new file mode 100644
index 00000000..ca7fb38d
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.cpp
@@ -0,0 +1,118 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LXcbEventFilter.h"
+
+//For all the XCB interactions and atoms
+// is accessed via
+// session->XCB->EWMH.(atom name)
+// session->XCB->(do something)
+#include <LuminaX11.h>
+#include <QDebug>
+
+XCBEventFilter::XCBEventFilter(LSession *sessionhandle) : QAbstractNativeEventFilter(){
+ session = sessionhandle; //save this for interaction with the session later
+ TrayDmgFlag = 0;
+ stopping = false;
+ session->XCB->SelectInput(QX11Info::appRootWindow()); //make sure we get root window events
+ InitAtoms();
+}
+
+void XCBEventFilter::setTrayDamageFlag(int flag){
+ //Special flag for system tray damage events
+ TrayDmgFlag = flag + XCB_DAMAGE_NOTIFY; //save the whole flag (no calculations later)
+}
+
+//This function format taken directly from the Qt5.3 documentation
+bool XCBEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *){
+ if(stopping){ return false; } //don't do any parsing
+ //qDebug() << "New Event";
+ if(eventType=="xcb_generic_event_t"){
+ //qDebug() << " - XCB event";
+ //Convert to known event type (for X11 systems)
+ xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
+ //Now parse the event and emit signals as necessary
+ switch( ev->response_type & ~0x80){
+//==============================
+ case XCB_PROPERTY_NOTIFY:
+ //qDebug() << "Property Notify Event:";
+ //qDebug() << " - Root Window:" << QX11Info::appRootWindow();
+ //qDebug() << " - Given Window:" << ((xcb_property_notify_event_t*)ev)->window;
+ //System-specific proprty change
+ if( ((xcb_property_notify_event_t*)ev)->window == QX11Info::appRootWindow() \
+ && ( ( ((xcb_property_notify_event_t*)ev)->atom == session->XCB->EWMH._NET_DESKTOP_GEOMETRY) \
+ || (((xcb_property_notify_event_t*)ev)->atom == session->XCB->EWMH._NET_WORKAREA) )){
+ session->RootSizeChange();
+ }else if( ((xcb_property_notify_event_t*)ev)->window == QX11Info::appRootWindow() \
+ && ( ( ((xcb_property_notify_event_t*)ev)->atom == session->XCB->EWMH._NET_CURRENT_DESKTOP) )){
+ //qDebug() << "Got Workspace Change";
+ session->emit WorkspaceChanged();
+ }else if( SysNotifyAtoms.contains( ((xcb_property_notify_event_t*)ev)->atom ) ){
+ //Update the status/list of all running windows
+ session->WindowPropertyEvent();
+
+ //window-specific property change
+ }else if( WinNotifyAtoms.contains( ((xcb_property_notify_event_t*)ev)->atom ) ){
+ //Ping only that window
+ //session->WindowPropertyEvent( ((xcb_property_notify_event_t*)ev)->window );
+ session->WindowPropertyEvent();
+ }
+ break;
+//==============================
+ case XCB_CLIENT_MESSAGE:
+ //qDebug() << "Client Message Event";
+ //qDebug() << " - Root Window:" << QX11Info::appRootWindow();
+ //qDebug() << " - Given Window:" << ((xcb_client_message_event_t*)ev)->window;
+ if( TrayDmgFlag!=0 && ((xcb_client_message_event_t*)ev)->type == _NET_SYSTEM_TRAY_OPCODE && ((xcb_client_message_event_t*)ev)->format == 32){
+ //data32[0] is timestamp, [1] is opcode, [2] is window handle
+ if(SYSTEM_TRAY_REQUEST_DOCK == ((xcb_client_message_event_t*)ev)->data.data32[1]){
+ session->SysTrayDockRequest( ((xcb_client_message_event_t*)ev)->data.data32[2] );
+ }
+ //Ignore the System Tray messages at the moment (let the WM handle it)
+
+ //window-specific property changes
+ /*}else if( ((xcb_client_message_event_t*)ev)->type == session->XCB->EWMH._NET_WM_STATE ){
+ if( session->XCB->WindowIsMaximized( ((xcb_client_message_event_t*)ev)->window ) ){
+ //Quick fix for maximized windows (since Fluxbox is not doing the STRUT detection properly)
+ session->adjustWindowGeom( ((xcb_client_message_event_t*)ev)->window );
+ }
+ session->WindowPropertyEvent( ((xcb_client_message_event_t*)ev)->window );*/
+ }else if( WinNotifyAtoms.contains( ((xcb_client_message_event_t*)ev)->type ) ){
+ //Ping only that window
+ //session->WindowPropertyEvent( ((xcb_client_message_event_t*)ev)->window );
+ session->WindowPropertyEvent();
+ }
+ break;
+//==============================
+ case XCB_DESTROY_NOTIFY:
+ //qDebug() << "Window Closed Event";
+ session->WindowClosedEvent( ( (xcb_destroy_notify_event_t*)ev )->window );
+ break;
+//==============================
+ case XCB_CONFIGURE_NOTIFY:
+ //qDebug() << "Configure Notify Event";
+ session->WindowConfigureEvent( ((xcb_configure_notify_event_t*)ev)->window );
+ break;
+//==============================
+ case XCB_SELECTION_CLEAR:
+ //qDebug() << "Selection Clear Event";
+ session->WindowSelectionClearEvent( ((xcb_selection_clear_event_t*)ev)->owner );
+ break;
+//==============================
+ default:
+ if(TrayDmgFlag!=0){
+ //if( (ev->response_type & ~0x80)==TrayDmgFlag){
+ session->WindowDamageEvent( ((xcb_damage_notify_event_t*)ev)->drawable );
+ //}
+ }/*else{
+ qDebug() << "Default Event:" << (ev->response_type & ~0x80);
+ }*/
+//==============================
+ }
+ }
+ //qDebug() << " - finished event";
+ return false; //make sure the handling keeps going (transparent watching of events)
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.h b/src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.h
new file mode 100644
index 00000000..c56471c9
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/LXcbEventFilter.h
@@ -0,0 +1,104 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class provides the XCB ->Xlib conversion necessary for Qt5 usage
+//===========================================
+#ifndef _LUMINA_DESKTOP_XCB_FILTER_H
+#define _LUMINA_DESKTOP_XCB_FILTER_H
+
+#include <QAbstractNativeEventFilter>
+#include <QList>
+#include <QStringList>
+#include <QX11Info>
+
+#include <xcb/xcb.h>
+#include <xcb/xproto.h>
+#include <xcb/damage.h>
+#include <xcb/xcb_atom.h>
+#include "LSession.h"
+
+/*
+List of XCB response types (since almost impossible to find good docs on XCB)
+switch (xcb_generic_event_t*->response_type & ~0x80)
+case values:
+XCB_KEY_[PRESS | RELEASE]
+XCB_BUTTON_[PRESS | RELEASE]
+XCB_MOTION_NOTIFY
+XCB_ENTER_NOTIFY
+XCB_LEAVE_NOTIFY
+XCB_FOCUS_[IN | OUT]
+XCB_KEYMAP_NOTIFY
+XCB_EXPOSE
+XCB_GRAPHICS_EXPOSURE
+XCB_VISIBILITY_NOTIFY
+XCB_CREATE_NOTIFY
+XCB_DESTROY_NOTIFY
+XCB_UNMAP_NOTIFY
+XCB_MAP_[NOTIFY | REQUEST]
+XCB_REPARENT_NOTIFY
+XCB_CONFIGURE_[NOTIFY | REQUEST]
+XCB_GRAVITY_NOTIFY
+XCB_RESIZE_REQUEST
+XCB_CIRCULATE_[NOTIFY | REQUEST]
+XCB_PROPERTY_NOTIFY
+XCB_SELECTION_[CLEAR | REQUEST | NOTIFY]
+XCB_COLORMAP_NOTIFY
+XCB_CLIENT_MESSAGE
+
+*/
+
+//SYSTEM TRAY STANDARD DEFINITIONS
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+class XCBEventFilter : public QAbstractNativeEventFilter{
+private:
+ LSession *session;
+ xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
+ QList<xcb_atom_t> WinNotifyAtoms, SysNotifyAtoms;
+ int TrayDmgFlag; //internal damage event offset value for the system tray
+ bool stopping;
+
+ void InitAtoms(){
+ //Initialize any special atoms that we need to save/use regularly
+ //NOTE: All the EWMH atoms are already saved in session->XCB->EWMH
+ WinNotifyAtoms.clear();
+ WinNotifyAtoms << session->XCB->EWMH._NET_WM_NAME \
+ << session->XCB->EWMH._NET_WM_VISIBLE_NAME \
+ << session->XCB->EWMH._NET_WM_ICON_NAME \
+ << session->XCB->EWMH._NET_WM_VISIBLE_ICON_NAME \
+ << session->XCB->EWMH._NET_WM_ICON \
+ << session->XCB->EWMH._NET_WM_ICON_GEOMETRY;
+
+ SysNotifyAtoms.clear();
+ SysNotifyAtoms << session->XCB->EWMH._NET_CLIENT_LIST \
+ << session->XCB->EWMH._NET_CLIENT_LIST_STACKING \
+ << session->XCB->EWMH._NET_CURRENT_DESKTOP \
+ << session->XCB->EWMH._NET_WM_STATE \
+ << session->XCB->EWMH._NET_ACTIVE_WINDOW \
+ << session->XCB->EWMH._NET_WM_ICON \
+ << session->XCB->EWMH._NET_WM_ICON_GEOMETRY;
+ //_NET_SYSTEM_TRAY_OPCODE
+ xcb_intern_atom_cookie_t cookie = xcb_intern_atom(QX11Info::connection(), 0, 23,"_NET_SYSTEM_TRAY_OPCODE");
+ xcb_intern_atom_reply_t *r = xcb_intern_atom_reply(QX11Info::connection(), cookie, NULL);
+ if(r){
+ _NET_SYSTEM_TRAY_OPCODE = r->atom;
+ free(r);
+ }
+ }
+
+public:
+ XCBEventFilter(LSession *sessionhandle);
+ void setTrayDamageFlag(int flag);
+ void StopEventHandling(){ stopping = true; }
+
+ //This function format taken directly from the Qt5.3 documentation
+ virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *) Q_DECL_OVERRIDE;
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.cpp
new file mode 100644
index 00000000..58208931
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.cpp
@@ -0,0 +1,67 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "SettingsMenu.h"
+#include "LSession.h"
+
+#include <LuminaOS.h>
+
+SettingsMenu::SettingsMenu() : QMenu(){
+ this->setObjectName("SettingsMenu");
+ connect(this, SIGNAL(triggered(QAction*)), this, SLOT(runApp(QAction*)) );
+ connect(QApplication::instance(), SIGNAL(LocaleChanged()), this, SLOT(UpdateMenu()) );
+ connect(QApplication::instance(), SIGNAL(IconThemeChanged()), this, SLOT(UpdateMenu()) );
+ QTimer::singleShot(100, this, SLOT(UpdateMenu()) );
+}
+
+SettingsMenu::~SettingsMenu(){
+
+}
+
+void SettingsMenu::UpdateMenu(){
+ //Change the title/icon to account for locale/icon changes
+ this->setTitle( tr("Preferences") );
+ this->setIcon( LXDG::findIcon("configure","") );
+ this->clear();
+ //Now setup the possible configuration options
+ QAction *act = new QAction(LXDG::findIcon("preferences-desktop-screensaver",""), tr("Screensaver"), this);
+ act->setWhatsThis("xscreensaver-demo");
+ this->addAction(act);
+ act = new QAction( LXDG::findIcon("preferences-desktop-wallpaper",""), tr("Wallpaper"), this);
+ act->setWhatsThis("lumina-config --page wallpaper");
+ this->addAction(act);
+ act = new QAction( LXDG::findIcon("preferences-other",""), tr("Display"), this);
+ act->setWhatsThis("lumina-xconfig");
+ this->addAction(act);
+ act = new QAction( LXDG::findIcon("preferences-desktop",""), tr("All Desktop Settings"), this);
+ act->setWhatsThis("lumina-config");
+ this->addAction(act);
+ this->addSeparator();
+ /*QString qtconfig = LOS::QtConfigShortcut();
+ if(QFile::exists(qtconfig) && !qtconfig.isEmpty()){
+ act = new QAction( LXDG::findIcon("preferences-desktop-theme",""), tr("Window Theme"), this);
+ act->setWhatsThis(qtconfig);
+ this->addAction(act);
+ }*/
+ QString CONTROLPANEL = LOS::ControlPanelShortcut();
+ if(QFile::exists(CONTROLPANEL) && !CONTROLPANEL.isEmpty()){
+ //Now load the info
+ XDGDesktop cpan(CONTROLPANEL);
+ if(cpan.isValid()){
+ act = new QAction( LXDG::findIcon(cpan.icon,""), tr("Control Panel"), this);
+ act->setWhatsThis("lumina-open \""+CONTROLPANEL+"\"");
+ this->addAction(act);
+ }
+ }
+ act = new QAction( LXDG::findIcon("lumina",""), tr("About Lumina"), this);
+ act->setWhatsThis("lumina-info");
+ this->addAction(act);
+}
+
+
+void SettingsMenu::runApp(QAction* act){
+ LSession::LaunchApplication(act->whatsThis());
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.h b/src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.h
new file mode 100644
index 00000000..eeabab85
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/SettingsMenu.h
@@ -0,0 +1,28 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_SETTINGS_MENU_H
+#define _LUMINA_DESKTOP_SETTINGS_MENU_H
+
+#include <QMenu>
+#include <QProcess>
+#include <QAction>
+
+#include <LuminaXDG.h>
+
+class SettingsMenu : public QMenu{
+ Q_OBJECT
+public:
+ SettingsMenu();
+ ~SettingsMenu();
+
+private slots:
+ void UpdateMenu();
+ void runApp(QAction* act);
+
+};
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.cpp
new file mode 100644
index 00000000..1c0b59a5
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.cpp
@@ -0,0 +1,104 @@
+#include "SystemWindow.h"
+#include "ui_SystemWindow.h"
+
+#include "LSession.h"
+#include <LuminaOS.h>
+#include <QPoint>
+#include <QCursor>
+#include <QDebug>
+#include <QProcess>
+#include <QDesktopWidget>
+#include <QMessageBox>
+
+SystemWindow::SystemWindow() : QDialog(), ui(new Ui::SystemWindow){
+ ui->setupUi(this); //load the designer file
+ //Setup the window flags
+ this->setWindowFlags( Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
+ //Setup the icons based on the current theme
+ ui->tool_logout->setIcon( LXDG::findIcon("system-log-out","") );
+ ui->tool_restart->setIcon( LXDG::findIcon("system-reboot","") );
+ ui->tool_shutdown->setIcon( LXDG::findIcon("system-shutdown","") );
+ ui->tool_suspend->setIcon( LXDG::findIcon("system-suspend","") );
+ ui->push_cancel->setIcon( LXDG::findIcon("dialog-cancel","") );
+ ui->push_lock->setIcon( LXDG::findIcon("system-lock-screen","") );
+ //Connect the signals/slots
+ connect(ui->tool_logout, SIGNAL(clicked()), this, SLOT(sysLogout()) );
+ connect(ui->tool_restart, SIGNAL(clicked()), this, SLOT(sysRestart()) );
+ connect(ui->tool_shutdown, SIGNAL(clicked()), this, SLOT(sysShutdown()) );
+ connect(ui->tool_suspend, SIGNAL(clicked()), this, SLOT(sysSuspend()) );
+ connect(ui->push_cancel, SIGNAL(clicked()), this, SLOT(sysCancel()) );
+ connect(ui->push_lock, SIGNAL(clicked()), this, SLOT(sysLock()) );
+ //Disable buttons if necessary
+ updateWindow();
+ ui->tool_suspend->setVisible(LOS::systemCanSuspend()); //does not change with time - just do a single check
+ connect(QApplication::instance(), SIGNAL(LocaleChanged()), this, SLOT(updateWindow()) );
+ connect(QApplication::instance(), SIGNAL(IconThemeChanged()), this, SLOT(updateWindow()) );
+}
+
+SystemWindow::~SystemWindow(){
+
+}
+
+void SystemWindow::updateWindow(){
+ //Disable the shutdown/restart buttons if necessary
+ ui->retranslateUi(this);
+ bool ok = LOS::userHasShutdownAccess();
+ ui->tool_restart->setEnabled(ok);
+ ui->tool_shutdown->setEnabled(ok);
+ //Center this window on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(QCursor::pos()).center(); //get the center of the current screen
+ this->move(center.x() - this->width()/2, center.y() - this->height()/2);
+}
+
+bool SystemWindow::promptAboutUpdates(bool &skip){
+ QString pending = LOS::systemPendingUpdates();
+ if(pending.isEmpty()){ skip = false; } //continue without skip
+ else{
+ QMessageBox dlg(QMessageBox::Question, tr("Apply Updates?"), tr("You have system updates waiting to be applied! Do you wish to install them now?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this);
+ dlg.setDetailedText(pending);
+ dlg.setDefaultButton(QMessageBox::Yes);
+ dlg.show();
+ int ret = dlg.exec();
+ if(ret == QMessageBox::Cancel){ return false; } //do not continue
+ else{ skip = (ret==QMessageBox::No); }
+ }
+ return true;
+}
+
+void SystemWindow::sysLogout(){
+ this->close();
+ LSession::processEvents();
+ QTimer::singleShot(0, LSession::handle(), SLOT(StartLogout()) );
+}
+
+void SystemWindow::sysRestart(){
+ bool skip = false;
+ if(!promptAboutUpdates(skip)){ this->close(); return; } //cancelled
+ this->close();
+ LSession::processEvents();
+ LSession::handle()->StartReboot(skip);
+}
+
+void SystemWindow::sysShutdown(){
+ bool skip = false;
+ if(!promptAboutUpdates(skip)){ this->close(); return; } //cancelled
+ this->close();
+ LSession::processEvents();
+ LSession::handle()->StartShutdown(skip);
+}
+
+void SystemWindow::sysSuspend(){
+ this->hide();
+ LSession::processEvents();
+ //Make sure to lock the system first (otherwise anybody can access it again)
+ LUtils::runCmd("xscreensaver-command -lock");
+ //Now suspend the system
+ LOS::systemSuspend();
+}
+
+void SystemWindow::sysLock(){
+ this->hide();
+ LSession::processEvents();
+ qDebug() << "Locking the desktop...";
+ QProcess::startDetached("xscreensaver-command -lock");
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.h b/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.h
new file mode 100644
index 00000000..bbef36a3
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.h
@@ -0,0 +1,46 @@
+#ifndef _LUMINA_DESKTOP_SYSTEM_WINDOW_H
+#define _LUMINA_DESKTOP_SYSTEM_WINDOW_H
+
+#include <QDialog>
+
+#include "ui_SystemWindow.h"
+
+
+
+
+namespace Ui{
+ class SystemWindow;
+};
+
+class SystemWindow : public QDialog{
+ Q_OBJECT
+public:
+ SystemWindow();
+ ~SystemWindow();
+
+public slots:
+ void updateWindow();
+
+private:
+ Ui::SystemWindow *ui;
+
+ //void closeAllWindows();
+ bool promptAboutUpdates(bool &skip); //main bool return: continue/cancel, skip: skip updates or not
+
+private slots:
+ void sysLogout();
+
+ void sysRestart();
+
+ void sysShutdown();
+
+ void sysSuspend();
+
+ void sysCancel(){
+ this->close();
+ }
+
+ void sysLock();
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.ui b/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.ui
new file mode 100644
index 00000000..9e25509b
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/SystemWindow.ui
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SystemWindow</class>
+ <widget class="QDialog" name="SystemWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>289</width>
+ <height>135</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>System Options</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="tool_logout">
+ <property name="text">
+ <string>Log Out</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_restart">
+ <property name="text">
+ <string>Restart</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_shutdown">
+ <property name="text">
+ <string>Shutdown</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QToolButton" name="push_cancel">
+ <property name="text">
+ <string>Cancel</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="push_lock">
+ <property name="text">
+ <string>Lock</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_suspend">
+ <property name="text">
+ <string>Suspend</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextUnderIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.cpp
new file mode 100644
index 00000000..545ba430
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.cpp
@@ -0,0 +1,63 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LDPlugin.h"
+
+#include "../LSession.h"
+#include <LuminaXDG.h>
+
+LDPlugin::LDPlugin(QWidget *parent, QString id) : QFrame(parent){
+ PLUGID=id;
+ prefix = id.replace("/","_")+"/";
+ //qDebug() << "ID:" << PLUGID << prefix;
+ settings = LSession::handle()->DesktopPluginSettings();
+ //Setup the plugin system control menu
+ menu = new QMenu(this);
+ setupMenu();
+ //Setup the internal timer for when to start/stop drag events
+ dragTimer = new QTimer(this);
+ dragTimer->setSingleShot(true);
+ dragTimer->setInterval(500); //1/2 second to show the plugin menu
+ connect(dragTimer, SIGNAL(timeout()), this, SLOT(showPluginMenu()));
+ //Use plugin-specific values for stylesheet control (applauncher, desktopview, etc...)
+ this->setObjectName(id.section("---",0,0).section("::",0,0));
+ this->setContextMenuPolicy(Qt::CustomContextMenu);
+ this->setMouseTracking(false); //only catch mouse movement events if the mouse is clicked/held on the plugin
+ connect(QApplication::instance(), SIGNAL(LocaleChanged()), this, SLOT(LocaleChange()) );
+ connect(QApplication::instance(), SIGNAL(IconThemeChanged()), this, SLOT(ThemeChange()) );
+ connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showPluginMenu()) );
+}
+
+void LDPlugin::setupMenu(){
+ menu->clear();
+ //SPECIAL CONTEXT MENU OPTIONS FOR PARTICULAR PLUGIN TYPES
+ if(PLUGID.startsWith("applauncher::")){
+ menu->addAction( LXDG::findIcon("quickopen",""), tr("Launch Item"), this, SIGNAL(PluginActivated()) );
+ menu->addSeparator();
+ }
+ //General Options
+ menu->addAction( LXDG::findIcon("transform-move",""), tr("Start Moving Item"), this, SLOT(slotStartMove()) );
+ menu->addAction( LXDG::findIcon("transform-scale",""), tr("Start Resizing Item"), this, SLOT(slotStartResize()) );
+ menu->addSeparator();
+ menu->addAction( LXDG::findIcon("zoom-in",""), tr("Increase Item Sizes"), this, SIGNAL(IncreaseIconSize()) );
+ menu->addAction( LXDG::findIcon("zoom-out",""), tr("Decrease Item Sizes"), this, SIGNAL(DecreaseIconSize()) );
+ menu->addSeparator();
+ menu->addAction( LXDG::findIcon("edit-delete",""), tr("Remove Item"), this, SLOT(slotRemovePlugin()) );
+}
+
+/*void LDPlugin::setInitialSize(int width, int height){
+ //Note: Only run this in the plugin initization routine:
+ // if the plugin is completely new (first time used), it will be this size
+ if(settings->allKeys().filter(prefix+"location").isEmpty()){
+ //Brand new plugin: set initial size
+ //qDebug() << "Setting Initial Size:" << PLUGID << width << height;
+ settings->setValue(prefix+"location/width",width);
+ settings->setValue(prefix+"location/height",height);
+ settings->sync();
+ }
+ //Now make sure the plugin is the saved size right away
+ this->resize( settings->value(prefix+"location/width").toInt(), settings->value(prefix+"location/height").toInt());
+}*/
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.h
new file mode 100644
index 00000000..820880ed
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/LDPlugin.h
@@ -0,0 +1,156 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is the generic container layout for all desktop plugins
+// Simply subclass this when creating a new plugin to enable correct
+// visibility and usage within the desktop window
+//===========================================
+// WARNING: Do *not* setup a custom context menu for the entire plugins area!
+// This can prevent access to the general desktop context menu if
+// the plugin was maximized to fill the desktop area!
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKTOP_PLUGIN_H
+#define _LUMINA_DESKTOP_DESKTOP_PLUGIN_H
+
+#include <QObject>
+#include <QFrame>
+#include <QWidget>
+#include <QString>
+#include <QDebug>
+#include <QSettings>
+#include <QMoveEvent>
+#include <QResizeEvent>
+#include <QMouseEvent>
+#include <QTimer>
+#include <QMenu>
+
+class LDPlugin : public QFrame{
+ Q_OBJECT
+
+private:
+ QString PLUGID, prefix;
+ QSettings *settings;
+ QMenu *menu;
+ QTimer *dragTimer;
+
+ void setupMenu();
+
+public:
+ LDPlugin(QWidget *parent = 0, QString id="unknown");
+
+ ~LDPlugin(){}
+
+ QString ID(){
+ return PLUGID;
+ }
+
+ virtual QSize defaultPluginSize(){
+ //This needs to be re-implemented in the subclassed plugin
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(1,1); //1x1 grid size
+ }
+
+ void savePluginGeometry(QRect geom){
+ settings->setValue(prefix+"geometry/desktopGridPoints", geom);
+ settings->sync();
+ }
+
+ QRect loadPluginGeometry(){
+ return settings->value(prefix+"geometry/desktopGridPoints", QRect()).toRect();
+ }
+
+ void saveSetting(QString var, QVariant val){
+ //qDebug() << "Saving Setting:" << prefix+var+QString(" = ")+val.toString();
+ settings->setValue(prefix+var, val);
+ settings->sync();
+ }
+
+ QVariant readSetting(QString var, QVariant defaultval){
+ return settings->value(prefix+var, defaultval);
+ }
+
+ virtual void Cleanup(){
+ //This needs to be re-implemented in the subclassed plugin
+ //This is where any last-minute changes are performed before a plugin is removed permanently
+ //Note1: This is *not* called if the plugin is being temporarily closed
+ //Note2: All the settings for this plugin will be automatically removed after this is finished
+ }
+
+ void removeSettings(bool permanent = false){ //such as when a plugin is deleted
+ if(permanent){ Cleanup(); }
+ QStringList list = settings->allKeys().filter(prefix);
+ for(int i=0; i<list.length(); i++){ settings->remove(list[i]); }
+
+ }
+
+public slots:
+ virtual void LocaleChange(){
+ //This needs to be re-implemented in the subclassed plugin
+ //This is where all text is set/translated
+ setupMenu();
+ }
+ virtual void ThemeChange(){
+ //This needs to be re-implemented in the subclassed plugin
+ //This is where all the visuals are set if using Theme-dependant icons.
+ setupMenu();
+ }
+ void showPluginMenu(){
+ emit CloseDesktopMenu();
+ menu->popup( QCursor::pos() );
+ }
+
+signals:
+ void OpenDesktopMenu();
+ void CloseDesktopMenu();
+ void PluginResized();
+ void PluginActivated();
+
+ //Signals for communication with the desktop layout system (not generally used by hand)
+ void StartMoving(QString); //ID of plugin
+ void StartResizing(QString); //ID of plugin
+ void RemovePlugin(QString); //ID of plugin
+ void IncreaseIconSize(); // only used for desktop icons
+ void DecreaseIconSize(); // only used for desktop icons
+
+private slots:
+ void slotStartMove(){
+ QCursor::setPos( this->mapToGlobal(QPoint(this->width()/2, this->height()/2)) );
+ emit StartMoving(PLUGID);
+ }
+
+ void slotStartResize(){
+ QCursor::setPos( this->mapToGlobal(QPoint(this->width()/2, this->height()/2)) );
+ emit StartResizing(PLUGID);
+ }
+
+ void slotRemovePlugin(){
+ removeSettings(true);
+ emit RemovePlugin(PLUGID);
+ }
+
+protected:
+ void mousePressEvent(QMouseEvent *ev){
+ if(!dragTimer->isActive() && ev->buttons().testFlag(Qt::LeftButton) ){ dragTimer->start(); }
+ QWidget::mousePressEvent(ev);
+ }
+ void mouseReleaseEvent(QMouseEvent *ev){
+ if(dragTimer->isActive()){ dragTimer->stop(); }
+ QWidget::mouseReleaseEvent(ev);
+ }
+ void mouseMoveEvent(QMouseEvent *ev){
+ if(ev->buttons().testFlag(Qt::LeftButton)){
+ if(dragTimer->isActive()){ dragTimer->stop(); }
+ slotStartMove();
+ }
+ QWidget::mouseMoveEvent(ev);
+ }
+ void resizeEvent(QResizeEvent *ev){
+ emit PluginResized();
+ QFrame::resizeEvent(ev); //do normal processing
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/NewDP.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/NewDP.h
new file mode 100644
index 00000000..e28b8c61
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/NewDP.h
@@ -0,0 +1,63 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is the interface to load all the different desktop plugins
+//===========================================
+#ifndef _LUMINA_DESKTOP_NEW_DESKTOP_PLUGIN_H
+#define _LUMINA_DESKTOP_NEW_DESKTOP_PLUGIN_H
+
+#include <QDebug>
+
+//List all the individual plugin includes here
+#include "LDPlugin.h"
+//#include "SamplePlugin.h"
+#include "calendar/CalendarPlugin.h"
+#include "applauncher/AppLauncherPlugin.h"
+#include "desktopview/DesktopViewPlugin.h"
+#include "notepad/NotepadPlugin.h"
+#include "audioplayer/PlayerWidget.h"
+#include "systemmonitor/MonitorWidget.h"
+//#include "quickcontainer/QuickDPlugin.h"
+//#include "messagecenter/MessageCenter.h"
+#include "rssreader/RSSFeedPlugin.h"
+
+class NewDP{
+public:
+ static LDPlugin* createPlugin(QString plugin, QWidget* parent=0){
+ //qDebug() << "Create Plugin:" << plugin;
+ LDPlugin *plug = 0;
+ /*if(plugin.section("---",0,0)=="sample"){
+ plug = new SamplePlugin(parent, plugin);
+ }else */
+ if(plugin.section("---",0,0)=="calendar"){
+ plug = new CalendarPlugin(parent, plugin);
+ }else if(plugin.section("---",0,0).section("::",0,0)=="applauncher"){
+ //This plugin can be pre-initialized to a file path after the "::" delimiter
+ plug = new AppLauncherPlugin(parent, plugin);
+ }else if(plugin.section("---",0,0)=="desktopview"){
+ plug = new DesktopViewPlugin(parent, plugin);
+ }else if(plugin.section("---",0,0)=="notepad"){
+ plug = new NotePadPlugin(parent, plugin);
+ }else if(plugin.section("---",0,0)=="audioplayer"){
+ plug = new AudioPlayerPlugin(parent, plugin);
+ }else if(plugin.section("---",0,0)=="systemmonitor"){
+ plug = new SysMonitorPlugin(parent, plugin);
+ //}else if(plugin.section("---",0,0)=="messagecenter"){
+ //plug = new MessageCenterPlugin(parent, plugin);
+ //}else if(plugin.section("---",0,0).startsWith("quick-") && LUtils::validQuickPlugin(plugin.section("---",0,0)) ){
+ //plug = new QuickDPlugin(parent, plugin);
+ }else if(plugin.section("---",0,0)=="rssreader"){
+ plug = new RSSFeedPlugin(parent, plugin);
+ }else{
+ qWarning() << "Invalid Desktop Plugin:"<<plugin << " -- Ignored";
+ }
+ //qDebug() << " -- done";
+ return plug;
+ }
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/SamplePlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/SamplePlugin.h
new file mode 100644
index 00000000..4a790c2d
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/SamplePlugin.h
@@ -0,0 +1,38 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a quick sample desktop plugin
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKTOP_PLUGIN_SAMPLE_H
+#define _LUMINA_DESKTOP_DESKTOP_PLUGIN_SAMPLE_H
+
+#include <QPushButton>
+#include <QMessageBox>
+#include <QVBoxLayout>
+#include "LDPlugin.h"
+
+class SamplePlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ SamplePlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ this->setLayout( new QVBoxLayout());
+ this->layout()->setContentsMargins(0,0,0,0);
+ button = new QPushButton("sample");
+ this->layout()->addWidget(button);
+ connect(button, SIGNAL(clicked()), this, SLOT(showMessage()) );
+ }
+
+ ~SamplePlugin(){}
+
+private:
+ QPushButton *button;
+
+private slots:
+ void showMessage(){
+ QMessageBox::information(this,"sample","sample desktop plugin works");
+ }
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.cpp
new file mode 100644
index 00000000..3be19faa
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.cpp
@@ -0,0 +1,145 @@
+#include "AppLauncherPlugin.h"
+#include "../../LSession.h"
+#include "OutlineToolButton.h"
+
+#define OUTMARGIN 10 //special margin for fonts due to the outlining effect from the OutlineToolbutton
+
+AppLauncherPlugin::AppLauncherPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ QVBoxLayout *lay = new QVBoxLayout();
+ this->setLayout(lay);
+ lay->setContentsMargins(0,0,0,0);
+ button = new OutlineToolButton(this);
+ button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
+ button->setAutoRaise(true);
+ button->setText("...\n..."); //Need to set something here so that initial sizing works properly
+ button->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
+ lay->addWidget(button, 0, Qt::AlignCenter);
+ connect(button, SIGNAL(DoubleClicked()), this, SLOT(buttonClicked()) );
+ button->setContextMenuPolicy(Qt::NoContextMenu);
+ watcher = new QFileSystemWatcher(this);
+ connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT( loadButton()) );
+
+ connect(this, SIGNAL(PluginActivated()), this, SLOT(buttonClicked()) ); //in case they use the context menu to launch it.
+ loadButton();
+ //QTimer::singleShot(0,this, SLOT(loadButton()) );
+}
+
+void AppLauncherPlugin::Cleanup(){
+ //This is run only when the plugin was forcibly closed/removed
+
+}
+
+void AppLauncherPlugin::loadButton(){
+ QString def = this->ID().section("::",1,50).section("---",0,0).simplified();
+ QString path = this->readSetting("applicationpath",def).toString(); //use the default if necessary
+ //qDebug() << "Default Application Launcher:" << def << path;
+ bool ok = QFile::exists(path);
+ if(!ok){ emit RemovePlugin(this->ID()); return;}
+ int icosize = this->height()-4 - 2.2*button->fontMetrics().height();
+ button->setFixedSize( this->width()-4, this->height()-4);
+ button->setIconSize( QSize(icosize,icosize) );
+ QString txt;
+ if(path.endsWith(".desktop") && ok){
+ XDGDesktop file(path);
+ ok = !file.name.isEmpty();
+ if(!ok){
+ button->setWhatsThis("");
+ button->setIcon( QIcon(LXDG::findIcon("quickopen-file","").pixmap(QSize(icosize,icosize)).scaledToHeight(icosize, Qt::SmoothTransformation) ) );
+ txt = tr("Click to Set");
+ if(!watcher->files().isEmpty()){ watcher->removePaths(watcher->files()); }
+ }else{
+ button->setWhatsThis(file.filePath);
+ button->setIcon( QIcon(LXDG::findIcon(file.icon,"system-run").pixmap(QSize(icosize,icosize)).scaledToHeight(icosize, Qt::SmoothTransformation) ) );
+ txt = file.name;
+ if(!watcher->files().isEmpty()){ watcher->removePaths(watcher->files()); }
+ watcher->addPath(file.filePath); //make sure to update this shortcut if the file changes
+ }
+ }else if(ok){
+ QFileInfo info(path);
+ button->setWhatsThis(info.absoluteFilePath());
+ if(info.isDir()){
+ button->setIcon( LXDG::findIcon("folder","") );
+ }else if(LUtils::imageExtensions().contains(info.suffix().toLower()) ){
+ QPixmap pix;
+ if(pix.load(path)){ button->setIcon( QIcon(pix.scaled(256,256)) ); } //max size for thumbnails in memory
+ else{ button->setIcon( LXDG::findIcon("dialog-cancel","") ); }
+ }else{
+ button->setIcon( QIcon(LXDG::findMimeIcon(path).pixmap(QSize(icosize,icosize)).scaledToHeight(icosize, Qt::SmoothTransformation) ) );
+ }
+ txt = info.fileName();
+ if(!watcher->files().isEmpty()){ watcher->removePaths(watcher->files()); }
+ watcher->addPath(path); //make sure to update this shortcut if the file changes
+ }else{
+ //InValid File
+ button->setWhatsThis("");
+ button->setIcon( QIcon(LXDG::findIcon("quickopen","dialog-cancel").pixmap(QSize(icosize,icosize)).scaledToHeight(icosize, Qt::SmoothTransformation) ) );
+ button->setText( tr("Click to Set") );
+ if(!watcher->files().isEmpty()){ watcher->removePaths(watcher->files()); }
+ }
+ //If the file is a symlink, put the overlay on the icon
+ if(QFileInfo(path).isSymLink()){
+ QImage img = button->icon().pixmap(QSize(icosize,icosize)).toImage();
+ int oSize = icosize/3; //overlay size
+ QPixmap overlay = LXDG::findIcon("emblem-symbolic-link").pixmap(oSize,oSize).scaled(oSize,oSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPainter painter(&img);
+ painter.drawPixmap(icosize-oSize,icosize-oSize,overlay); //put it in the bottom-right corner
+ button->setIcon( QIcon(QPixmap::fromImage(img)) );
+ }
+ //Now adjust the visible text as necessary based on font/grid sizing
+ button->setToolTip(txt);
+ //Double check that the visual icon size matches the requested size - otherwise upscale the icon
+ if(button->fontMetrics().width(txt) > (button->width()-OUTMARGIN) ){
+ //Text too long, try to show it on two lines
+ //txt = button->fontMetrics().elidedText(txt, Qt::ElideRight, 2*(button->width()-OUTMARGIN), Qt::TextWordWrap);
+ txt =txt.section(" ",0,2).replace(" ","\n"); //First take care of any natural breaks
+ //Go through and combine any lines
+ if(txt.contains("\n")){
+ //need to check each line
+ QStringList txtL = txt.split("\n");
+ for(int i=0; i<txtL.length(); i++){
+ if(( i+1<txtL.length()) && (button->fontMetrics().width(txtL[i]) < button->width()/2) ){
+ txtL[i] = txtL[i]+" "+txtL[i+1];
+ txtL.removeAt(i+1);
+ }
+ }
+ txt = txtL.join("\n").section("\n",0,2);
+ }
+
+ if(txt.contains("\n")){
+ //need to check each line
+ QStringList txtL = txt.split("\n");
+ for(int i=0; i<txtL.length(); i++){
+ if(i>1){ txtL.removeAt(i); i--; } //Only take the first two lines
+ else{ txtL[i] = button->fontMetrics().elidedText(txtL[i], Qt::ElideRight, (button->width()-OUTMARGIN) ); }
+ }
+ txt = txtL.join("\n");
+ }else{
+ txt = this->fontMetrics().elidedText(txt,Qt::ElideRight, 2*(button->width()-OUTMARGIN));
+ //Now split the line in half for the two lines
+ txt.insert( ((txt.count())/2), "\n");
+ }
+ }
+ if(!txt.contains("\n")){ txt.append("\n "); } //always use two lines
+ //qDebug() << " - Setting Button Text:" << txt;
+ button->setText(txt);
+
+ QTimer::singleShot(100, this, SLOT(update()) ); //Make sure to re-draw the image in a moment
+}
+
+void AppLauncherPlugin::buttonClicked(){
+ QString path = button->whatsThis();
+ if(path.isEmpty() || !QFile::exists(path) ){
+ //prompt for the user to select an application
+ QList<XDGDesktop*> apps = LSession::handle()->applicationMenu()->currentAppHash()->value("All"); //LXDG::sortDesktopNames( LXDG::systemDesktopFiles() );
+ QStringList names;
+ for(int i=0; i<apps.length(); i++){ names << apps[i]->name; }
+ bool ok = false;
+ QString app = QInputDialog::getItem(this, tr("Select Application"), tr("Name:"), names, 0, false, &ok);
+ if(!ok || names.indexOf(app)<0){ return; } //cancelled
+ this->saveSetting("applicationpath", apps[ names.indexOf(app) ]->filePath);
+ QTimer::singleShot(0,this, SLOT(loadButton()));
+ }else{
+ LSession::LaunchApplication("lumina-open \""+path+"\"");
+ }
+
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.h
new file mode 100644
index 00000000..a0f6a7cd
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/AppLauncherPlugin.h
@@ -0,0 +1,59 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a quick sample desktop plugin
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKTOP_PLUGIN_APPLICATION_LAUNCHER_H
+#define _LUMINA_DESKTOP_DESKTOP_PLUGIN_APPLICATION_LAUNCHER_H
+
+#include <QToolButton>
+#include <QInputDialog>
+#include <QVBoxLayout>
+#include <QProcess>
+#include <QFile>
+#include <QFileSystemWatcher>
+#include <QTimer>
+#include <QMenu>
+#include <QCursor>
+
+#include "../LDPlugin.h"
+
+#include <LuminaXDG.h>
+
+class AppLauncherPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ AppLauncherPlugin(QWidget* parent, QString ID);
+ ~AppLauncherPlugin(){}
+
+ void Cleanup(); //special function for final cleanup
+
+private:
+ QToolButton *button;
+ QFileSystemWatcher *watcher;
+ //QMenu *menu;
+
+private slots:
+ void loadButton();
+ void buttonClicked();
+ //void openContextMenu();
+
+ //void increaseIconSize();
+ //void decreaseIconSize();
+ //void deleteFile();
+
+public slots:
+ void LocaleChange(){
+ loadButton(); //force reload
+ }
+
+protected:
+ void resizeEvent(QResizeEvent *ev){
+ LDPlugin::resizeEvent(ev);
+ QTimer::singleShot(100, this, SLOT(loadButton()) );
+ }
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/OutlineToolButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/OutlineToolButton.h
new file mode 100644
index 00000000..24410e75
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/applauncher/OutlineToolButton.h
@@ -0,0 +1,99 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This is a simple subclass for a QToolButton with black/white text (for transparent backgrounds)
+//===========================================
+#ifndef _LUMINA_DESKTOP_PLUGIN_APPLAUNCHER_OUTLINE_TOOLBUTTON_H
+#define _LUMINA_DESKTOP_PLUGIN_APPLAUNCHER_OUTLINE_TOOLBUTTON_H
+
+#include <QToolButton>
+#include <QPainter>
+#include <QPainterPath>
+#include <QPen>
+#include <QStyle>
+#include <QStyleOption>
+#include <QStylePainter>
+#include <QFont>
+#include <QDebug>
+#include <QMouseEvent>
+
+
+class OutlineToolButton : public QToolButton{
+ Q_OBJECT
+public:
+ OutlineToolButton(QWidget *parent=0) : QToolButton(parent){
+ //This button needs slightly different font settings - do this in the constructor so that other widgets can take it into account.
+ QFont font = this->font();
+ font.setStyleStrategy(QFont::PreferAntialias); //Always set the font strategy (just in case it starts working down the road)
+ this->setFont(font);
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ }
+ ~OutlineToolButton(){}
+
+signals:
+ void DoubleClicked();
+
+protected:
+ void mouseDoubleClickEvent(QMouseEvent *ev){
+ ev->accept();
+ emit DoubleClicked();
+ }
+ void mousePressEvent(QMouseEvent *ev){
+ ev->ignore();
+ }
+ void mouseReleaseEvent(QMouseEvent *ev){
+ ev->ignore();
+ }
+
+ void paintEvent(QPaintEvent*){
+ /* NOTE: This is what a standard QToolButton performs (peeked at Qt source code for this tidbit)
+ QStylePainter p(this);
+ QStyleOptionToolButton opt;
+ initStyleOption(&opt);
+ p.drawComplexControl(QStyle::CC_ToolButton, opt);
+ */
+
+ //Modify the standard QToolButton routine to paint the text differently
+ QStylePainter p(this);
+ QStyleOptionToolButton opt;
+ initStyleOption(&opt);
+ opt.font = this->property("font").value<QFont>(); //This ensures that the stylesheet values are incorporated
+ opt.font.setStyleStrategy(QFont::PreferAntialias); //Always set the font strategy (just in case it starts working down the road)
+ opt.font.setKerning(true);
+ opt.fontMetrics = QFontMetrics(opt.font);
+ opt.text.clear(); //Don't paint the text yet - just the background/icon
+ p.drawComplexControl(QStyle::CC_ToolButton, opt); //This does all the normal QToolButton stuff - just not text
+ //Now get the text rectangle for the widget
+ QRect box = p.style()->itemTextRect(opt.fontMetrics, opt.rect, Qt::AlignHCenter | Qt::AlignBottom, true, this->text());
+ //Get the QColors for the outline/text
+ QColor textC = opt.palette.text().color().toHsl(); //need the lightness value in a moment
+ QColor outC = textC;
+ //qDebug() << "Font Color Values:" << textC << textC.lightness() << textC.lightnessF();
+ if(textC.lightnessF() > 0.5){ outC.setHsl(textC.hue(), textC.hslSaturation(), 0, 90); }
+ else{outC.setHsl(textC.hue(), textC.hslSaturation(), 255, 50); }
+ //qDebug() << "Outline Color Values:" << outC;
+ //Now get the size of the outline border (need to scale for high-res monitors)
+ qreal OWidth = opt.fontMetrics.width("o")/2.0;
+ //qDebug() << "Outline Width:" << OWidth;
+ //Now generate a QPainterPath for the text
+ QPainterPath path;
+ QStringList txt = this->text().split("\n"); //need each line independently, the newline actually gets painted otherwise
+ for(int i=0; i<txt.length(); i++){
+ path.addText(box.center().x() - (opt.fontMetrics.width(txt[i])/2), box.y()+((i+1)*(box.height()/txt.length()))-opt.fontMetrics.descent(), opt.font, txt[i] );
+ }
+ path.setFillRule(Qt::WindingFill);
+ //Now paint the text
+ QRadialGradient RG(box.center(), box.width()*1.5); //width is always going to be greater than height
+ RG.setColorAt(0, outC);
+ RG.setColorAt(1, Qt::transparent);
+ p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); //need antialiasing for this to work well (sub-pixel painting)
+ p.strokePath(path, QPen(QBrush(RG),OWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) ); //This will be the outline - 1pixel thick, semi-transparent
+ p.fillPath(path, QBrush(textC)); //this will be the inside/text color
+
+ }
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.cpp
new file mode 100644
index 00000000..722a5865
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.cpp
@@ -0,0 +1,271 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "PlayerWidget.h"
+#include "ui_PlayerWidget.h"
+
+#include <QDir>
+#include <QUrl>
+#include <QInputDialog>
+#include <QFileDialog>
+#include <LuminaXDG.h>
+#include <QDebug>
+#include <QDesktopWidget>
+
+PlayerWidget::PlayerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::PlayerWidget()){
+ ui->setupUi(this); //load the designer form
+ PLAYER = new QMediaPlayer(this);
+ PLAYER->setVolume(100);
+ PLAYER->setNotifyInterval(1000); //1 second interval (just needs to be a rough estimate)
+ PLAYLIST = new QMediaPlaylist(this);
+ PLAYLIST->setPlaybackMode(QMediaPlaylist::Sequential);
+ PLAYER->setPlaylist(PLAYLIST);
+
+ configMenu = new QMenu(this);
+ ui->tool_config->setMenu(configMenu);
+ addMenu = new QMenu(this);
+ ui->tool_add->setMenu(addMenu);
+
+ updatinglists = false; //start off as false
+
+ ui->combo_playlist->setContextMenuPolicy(Qt::NoContextMenu);
+
+ LoadIcons();
+ playerStateChanged(); //update button visibility
+ currentSongChanged();
+ //Connect all the signals/slots
+ //connect(infoTimer, SIGNAL(timeout()), this, SLOT(rotateTrackInfo()) );
+ connect(PLAYER, SIGNAL(positionChanged(qint64)),this, SLOT(updateProgress(qint64)) );
+ connect(PLAYER, SIGNAL(durationChanged(qint64)), this, SLOT(updateMaxProgress(qint64)) );
+ connect(PLAYLIST, SIGNAL(mediaChanged(int, int)), this, SLOT(playlistChanged()) );
+ connect(PLAYER, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(playerStateChanged()) );
+ connect(PLAYLIST, SIGNAL(currentMediaChanged(const QMediaContent&)), this, SLOT(currentSongChanged()) );
+ connect(ui->combo_playlist, SIGNAL(currentIndexChanged(int)), this, SLOT(userlistSelectionChanged()) );
+ connect(ui->tool_play, SIGNAL(clicked()), this, SLOT(playClicked()) );
+ connect(ui->tool_pause, SIGNAL(clicked()), this, SLOT(pauseClicked()) );
+ connect(ui->tool_stop, SIGNAL(clicked()), this, SLOT(stopClicked()) );
+ connect(ui->tool_next, SIGNAL(clicked()), this, SLOT(nextClicked()) );
+ connect(ui->tool_prev, SIGNAL(clicked()), this, SLOT(prevClicked()) );
+
+}
+
+PlayerWidget::~PlayerWidget(){
+ //qDebug() << "Removing PlayerWidget";
+}
+
+void PlayerWidget::LoadIcons(){
+ ui->tool_stop->setIcon( LXDG::findIcon("media-playback-stop","") );
+ ui->tool_play->setIcon( LXDG::findIcon("media-playback-start","") );
+ ui->tool_pause->setIcon( LXDG::findIcon("media-playback-pause","") );
+ ui->tool_next->setIcon( LXDG::findIcon("media-skip-forward","") );
+ ui->tool_prev->setIcon( LXDG::findIcon("media-skip-backward","") );
+ ui->tool_add->setIcon( LXDG::findIcon("list-add","") );
+ ui->tool_config->setIcon( LXDG::findIcon("configure","") );
+ //Now re-assemble the menus as well
+ configMenu->clear();
+ configMenu->addAction(LXDG::findIcon("media-eject",""), tr("Clear Playlist"), this, SLOT(ClearPlaylist()));
+ configMenu->addAction(LXDG::findIcon("roll",""), tr("Shuffle Playlist"), this, SLOT(ShufflePlaylist()));
+ addMenu->clear();
+ addMenu->addAction(LXDG::findIcon("document-new",""), tr("Add Files"), this, SLOT(AddFilesToPlaylist()));
+ addMenu->addAction(LXDG::findIcon("folder-new",""), tr("Add Directory"), this, SLOT(AddDirToPlaylist()));
+ addMenu->addAction(LXDG::findIcon("download",""), tr("Add URL"), this, SLOT(AddURLToPlaylist()));
+}
+
+void PlayerWidget::playClicked(){
+ PLAYER->play();
+}
+
+void PlayerWidget::pauseClicked(){
+ PLAYER->pause();
+}
+
+void PlayerWidget::stopClicked(){
+ PLAYER->stop();
+}
+
+void PlayerWidget::nextClicked(){
+ PLAYLIST->next();
+}
+
+void PlayerWidget::prevClicked(){
+ PLAYLIST->previous();
+}
+
+void PlayerWidget::AddFilesToPlaylist(){
+ //Prompt the user to select multimedia files
+ QFileDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setFileMode(QFileDialog::ExistingFiles);
+ dlg.setAcceptMode(QFileDialog::AcceptOpen);
+ dlg.setNameFilter( tr("Multimedia Files")+" ("+LXDG::findAVFileExtensions().join(" ")+")");
+ dlg.setWindowTitle(tr("Select Multimedia Files"));
+ dlg.setWindowIcon( LXDG::findIcon("file-open","") );
+ dlg.setDirectory(QDir::homePath()); //start in the home directory
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ QList<QUrl> files = dlg.selectedUrls();
+ if(files.isEmpty() || dlg.result()!=QDialog::Accepted){ return; } //cancelled
+ //Make this use show/processEvents later
+ //QList<QUrl> files = QFileDialog::getOpenFileUrls(0, tr("Select Multimedia Files"), QDir::homePath(), "Multimedia Files ("+LXDG::findAVFileExtensions().join(" ")+")");
+ QList<QMediaContent> urls;
+ for(int i=0; i<files.length(); i++){
+ urls << QMediaContent(files[i]);
+ }
+ PLAYLIST->addMedia(urls);
+ playlistChanged();
+}
+
+void PlayerWidget::AddDirToPlaylist(){
+ QFileDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setFileMode(QFileDialog::Directory);
+ dlg.setOption(QFileDialog::ShowDirsOnly, true);
+ dlg.setAcceptMode(QFileDialog::AcceptOpen);
+ dlg.setWindowTitle(tr("Select Multimedia Directory"));
+ dlg.setWindowIcon( LXDG::findIcon("folder-open","") );
+ dlg.setDirectory(QDir::homePath()); //start in the home directory
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ if(dlg.result() != QDialog::Accepted){ return; } //cancelled
+ QStringList sel = dlg.selectedFiles();
+ if(sel.isEmpty()){ return; } //cancelled
+ QString dirpath = sel.first(); //QFileDialog::getExistingDirectory(0, tr("Select a Multimedia Directory"), QDir::homePath() );
+ if(dirpath.isEmpty()){ return; } //cancelled
+ QDir dir(dirpath);
+ QFileInfoList files = dir.entryInfoList(LXDG::findAVFileExtensions(), QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
+ if(files.isEmpty()){ return; } //nothing in this directory
+ QList<QMediaContent> urls;
+ for(int i=0; i<files.length(); i++){
+ urls << QMediaContent(QUrl::fromLocalFile(files[i].absoluteFilePath()) );
+ }
+ PLAYLIST->addMedia(urls);
+ playlistChanged();
+}
+
+void PlayerWidget::AddURLToPlaylist(){
+ QInputDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setInputMode(QInputDialog::TextInput);
+ dlg.setLabelText(tr("Enter a valid URL for a multimedia file or stream:"));
+ dlg.setTextEchoMode(QLineEdit::Normal);
+ dlg.setWindowTitle(tr("Multimedia URL"));
+ dlg.setWindowIcon( LXDG::findIcon("download","") );
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ QString url = dlg.textValue();
+ if(url.isEmpty() || dlg.result()!=QDialog::Accepted){ return; } //cancelled
+
+ //QString url = QInputDialog::getText(0, tr("Multimedia URL"), tr("Enter a valid URL for a multimedia file or stream"), QLineEdit::Normal);
+ //if(url.isEmpty()){ return; }
+ QUrl newurl(url);
+ if(!newurl.isValid()){ return; } //invalid URL
+ PLAYLIST->addMedia(newurl);
+ playlistChanged();
+}
+
+void PlayerWidget::ClearPlaylist(){
+ PLAYER->stop();
+ PLAYLIST->clear();
+ playlistChanged();
+}
+
+void PlayerWidget::ShufflePlaylist(){
+ PLAYLIST->shuffle();
+}
+
+
+void PlayerWidget::userlistSelectionChanged(){ //front-end combobox was changed by the user
+ if(updatinglists){ return; }
+ PLAYLIST->setCurrentIndex( ui->combo_playlist->currentIndex() );
+}
+
+void PlayerWidget::playerStateChanged(){
+ switch( PLAYER->state() ){
+ case QMediaPlayer::StoppedState:
+ ui->tool_stop->setVisible(false);
+ ui->tool_play->setVisible(true);
+ ui->tool_pause->setVisible(false);
+ ui->progressBar->setVisible(false);
+ break;
+ case QMediaPlayer::PausedState:
+ ui->tool_stop->setVisible(true);
+ ui->tool_play->setVisible(true);
+ ui->tool_pause->setVisible(false);
+ ui->progressBar->setVisible(true);
+ break;
+ case QMediaPlayer::PlayingState:
+ ui->tool_stop->setVisible(true);
+ ui->tool_play->setVisible(false);
+ ui->tool_pause->setVisible(true);
+ ui->progressBar->setVisible(true);
+ break;
+ }
+
+}
+
+void PlayerWidget::playlistChanged(){
+ updatinglists = true;
+ ui->combo_playlist->clear();
+ for(int i=0; i<PLAYLIST->mediaCount(); i++){
+ QUrl url = PLAYLIST->media(i).canonicalUrl();
+ if(url.isLocalFile()){
+ ui->combo_playlist->addItem(LXDG::findMimeIcon(url.fileName().section(".",-1)), url.fileName() );
+ }else{
+ ui->combo_playlist->addItem(LXDG::findIcon("download",""), url.toString() );
+ }
+ }
+ if(PLAYLIST->currentIndex()<0 && PLAYLIST->mediaCount()>0){ PLAYLIST->setCurrentIndex(0); }
+ ui->combo_playlist->setCurrentIndex(PLAYLIST->currentIndex());
+
+ updatinglists = false;
+}
+
+void PlayerWidget::currentSongChanged(){
+ if(PLAYLIST->currentIndex() != ui->combo_playlist->currentIndex()){
+ updatinglists = true;
+ ui->combo_playlist->setCurrentIndex(PLAYLIST->currentIndex());
+ updatinglists = false;
+ }
+ ui->tool_next->setEnabled( PLAYLIST->nextIndex() >= 0 );
+ ui->tool_prev->setEnabled( PLAYLIST->previousIndex() >= 0);
+ ui->label_num->setText( QString::number( PLAYLIST->currentIndex()+1)+"/"+QString::number(PLAYLIST->mediaCount()) );
+ ui->progressBar->setRange(0, PLAYER->duration() );
+ ui->progressBar->setValue(0);
+}
+
+void PlayerWidget::updateProgress(qint64 val){
+ //qDebug() << "Update Progress Bar:" << val;
+ ui->progressBar->setValue(val);
+}
+
+void PlayerWidget::updateMaxProgress(qint64 val){
+ ui->progressBar->setRange(0,val);
+}
+
+
+AudioPlayerPlugin::AudioPlayerPlugin(QWidget *parent, QString ID) : LDPlugin(parent, ID){
+ player = new PlayerWidget(this);
+ this->setLayout( new QVBoxLayout() );
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(player);
+
+}
+
+AudioPlayerPlugin::~AudioPlayerPlugin(){
+ //qDebug() << "Remove AudioPlayerPlugin";
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.h
new file mode 100644
index 00000000..6aaeac4c
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.h
@@ -0,0 +1,84 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a simple audio player on the desktop
+//===========================================
+#ifndef _LUMINA_DESKTOP_PLUGIN_AUDIO_PLAYER_WIDGET_H
+#define _LUMINA_DESKTOP_PLUGIN_AUDIO_PLAYER_WIDGET_H
+
+#include <QMediaPlaylist>
+#include <QMediaPlayer>
+#include <QTimer>
+#include <QWidget>
+#include <QMenu>
+
+#include "../LDPlugin.h"
+
+namespace Ui{
+ class PlayerWidget;
+};
+
+class PlayerWidget : public QWidget{
+ Q_OBJECT
+public:
+ PlayerWidget(QWidget *parent = 0);
+ ~PlayerWidget();
+
+public slots:
+ void LoadIcons();
+
+private:
+ Ui::PlayerWidget *ui;
+ QMediaPlaylist *PLAYLIST;
+ QMediaPlayer *PLAYER;
+ QMenu *configMenu, *addMenu;
+ bool updatinglists;
+
+private slots:
+ void playClicked();
+ void pauseClicked();
+ void stopClicked();
+ void nextClicked();
+ void prevClicked();
+
+ void AddFilesToPlaylist();
+ void AddDirToPlaylist();
+ void AddURLToPlaylist();
+ void ClearPlaylist();
+ void ShufflePlaylist();
+ void userlistSelectionChanged(); //front-end combobox was changed by the user
+ void playerStateChanged();
+ void playlistChanged(); //list of items changed
+ void currentSongChanged();
+ void updateProgress(qint64 val);
+ void updateMaxProgress(qint64 val);
+};
+
+// Wrapper class to put this into a desktop plugin container
+class AudioPlayerPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ AudioPlayerPlugin(QWidget* parent, QString ID);
+ ~AudioPlayerPlugin();
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(3,1);
+ }
+
+private:
+ PlayerWidget *player;
+
+public slots:
+ void LocaleChange(){
+ QTimer::singleShot(0,player, SLOT(LoadIcons()));
+ }
+ void ThemeChange(){
+ QTimer::singleShot(0,player, SLOT(LoadIcons()));
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.ui b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.ui
new file mode 100644
index 00000000..b1e7ee59
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/audioplayer/PlayerWidget.ui
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PlayerWidget</class>
+ <widget class="QWidget" name="PlayerWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>346</width>
+ <height>81</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QToolButton::menu-indicator{ image: none; }</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QToolButton" name="tool_config">
+ <property name="text">
+ <string notr="true">Config</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_add">
+ <property name="text">
+ <string notr="true">Add</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_prev">
+ <property name="text">
+ <string notr="true">prev</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_num">
+ <property name="text">
+ <string notr="true">1/10</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_next">
+ <property name="text">
+ <string notr="true">next</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QComboBox" name="combo_playlist"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="tool_play">
+ <property name="text">
+ <string notr="true">Play</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_pause">
+ <property name="text">
+ <string notr="true">Pause</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_stop">
+ <property name="text">
+ <string notr="true">Stop</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="value">
+ <number>24</number>
+ </property>
+ <property name="textVisible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/calendar/CalendarPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/calendar/CalendarPlugin.h
new file mode 100644
index 00000000..abb138f7
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/calendar/CalendarPlugin.h
@@ -0,0 +1,59 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a quick sample desktop plugin
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKTOP_PLUGIN_CALENDAR_H
+#define _LUMINA_DESKTOP_DESKTOP_PLUGIN_CALENDAR_H
+
+#include <QCalendarWidget>
+#include <QVBoxLayout>
+#include <QDate>
+#include <QTimer>
+#include "../LDPlugin.h"
+
+class CalendarPlugin : public LDPlugin{
+ Q_OBJECT
+private:
+ QCalendarWidget *cal;
+ QTimer *timer;
+
+public:
+ CalendarPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ this->setLayout( new QVBoxLayout());
+ this->layout()->setContentsMargins(0,0,0,0);
+ cal = new QCalendarWidget(this);
+ cal->setSelectionMode(QCalendarWidget::NoSelection);
+ this->layout()->addWidget(cal);
+ timer = new QTimer(this);
+ timer->setInterval(1800000); //30 minute refresh timer
+ timer->start();
+ connect(timer, SIGNAL(timeout()), this, SLOT(updateDate()) );
+ QTimer::singleShot(0,this, SLOT(updateDate()) );
+ connect(this, SIGNAL(PluginResized()), this, SLOT(UpdateCalendarSize()));
+ }
+
+ ~CalendarPlugin(){ timer->stop(); }
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(3,2);
+ }
+
+private slots:
+ void updateDate(){
+ if(cal->selectedDate() != QDate::currentDate()){
+ cal->setSelectedDate(QDate::currentDate());
+ cal->showSelectedDate();
+ }
+ }
+ void UpdateCalendarSize(){
+ cal->setFixedSize(this->size());
+ }
+
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktop-plugins.pri b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktop-plugins.pri
new file mode 100644
index 00000000..8376316a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktop-plugins.pri
@@ -0,0 +1,24 @@
+SOURCES += $$PWD/applauncher/AppLauncherPlugin.cpp \
+ $$PWD/desktopview/DesktopViewPlugin.cpp \
+ $$PWD/notepad/NotepadPlugin.cpp \
+ $$PWD/audioplayer/PlayerWidget.cpp \
+ $$PWD/systemmonitor/MonitorWidget.cpp \
+ $$PWD/rssreader/RSSFeedPlugin.cpp \
+ $$PWD/rssreader/RSSObjects.cpp
+# $$PWD/messagecenter/MessageCenter.cpp
+
+HEADERS += $$PWD/calendar/CalendarPlugin.h \
+ $$PWD/applauncher/AppLauncherPlugin.h \
+ $$PWD/applauncher/OutlineToolButton.h \
+ $$PWD/desktopview/DesktopViewPlugin.h \
+ $$PWD/notepad/NotepadPlugin.h \
+ $$PWD/audioplayer/PlayerWidget.h \
+ $$PWD/systemmonitor/MonitorWidget.h \
+ $$PWD/rssreader/RSSFeedPlugin.h \
+ $$PWD/rssreader/RSSObjects.h
+# $$PWD/quickcontainer/QuickDPlugin.h
+# $$PWD/messagecenter/MessageCenter.h
+
+FORMS += $$PWD/audioplayer/PlayerWidget.ui \
+ $$PWD/systemmonitor/MonitorWidget.ui \
+ $$PWD/rssreader/RSSFeedPlugin.ui
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.cpp
new file mode 100644
index 00000000..90f3374b
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.cpp
@@ -0,0 +1,214 @@
+#include "DesktopViewPlugin.h"
+
+#include <QFileInfo>
+#include <QDir>
+#include <QClipboard>
+#include <QMimeData>
+#include <QImageReader>
+
+#include <LuminaXDG.h>
+#include "LSession.h"
+
+
+DesktopViewPlugin::DesktopViewPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ this->setLayout( new QVBoxLayout());
+ this->layout()->setContentsMargins(0,0,0,0);
+
+ list = new QListWidget(this);
+ list->setViewMode(QListView::IconMode);
+ list->setFlow(QListWidget::TopToBottom); //Qt bug workaround - need the opposite flow in the widget constructor
+ list->setWrapping(true);
+ list->setSpacing(4);
+ list->setSelectionBehavior(QAbstractItemView::SelectItems);
+ list->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ list->setContextMenuPolicy(Qt::CustomContextMenu);
+ list->setMovement(QListView::Snap); //make sure items are "stuck" in the grid
+
+ menu = new QMenu(this);
+ menu->addAction( LXDG::findIcon("run-build-file",""), tr("Open"), this, SLOT(runItems()) );
+ menu->addSeparator();
+ menu->addAction( LXDG::findIcon("edit-cut",""), tr("Cut"), this, SLOT(cutItems()) );
+ menu->addAction( LXDG::findIcon("edit-copy",""), tr("Copy"), this, SLOT(copyItems()) );
+ menu->addSeparator();
+ menu->addAction( LXDG::findIcon("zoom-in",""), tr("Increase Icons"), this, SLOT(increaseIconSize()) );
+ menu->addAction( LXDG::findIcon("zoom-out",""), tr("Decrease Icons"), this, SLOT(decreaseIconSize()) );
+ menu->addSeparator();
+ menu->addAction( LXDG::findIcon("edit-delete",""), tr("Delete"), this, SLOT(deleteItems()) );
+ menu->addSeparator();
+ if(LUtils::isValidBinary("lumina-fileinfo")){
+ menu->addAction( LXDG::findIcon("system-search",""), tr("Properties"), this, SLOT(displayProperties()) );
+ }
+ this->layout()->addWidget(list);
+
+ connect(QApplication::instance(), SIGNAL(DesktopFilesChanged()), this, SLOT(updateContents()) );
+ connect(list, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(runItems()) );
+ connect(list, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showMenu(const QPoint&)) );
+ QTimer::singleShot(1000,this, SLOT(updateContents()) ); //wait a second before loading contents
+}
+
+DesktopViewPlugin::~DesktopViewPlugin(){
+
+}
+
+void DesktopViewPlugin::runItems(){
+ QList<QListWidgetItem*> sel = list->selectedItems();
+ for(int i=0; i<sel.length(); i++){
+ LSession::LaunchApplication("lumina-open \""+sel[i]->whatsThis()+"\"");
+ }
+}
+
+void DesktopViewPlugin::copyItems(){
+ QList<QListWidgetItem*> sel = list->selectedItems();
+ if(sel.isEmpty()){ return; } //nothing selected
+ QStringList items;
+ //Format the data string
+ for(int i=0; i<sel.length(); i++){
+ items << "copy::::"+sel[i]->whatsThis();
+ }
+ //Now save that data to the global clipboard
+ QMimeData *dat = new QMimeData;
+ dat->clear();
+ dat->setData("x-special/lumina-copied-files", items.join("\n").toLocal8Bit());
+ QApplication::clipboard()->clear();
+ QApplication::clipboard()->setMimeData(dat);
+}
+
+void DesktopViewPlugin::cutItems(){
+ QList<QListWidgetItem*> sel = list->selectedItems();
+ if(sel.isEmpty()){ return; } //nothing selected
+ QStringList items;
+ //Format the data string
+ for(int i=0; i<sel.length(); i++){
+ items << "cut::::"+sel[i]->whatsThis();
+ }
+ //Now save that data to the global clipboard
+ QMimeData *dat = new QMimeData;
+ dat->clear();
+ dat->setData("x-special/lumina-copied-files", items.join("\n").toLocal8Bit());
+ QApplication::clipboard()->clear();
+ QApplication::clipboard()->setMimeData(dat);
+}
+
+void DesktopViewPlugin::deleteItems(){
+ QList<QListWidgetItem*> sel = list->selectedItems();
+ for(int i=0; i<sel.length(); i++){
+ if(QFileInfo(sel[i]->whatsThis()).isDir()){
+ QProcess::startDetached("rm -r \""+sel[i]->whatsThis()+"\"");
+ }else{
+ QFile::remove(sel[i]->whatsThis());
+ }
+ }
+}
+
+void DesktopViewPlugin::showMenu(const QPoint &pos){
+ //Make sure there is an item underneath the mouse first
+ if(list->itemAt(pos)!=0){
+ menu->popup(this->mapToGlobal(pos));
+ }else{
+ //Pass the context menu request on to the desktop (emit it from the plugin)
+ this->showPluginMenu();
+ //emit OpenDesktopMenu();
+ }
+}
+
+void DesktopViewPlugin::increaseIconSize(){
+ int icosize = this->readSetting("IconSize",64).toInt();
+ icosize+=16; //go in orders of 16 pixels
+ //list->setIconSize(QSize(icosize,icosize));
+ this->saveSetting("IconSize",icosize);
+ QTimer::singleShot(10, this, SLOT(updateContents()));
+}
+
+void DesktopViewPlugin::decreaseIconSize(){
+ int icosize = this->readSetting("IconSize",64).toInt();
+ if(icosize < 20){ return; } //too small to decrease more
+ icosize-=16; //go in orders of 16 pixels
+ //list->setIconSize(QSize(icosize,icosize));
+ this->saveSetting("IconSize",icosize);
+ QTimer::singleShot(10,this, SLOT(updateContents()));
+}
+
+void DesktopViewPlugin::updateContents(){
+ list->clear();
+
+ int icosize = this->readSetting("IconSize",64).toInt();
+ QSize gridSZ = QSize(qRound(1.8*icosize),icosize+4+(2*this->fontMetrics().height()) );
+ //qDebug() << "Icon Size:" << icosize <<"Grid Size:" << gridSZ.width() << gridSZ.height();
+ list->setGridSize(gridSZ);
+ list->setIconSize(QSize(icosize,icosize));
+ QDir dir(QDir::homePath()+"/Desktop");
+ QFileInfoList files = dir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::Type | QDir::DirsFirst);
+ for(int i=0; i<files.length(); i++){
+ QListWidgetItem *it = new QListWidgetItem;
+ it->setSizeHint(gridSZ); //ensure uniform item sizes
+ //it->setForeground(QBrush(Qt::black, Qt::Dense2Pattern)); //Try to use a font color which will always be visible
+ it->setTextAlignment(Qt::AlignCenter);
+ it->setWhatsThis(files[i].absoluteFilePath());
+ QString txt;
+ if(files[i].isDir()){
+ it->setIcon( LXDG::findIcon("folder","") );
+ txt = files[i].fileName();
+ }else if(files[i].suffix() == "desktop" ){
+ XDGDesktop desk(files[i].absoluteFilePath());
+ if(desk.isValid()){
+ it->setIcon( LXDG::findIcon(desk.icon,"unknown") );
+ if(desk.name.isEmpty()){
+ txt = files[i].fileName();
+ }else{
+ txt = desk.name;
+ }
+ }else{
+ //Revert back to a standard file handling
+ it->setIcon( LXDG::findMimeIcon(files[i].fileName()) );
+ txt = files[i].fileName();
+ }
+ }else if(LUtils::imageExtensions().contains(files[i].suffix().toLower()) ){
+ it->setIcon( QIcon( QPixmap(files[i].absoluteFilePath()).scaled(icosize,icosize,Qt::IgnoreAspectRatio, Qt::SmoothTransformation) ) );
+ txt = files[i].fileName();
+ }else{
+ it->setIcon( LXDG::findMimeIcon( files[i].fileName() ) );
+ txt = files[i].fileName();
+ }
+ //Add the sym-link overlay to the icon as necessary
+ if(files[i].isSymLink()){
+ QImage img = it->icon().pixmap(QSize(icosize,icosize)).toImage();
+ int oSize = icosize/2; //overlay size
+ QPixmap overlay = LXDG::findIcon("emblem-symbolic-link").pixmap(oSize,oSize).scaled(oSize,oSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPainter painter(&img);
+ painter.drawPixmap(icosize-oSize,icosize-oSize,overlay); //put it in the bottom-right corner
+ it->setIcon( QIcon(QPixmap::fromImage(img)) );
+ }
+ //Now adjust the visible text as necessary based on font/grid sizing
+ it->setToolTip(txt);
+ if(this->fontMetrics().width(txt) > (gridSZ.width()-4) ){
+ //int dash = this->fontMetrics().width("-");
+ //Text too long, try to show it on two lines
+ txt = txt.section(" ",0,2).replace(" ","\n"); //First take care of any natural breaks
+ if(txt.contains("\n")){
+ //need to check each line
+ QStringList txtL = txt.split("\n");
+ for(int i=0; i<txtL.length(); i++){ txtL[i] = this->fontMetrics().elidedText(txtL[i], Qt::ElideRight, gridSZ.width()-4); }
+ txt = txtL.join("\n");
+ if(txtL.length()>2){ txt = txt.section("\n",0,1); } //only keep the first two lines
+ }else{
+ txt = this->fontMetrics().elidedText(txt,Qt::ElideRight, 2*(gridSZ.width()-4));
+ //Now split the line in half for the two lines
+ txt.insert( (txt.count()/2), "\n");
+ }
+ }else{
+ txt.append("\n "); //ensure two lines (2nd one invisible) - keeps formatting sane
+ }
+ it->setText(txt);
+ list->addItem(it);
+ if( (i%10) == 0){ QApplication::processEvents(); }//keep the UI snappy, every 10 items
+ }
+ list->setFlow(QListWidget::TopToBottom); //To ensure this is consistent - issues with putting it in the constructor
+ list->update(); //Re-paint the widget after all items are added
+}
+
+void DesktopViewPlugin::displayProperties(){
+ QList<QListWidgetItem*> sel = list->selectedItems();
+ for(int i=0; i<sel.length(); i++){
+ LSession::LaunchApplication("lumina-fileinfo \""+sel[i]->whatsThis());
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.h
new file mode 100644
index 00000000..046b6e5c
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/desktopview/DesktopViewPlugin.h
@@ -0,0 +1,55 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a listing/launcher for things in the ~/Desktop folder
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKTOP_VIEW_PLUGIN_H
+#define _LUMINA_DESKTOP_DESKTOP_VIEW_PLUGIN_H
+
+#include <QListWidget>
+#include <QVBoxLayout>
+#include <QTimer>
+#include <QFileSystemWatcher>
+#include <QMouseEvent>
+
+#include "../LDPlugin.h"
+
+class DesktopViewPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ DesktopViewPlugin(QWidget* parent, QString ID);
+ ~DesktopViewPlugin();
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(3,3);
+ }
+private:
+ QListWidget *list;
+ QMenu *menu;
+
+private slots:
+ void runItems();
+ void copyItems();
+ void cutItems();
+ void deleteItems();
+ void showMenu(const QPoint&);
+ void increaseIconSize();
+ void decreaseIconSize();
+ void updateContents();
+ void displayProperties();
+
+
+public slots:
+ void LocaleChange(){
+ QTimer::singleShot(0,this, SLOT(updateContents()));
+ }
+ void ThemeChange(){
+ QTimer::singleShot(0,this, SLOT(updateContents()));
+ }
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/LXDG-DBusNotifier.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/LXDG-DBusNotifier.h
new file mode 100644
index 00000000..64413e95
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/LXDG-DBusNotifier.h
@@ -0,0 +1,17 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// Simple DBUS message handler for the FreeDesktop desktop notifications specification
+
+
+class LXDG-DBusNotifier : public QDBusVirtualObkect{
+ Q_OBJECT
+public:
+
+private:
+
+
+};
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.cpp
new file mode 100644
index 00000000..df07a122
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.cpp
@@ -0,0 +1,90 @@
+#include "MessageCenter.h"
+
+#include <LuminaXDG.h>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QDBusConnection>
+#include <QDBusConnectionInterface>
+
+MessageCenterPlugin::MessageCenterPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ //Setup the UI
+ QVBoxLayout *vlay = new QVBoxLayout();
+ this->setLayout( new QVBoxLayout() );
+ this->layout()->setContentsMargins(0,0,0,0);
+ vlay->setContentsMargins(3,3,3,3);
+ frame = new QFrame(this);
+ frame->setObjectName("messagecenterbase");
+ this->layout()->addWidget(frame);
+ frame->setLayout(vlay);
+
+
+ //Setup the title bar header buttons
+ QHBoxLayout *hlay = new QHBoxLayout();
+ tool_clearall = new QToolButton(this);
+ tool_clearall->setAutoRaise(true);
+ tool_clearone = new QToolButton(this);
+ tool_clearone->setAutoRaise(true);
+ QWidget *spacer = new QWidget(this);
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ hlay->addWidget(spacer);
+ hlay->addWidget(tool_clearone);
+ hlay->addWidget(tool_clearall);
+ vlay->addLayout(hlay);
+
+ //Setup the main text widget
+ list_messages = new QListWidget(this);
+ list_messages->setSelectionMode(QAbstractItemView::SingleSelection);
+ list_messages->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ vlay->addWidget(list_messages);
+
+ //Now setup the initial values
+ this->setInitialSize(200,300);
+ //Setup the button connections
+ connect(tool_clearall, SIGNAL(clicked()), this, SLOT(clearAllMessages()) );
+ connect(tool_clearone, SIGNAL(clicked()), this, SLOT(clearSelectedMessage()) );
+
+ //Setup the DBUS signals/slots
+ if(QDBusConnection::sessionBus().isConnected()){
+ if( QDBusConnection::sessionBus().registerService("org.freedesktop.Notifications") ){
+ //Was able to register this service, also register everything it can do...
+ //SUPPORTED: "body", "body-hyperlinks", "body-markup", "icon-static"
+
+
+ }
+ QDBusConnection::sessionBus().connect("", "", "org.freedesktop.Notifications", "Notify", this, SLOT(newMessage(QString, uint, QString, QString, QString, QStringList, dict, int)) );
+ QDBusConnection::sessionBus().interface().call("AddMatch", "interface='org.freedesktop.Notifications',member='Notify',type='method_call',eavesdrop='true'");
+ qDebug() << "Available Session DBUS Services:" << QDBusConnection::sessionBus().interface()->registeredServiceNames().value();
+ //connect(QString(), QString(),
+ }
+ if(QDBusConnection::systemBus().isConnected()){
+ qDebug() << "Available System DBUS Services:" << QDBusConnection::systemBus().interface()->registeredServiceNames().value();
+ }
+
+ QTimer::singleShot(0,this, SLOT(loadIcons()) );
+}
+
+MessageCenterPlugin::~MessageCenterPlugin(){
+
+}
+
+void MessageCenterPlugin::newMessage(QString summary, QString body){
+ qDebug() << "New Message:" << summary, body;
+}
+
+void MessageCenterPlugin::clearAllMessages(){
+ list_messages->clear();
+}
+
+void MessageCenterPlugin::clearSelectedMessage(){
+ if( list_messages->currentItem()==0){ return; } //nothing selected
+ list_messages->removeItemWidget( list_messages->currentItem() );
+}
+
+
+void MessageCenterPlugin::loadIcons(){
+ tool_clearall->setIcon( LXDG::findIcon("edit-clear-list","") );
+ tool_clearall->setToolTip( tr("Clear all messages") );
+ tool_clearone->setIcon( LXDG::findIcon("edit-delete","") );
+ tool_clearone->setToolTip( tr("Clear selected message") );
+
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.h
new file mode 100644
index 00000000..8491546f
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/messagecenter/MessageCenter.h
@@ -0,0 +1,48 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a simple DBUS monitor which display's messages that come in
+//===========================================
+#ifndef _LUMINA_DESKTOP_MESSAGE_CENTER_PLUGIN_H
+#define _LUMINA_DESKTOP_MESSAGE_CENTER_PLUGIN_H
+
+#include <QListWidget>
+#include <QToolButton>
+#include <QFrame>
+
+#include <QTimer>
+#include "../LDPlugin.h"
+
+class MessageCenterPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ MessageCenterPlugin(QWidget* parent, QString ID);
+ ~MessageCenterPlugin();
+
+private:
+ //QDBusConnection *sess, *sys;
+ QListWidget *list_messages;
+ QFrame *frame;
+ QToolButton *tool_clearall; //clear all messages
+ QToolButton *tool_clearone; //clear selected message
+
+private slots:
+ //void newMessage(QDBusMessage *message);
+ void clearAllMessages();
+ void clearSelectedMessage();
+
+ void loadIcons();
+
+public slots:
+ void LocaleChange(){
+ QTimer::singleShot(0,this, SLOT(loadIcons()));
+ }
+ void ThemeChange(){
+ QTimer::singleShot(0,this, SLOT(loadIcons()));
+ }
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.cpp
new file mode 100644
index 00000000..6d321305
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.cpp
@@ -0,0 +1,330 @@
+#include "NotepadPlugin.h"
+
+#include <LuminaXDG.h>
+#include "LSession.h"
+#include <LUtils.h>
+#include <QDir>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QtConcurrent>
+
+NotePadPlugin::NotePadPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ //qDebug() << "Creating Notepad Plugin:";
+ QVBoxLayout *vlay = new QVBoxLayout();
+ this->setLayout( new QVBoxLayout() );
+ this->layout()->setContentsMargins(0,0,0,0);
+ vlay->setContentsMargins(3,3,3,3);
+ frame = new QFrame(this);
+ frame->setObjectName("notepadbase");
+ //frame->setStyleSheet("QFrame#notepadbase{border-width: 1px; background: rgba(255,255,255,50); color: black;} QFrame{ border: none; border-radius: 3px; background: rgba(255,255,255,100); color: black;}");
+ this->layout()->addWidget(frame);
+ frame->setLayout(vlay);
+
+ if(!QFile::exists(QDir::homePath()+"/Notes")){
+ //Create the notes directory if non-existant
+ QDir dir;
+ dir.mkpath(QDir::homePath()+"/Notes");
+ }
+ watcher = new QFileSystemWatcher(this);
+ //Always watch the notes directory for new files/changes
+ watcher->addPath(QDir::homePath()+"/Notes");
+
+ typeTimer = new QTimer(this);
+ typeTimer->setInterval(1000); // 1 second before it saves
+ typeTimer->setSingleShot(true); //compress lots of signals into a single save
+
+ updating = false;
+ //Setup the title bar header buttons
+ QHBoxLayout *hlay = new QHBoxLayout();
+ config = new QToolButton(this);
+ config->setAutoRaise(true);
+ config->setMenu(new QMenu(this));
+ config->setPopupMode(QToolButton::InstantPopup);
+ /*open = new QToolButton(this);
+ open->setAutoRaise(true);
+ add = new QToolButton(this);
+ add->setAutoRaise(true);
+ rem = new QToolButton(this);
+ rem->setAutoRaise(true);*/
+ cnote = new QComboBox(this);
+
+ hlay->addWidget(cnote);
+ hlay->addWidget(config);
+ //hlay->addWidget(open);
+ //hlay->addWidget(add);
+ //hlay->addWidget(rem);
+ vlay->addLayout(hlay);
+
+ //Setup the main text widget
+ edit = new QPlainTextEdit(this);
+ edit->setReadOnly(false);
+ edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+ vlay->addWidget(edit);
+ edit->setContextMenuPolicy(Qt::NoContextMenu);
+
+ //Now load the new file-based system for saving notes
+ //qDebug() << "Saving a new setting";
+ this->saveSetting("customFile",""); //always clear this when the plugin is initialized (only maintained per-session)
+ //qDebug() << "Loading Notes Dir";
+ QTimer::singleShot(10, this, SLOT(notesDirChanged()));
+ //qDebug() << "Set Sizing";
+
+ //qDebug() << "Connect Signals/slots";
+ //Setup the button connections
+ /*connect(open, SIGNAL(clicked()), this, SLOT(openNoteClicked()) );
+ connect(add, SIGNAL(clicked()), this, SLOT(newNoteClicked()) );
+ connect(rem, SIGNAL(clicked()), this, SLOT(remNote()) );*/
+ //connect(config, SIGNAL(clicked()), this, SLOT(openConfigMenu()) );
+ connect(edit, SIGNAL(textChanged()), this, SLOT(newTextAvailable()) );
+ connect(cnote, SIGNAL(currentIndexChanged(QString)), this, SLOT(noteChanged()) );
+ connect(typeTimer, SIGNAL(timeout()), this, SLOT(updateContents()) );
+ connect(watcher, SIGNAL(directoryChanged(QString)), this, SLOT(notesDirChanged()) ); //re-load the available notes
+ connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(noteChanged()) ); //re-load the current file
+ QTimer::singleShot(0,this, SLOT(loadIcons()) );
+ //qDebug() << " - Done with init";
+}
+
+NotePadPlugin::~NotePadPlugin(){
+
+}
+
+
+void NotePadPlugin::openNote(){
+ //qDebug() << "Open New Note:";
+ //Prompt for a name for the new note
+ QFileDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setFileMode(QFileDialog::ExistingFile);
+ dlg.setAcceptMode(QFileDialog::AcceptOpen);
+ dlg.setNameFilters( QStringList() << tr("Note Files (*.note)") << tr("Text Files (*)"));
+ dlg.setWindowTitle(tr("Open a note file"));
+ dlg.setWindowIcon( LXDG::findIcon("document-open","") );
+ dlg.setDirectory(QDir::homePath()); //start in the home directory
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ QStringList sel = dlg.selectedFiles();
+ if(sel.isEmpty() || dlg.result()!=QDialog::Accepted){ return; } //cancelled
+ QString fullpath = sel.first();
+ QString name = fullpath.section("/",-1);
+ //qDebug() << " - Found Note:" << name << fullpath;
+ int index = cnote->findText(name, Qt::MatchExactly | Qt::MatchCaseSensitive);
+ if(QFile::exists(fullpath) && index <0){
+ //Alternate option of searching for the file in the list
+ index = cnote->findText(fullpath, Qt::MatchExactly | Qt::MatchCaseSensitive);
+ }
+ if(index>=0){
+ //This note already exists: just load it
+ cnote->setCurrentIndex(index);
+ }else{
+ //New note - add it to the end of the list and then load it
+ cnote->addItem(name, fullpath);
+ this->saveSetting("customFile", fullpath); //save this as a custom file
+ cnote->setCurrentIndex( cnote->count()-1 );
+ QTimer::singleShot(1000, this, SLOT(notesDirChanged())); //Make sure to refresh the list (only one custom file at a time)
+ }
+}
+
+QString NotePadPlugin::newNoteName(QString oldname, bool tryagain){
+ //Prompt for a name for the new note
+ //qDebug() << "Create new note";
+ QInputDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setInputMode(QInputDialog::TextInput);
+ dlg.setLabelText(tr("Name:"));
+ dlg.setTextEchoMode(QLineEdit::Normal);
+ if(tryagain){ dlg.setWindowTitle(tr("Invalid Note Name: Try Again")); }
+ else{ dlg.setWindowTitle(tr("Select a Note Name")); }
+ dlg.setWindowIcon( LXDG::findIcon("document-new","") );
+ dlg.setTextValue(oldname);
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ //this->thread()->usleep(300000); //300 ms between updates
+ QApplication::processEvents();
+ }
+ QString name = dlg.textValue();
+ //make sure to remove any "bad" characters from the name
+ name.remove("\""); name.remove(";"); name.remove("\'"); name.replace("/","_");
+ if(name.isEmpty() || dlg.result()!=QDialog::Accepted){ return ""; } //cancelled
+ //Check validity of the new note filename
+ QString fullpath = QDir::homePath()+"/Notes/"+name;
+ if(!fullpath.endsWith(".note")){ fullpath.append(".note"); }
+ if(QFile::exists(fullpath)){
+ return newNoteName(name, true); //try again
+ }
+ return name; //good name - go ahead and return it
+}
+
+void NotePadPlugin::updateConfigMenu(){
+ //Re-create the menu and open it
+ config->menu()->clear();
+ config->menu()->addAction(LXDG::findIcon("document-open",""), tr("Open Text File"), this, SLOT(openNoteClicked()) );
+ config->menu()->addAction(LXDG::findIcon("document-new",""), tr("Create a Note"), this, SLOT(newNoteClicked()) );
+ if(cnote->currentIndex()>=0){
+ config->menu()->addSeparator();
+ config->menu()->addAction(LXDG::findIcon("document-edit",""), tr("Rename Note"), this, SLOT(renameNote()) );
+ config->menu()->addAction(LXDG::findIcon("document-close",""), tr("Delete Note"), this, SLOT(remNote()) );
+ }
+}
+
+void NotePadPlugin::openNoteClicked(){
+ openNote();
+}
+
+void NotePadPlugin::newNoteClicked(){
+ //QtConcurrent::run(this, &NotePadPlugin::newNote);
+ QString name = newNoteName();
+ if(name.isEmpty()){ return; }
+ QString fullpath = QDir::homePath()+"/Notes/"+name;
+ if(!fullpath.endsWith(".note")){ fullpath.append(".note"); }
+ //qDebug() << " - New Note:" << name << fullpath;
+ int index = cnote->findText(name, Qt::MatchExactly | Qt::MatchCaseSensitive);
+ if(QFile::exists(fullpath) && index <0){
+ //Alternate option of searching for the file in the list
+ index = cnote->findText(fullpath, Qt::MatchExactly | Qt::MatchCaseSensitive);
+ }
+ if(index>=0){
+ //This note already exists: just load it
+ cnote->setCurrentIndex(index);
+ }else{
+ //New note - add it to the end of the list and then load it
+ cnote->addItem(name, fullpath);
+ cnote->setCurrentIndex( cnote->count()-1 );
+ }
+}
+
+void NotePadPlugin::remNote(){
+ QString note = cnote->currentData().toString();
+ if(note.isEmpty()){ return; }
+ watcher->removePath(note); //remove this file from the watcher
+ this->saveSetting("currentFile",""); //reset the internal value
+ QFile::remove(note); //remove the file
+ //if(!note.startsWith(QDir::homePath()+"/Notes/") ){
+ //If the file was not in the notes directory, need to manually prompt for a re-load
+ // otherwise, the directory watcher will catch it and trigger a re-load (no need to double-load)
+ notesDirChanged();
+ //}
+}
+
+void NotePadPlugin::renameNote(){
+ int item = cnote->currentIndex();
+ if(item<0){ return; } //nothing selected
+ QString oldpath = cnote->currentData().toString();
+ if(oldpath.isEmpty() || !oldpath.endsWith(".note")){ return; }
+ QString name = newNoteName(cnote->currentText());
+ if(name.isEmpty()){ return; }
+ QString fullpath = QDir::homePath()+"/Notes/"+name;
+ if(!fullpath.endsWith(".note")){ fullpath.append(".note"); }
+ //qDebug() << " - New Note:" << name << fullpath;
+ //Update the current item data to point to this file
+ cnote->setItemText(item, name);
+ cnote->setItemData(item, fullpath);
+ //Now move the file over
+ QFile::rename(oldpath, fullpath);
+ noteChanged();
+}
+
+void NotePadPlugin::newTextAvailable(){
+ if(updating){ return; } //programmatic change of the widget
+ if(typeTimer->isActive()){ typeTimer->stop(); }
+ typeTimer->start();
+}
+
+void NotePadPlugin::updateContents(){
+ if(updating){ return; } //this was a programmatic change to the widget
+ //The text was changed in the plugin - save it in the file
+ QString note = cnote->currentData().toString();
+ updating = true;
+ LUtils::writeFile(note, edit->toPlainText().split("\n"), true);
+ QApplication::processEvents(); //make sure to process/discard the file changed signal before disabling the flag
+ updating = false;
+}
+
+void NotePadPlugin::notesDirChanged(){
+ if(updating){ return; }
+ QString cfile = this->readSetting("currentFile","").toString();
+ QStringList notes;
+ QDir dir(QDir::homePath()+"/Notes");
+ QStringList files = dir.entryList(QStringList() << "*.note", QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
+ for(int i=0; i<files.length(); i++){
+ notes << dir.absoluteFilePath(files[i]);
+ }
+ QString custom = this->readSetting("customFile","").toString();
+ if(!custom.isEmpty() && QFile::exists(custom) ){ notes << custom; }
+ //qDebug() << "Available Notes:" << notes << cfile;
+ //Now update the UI list
+ updating = true; //don't refresh the UI until done changing lists
+ cnote->clear();
+ bool found = false;
+ for(int i=0; i<notes.length(); i++){
+ QString name = notes[i].section("/",-1);
+ if(name.endsWith(".note")){ name.chop(5); }
+ cnote->addItem(name, notes[i]);
+ if(notes[i]==cfile){ cnote->setCurrentIndex(i); found = true;}
+ }
+ if(!found && !cfile.isEmpty() && QFile::exists(cfile)){
+ //Current note is a manually-loaded text file
+ cnote->addItem(cfile.section("/",-1), cfile);
+ cnote->setCurrentIndex( cnote->count()-1 ); //last item
+ found = true;
+ }
+ if(!found && cnote->count()>0){ cnote->setCurrentIndex(0); }
+ updating =false;
+ noteChanged();
+}
+
+void NotePadPlugin::noteChanged(){
+ if(updating){ return; }
+ updating =true;
+ QString note;
+ if(cnote->currentIndex()>=0){
+ note = cnote->currentData().toString();
+ }
+ QTimer::singleShot(0, this, SLOT(updateConfigMenu()) );
+ if(note.isEmpty() && cnote->count()>0){
+ updating=false;
+ cnote->setCurrentIndex(0);
+ return;
+ }
+ QString oldnote = this->readSetting("currentFile","").toString();
+ //qDebug() << "Note Changed:" << note << oldnote;
+ if( oldnote!=note ){
+ //Clear the old note file/setting
+ if(!oldnote.isEmpty()){
+ watcher->removePath(oldnote);
+ this->saveSetting("currentFile","");
+ }
+ if(!note.isEmpty()){
+ this->saveSetting("currentFile",note);
+ watcher->addPath(note);
+ }
+ }
+
+ if(!note.isEmpty()){
+ QString text = LUtils::readFile(note).join("\n");
+ if(text!=edit->toPlainText()){
+ edit->setPlainText( text );
+ }
+ }else{
+ edit->clear();
+ }
+ //If no notes available - disable the editor until a new one is created
+ edit->setEnabled(!note.isEmpty());
+ //rem->setEnabled(!note.isEmpty());
+ cnote->setEnabled(!note.isEmpty());
+ //leave the new/open buttons enabled all the time
+ updating = false;
+}
+
+
+void NotePadPlugin::loadIcons(){
+ /*open->setIcon( LXDG::findIcon("document-open","") );
+ add->setIcon( LXDG::findIcon("document-new","") );
+ rem->setIcon( LXDG::findIcon("document-close","") );*/
+ config->setIcon( LXDG::findIcon("configure","") );
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.h
new file mode 100644
index 00000000..5084dadf
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/notepad/NotepadPlugin.h
@@ -0,0 +1,66 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a simple text editor for notes on the desktop
+//===========================================
+#ifndef _LUMINA_DESKTOP_NOTEPAD_PLUGIN_H
+#define _LUMINA_DESKTOP_NOTEPAD_PLUGIN_H
+
+#include <QPlainTextEdit>
+#include <QToolButton>
+#include <QComboBox>
+#include <QVBoxLayout>
+#include <QTimer>
+#include <QFileSystemWatcher>
+#include "../LDPlugin.h"
+
+class NotePadPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ NotePadPlugin(QWidget* parent, QString ID);
+ ~NotePadPlugin();
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(3,3);
+ }
+private:
+ QPlainTextEdit *edit;
+ QToolButton *config; //*open, *add, *rem;
+ QComboBox *cnote;
+ QFrame *frame;
+ QFileSystemWatcher *watcher;
+ bool updating;
+ QTimer *typeTimer;
+
+ void openNote();
+ QString newNoteName(QString oldname = "", bool tryagain = false);
+
+private slots:
+ void updateConfigMenu();
+
+ void openNoteClicked();
+ void newNoteClicked();
+ void remNote();
+ void renameNote();
+ void newTextAvailable();
+ void updateContents();
+
+ void notesDirChanged();
+ void noteChanged();
+
+ void loadIcons();
+
+public slots:
+ void LocaleChange(){
+ QTimer::singleShot(0,this, SLOT(noteChanged()));
+ }
+ void ThemeChange(){
+ QTimer::singleShot(0,this, SLOT(loadIcons()));
+ }
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/quickcontainer/QuickDPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/quickcontainer/QuickDPlugin.h
new file mode 100644
index 00000000..d6039ac0
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/quickcontainer/QuickDPlugin.h
@@ -0,0 +1,51 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a simple container for a QtQuick plugin
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKTOP_PLUGIN_QUICK_H
+#define _LUMINA_DESKTOP_DESKTOP_PLUGIN_QUICK_H
+
+#include <QQuickWidget>
+#include <QVBoxLayout>
+#include "../LDPlugin.h"
+
+#include <LUtils.h>
+
+class QuickDPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ QuickDPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID){
+ this->setLayout( new QVBoxLayout());
+ this->layout()->setContentsMargins(0,0,0,0);
+ container = new QQuickWidget(this);
+ container->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ connect(container, SIGNAL(statusChanged(QQuickWidget::Status)), this, SLOT(statusChange(QQuickWidget::Status)) );
+ this->layout()->addWidget(container);
+ container->setSource(QUrl::fromLocalFile( LUtils::findQuickPluginFile(ID.section("---",0,0)) ));
+ QApplication::processEvents(); //to check for errors right away
+ //this->setInitialSize(container->initialSize().width(), container->initialSize().height());
+ }
+
+ ~QuickDPlugin(){}
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(2,2);
+ }
+private:
+ QQuickWidget *container;
+
+private slots:
+ void statusChange(QQuickWidget::Status status){
+ if(status == QQuickWidget::Error){
+ qDebug() << "Quick Widget Error:" << this->ID();
+ container->setSource(QUrl()); //clear out the script - experienced an error
+ }
+ }
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp
new file mode 100644
index 00000000..c330d6c0
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp
@@ -0,0 +1,363 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "RSSFeedPlugin.h"
+#include "ui_RSSFeedPlugin.h"
+
+#include <LuminaXDG.h>
+#include "LSession.h"
+#include <LUtils.h>
+#include <QDir>
+#include <QFileDialog>
+#include <QInputDialog>
+#include <QtConcurrent>
+
+RSSFeedPlugin::RSSFeedPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID), ui(new Ui::RSSFeedPlugin()){
+ ui->setupUi(this);
+ //Load the global settings
+ setprefix = "rssreader/"; //this structure/prefix should be used for *all* plugins of this type
+ RSS = new RSSReader(this, setprefix);
+ ui->text_feed->setContextMenuPolicy(Qt::NoContextMenu);
+ //Create the options menu
+ optionsMenu = new QMenu(this);
+ ui->tool_options->setMenu(optionsMenu);
+ presetMenu = new QMenu(this);
+ ui->tool_add_preset->setMenu(presetMenu);
+
+ //Setup any signal/slot connections
+ connect(ui->push_back1, SIGNAL(clicked()), this, SLOT(backToFeeds()) );
+ connect(ui->push_back2, SIGNAL(clicked()), this, SLOT(backToFeeds()) );
+ connect(ui->push_back3, SIGNAL(clicked()), this, SLOT(backToFeeds()) );
+ connect(ui->push_save_settings, SIGNAL(clicked()), this, SLOT(saveSettings()) );
+ connect(RSS, SIGNAL(rssChanged(QString)), this, SLOT(RSSItemChanged(QString)) );
+ connect(RSS, SIGNAL(newChannelsAvailable()), this, SLOT(UpdateFeedList()));
+ connect(ui->tool_gotosite, SIGNAL(clicked()), this, SLOT(openFeedPage()) );
+ connect(ui->push_rm_feed, SIGNAL(clicked()), this, SLOT(removeFeed()) );
+ connect(ui->push_add_url, SIGNAL(clicked()), this, SLOT(addNewFeed()) );
+ connect(ui->combo_feed, SIGNAL(currentIndexChanged(int)), this, SLOT(currentFeedChanged()) );
+
+ connect(presetMenu, SIGNAL(triggered(QAction*)), this, SLOT(loadPreset(QAction*)) );
+
+ updateOptionsMenu();
+ QTimer::singleShot(0,this, SLOT(ThemeChange()) );
+ //qDebug() << " - Done with init";
+ QStringList feeds;
+ if( !LSession::handle()->DesktopPluginSettings()->contains(setprefix+"currentfeeds") ){
+ //First-time run of the plugin - automatically load the default feeds
+ feeds = LOS::RSSFeeds();
+ for(int i=0; i<feeds.length(); i++){ feeds[i] = feeds[i].section("::::",1,-1); } //just need url right now
+ feeds << "http://lumina-desktop.org/?feed=rss2"; //Lumina Desktop blog feed
+ LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds);
+ }else{
+ feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList();
+ }
+ RSS->addUrls(feeds);
+ backToFeeds(); //always load the first page
+}
+
+RSSFeedPlugin::~RSSFeedPlugin(){
+
+}
+
+//================
+// PRIVATE
+//================
+void RSSFeedPlugin::updateOptionsMenu(){
+ optionsMenu->clear();
+ optionsMenu->addAction(LXDG::findIcon("list-add",""), tr("Add RSS Feed"), this, SLOT(openFeedNew()) );
+ optionsMenu->addAction(LXDG::findIcon("help-about",""), tr("View Feed Details"), this, SLOT(openFeedInfo()) );
+ optionsMenu->addAction(LXDG::findIcon("configure",""), tr("Settings"), this, SLOT(openSettings()) );
+ optionsMenu->addSeparator();
+ optionsMenu->addAction(LXDG::findIcon("download",""), tr("Update Feeds Now"), this, SLOT(resyncFeeds()) );
+
+ presetMenu->clear();
+ QStringList feeds = LOS::RSSFeeds();
+ feeds << tr("Lumina Desktop RSS")+"::::http://lumina-desktop.org/?feed=rss2";
+ feeds.sort();
+ for(int i=0; i<feeds.length(); i++){
+ QAction *tmp = presetMenu->addAction(feeds[i].section("::::",0,0) );
+ tmp->setWhatsThis( feeds[i].section("::::",1,-1) );
+ }
+}
+
+void RSSFeedPlugin::checkFeedNotify(){
+ bool notify = false;
+ for(int i=0; i<ui->combo_feed->count() && !notify; i++){
+ if( !ui->combo_feed->itemData(i, Qt::WhatsThisRole).toString().isEmpty()){ notify = true; }
+ }
+ QString style;
+ if(notify){ style = "QComboBox{ background-color: rgba(255,0,0,120); }"; }
+ ui->combo_feed->setStyleSheet(style);
+}
+
+//Simplification functions for loading feed info onto widgets
+void RSSFeedPlugin::updateFeed(QString ID){
+ //Now clear/update the feed viewer (HTML)
+ ui->text_feed->clear();
+ if(ID.isEmpty()){ return; } //nothing to show
+
+ //Save the datetime this feed was read
+ LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"feedReads/"+ID, QDateTime::currentDateTime() );
+ //Get the color to use for hyperlinks (need to specify in html)
+ QString color = ui->text_feed->palette().text().color().name(); //keep the hyperlinks the same color as the main text (different formatting still applies)
+ QString html;
+ RSSchannel data = RSS->dataForID(ID);
+ ui->label_lastupdate->setText( data.lastsync.toString(Qt::DefaultLocaleShortDate) );
+ // - generate the html
+ // html.append("<ul style=\"margin-left: 3px;\">\n");
+ for(int i=0; i<data.items.length(); i++){
+ //html.append("<li>");
+ html.append("<h4><a href=\""+data.items[i].link+"\" style=\"color: "+color+";\">"+data.items[i].title+"</a></h4>");
+ if(!data.items[i].pubdate.isNull() || !data.items[i].author.isEmpty()){
+ html.append("<i>(");
+ if(!data.items[i].pubdate.isNull()){ html.append(data.items[i].pubdate.toString(Qt::DefaultLocaleShortDate)); }
+ if(!data.items[i].author.isEmpty()){
+ if(!html.endsWith("(")){ html.append(", "); } //spacing between date/author
+ if(!data.items[i].author_email.isEmpty()){ html.append("<a href=\"mailto:"+data.items[i].author_email+"\" style=\"color: "+color+";\">"+data.items[i].author+"</a>"); }
+ else{ html.append(data.items[i].author); }
+ }
+ html.append(")</i><br>");
+ }
+ html.append(data.items[i].description);
+ //html.append("</li>\n");
+ if(i+1 < data.items.length()){ html.append("<br>"); }
+ }
+ //html.append("</ul>\n");
+ // - load that html into the viewer
+ ui->text_feed->setHtml(html);
+}
+
+void RSSFeedPlugin::updateFeedInfo(QString ID){
+ ui->page_feed_info->setWhatsThis(ID);
+ ui->text_feed_info->clear();
+ if(ID.isEmpty()){ return; }
+ //Get the color to use for hyperlinks (need to specify in html)
+ QString color = ui->text_feed->palette().text().color().name(); //keep the hyperlinks the same color as the main text (different formatting still applies)
+ QString html;
+ RSSchannel data = RSS->dataForID(ID);
+ // - generate the html
+ // <a href=\""+LINK+"\" style=\"color: "+color+";\">"+TEXT+"</a>
+ html.append( QString(tr("Feed URL: %1")).arg("<a href=\""+data.originalURL+"\" style=\"color: "+color+";\">"+data.originalURL+"</a>") +"<br><hr>");
+ html.append( QString(tr("Title: %1")).arg(data.title) +"<br>");
+ html.append( QString(tr("Description: %1")).arg(data.description) +"<br>");
+ html.append( QString(tr("Website: %1")).arg("<a href=\""+data.link+"\" style=\"color: "+color+";\">"+data.link+"</a>") +"<br><hr>");
+ if(!data.lastBuildDate.isNull()){ html.append( QString(tr("Last Build Date: %1")).arg(data.lastBuildDate.toString(Qt::DefaultLocaleShortDate)) +"<br>"); }
+ html.append( QString(tr("Last Sync: %1")).arg(data.lastsync.toString(Qt::DefaultLocaleShortDate)) +"<br>");
+ html.append( QString(tr("Next Sync: %1")).arg(data.nextsync.toString(Qt::DefaultLocaleShortDate)) +"<br>");
+ // - load that html into the viewer
+ ui->text_feed_info->setHtml(html);
+}
+
+//================
+// PRIVATE SLOTS
+//================
+void RSSFeedPlugin::loadIcons(){
+ ui->tool_options->setIcon( LXDG::findIcon("configure","") );
+ ui->tool_gotosite->setIcon( LXDG::findIcon("applications-internet","") );
+ ui->push_back1->setIcon( LXDG::findIcon("go-previous","") );
+ ui->push_back2->setIcon( LXDG::findIcon("go-previous","") );
+ ui->push_back3->setIcon( LXDG::findIcon("go-previous","") );
+ ui->push_rm_feed->setIcon( LXDG::findIcon("list-remove","") );
+ ui->push_add_url->setIcon( LXDG::findIcon("list-add","") );
+ ui->push_save_settings->setIcon( LXDG::findIcon("document-save","") );
+ ui->tool_add_preset->setIcon( LXDG::findIcon("bookmark-new-list","") );
+}
+
+//GUI slots
+// - Page management
+void RSSFeedPlugin::backToFeeds(){
+ ui->stackedWidget->setCurrentWidget(ui->page_feed);
+}
+
+void RSSFeedPlugin::openFeedInfo(){
+ QString ID = ui->combo_feed->currentData().toString();
+ if(ID.isEmpty()){ return; }
+ updateFeedInfo(ID);
+ ui->stackedWidget->setCurrentWidget(ui->page_feed_info);
+
+}
+
+void RSSFeedPlugin::openFeedNew(){
+ ui->line_new_url->setText("");
+ ui->stackedWidget->setCurrentWidget(ui->page_new_feed);
+}
+
+void RSSFeedPlugin::openSettings(){
+ //Sync the widget with the current settings
+ QSettings *set = LSession::handle()->DesktopPluginSettings();
+
+ ui->check_manual_sync->setChecked( set->value(setprefix+"manual_sync_only", false).toBool() );
+ int DI = set->value(setprefix+"default_interval_minutes", 60).toInt();
+ if(DI<1){ DI = 60; }
+ if( (DI%60) == 0 ){DI = DI/60; ui->combo_sync_units->setCurrentIndex(1); } //hourly setting
+ else{ ui->combo_sync_units->setCurrentIndex(1); } //minutes setting
+ ui->spin_synctime->setValue(DI);
+
+ //Now show the page
+ ui->stackedWidget->setCurrentWidget(ui->page_settings);
+}
+
+// - Feed Management
+void RSSFeedPlugin::addNewFeed(){
+ if(ui->line_new_url->text().isEmpty()){ return; } //nothing to add
+ //Validate the URL
+ QUrl url(ui->line_new_url->text());
+ if(!url.isValid()){
+ ui->line_new_url->setFocus();
+ return;
+ }
+ //Add the URL to the settings file for next login
+ QStringList feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList();
+ feeds << url.toString();
+ LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds);
+
+ //Set this URL as the current selection
+ ui->combo_feed->setWhatsThis(url.toString()); //hidden field - will trigger an update in a moment
+ //Add the URL to the backend
+ RSS->addUrls(QStringList() << url.toString());
+ //UpdateFeedList(); //now re-load the feeds which are available
+
+ //Now go back to the main page
+ backToFeeds();
+}
+
+void RSSFeedPlugin::loadPreset(QAction *act){
+ ui->line_new_url->setText( act->whatsThis() );
+}
+
+void RSSFeedPlugin::removeFeed(){
+ QString ID = ui->page_feed_info->whatsThis();
+ if(ID.isEmpty()){ return; }
+ //Remove from the RSS feed object
+ RSSchannel info = RSS->dataForID(ID);
+ RSS->removeUrl(ID);
+ //Remove the URL from the settings file for next login
+ QStringList feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList();
+ feeds.removeAll(info.originalURL);
+ LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds);
+ LSession::handle()->DesktopPluginSettings()->remove(setprefix+"feedReads/"+ID);
+ //Now go back to the main page
+ backToFeeds();
+}
+
+void RSSFeedPlugin::resyncFeeds(){
+ RSS->addUrls( LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList() );
+ RSS->syncNow();
+}
+
+// - Feed Interactions
+void RSSFeedPlugin::currentFeedChanged(){
+ QString ID = ui->combo_feed->currentData().toString();
+ //Remove the "unread" color/flag from the feed
+ ui->combo_feed->setItemData( ui->combo_feed->currentIndex(), QBrush(Qt::transparent) , Qt::BackgroundRole);
+ ui->combo_feed->setItemData( ui->combo_feed->currentIndex(), "", Qt::WhatsThisRole);
+ checkFeedNotify();
+ updateFeed(ID);
+}
+
+void RSSFeedPlugin::openFeedPage(){ //Open main website for feed
+ QString ID = ui->combo_feed->currentData().toString();
+ //Find the data associated with this feed
+ RSSchannel data = RSS->dataForID(ID);
+ QString url = data.link;
+ //qDebug() << "Open Feed Page:" << url;
+ //Now launch the browser
+ if(!url.isEmpty()){
+ LSession::LaunchApplication("lumina-open \""+url+"\"");
+ }
+}
+
+void RSSFeedPlugin::saveSettings(){
+ QSettings *set = LSession::handle()->DesktopPluginSettings();
+ set->setValue(setprefix+"manual_sync_only", ui->check_manual_sync->isChecked() );
+ int DI = ui->spin_synctime->value();
+ if(ui->combo_sync_units->currentIndex()==1){ DI = DI*60; } //convert from hours to minutes
+ set->setValue(setprefix+"default_interval_minutes", DI);
+ set->sync();
+
+ //Now go back to the feeds
+ backToFeeds();
+}
+
+//Feed Object interactions
+void RSSFeedPlugin::UpdateFeedList(){
+
+ QString activate = ui->combo_feed->whatsThis();
+ if(!activate.isEmpty()){ ui->combo_feed->setWhatsThis(""); }
+ if(activate.isEmpty()){ activate = ui->combo_feed->currentData().toString(); } // keep current item selected
+ //Now get/list all the available feeds
+ QStringList IDS = RSS->channels(); //this is pre-sorted by title of the feed
+ //qDebug() << "Update RSS Feed List:" << IDS << activate;
+ for(int i=0; i<IDS.length(); i++){
+ bool newitem = false;
+ if(ui->combo_feed->count()<=i){ newitem = true; }
+ else{
+ QString cid = ui->combo_feed->itemData(i).toString();
+ if(IDS[i]!=cid){
+ if(IDS.contains(cid)){ newitem = true; } //this item is just out of order
+ else{ ui->combo_feed->removeItem(i); } //item no longer is valid
+ }
+ }
+ if(newitem){
+ //Need to add a new item at this point in the menu
+ RSSchannel info = RSS->dataForID(IDS[i]);
+ if(info.title.isEmpty()){
+ //invalid/empty channel
+ ui->combo_feed->insertItem(i, IDS[i], IDS[i]); //just show the URL
+ }else{
+ ui->combo_feed->insertItem(i, info.icon, info.title, IDS[i]);
+ }
+ }
+ }
+ //Remove any extra items on the end of the list
+ for(int i=IDS.length(); i<ui->combo_feed->count(); i++){
+ ui->combo_feed->removeItem(i); i--;
+ }
+ //Now activate the proper item as needed
+ if(IDS.contains(activate)){
+ ui->combo_feed->setCurrentIndex( IDS.indexOf(activate) );
+ }
+ checkFeedNotify();
+}
+
+void RSSFeedPlugin::RSSItemChanged(QString ID){
+ for(int i=0; i<ui->combo_feed->count(); i++){
+ if(ui->combo_feed->itemData(i).toString()!=ID){ continue; }
+ RSSchannel info = RSS->dataForID(ID);
+ if(info.title.isEmpty()){
+ ui->combo_feed->setItemText(i, ID);
+ ui->combo_feed->setItemIcon(i, LXDG::findIcon("dialog-cancel","") );
+ }else{
+ ui->combo_feed->setItemText(i, info.title);
+ ui->combo_feed->setItemIcon(i, info.icon );
+ QColor color(Qt::transparent);
+ if( info.lastBuildDate > LSession::handle()->DesktopPluginSettings()->value(setprefix+"feedReads/"+ID,QDateTime()).toDateTime() ){
+ color = QColor(255,10,10,100); //semi-transparent red
+ ui->combo_feed->setItemData(i, "notify", Qt::WhatsThisRole);
+ }else{
+ ui->combo_feed->setItemData(i, "", Qt::WhatsThisRole);
+ }
+ ui->combo_feed->setItemData(i, QBrush(color) , Qt::BackgroundRole);
+ }
+ }
+ if(ID == ui->combo_feed->currentData().toString()){
+ currentFeedChanged(); //re-load the current feed
+ }else{
+ checkFeedNotify();
+ }
+}
+
+//==================
+// PUBLIC SLOTS
+//==================
+void RSSFeedPlugin::LocaleChange(){
+ ui->retranslateUi(this);
+ updateOptionsMenu();
+}
+void RSSFeedPlugin::ThemeChange(){
+ QTimer::singleShot(0,this, SLOT(loadIcons()));
+ updateOptionsMenu();
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h
new file mode 100644
index 00000000..68b36760
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h
@@ -0,0 +1,72 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a simple RSS feed reader for the desktop
+//===========================================
+#ifndef _LUMINA_DESKTOP_RSS_FEEDER_PLUGIN_H
+#define _LUMINA_DESKTOP_RSS_FEEDER_PLUGIN_H
+
+#include <QTimer>
+#include "../LDPlugin.h"
+
+#include "RSSObjects.h"
+
+namespace Ui{
+ class RSSFeedPlugin;
+};
+
+class RSSFeedPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ RSSFeedPlugin(QWidget* parent, QString ID);
+ ~RSSFeedPlugin();
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(3,3);
+ }
+private:
+ Ui::RSSFeedPlugin *ui;
+ QMenu *optionsMenu, *presetMenu;
+ QString setprefix; //settings prefix
+ RSSReader *RSS;
+
+ void updateOptionsMenu();
+ void checkFeedNotify(); //check if unread feeds are available and change the styling a bit as needed
+
+ //Simplification functions for loading feed info onto widgets
+ void updateFeed(QString ID);
+ void updateFeedInfo(QString ID);
+
+private slots:
+ void loadIcons();
+
+ //GUI slots
+ // - Page management
+ void backToFeeds();
+ void openFeedInfo();
+ void openFeedNew();
+ void openSettings();
+ // - Feed Management
+ void addNewFeed(); // the "add" button (current url in widget on page)
+ void loadPreset(QAction*); //the add-preset menu
+ void removeFeed(); // the "remove" button (current feed for page)
+ void resyncFeeds();
+ // - Feed Interactions
+ void currentFeedChanged();
+ void openFeedPage(); //Open the website in a browser
+ void saveSettings();
+
+ //Feed Object interactions
+ void UpdateFeedList();
+ void RSSItemChanged(QString ID);
+
+public slots:
+ void LocaleChange();
+ void ThemeChange();
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui
new file mode 100644
index 00000000..dc7acd0b
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui
@@ -0,0 +1,552 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RSSFeedPlugin</class>
+ <widget class="QWidget" name="RSSFeedPlugin">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>238</width>
+ <height>278</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>3</number>
+ </property>
+ <property name="topMargin">
+ <number>3</number>
+ </property>
+ <property name="rightMargin">
+ <number>3</number>
+ </property>
+ <property name="bottomMargin">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="page_feed">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QComboBox" name="combo_feed"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_options">
+ <property name="toolTip">
+ <string>View Options</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_lastupdate">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_gotosite">
+ <property name="toolTip">
+ <string>Open Website</string>
+ </property>
+ <property name="text">
+ <string>More</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::NoArrow</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="text_feed">
+ <property name="undoRedoEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="html">
+ <string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_feed_info">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>5</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QPushButton" name="push_back1">
+ <property name="text">
+ <string>Back to Feeds</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Feed Information</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTextBrowser" name="text_feed_info">
+ <property name="html">
+ <string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QPushButton" name="push_rm_feed">
+ <property name="text">
+ <string>Remove Feed</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_new_feed">
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QPushButton" name="push_back2">
+ <property name="text">
+ <string>Back to Feeds</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>New Feed Subscription</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>RSS URL</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <item>
+ <widget class="QLineEdit" name="line_new_url"/>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_add_preset">
+ <property name="toolTip">
+ <string>Load a preset RSS Feed</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="push_add_url">
+ <property name="text">
+ <string>Add to Feeds</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_settings">
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QPushButton" name="push_back3">
+ <property name="text">
+ <string>Back to Feeds</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Feed Reader Settings</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="check_manual_sync">
+ <property name="text">
+ <string>Manual Sync Only</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_4">
+ <property name="toolTip">
+ <string>Some RSS feeds may request custom update intervals instead of using this setting</string>
+ </property>
+ <property name="title">
+ <string>Default Sync Interval</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QSpinBox" name="spin_synctime">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>60</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="combo_sync_units">
+ <property name="currentText">
+ <string>Hour(s)</string>
+ </property>
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <item>
+ <property name="text">
+ <string>Minutes</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Hour(s)</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_9">
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="push_save_settings">
+ <property name="text">
+ <string>Save Settings</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp
new file mode 100644
index 00000000..0a805252
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp
@@ -0,0 +1,287 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "RSSObjects.h"
+#include <QNetworkRequest>
+#include <QXmlStreamReader>
+
+#include "LSession.h"
+
+//============
+// PUBLIC
+//============
+RSSReader::RSSReader(QObject *parent, QString settingsPrefix) : QObject(parent){
+ NMAN = new QNetworkAccessManager(this);
+ connect(NMAN, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)) );
+ connect(NMAN, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(sslErrors(QNetworkReply*, const QList<QSslError>&)) );
+
+ setprefix = settingsPrefix;
+ syncTimer = new QTimer(this);
+ syncTimer->setInterval(300000); //5 minutes
+ connect(syncTimer, SIGNAL(timeout()), this, SLOT(checkTimes()));
+ syncTimer->start();
+}
+
+RSSReader::~RSSReader(){
+
+}
+
+//Information retrieval
+QStringList RSSReader::channels(){
+ QStringList urls = hash.keys();
+ QStringList ids;
+ //sort all the channels by title before output
+ for(int i=0; i<urls.length(); i++){
+ QString title = hash[urls[i]].title;
+ if(title.isEmpty()){ title = "ZZZ"; } //put currently-invalid ones at the end of the list
+ ids << title+" : "+hash[urls[i]].originalURL;
+ }
+ ids.sort();
+ //Now strip off all the titles again to just get the IDs
+ for(int i=0; i<ids.length(); i++){
+ ids[i] = ids[i].section(" : ",-1);
+ }
+ return ids;
+}
+
+RSSchannel RSSReader::dataForID(QString ID){
+ QString key = keyForUrl(ID);
+ if(hash.contains(key)){ return hash[key]; }
+ else{ return RSSchannel(); }
+}
+
+//Initial setup
+void RSSReader::addUrls(QStringList urls){
+ //qDebug() << "Add URLS:" << urls;
+ for(int i=0; i<urls.length(); i++){
+ //Note: Make sure we get the complete URL form for accurate comparison later
+ QString url = QUrl(urls[i]).toString();
+ QString key = keyForUrl(url);
+ if(hash.contains(key)){ continue; } //already handled
+ RSSchannel blank;
+ blank.originalURL = url;
+ hash.insert(url, blank); //put the empty struct into the hash for now
+ requestRSS(url); //startup the initial request for this url
+ }
+ emit newChannelsAvailable();
+}
+
+void RSSReader::removeUrl(QString ID){
+ QString key = keyForUrl(ID);
+ if(hash.contains(key)){ hash.remove(key); }
+ emit newChannelsAvailable();
+}
+
+//=================
+// PUBLIC SLOTS
+//=================
+void RSSReader::syncNow(){
+ QStringList urls = hash.keys();
+ for(int i=0; i<urls.length(); i++){
+ requestRSS(hash[urls[i]].originalURL);
+ }
+}
+
+//=================
+// PRIVATE
+//=================
+QString RSSReader::keyForUrl(QString url){
+ //get current hash key for this URL
+ QStringList keys = hash.keys();
+ if(keys.contains(url)){ return url; } //this is already a valid key
+ for(int i=0; i<keys.length(); i++){
+ if(hash[keys[i]].originalURL == url){ return keys[i]; } //this was an original URL
+ }
+ return "";
+}
+
+void RSSReader::requestRSS(QString url){
+ if(!outstandingURLS.contains(url)){
+ //qDebug() << "Request URL:" << url;
+ NMAN->get( QNetworkRequest( QUrl(url) ) );
+ outstandingURLS << url;
+ }
+}
+
+//RSS parsing functions
+RSSchannel RSSReader::readRSS(QByteArray bytes){
+ //Note: We could expand this later to support multiple "channel"s per Feed
+ // but it seems like there is normally only one channel anyway
+ //qDebug() << "Read RSS:" << bytes.left(100);
+ QXmlStreamReader xml(bytes);
+ RSSchannel rssinfo;
+ //qDebug() << "Can Read XML Stream:" << !xml.hasError();
+ if(xml.readNextStartElement()){
+ //qDebug() << " - RSS Element:" << xml.name();
+ if(xml.name() == "rss" && (xml.attributes().value("version") =="2.0" || xml.attributes().value("version") =="0.91") ){
+ while(xml.readNextStartElement()){
+ //qDebug() << " - RSS Element:" << xml.name();
+ if(xml.name()=="channel"){ rssinfo = readRSSChannel(&xml); }
+ else{ xml.skipCurrentElement(); }
+ }
+ }
+ }
+ if(xml.hasError()){ qDebug() << " - XML Read Error:" << xml.errorString() << "\n" << bytes; }
+ return rssinfo;
+}
+RSSchannel RSSReader::readRSSChannel(QXmlStreamReader *rss){
+ RSSchannel info;
+ info.timetolive = -1;
+ while(rss->readNextStartElement()){
+ //qDebug() << " - RSS Element (channel):" <<rss->name();
+ if(rss->name()=="item"){ info.items << readRSSItem(rss); }
+ else if(rss->name()=="title"){ info.title = rss->readElementText(); }
+ else if(rss->name()=="link"){
+ QString txt = rss->readElementText();
+ if(!txt.isEmpty()){ info.link = txt; }
+ }
+ else if(rss->name()=="description"){ info.description = rss->readElementText(); }
+ else if(rss->name()=="lastBuildDate"){ info.lastBuildDate = RSSDateTime(rss->readElementText()); }
+ else if(rss->name()=="pubDate"){ info.lastPubDate = RSSDateTime(rss->readElementText()); }
+ else if(rss->name()=="image"){ readRSSImage(&info, rss); }
+ //else if(rss->name()=="skipHours"){ info.link = rss->readElementText(); }
+ //else if(rss->name()=="skipDays"){ info.link = rss->readElementText(); }
+ else if(rss->name()=="ttl"){ info.timetolive = rss->readElementText().toInt(); }
+ else{ rss->skipCurrentElement(); }
+ }
+ return info;
+}
+
+RSSitem RSSReader::readRSSItem(QXmlStreamReader *rss){
+ RSSitem item;
+ while(rss->readNextStartElement()){
+ //qDebug() << " - RSS Element (Item):" << rss->name();
+ if(rss->name()=="title"){ item.title = rss->readElementText(); }
+ else if(rss->name()=="link"){ item.link = rss->readElementText(); }
+ else if(rss->name()=="description"){ item.description = rss->readElementText(); }
+ else if(rss->name()=="comments"){ item.comments_url = rss->readElementText(); }
+ else if(rss->name()=="author"){
+ //Special handling - this field can contain both email and name
+ QString raw = rss->readElementText();
+ if(raw.contains("@")){
+ item.author_email = raw.split(" ").filter("@").first();
+ item.author = raw.remove(item.author_email).remove("(").remove(")").simplified(); //the name is often put within parentheses after the email
+ }else{ item.author = raw; }
+ }
+ else if(rss->name()=="guid"){ item.guid = rss->readElementText(); }
+ else if(rss->name()=="pubDate"){ item.pubdate = RSSDateTime(rss->readElementText()); }
+ else{ rss->skipCurrentElement(); }
+ }
+ return item;
+}
+
+void RSSReader::readRSSImage(RSSchannel *item, QXmlStreamReader *rss){
+ while(rss->readNextStartElement()){
+ //qDebug() << " - RSS Element (Image):" << rss->name();
+ if(rss->name()=="url"){ item->icon_url = rss->readElementText(); }
+ else if(rss->name()=="title"){ item->icon_title = rss->readElementText(); }
+ else if(rss->name()=="link"){ item->icon_link = rss->readElementText(); }
+ else if(rss->name()=="width"){ item->icon_size.setWidth(rss->readElementText().toInt()); }
+ else if(rss->name()=="height"){ item->icon_size.setHeight(rss->readElementText().toInt()); }
+ else if(rss->name()=="description"){ item->icon_description = rss->readElementText(); }
+ }
+ //Go ahead and kick off the request for the icon
+ if(!item->icon_url.isEmpty()){ requestRSS(item->icon_url); }
+}
+
+QDateTime RSSReader::RSSDateTime(QString datetime){
+ return QDateTime::fromString(datetime, Qt::RFC2822Date);
+}
+
+//=================
+// PRIVATE SLOTS
+//=================
+void RSSReader::replyFinished(QNetworkReply *reply){
+ QString url = reply->request().url().toString();
+ //qDebug() << "Got Reply:" << url;
+ QString key = keyForUrl(url); //current hash key for this URL
+ QByteArray data = reply->readAll();
+ outstandingURLS.removeAll(url);
+ if(data.isEmpty()){
+ //qDebug() << "No data returned:" << url;
+ //see if the URL can be adjusted for known issues
+ bool handled = false;
+ QUrl redirecturl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ if(redirecturl.isValid() && (redirecturl.toString() != url )){
+ //New URL redirect - make the change and send a new request
+ QString newurl = redirecturl.toString();
+ //qDebug() << " - Redirect to:" << newurl;
+ if(hash.contains(key) && !hash.contains(newurl)){
+ hash.insert(newurl, hash.take(key) ); //just move the data over to the new url
+ requestRSS(newurl);
+ emit newChannelsAvailable();
+ handled = true;
+ }
+ }
+ if(!handled && hash.contains(key) ){
+ emit rssChanged(hash[key].originalURL);
+ }
+ return;
+ }
+
+ if(!hash.contains(key)){
+ //qDebug() << " - hash does not contain URL:" << url;
+ //URL removed from list while a request was outstanding?
+ //Could also be an icon fetch response
+ QStringList keys = hash.keys();
+ for(int i=0; i<keys.length(); i++){
+ //qDebug() << " - Check for icon URL:" << hash[keys[i]].icon_url;
+ if(hash[keys[i]].icon_url.toLower() == url.toLower()){ //needs to be case-insensitive
+ //Icon fetch response
+ RSSchannel info = hash[keys[i]];
+ QImage img = QImage::fromData(data);
+ info.icon = QIcon( QPixmap::fromImage(img) );
+ //qDebug() << "Got Icon response:" << url << info.icon;
+ hash.insert(keys[i], info); //insert back into the hash
+ emit rssChanged( hash[keys[i]].originalURL );
+ break;
+ }
+ }
+ reply->deleteLater();
+ }else{
+ //RSS reply
+ RSSchannel info = readRSS(data); //QNetworkReply can be used as QIODevice
+ reply->deleteLater(); //clean up
+ //Validate the info and announce any changes
+ if(info.title.isEmpty() || info.link.isEmpty() || info.description.isEmpty()){
+ qDebug() << "Missing XML Information:" << url << info.title << info.link << info.description;
+ return;
+ } //bad info/read
+ //Update the bookkeeping elements of the info
+ if(info.timetolive<=0){ info.timetolive = LSession::handle()->DesktopPluginSettings()->value(setprefix+"default_interval_minutes", 60).toInt(); }
+ if(info.timetolive <=0){ info.timetolive = 60; } //error in integer conversion from settings?
+ info.lastsync = QDateTime::currentDateTime(); info.nextsync = info.lastsync.addSecs(info.timetolive * 60);
+ //Now see if anything changed and save the info into the hash
+ bool changed = (hash[key].lastBuildDate.isNull() || (hash[key].lastBuildDate < info.lastBuildDate) );
+ bool newinfo = false;
+ if(changed){ newinfo = hash[key].title.isEmpty(); } //no previous info from this URL
+ info.originalURL = hash[key].originalURL; //make sure this info gets preserved across updates
+ if(!hash[key].icon.isNull()){ info.icon = hash[key].icon; } //copy over the icon from the previous reply
+ hash.insert(key, info);
+ if(newinfo){ emit newChannelsAvailable(); } //new channel
+ else if(changed){ emit rssChanged(info.originalURL); } //update to existing channel
+ }
+}
+
+void RSSReader::sslErrors(QNetworkReply *reply, const QList<QSslError> &errors){
+ int ok = 0;
+ qDebug() << "SSL Errors Detected (RSS Reader):" << reply->url();
+ for(int i=0; i<errors.length(); i++){
+ if(errors[i].error()==QSslError::SelfSignedCertificate || errors[i].error()==QSslError::SelfSignedCertificateInChain){ ok++; }
+ else{ qDebug() << "Unhandled SSL Error:" << errors[i].errorString(); }
+ }
+ if(ok==errors.length()){ qDebug() << " - Permitted:" << reply->url(); reply->ignoreSslErrors(); }
+ else{ qDebug() << " - Denied:" << reply->url(); }
+}
+
+void RSSReader::checkTimes(){
+ if(LSession::handle()->DesktopPluginSettings()->value(setprefix+"manual_sync_only", false).toBool()){ return; }
+ QStringList urls = hash.keys();
+ QDateTime cdt = QDateTime::currentDateTime();
+ for(int i=0; i<urls.length(); i++){
+ if(hash[urls[i]].nextsync < cdt){ requestRSS(urls[i]); }
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h
new file mode 100644
index 00000000..3069bf8d
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h
@@ -0,0 +1,105 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_RSS_READER_PLUGIN_OBJECT_H
+#define _LUMINA_DESKTOP_RSS_READER_PLUGIN_OBJECT_H
+
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QString>
+#include <QDateTime>
+#include <QList>
+#include <QIcon>
+#include <QTimer>
+#include <QXmlStreamReader> //Contained in the Qt "core" module - don't need the full "xml" module for this
+#include <QSslError>
+
+struct RSSitem{
+ //Required Fields
+ QString title, link, description;
+
+ //Optional Fields
+ QString comments_url, author_email, author, guid;
+ QDateTime pubdate; //when the item was published
+ //IGNORED INFO from RSS2 spec: "category", "source", "enclosure"
+};
+
+struct RSSchannel{
+ //Required fields
+ QString title, link, description;
+
+ //Optional Fields
+ QDateTime lastBuildDate, lastPubDate; //last build/publish dates
+ // - channel refresh information
+ int timetolive; //in minutes - time until next sync should be performed
+ //QList<int> skiphours;
+ //QStringList skipdays;
+ // - icon info
+ QIcon icon;
+ QString icon_url, icon_title, icon_link, icon_description;
+ QSize icon_size;
+ //All items within this channel
+ QList<RSSitem> items;
+
+ //Optional RSS2 elements ignored:
+ // "cloud", "textInput", "rating", "language", "copyright", "managingEditor", "webMaster",
+ // "category", "generator", "docs"
+
+ //Internal data for bookkeeping
+ QDateTime lastsync, nextsync;
+ QString originalURL; //in case it was redirected to some "fixed" url later
+};
+
+class RSSReader : public QObject{
+ Q_OBJECT
+public:
+ RSSReader(QObject *parent, QString settingsPrefix);
+ ~RSSReader();
+
+ //Information retrieval
+ QStringList channels(); //returns all ID's
+ RSSchannel dataForID(QString ID);
+
+ //Initial setup
+ void addUrls(QStringList urls);
+ void removeUrl(QString ID);
+
+public slots:
+ void syncNow(); //not generally needed
+
+private:
+ //Internal data objects
+ QHash<QString, RSSchannel> hash; // ID/data
+ QString setprefix;
+ QTimer *syncTimer;
+ QNetworkAccessManager *NMAN;
+ QStringList outstandingURLS;
+
+
+ //Simple hash data search functions
+ QString keyForUrl(QString url);
+
+ //Network request function
+ void requestRSS(QString url);
+
+ //RSS parsing functions
+ RSSchannel readRSS(QByteArray bytes);
+ RSSchannel readRSSChannel(QXmlStreamReader *rss);
+ RSSitem readRSSItem(QXmlStreamReader *rss);
+ void readRSSImage(RSSchannel *item, QXmlStreamReader *rss);
+ QDateTime RSSDateTime(QString datetime);
+
+private slots:
+ void replyFinished(QNetworkReply *reply);
+ void sslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
+ void checkTimes();
+
+signals:
+ void rssChanged(QString); //ID
+ void newChannelsAvailable();
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.cpp
new file mode 100644
index 00000000..951bcc98
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.cpp
@@ -0,0 +1,63 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "MonitorWidget.h"
+#include "ui_MonitorWidget.h"
+
+
+#include <LuminaXDG.h>
+#include <LuminaOS.h>
+
+MonitorWidget::MonitorWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MonitorWidget()){
+ ui->setupUi(this); //load the designer form
+ upTimer = new QTimer(this);
+ upTimer->setInterval(2000); //update every 2 seconds
+ connect(upTimer, SIGNAL(timeout()), this, SLOT(UpdateStats()) );
+ LoadIcons();
+ upTimer->start();
+}
+
+MonitorWidget::~MonitorWidget(){
+ //qDebug() << "Removing MonitorWidget";
+}
+
+void MonitorWidget::LoadIcons(){
+ ui->tabWidget->setTabIcon(0,LXDG::findIcon("appointment-recurring","") ); //Summary
+ ui->tabWidget->setTabIcon(1,LXDG::findIcon("drive-harddisk","") ); //Disk Usage
+ //ui->tabWidget->setTabIcon(1,LXDG::findIcon("cpu","") ); //CPU Log
+ //ui->tabWidget->setTabIcon(2,LXDG::findIcon("media-flash-memory-stick","") ); //Mem Log
+}
+
+void MonitorWidget::UpdateStats(){
+ //qDebug() << "Updating System statistics...";
+ ui->label_temps->setText( LOS::CPUTemperatures().join(", ") );
+ if(ui->progress_cpu->isEnabled()){
+ int perc = LOS::CPUUsagePercent();
+ ui->progress_cpu->setValue(perc);
+ if(perc<0){ ui->progress_cpu->setEnabled(false); } //disable this for future checks
+ }
+ if(ui->progress_mem->isEnabled()){
+ int perc = LOS::MemoryUsagePercent();
+ ui->progress_mem->setValue(perc);
+ if(perc<0){ ui->progress_mem->setEnabled(false); } //disable this for future checks
+ }
+ ui->label_diskinfo->setText( LOS::DiskUsage().join("\n") );
+ //Also perform/update the logs as necessary
+ // -- TO DO --
+}
+
+SysMonitorPlugin::SysMonitorPlugin(QWidget *parent, QString ID) : LDPlugin(parent, ID){
+ monitor = new MonitorWidget(this);
+ this->setLayout( new QVBoxLayout() );
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(monitor);
+
+ //this->setInitialSize(monitor->sizeHint().width(),monitor->sizeHint().height());
+}
+
+SysMonitorPlugin::~SysMonitorPlugin(){
+ //qDebug() << "Remove SysMonitorPlugin";
+} \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.h
new file mode 100644
index 00000000..618ac8f4
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.h
@@ -0,0 +1,62 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a simple hardware status monitor on the desktop
+//===========================================
+#ifndef _LUMINA_DESKTOP_PLUGIN_HW_MONITOR_WIDGET_H
+#define _LUMINA_DESKTOP_PLUGIN_HW_MONITOR_WIDGET_H
+
+#include <QTimer>
+#include <QWidget>
+
+#include "../LDPlugin.h"
+
+namespace Ui{
+ class MonitorWidget;
+};
+
+class MonitorWidget : public QWidget{
+ Q_OBJECT
+public:
+ MonitorWidget(QWidget *parent = 0);
+ ~MonitorWidget();
+
+public slots:
+ void LoadIcons();
+
+private:
+ Ui::MonitorWidget *ui;
+ QTimer *upTimer;
+
+private slots:
+ void UpdateStats();
+};
+
+// Wrapper class to put this into a desktop plugin container
+class SysMonitorPlugin : public LDPlugin{
+ Q_OBJECT
+public:
+ SysMonitorPlugin(QWidget* parent, QString ID);
+ ~SysMonitorPlugin();
+
+ virtual QSize defaultPluginSize(){
+ // The returned QSize is in grid points (typically 100 or 200 pixels square)
+ return QSize(3,2);
+ }
+
+private:
+ MonitorWidget *monitor;
+
+public slots:
+ void LocaleChange(){
+ QTimer::singleShot(0,monitor, SLOT(LoadIcons()));
+ }
+ void ThemeChange(){
+ QTimer::singleShot(0,monitor, SLOT(LoadIcons()));
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.ui b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.ui
new file mode 100644
index 00000000..8798bc25
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/systemmonitor/MonitorWidget.ui
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MonitorWidget</class>
+ <widget class="QWidget" name="MonitorWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>300</width>
+ <height>122</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_summary">
+ <attribute name="title">
+ <string>Summary</string>
+ </attribute>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>CPU Temp:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_temps">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>CPU Usage:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QProgressBar" name="progress_cpu">
+ <property name="value">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Mem Usage:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QProgressBar" name="progress_mem">
+ <property name="value">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_disks">
+ <attribute name="title">
+ <string>Disk I/O</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>292</width>
+ <height>89</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QLabel" name="label_diskinfo">
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LPPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LPPlugin.h
new file mode 100644
index 00000000..c4c76297
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LPPlugin.h
@@ -0,0 +1,77 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is the generic container layout for all panel plugins
+// Simply subclass this when creating a new plugin to enable correct
+// visibility and usage within a panel
+//===========================================
+#ifndef _LUMINA_DESKTOP_PANEL_PLUGIN_H
+#define _LUMINA_DESKTOP_PANEL_PLUGIN_H
+
+#include <QObject>
+#include <QWidget>
+#include <QString>
+#include <QBoxLayout>
+#include <QApplication>
+
+class LPPlugin : public QWidget{
+ Q_OBJECT
+
+private:
+ QBoxLayout *LY;
+ QString plugintype;
+
+public:
+ LPPlugin(QWidget *parent = 0, QString ptype="unknown", bool horizontal = true) : QWidget(parent){
+ plugintype=ptype;
+ this->setContentsMargins(1,1,1,1);
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ this->setFocusPolicy(Qt::NoFocus); //no keyboard focus on the panel/plugins
+ if(horizontal){LY = new QBoxLayout(QBoxLayout::LeftToRight, this); }
+ else{ LY = new QBoxLayout(QBoxLayout::TopToBottom, this); }
+ this->setObjectName(ptype.section("---",0,0));
+ LY->setContentsMargins(0,0,0,0);
+ LY->setSpacing(1);
+ this->setLayout(LY);
+ connect(QApplication::instance(), SIGNAL(LocaleChanged()), this, SLOT(LocaleChange()) );
+ connect(QApplication::instance(), SIGNAL(IconThemeChanged()), this, SLOT(ThemeChange()) );
+ }
+
+ ~LPPlugin(){
+ }
+
+ QBoxLayout* layout(){
+ return LY;
+ }
+
+ QString type(){
+ return plugintype;
+ }
+
+ virtual void AboutToClose(){
+ //This needs to be re-implemented in the subclasses plugin
+ //This is for any last-minute cleanup before the plugin gets deleted
+ }
+
+public slots:
+ virtual void LocaleChange(){
+ //This needs to be re-implemented in the subclassed plugin
+ //This is where all text is set/translated
+ }
+ virtual void ThemeChange(){
+ //This needs to be re-implemented in the subclasses plugin
+ //This is where all the visuals are set if using Theme-dependant icons.
+ }
+ virtual void OrientationChange(){
+ //This needs to be re-implemented in the subclasses plugin
+ //This is where any horizontal/vertical orientations can be changed appropriately
+ }
+
+signals:
+ void MenuClosed(); //This needs to be emitted when any plugin's menu is closed for some reason (check/set focus properly)
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LTBWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LTBWidget.h
new file mode 100644
index 00000000..560e5811
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/LTBWidget.h
@@ -0,0 +1,71 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2013, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_TOOLBAR_WIDGET_H
+#define _LUMINA_TOOLBAR_WIDGET_H
+
+#include <QToolButton>
+#include <QEvent>
+#include <QWheelEvent>
+
+#include "Globals.h"
+#include <LuminaX11.h>
+
+class LTBWidget : public QToolButton{
+ Q_OBJECT
+
+private:
+ LXCB::WINDOWVISIBILITY cstate;
+ //QString rawstyle;
+ void updateBackground(){
+ //QString background = "background: transparent; "; //default value
+ //QString border = "border: 1px solid transparent;";
+ if(cstate == LXCB::IGNORE){ this->setObjectName(""); } //just use the defaults
+ else if(cstate == LXCB::VISIBLE){ this->setObjectName("WindowVisible"); }//background = "background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(255, 255, 255, 240), stop:0.505682 rgba(240, 240, 240, 150), stop:1 rgba(210, 210, 210, 55));"; border="border: 1px solid transparent;"; }
+ else if(cstate == LXCB::INVISIBLE){this->setObjectName("WindowInvisible"); } //background = "background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(215, 215, 215, 240), stop:0.505682 rgba(184, 185, 186, 150), stop:1 rgba(221, 246, 255, 55));"; border="border: 1px solid transparent;"; }
+ else if(cstate == LXCB::ACTIVE){ this->setObjectName("WindowActive"); }//background= "background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(241, 233, 156, 240), stop:0.355682 rgba(255, 243, 127, 150), stop:1 rgba(221, 246, 255, 55));"; border ="border: 1px solid transparent;"; }
+ else if(cstate == LXCB::ATTENTION){ this->setObjectName("WindowAttention"); }//background= "background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 rgba(252, 187, 127, 240), stop:0.505682 rgba(255, 222, 197, 150), stop:1 rgba(221, 246, 255, 55));"; border="border: 1px solid transparent;"; }
+ this->setStyleSheet(""); //force the object to re-evaluate the current theme stylesheet and update visuals
+ //QString raw = rawstyle;
+ //this->setStyleSheet( raw.replace("%1",background).replace("%2", border) );
+ }
+
+signals:
+
+ void wheelScroll(int change);
+
+public:
+ LTBWidget(QWidget* parent) : QToolButton(parent){
+ //this->setStyleSheet( this->styleSheet()+" LTBWidget::menu-indicator{image: none;}");
+ cstate = LXCB::IGNORE;
+
+ this->setPopupMode(QToolButton::InstantPopup);
+ this->setAutoRaise(true);
+
+ //rawstyle = "LTBWidget{ %1 %2 border-radius: 5px;} LTBWidget::menu-indicator{image: none;} LTBWidget::hover{ %1 border: 1px solid black; border-radius: 5px; } LTBWidget::menu-button{ background: transparent; width: 15px; } LTBWidget[popupMode=\"1\"]{%1 %2 border-radius: 5px; padding-right: 15px;} LTBWidget[popupMode=\"1\"]::hover{%1 border: 1px solid black; border-radius: 5px; padding-right: 15px}";
+ updateBackground();
+ }
+
+ ~LTBWidget(){
+ }
+
+ void setState(LXCB::WINDOWVISIBILITY newstate){
+ cstate = newstate;
+ updateBackground();
+ }
+
+public slots:
+
+
+protected:
+ void wheelEvent(QWheelEvent *event){
+ int change = event->delta()/120; // 1/15th of a rotation (delta/120) is usually one "click" of the wheel
+ emit wheelScroll(change);
+ }
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/NewPP.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/NewPP.h
new file mode 100644
index 00000000..3a5f6a5b
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/NewPP.h
@@ -0,0 +1,82 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is the interface to load all the different panel plugins
+//===========================================
+#ifndef _LUMINA_DESKTOP_NEW_PANEL_PLUGIN_H
+#define _LUMINA_DESKTOP_NEW_PANEL_PLUGIN_H
+
+#include <QDebug>
+
+//List all the individual plugin includes here
+#include "LPPlugin.h"
+#include "userbutton/LUserButton.h"
+#include "desktopbar/LDeskBar.h"
+#include "spacer/LSpacer.h"
+#include "line/LLine.h"
+#include "clock/LClock.h"
+#include "battery/LBattery.h"
+#include "desktopswitcher/LDesktopSwitcher.h"
+#include "taskmanager/LTaskManagerPlugin.h"
+#include "systemdashboard/LSysDashboard.h"
+#include "showdesktop/LHomeButton.h"
+#include "appmenu/LAppMenuPlugin.h"
+#include "applauncher/AppLaunchButton.h"
+#include "systemstart/LStartButton.h"
+#include "audioplayer/LPAudioPlayer.h"
+#include "jsonmenu/PPJsonMenu.h"
+//#include "quickcontainer/QuickPPlugin.h"
+#include "systemtray/LSysTray.h" //must be last due to X11 compile issues
+
+
+class NewPP{
+public:
+ static LPPlugin* createPlugin(QString plugin, QWidget* parent = 0, bool horizontal = true){
+ LPPlugin *plug = 0;
+ if(plugin.startsWith("userbutton---")){
+ plug = new LUserButtonPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("homebutton---")){
+ plug = new LHomeButtonPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("desktopbar---")){
+ plug = new LDeskBarPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("spacer---")){
+ plug = new LSpacerPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("line---")){
+ plug = new LLinePlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("taskmanager")){
+ //This one can be "taskmanager[-nogroups]---"
+ plug = new LTaskManagerPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("systemtray---")){
+ plug = new LSysTray(parent, plugin, horizontal);
+ }else if(plugin.startsWith("desktopswitcher---")){
+ plug = new LDesktopSwitcher(parent, plugin, horizontal);
+ }else if(plugin.startsWith("battery---") && LOS::hasBattery()){
+ plug = new LBattery(parent, plugin, horizontal);
+ }else if(plugin.startsWith("clock---")){
+ plug = new LClock(parent, plugin, horizontal);
+ }else if(plugin.startsWith("systemdashboard---")){
+ plug = new LSysDashboard(parent, plugin, horizontal);
+ }else if(plugin.startsWith("appmenu---")){
+ plug = new LAppMenuPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("systemstart---")){
+ plug = new LStartButtonPlugin(parent, plugin, horizontal);
+ }else if(plugin.startsWith("audioplayer---")){
+ plug = new LPAudioPlayer(parent, plugin, horizontal);
+ }else if(plugin.section("::::",0,0)=="jsonmenu" && plugin.split("::::").length()>=3 ){
+ plug = new LPJsonMenu(parent, plugin, horizontal);
+ }else if(plugin.section("---",0,0).section("::",0,0)=="applauncher"){
+ plug = new AppLaunchButtonPlugin(parent, plugin, horizontal);
+ //}else if( plugin.section("---",0,0).startsWith("quick-") && LUtils::validQuickPlugin(plugin.section("---",0,0)) ){
+ //plug = new QuickPPlugin(parent, plugin, horizontal);
+ }else{
+ qWarning() << "Invalid Panel Plugin:"<<plugin << " -- Ignored";
+ }
+ return plug;
+ }
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/RotateToolButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/RotateToolButton.h
new file mode 100644
index 00000000..1c8085f6
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/RotateToolButton.h
@@ -0,0 +1,58 @@
+//===========================================
+// Lumina Desktop source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This is a simple subclass of a QToolButton so it can
+// provice text rotated vertically as needed
+//===========================================
+#ifndef _LUMINA_DESKTOP_ROTATE_TOOLBUTTON_H
+#define _LUMINA_DESKTOP_ROTATE_TOOLBUTTON_H
+
+#include <QStylePainter>
+#include <QStyleOptionToolButton>
+#include <QToolButton>
+#include <QTransform>
+
+class RotateToolButton : public QToolButton{
+ Q_OBJECT
+
+private:
+ int rotate_degrees;
+ void paintEvent(QPaintEvent*){
+ /* NOTE: This is what a standard QToolButton performs (peeked at Qt source code for this tidbit)
+ QStylePainter p(this);
+ QStyleOptionToolButton opt;
+ initStyleOption(&opt);
+ p.drawComplexControl(QStyle::CC_ToolButton, opt);
+ */
+ QStylePainter p(this);
+ QStyleOptionToolButton opt;
+ initStyleOption(&opt);
+ //Apply the rotation matrix to the painter before starting the paint
+ QTransform trans = QTransform( p.transform() ).rotate(rotate_degrees);
+ p.setTransform(trans, false); //merging already taken care of
+ //Now do the normal painting procedure
+ p.drawComplexControl(QStyle::CC_ToolButton, opt);
+ }
+
+public:
+ RotateToolButton(QWidget *parent = Q_NULLPTR) : QToolButton(parent){
+ rotate_degrees = 0; //no rotation initially
+ }
+
+ void setRotation(int degrees){
+ rotate_degrees = degrees;
+ this->update(); //trigger a paint event
+ }
+
+ /*virtual void setText(QString text){
+ this->setText(text);
+ if(rotate_degrees !=0){
+ this->setSizeHint( this->sizeHint()
+ }
+ }*/
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.cpp
new file mode 100644
index 00000000..1fd819b5
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.cpp
@@ -0,0 +1,77 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "AppLaunchButton.h"
+#include "../../LSession.h"
+
+#include <LuminaXDG.h>
+#include <LUtils.h>
+#include <QInputDialog>
+
+AppLaunchButtonPlugin::AppLaunchButtonPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ appfile = id.section("---",0,0).section("::",1,1);
+ if(!QFile::exists(appfile) && appfile.endsWith(".desktop")){
+ //might be a relative path - try to find the file
+ appfile = LUtils::AppToAbsolute(appfile.section("/",-1) );
+ }
+ if(!QFile::exists(appfile)){ appfile.clear(); }
+ connect(button, SIGNAL(clicked()), this, SLOT(AppClicked()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+}
+
+AppLaunchButtonPlugin::~AppLaunchButtonPlugin(){
+
+}
+
+void AppLaunchButtonPlugin::updateButtonVisuals(){
+ QIcon icon;
+ QString tooltip = tr("Click to assign an application");
+ if(appfile.endsWith(".desktop")){
+ XDGDesktop desk(appfile);
+ if(desk.isValid()){
+ icon = LXDG::findIcon(desk.icon, "unknown");
+ tooltip = QString(tr("Launch %1")).arg(desk.name);
+ }else{
+ icon = LXDG::findIcon("task-attention","");
+ appfile.clear();
+ }
+ }else if(QFile::exists(appfile)){
+ icon = LXDG::findMimeIcon(appfile.section("/",-1));
+ tooltip = QString(tr("Open %1")).arg(appfile.section("/",-1));
+ }else{
+ icon = LXDG::findIcon("task-attention", "");
+ }
+ button->setIcon( icon );
+ button->setToolTip(tooltip);
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void AppLaunchButtonPlugin::AppClicked(){
+ if(appfile.isEmpty()){
+ //No App File selected
+ QList<XDGDesktop*> apps = LSession::handle()->applicationMenu()->currentAppHash()->value("All");
+ QStringList names;
+ for(int i=0; i<apps.length(); i++){ names << apps[i]->name; }
+ bool ok = false;
+ QString app = QInputDialog::getItem(this, tr("Select Application"), tr("Name:"), names, 0, false, &ok);
+ if(!ok || names.indexOf(app)<0){ return; } //cancelled
+ appfile = apps[ names.indexOf(app) ]->filePath;
+ //Still need to find a way to set this value persistently
+ // --- perhaps replace the plugin in the desktop settings file with the new path?
+ // --- "applauncher::broken---<something>" -> "applauncher::fixed---<something>" ?
+ QTimer::singleShot(0,this, SLOT(updateButtonVisuals()));
+ }else{
+ LSession::LaunchApplication("lumina-open \""+appfile+"\"");
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.h
new file mode 100644
index 00000000..3aa3c7ad
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/applauncher/AppLaunchButton.h
@@ -0,0 +1,63 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin is a simple button to launch a single application
+//===========================================
+#ifndef _LUMINA_DESKTOP_LAUNCH_APP_PANEL_PLUGIN_H
+#define _LUMINA_DESKTOP_LAUNCH_APP_PANEL_PLUGIN_H
+
+// Qt includes
+#include <QToolButton>
+#include <QString>
+#include <QWidget>
+
+
+// Lumina-desktop includes
+#include "../LPPlugin.h" //main plugin widget
+
+// libLumina includes
+#include "LuminaXDG.h"
+
+// PANEL PLUGIN BUTTON
+class AppLaunchButtonPlugin : public LPPlugin{
+ Q_OBJECT
+
+public:
+ AppLaunchButtonPlugin(QWidget *parent = 0, QString id = "applauncher", bool horizontal=true);
+ ~AppLaunchButtonPlugin();
+
+private:
+ QToolButton *button;
+ QString appfile;
+
+ void updateButtonVisuals();
+
+private slots:
+ void AppClicked();
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ this->layout()->update();
+ updateButtonVisuals();
+ }
+
+ void LocaleChange(){
+ updateButtonVisuals();
+ }
+
+ void ThemeChange(){
+ updateButtonVisuals();
+ }
+};
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.cpp
new file mode 100644
index 00000000..e3be55c2
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.cpp
@@ -0,0 +1,142 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LAppMenuPlugin.h"
+#include "../../LSession.h"
+
+#include <LuminaXDG.h>
+
+LAppMenuPlugin::LAppMenuPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ mainmenu = new QMenu(this);
+ button->setMenu( mainmenu );
+ button->setPopupMode(QToolButton::InstantPopup);
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+
+ connect(mainmenu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ connect(mainmenu, SIGNAL(triggered(QAction*)), this, SLOT(LaunchItem(QAction*)) );
+ connect(LSession::handle()->applicationMenu(), SIGNAL(AppMenuUpdated()), this, SLOT(UpdateMenu()));
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+ QTimer::singleShot(0,this, SLOT(UpdateMenu()) );
+ //Setup the global shortcut handling for opening the start menu
+ connect(QApplication::instance(), SIGNAL(StartButtonActivated()), this, SLOT(shortcutActivated()) );
+ LSession::handle()->registerStartButton(this->type());
+}
+
+LAppMenuPlugin::~LAppMenuPlugin(){
+
+}
+
+void LAppMenuPlugin::updateButtonVisuals(){
+ button->setToolTip( tr("Quickly launch applications or open files"));
+ button->setText( tr("Applications") );
+ //Use the TrueOS icon by default (or the Lumina icon for non-TrueOS systems)
+ button->setIcon( LXDG::findIcon("start-here-lumina","Lumina-DE") );
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void LAppMenuPlugin::shortcutActivated(){
+ if(LSession::handle()->registerStartButton(this->type())){
+ if(button->menu()->isVisible()){ button->menu()->hide(); }
+ else{ button->showMenu(); }
+ }
+}
+
+void LAppMenuPlugin::LaunchItem(QAction* item){
+ QString appFile = item->whatsThis();
+ if(appFile.startsWith("internal::")){
+ appFile = appFile.section("::",1,50); //cut off the "internal" flag
+ if(appFile=="logout"){ LSession::handle()->systemWindow(); }
+ }else if(!appFile.isEmpty()){
+ LSession::LaunchApplication("lumina-open "+appFile);
+ }
+}
+
+void LAppMenuPlugin::UpdateMenu(){
+ mainmenu->clear();
+ QHash<QString, QList<XDGDesktop*> > *HASH = LSession::handle()->applicationMenu()->currentAppHash();
+ //Now Re-create the menu (orignally copied from the AppMenu class)
+ //Add link to the file manager
+ QAction *tmpact = mainmenu->addAction( LXDG::findIcon("user-home", ""), tr("Browse Files") );
+ tmpact->setWhatsThis("\""+QDir::homePath()+"\"");
+ //--Look for the app store
+ XDGDesktop store(LOS::AppStoreShortcut());
+ if(store.isValid()){
+ tmpact = mainmenu->addAction( LXDG::findIcon(store.icon, ""), tr("Install Applications") );
+ tmpact->setWhatsThis("\""+store.filePath+"\"");
+ }
+ //--Look for the control panel
+ XDGDesktop controlp(LOS::ControlPanelShortcut());
+ if(controlp.isValid()){
+ tmpact = mainmenu->addAction( LXDG::findIcon(controlp.icon, ""), tr("Control Panel") );
+ tmpact->setWhatsThis("\""+controlp.filePath+"\"");
+ }
+ mainmenu->addSeparator();
+ //--Now create the sub-menus
+ QStringList cats = HASH->keys();
+ cats.sort(); //make sure they are alphabetical
+ for(int i=0; i<cats.length(); i++){
+ //Make sure they are translated and have the right icons
+ QString name, icon;
+ if(cats[i]=="All"){continue; } //skip this listing for the menu
+ else if(cats[i] == "Multimedia"){ name = tr("Multimedia"); icon = "applications-multimedia"; }
+ else if(cats[i] == "Development"){ name = tr("Development"); icon = "applications-development"; }
+ else if(cats[i] == "Education"){ name = tr("Education"); icon = "applications-education"; }
+ else if(cats[i] == "Game"){ name = tr("Games"); icon = "applications-games"; }
+ else if(cats[i] == "Graphics"){ name = tr("Graphics"); icon = "applications-graphics"; }
+ else if(cats[i] == "Network"){ name = tr("Network"); icon = "applications-internet"; }
+ else if(cats[i] == "Office"){ name = tr("Office"); icon = "applications-office"; }
+ else if(cats[i] == "Science"){ name = tr("Science"); icon = "applications-science"; }
+ else if(cats[i] == "Settings"){ name = tr("Settings"); icon = "preferences-system"; }
+ else if(cats[i] == "System"){ name = tr("System"); icon = "applications-system"; }
+ else if(cats[i] == "Utility"){ name = tr("Utility"); icon = "applications-utilities"; }
+ else if(cats[i] == "Wine"){ name = tr("Wine"); icon = "wine"; }
+ else{ name = tr("Unsorted"); icon = "applications-other"; }
+
+ QMenu *menu = new QMenu(name, this);
+ menu->setIcon(LXDG::findIcon(icon,""));
+ QList<XDGDesktop*> appL = HASH->value(cats[i]);
+ for( int a=0; a<appL.length(); a++){
+ if(appL[a]->actions.isEmpty()){
+ //Just a single entry point - no extra actions
+ QAction *act = new QAction(LXDG::findIcon(appL[a]->icon, ""), appL[a]->name, menu);
+ act->setToolTip(appL[a]->comment);
+ act->setWhatsThis("\""+appL[a]->filePath+"\"");
+ menu->addAction(act);
+ }else{
+ //This app has additional actions - make this a sub menu
+ // - first the main menu/action
+ QMenu *submenu = new QMenu(appL[a]->name, menu);
+ submenu->setIcon( LXDG::findIcon(appL[a]->icon,"") );
+ //This is the normal behavior - not a special sub-action (although it needs to be at the top of the new menu)
+ QAction *act = new QAction(LXDG::findIcon(appL[a]->icon, ""), appL[a]->name, submenu);
+ act->setToolTip(appL[a]->comment);
+ act->setWhatsThis(appL[a]->filePath);
+ submenu->addAction(act);
+ //Now add entries for every sub-action listed
+ for(int sa=0; sa<appL[a]->actions.length(); sa++){
+ QAction *sact = new QAction(LXDG::findIcon(appL[a]->actions[sa].icon, appL[a]->icon), appL[a]->actions[sa].name, this);
+ sact->setToolTip(appL[a]->comment);
+ sact->setWhatsThis("-action \""+appL[a]->actions[sa].ID+"\" \""+appL[a]->filePath+"\"");
+ submenu->addAction(sact);
+ }
+ menu->addMenu(submenu);
+ }
+ }//end loop over apps within this category
+ mainmenu->addMenu(menu);
+ } //end loop over categories
+ //Now add any logout options
+ mainmenu->addSeparator();
+ //QMenu *tmpmenu = mainmenu->addMenu(LXDG::findIcon("system-log-out",""), tr("Leave"));
+ tmpact =mainmenu->addAction(LXDG::findIcon("system-log-out"),tr("Leave"));
+ tmpact->setWhatsThis("internal::logout");
+
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.h
new file mode 100644
index 00000000..659d781f
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/appmenu/LAppMenuPlugin.h
@@ -0,0 +1,64 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin is a re-creation of the classic "start" menu
+//===========================================
+#ifndef _LUMINA_DESKTOP_APP_MENU_PANEL_PLUGIN_H
+#define _LUMINA_DESKTOP_APP_MENU_PANEL_PLUGIN_H
+
+// Qt includes
+#include <QMenu>
+#include <QToolButton>
+#include <QString>
+#include <QWidget>
+
+
+// Lumina-desktop includes
+#include "../LPPlugin.h" //main plugin widget
+
+
+// PANEL PLUGIN BUTTON
+class LAppMenuPlugin : public LPPlugin{
+ Q_OBJECT
+
+public:
+ LAppMenuPlugin(QWidget *parent = 0, QString id = "appmenu", bool horizontal=true);
+ ~LAppMenuPlugin();
+
+private:
+ QToolButton *button;
+ QMenu *mainmenu;
+
+ void updateButtonVisuals();
+
+private slots:
+ void shortcutActivated();
+ void LaunchItem(QAction* item);
+ void UpdateMenu();
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ this->layout()->update();
+ updateButtonVisuals();
+ }
+
+ void LocaleChange(){
+ updateButtonVisuals();
+ }
+
+ void ThemeChange(){
+ updateButtonVisuals();
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.cpp
new file mode 100644
index 00000000..5669aaf5
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.cpp
@@ -0,0 +1,30 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Susanne Jaeckel
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LPAudioPlayer.h"
+#include "LSession.h"
+
+LPAudioPlayer::LPAudioPlayer(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ //Setup the button
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ button->setPopupMode(QToolButton::InstantPopup); //make sure it runs the update routine first
+ //connect(button, SIGNAL(clicked()), this, SLOT(openMenu()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+ wact = new QWidgetAction(this);
+ aplayer = new PPlayerWidget(this);
+ button ->setMenu(new QMenu(this) );
+ wact->setDefaultWidget(aplayer);
+ button->menu()->addAction(wact);
+ //Now start up the widgets
+ button->setIcon( LXDG::findIcon("audio-volume-high","") );
+ QTimer::singleShot(0,this,SLOT(OrientationChange()) ); //update the sizing/icon
+}
+
+LPAudioPlayer::~LPAudioPlayer(){
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.h
new file mode 100644
index 00000000..e5132b1f
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/LPAudioPlayer.h
@@ -0,0 +1,49 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_PANEL_AUDIO_PLAYER_PLUGIN_H
+#define _LUMINA_PANEL_AUDIO_PLAYER_PLUGIN_H
+
+#include "../../Globals.h"
+#include "../LTBWidget.h"
+#include "../LPPlugin.h"
+#include "PPlayerWidget.h"
+
+class LPAudioPlayer : public LPPlugin{
+ Q_OBJECT
+public:
+ LPAudioPlayer(QWidget *parent = 0, QString id = "audioplayer", bool horizontal=true);
+ ~LPAudioPlayer();
+
+private:
+ QToolButton *button;
+ QWidgetAction *wact;
+ PPlayerWidget *aplayer;
+
+ //int iconOld;
+
+private slots:
+ //void updateBattery(bool force = false);
+ //QString getRemainingTime();
+
+public slots:
+ void LocaleChange(){
+ //updateBattery(true);
+ }
+
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ this->layout()->update();
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.cpp
new file mode 100644
index 00000000..023e20c7
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.cpp
@@ -0,0 +1,258 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "PPlayerWidget.h"
+#include "ui_PPlayerWidget.h"
+
+#include <QDir>
+#include <QUrl>
+#include <QInputDialog>
+#include <QFileDialog>
+#include <LuminaXDG.h>
+#include <QDebug>
+#include <QDesktopWidget>
+
+PPlayerWidget::PPlayerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::PPlayerWidget()){
+ ui->setupUi(this); //load the designer form
+ PLAYER = new QMediaPlayer(this);
+ PLAYER->setVolume(100);
+ PLAYER->setNotifyInterval(1000); //1 second interval (just needs to be a rough estimate)
+ PLAYLIST = new QMediaPlaylist(this);
+ PLAYLIST->setPlaybackMode(QMediaPlaylist::Sequential);
+ PLAYER->setPlaylist(PLAYLIST);
+
+ configMenu = new QMenu(this);
+ ui->tool_config->setMenu(configMenu);
+ addMenu = new QMenu(this);
+ ui->tool_add->setMenu(addMenu);
+
+ updatinglists = false; //start off as false
+
+ ui->combo_playlist->setContextMenuPolicy(Qt::NoContextMenu);
+
+ LoadIcons();
+ playerStateChanged(); //update button visibility
+ currentSongChanged();
+ //Connect all the signals/slots
+ //connect(infoTimer, SIGNAL(timeout()), this, SLOT(rotateTrackInfo()) );
+ connect(PLAYER, SIGNAL(positionChanged(qint64)),this, SLOT(updateProgress(qint64)) );
+ connect(PLAYER, SIGNAL(durationChanged(qint64)), this, SLOT(updateMaxProgress(qint64)) );
+ connect(PLAYLIST, SIGNAL(mediaChanged(int, int)), this, SLOT(playlistChanged()) );
+ connect(PLAYER, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(playerStateChanged()) );
+ connect(PLAYLIST, SIGNAL(currentMediaChanged(const QMediaContent&)), this, SLOT(currentSongChanged()) );
+ connect(ui->combo_playlist, SIGNAL(currentIndexChanged(int)), this, SLOT(userlistSelectionChanged()) );
+ connect(ui->tool_play, SIGNAL(clicked()), this, SLOT(playClicked()) );
+ connect(ui->tool_pause, SIGNAL(clicked()), this, SLOT(pauseClicked()) );
+ connect(ui->tool_stop, SIGNAL(clicked()), this, SLOT(stopClicked()) );
+ connect(ui->tool_next, SIGNAL(clicked()), this, SLOT(nextClicked()) );
+ connect(ui->tool_prev, SIGNAL(clicked()), this, SLOT(prevClicked()) );
+
+}
+
+PPlayerWidget::~PPlayerWidget(){
+ //qDebug() << "Removing PPlayerWidget";
+}
+
+void PPlayerWidget::LoadIcons(){
+ ui->tool_stop->setIcon( LXDG::findIcon("media-playback-stop","") );
+ ui->tool_play->setIcon( LXDG::findIcon("media-playback-start","") );
+ ui->tool_pause->setIcon( LXDG::findIcon("media-playback-pause","") );
+ ui->tool_next->setIcon( LXDG::findIcon("media-skip-forward","") );
+ ui->tool_prev->setIcon( LXDG::findIcon("media-skip-backward","") );
+ ui->tool_add->setIcon( LXDG::findIcon("list-add","") );
+ ui->tool_config->setIcon( LXDG::findIcon("configure","") );
+ //Now re-assemble the menus as well
+ configMenu->clear();
+ configMenu->addAction(LXDG::findIcon("media-eject",""), tr("Clear Playlist"), this, SLOT(ClearPlaylist()));
+ configMenu->addAction(LXDG::findIcon("roll",""), tr("Shuffle Playlist"), this, SLOT(ShufflePlaylist()));
+ addMenu->clear();
+ addMenu->addAction(LXDG::findIcon("document-new",""), tr("Add Files"), this, SLOT(AddFilesToPlaylist()));
+ addMenu->addAction(LXDG::findIcon("folder-new",""), tr("Add Directory"), this, SLOT(AddDirToPlaylist()));
+ addMenu->addAction(LXDG::findIcon("download",""), tr("Add URL"), this, SLOT(AddURLToPlaylist()));
+}
+
+void PPlayerWidget::playClicked(){
+ PLAYER->play();
+}
+
+void PPlayerWidget::pauseClicked(){
+ PLAYER->pause();
+}
+
+void PPlayerWidget::stopClicked(){
+ PLAYER->stop();
+}
+
+void PPlayerWidget::nextClicked(){
+ PLAYLIST->next();
+}
+
+void PPlayerWidget::prevClicked(){
+ PLAYLIST->previous();
+}
+
+void PPlayerWidget::AddFilesToPlaylist(){
+ //Prompt the user to select multimedia files
+ QFileDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setFileMode(QFileDialog::ExistingFiles);
+ dlg.setAcceptMode(QFileDialog::AcceptOpen);
+ dlg.setNameFilter( tr("Multimedia Files")+" ("+LXDG::findAVFileExtensions().join(" ")+")");
+ dlg.setWindowTitle(tr("Select Multimedia Files"));
+ dlg.setWindowIcon( LXDG::findIcon("file-open","") );
+ dlg.setDirectory(QDir::homePath()); //start in the home directory
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ QList<QUrl> files = dlg.selectedUrls();
+ if(files.isEmpty() || dlg.result()!=QDialog::Accepted){ return; } //cancelled
+ //Make this use show/processEvents later
+ //QList<QUrl> files = QFileDialog::getOpenFileUrls(0, tr("Select Multimedia Files"), QDir::homePath(), "Multimedia Files ("+LXDG::findAVFileExtensions().join(" ")+")");
+ QList<QMediaContent> urls;
+ for(int i=0; i<files.length(); i++){
+ urls << QMediaContent(files[i]);
+ }
+ PLAYLIST->addMedia(urls);
+ playlistChanged();
+}
+
+void PPlayerWidget::AddDirToPlaylist(){
+ QFileDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setFileMode(QFileDialog::Directory);
+ dlg.setOption(QFileDialog::ShowDirsOnly, true);
+ dlg.setAcceptMode(QFileDialog::AcceptOpen);
+ dlg.setWindowTitle(tr("Select Multimedia Directory"));
+ dlg.setWindowIcon( LXDG::findIcon("folder-open","") );
+ dlg.setDirectory(QDir::homePath()); //start in the home directory
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ if(dlg.result() != QDialog::Accepted){ return; } //cancelled
+ QStringList sel = dlg.selectedFiles();
+ if(sel.isEmpty()){ return; } //cancelled
+ QString dirpath = sel.first(); //QFileDialog::getExistingDirectory(0, tr("Select a Multimedia Directory"), QDir::homePath() );
+ if(dirpath.isEmpty()){ return; } //cancelled
+ QDir dir(dirpath);
+ QFileInfoList files = dir.entryInfoList(LXDG::findAVFileExtensions(), QDir::Files | QDir::NoDotAndDotDot, QDir::Name);
+ if(files.isEmpty()){ return; } //nothing in this directory
+ QList<QMediaContent> urls;
+ for(int i=0; i<files.length(); i++){
+ urls << QMediaContent(QUrl::fromLocalFile(files[i].absoluteFilePath()) );
+ }
+ PLAYLIST->addMedia(urls);
+ playlistChanged();
+}
+
+void PPlayerWidget::AddURLToPlaylist(){
+ QInputDialog dlg(0, Qt::Dialog | Qt::WindowStaysOnTopHint );
+ dlg.setInputMode(QInputDialog::TextInput);
+ dlg.setLabelText(tr("Enter a valid URL for a multimedia file or stream:"));
+ dlg.setTextEchoMode(QLineEdit::Normal);
+ dlg.setWindowTitle(tr("Multimedia URL"));
+ dlg.setWindowIcon( LXDG::findIcon("download","") );
+ //ensure it is centered on the current screen
+ QPoint center = QApplication::desktop()->screenGeometry(this).center();
+ dlg.move( center.x()-(dlg.width()/2), center.y()-(dlg.height()/2) );
+ dlg.show();
+ while( dlg.isVisible() ){
+ QApplication::processEvents();
+ }
+ QString url = dlg.textValue();
+ if(url.isEmpty() || dlg.result()!=QDialog::Accepted){ return; } //cancelled
+
+ //QString url = QInputDialog::getText(0, tr("Multimedia URL"), tr("Enter a valid URL for a multimedia file or stream"), QLineEdit::Normal);
+ //if(url.isEmpty()){ return; }
+ QUrl newurl(url);
+ if(!newurl.isValid()){ return; } //invalid URL
+ PLAYLIST->addMedia(newurl);
+ playlistChanged();
+}
+
+void PPlayerWidget::ClearPlaylist(){
+ PLAYER->stop();
+ PLAYLIST->clear();
+ playlistChanged();
+}
+
+void PPlayerWidget::ShufflePlaylist(){
+ PLAYLIST->shuffle();
+}
+
+
+void PPlayerWidget::userlistSelectionChanged(){ //front-end combobox was changed by the user
+ if(updatinglists){ return; }
+ PLAYLIST->setCurrentIndex( ui->combo_playlist->currentIndex() );
+}
+
+void PPlayerWidget::playerStateChanged(){
+ switch( PLAYER->state() ){
+ case QMediaPlayer::StoppedState:
+ ui->tool_stop->setVisible(false);
+ ui->tool_play->setVisible(true);
+ ui->tool_pause->setVisible(false);
+ ui->progressBar->setVisible(false);
+ break;
+ case QMediaPlayer::PausedState:
+ ui->tool_stop->setVisible(true);
+ ui->tool_play->setVisible(true);
+ ui->tool_pause->setVisible(false);
+ ui->progressBar->setVisible(true);
+ break;
+ case QMediaPlayer::PlayingState:
+ ui->tool_stop->setVisible(true);
+ ui->tool_play->setVisible(false);
+ ui->tool_pause->setVisible(true);
+ ui->progressBar->setVisible(true);
+ break;
+ }
+
+}
+
+void PPlayerWidget::playlistChanged(){
+ updatinglists = true;
+ ui->combo_playlist->clear();
+ for(int i=0; i<PLAYLIST->mediaCount(); i++){
+ QUrl url = PLAYLIST->media(i).canonicalUrl();
+ if(url.isLocalFile()){
+ ui->combo_playlist->addItem(LXDG::findMimeIcon(url.fileName().section(".",-1)), url.fileName() );
+ }else{
+ ui->combo_playlist->addItem(LXDG::findIcon("download",""), url.toString() );
+ }
+ }
+ if(PLAYLIST->currentIndex()<0 && PLAYLIST->mediaCount()>0){ PLAYLIST->setCurrentIndex(0); }
+ ui->combo_playlist->setCurrentIndex(PLAYLIST->currentIndex());
+
+ updatinglists = false;
+}
+
+void PPlayerWidget::currentSongChanged(){
+ if(PLAYLIST->currentIndex() != ui->combo_playlist->currentIndex()){
+ updatinglists = true;
+ ui->combo_playlist->setCurrentIndex(PLAYLIST->currentIndex());
+ updatinglists = false;
+ }
+ ui->tool_next->setEnabled( PLAYLIST->nextIndex() >= 0 );
+ ui->tool_prev->setEnabled( PLAYLIST->previousIndex() >= 0);
+ ui->label_num->setText( QString::number( PLAYLIST->currentIndex()+1)+"/"+QString::number(PLAYLIST->mediaCount()) );
+ ui->progressBar->setRange(0, PLAYER->duration() );
+ ui->progressBar->setValue(0);
+}
+
+void PPlayerWidget::updateProgress(qint64 val){
+ //qDebug() << "Update Progress Bar:" << val;
+ ui->progressBar->setValue(val);
+}
+
+void PPlayerWidget::updateMaxProgress(qint64 val){
+ ui->progressBar->setRange(0,val);
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.h
new file mode 100644
index 00000000..a551d74f
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.h
@@ -0,0 +1,59 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin is a simple audio player on the desktop
+//===========================================
+#ifndef _LUMINA_PANEL_PLUGIN_AUDIO_PLAYER_WIDGET_H
+#define _LUMINA_PANEL_PLUGIN_AUDIO_PLAYER_WIDGET_H
+
+#include <QMediaPlaylist>
+#include <QMediaPlayer>
+#include <QTimer>
+#include <QWidget>
+#include <QMenu>
+
+
+namespace Ui{
+ class PPlayerWidget;
+};
+
+class PPlayerWidget : public QWidget{
+ Q_OBJECT
+public:
+ PPlayerWidget(QWidget *parent = 0);
+ ~PPlayerWidget();
+
+public slots:
+ void LoadIcons();
+
+private:
+ Ui::PPlayerWidget *ui;
+ QMediaPlaylist *PLAYLIST;
+ QMediaPlayer *PLAYER;
+ QMenu *configMenu, *addMenu;
+ bool updatinglists;
+
+private slots:
+ void playClicked();
+ void pauseClicked();
+ void stopClicked();
+ void nextClicked();
+ void prevClicked();
+
+ void AddFilesToPlaylist();
+ void AddDirToPlaylist();
+ void AddURLToPlaylist();
+ void ClearPlaylist();
+ void ShufflePlaylist();
+ void userlistSelectionChanged(); //front-end combobox was changed by the user
+ void playerStateChanged();
+ void playlistChanged(); //list of items changed
+ void currentSongChanged();
+ void updateProgress(qint64 val);
+ void updateMaxProgress(qint64 val);
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.ui b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.ui
new file mode 100644
index 00000000..2d2450be
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/audioplayer/PPlayerWidget.ui
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PPlayerWidget</class>
+ <widget class="QWidget" name="PPlayerWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>346</width>
+ <height>90</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QToolButton::menu-indicator{ image: none; }</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>4</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QToolButton" name="tool_config">
+ <property name="text">
+ <string notr="true">Config</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_add">
+ <property name="text">
+ <string notr="true">Add</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_prev">
+ <property name="text">
+ <string notr="true">prev</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_num">
+ <property name="text">
+ <string notr="true">1/10</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_next">
+ <property name="text">
+ <string notr="true">next</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QComboBox" name="combo_playlist"/>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="tool_play">
+ <property name="text">
+ <string notr="true">Play</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_pause">
+ <property name="text">
+ <string notr="true">Pause</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_stop">
+ <property name="text">
+ <string notr="true">Stop</string>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="value">
+ <number>24</number>
+ </property>
+ <property name="textVisible">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.cpp
new file mode 100644
index 00000000..ee379613
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.cpp
@@ -0,0 +1,115 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Susanne Jaeckel, 2015-2016 Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LBattery.h"
+#include "LSession.h"
+
+LBattery::LBattery(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ iconOld = -1;
+ //Setup the widget
+ label = new QLabel(this);
+ label->setScaledContents(true);
+ //label->setAlignment(Qt::AlignCenter);
+ this->layout()->addWidget(label);
+ //Setup the timer
+ timer = new QTimer();
+ timer->setInterval(5000); //update every 5 seconds
+ connect(timer,SIGNAL(timeout()), this, SLOT(updateBattery()) );
+ timer->start();
+ QTimer::singleShot(0,this,SLOT(OrientationChange()) ); //update the sizing/icon
+}
+
+LBattery::~LBattery(){
+ timer->stop();
+ delete timer;
+}
+
+void LBattery::updateBattery(bool force){
+ // Get current state of charge
+ //QStringList result = LUtils::getCmdOutput("/usr/sbin/apm", QStringList() << "-al");
+ int charge = LOS::batteryCharge(); //result.at(1).toInt();
+//qDebug() << "1: " << result.at(0).toInt() << " 2: " << result.at(1).toInt();
+ int icon = -1;
+ if (charge > 90) { icon = 4; }
+ else if (charge > 70) { icon = 3; }
+ else if (charge > 20) { icon = 2; }
+ else if (charge > 5) { icon = 1; }
+ else if (charge > 0 ) { icon = 0; }
+ if(LOS::batteryIsCharging()){ icon = icon+10; }
+ //icon = icon + result.at(0).toInt() * 10;
+ if (icon != iconOld || force) {
+ switch (icon) {
+ case 0:
+ label->setPixmap( LXDG::findIcon("battery-caution", "").pixmap(label->size()) );
+ break;
+ case 1:
+ label->setPixmap( LXDG::findIcon("battery-040", "").pixmap(label->size()) );
+ break;
+ case 2:
+ label->setPixmap( LXDG::findIcon("battery-060", "").pixmap(label->size()) );
+ break;
+ case 3:
+ label->setPixmap( LXDG::findIcon("battery-080", "").pixmap(label->size()) );
+ break;
+ case 4:
+ label->setPixmap( LXDG::findIcon("battery-100", "").pixmap(label->size()) );
+ break;
+ case 10:
+ label->setPixmap( LXDG::findIcon("battery-charging-caution", "").pixmap(label->size()) );
+ break;
+ case 11:
+ label->setPixmap( LXDG::findIcon("battery-charging-040", "").pixmap(label->size()) );
+ break;
+ case 12:
+ label->setPixmap( LXDG::findIcon("battery-charging-060", "").pixmap(label->size()) );
+ break;
+ case 13:
+ label->setPixmap( LXDG::findIcon("battery-charging-080", "").pixmap(label->size()) );
+ break;
+ case 14:
+ label->setPixmap( LXDG::findIcon("battery-charging", "").pixmap(label->size()) );
+ break;
+ default:
+ label->setPixmap( LXDG::findIcon("battery-missing", "").pixmap(label->size()) );
+ break;
+ }
+ if(icon<iconOld && icon==0){
+ //Play some audio warning chime when
+ LSession::handle()->playAudioFile(LOS::LuminaShare()+"low-battery.ogg");
+ }
+ if(icon==0){ label->setStyleSheet("QLabel{ background: red;}"); }
+ else if(icon==14 && charge>98){ label->setStyleSheet("QLabel{ background: green;}"); }
+ else{ label->setStyleSheet("QLabel{ background: transparent;}"); }
+ iconOld = icon;
+
+ }
+ //Now update the display
+ QString tt;
+ //Make sure the tooltip can be properly translated as necessary (Ken Moore 5/9/14)
+ if(icon > 9 && icon < 15){ tt = QString(tr("%1 % (Charging)")).arg(QString::number(charge)); }
+ else{ tt = QString( tr("%1 % (%2 Remaining)") ).arg(QString::number(charge), getRemainingTime() ); }
+ label->setToolTip(tt);
+}
+
+QString LBattery::getRemainingTime(){
+ int secs = LOS::batterySecondsLeft();
+ if(secs < 0){ return "??"; }
+ QString rem; //remaining
+ if(secs > 3600){
+ int hours = secs/3600;
+ rem.append( QString::number(hours)+"h ");
+ secs = secs - (hours*3600);
+ }
+ if(secs > 60){
+ int min = secs/60;
+ rem.append( QString::number(min)+"m ");
+ secs = secs - (min*60);
+ }
+ if(secs > 0){
+ rem.append(QString::number(secs)+"s");
+ }
+ return rem;
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.h
new file mode 100644
index 00000000..29562d5d
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/LBattery.h
@@ -0,0 +1,53 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Susanne Jaeckel
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_BATTERY_H
+#define _LUMINA_DESKTOP_BATTERY_H
+
+#include <QTimer>
+#include <QWidget>
+#include <QString>
+#include <QLabel>
+
+#include <LUtils.h>
+#include <LuminaXDG.h>
+#include <LuminaOS.h>
+
+#include "../../Globals.h"
+//#include "../LTBWidget.h"
+#include "../LPPlugin.h"
+
+class LBattery : public LPPlugin{
+ Q_OBJECT
+public:
+ LBattery(QWidget *parent = 0, QString id = "battery", bool horizontal=true);
+ ~LBattery();
+
+private:
+ QTimer *timer;
+ QLabel *label;
+ int iconOld;
+
+private slots:
+ void updateBattery(bool force = false);
+ QString getRemainingTime();
+
+public slots:
+ void LocaleChange(){
+ updateBattery(true);
+ }
+
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ label->setFixedSize( QSize(this->height(), this->height()) );
+ }else{
+ label->setFixedSize( QSize(this->width(), this->width()) );
+ }
+ updateBattery(true); //force icon refresh
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/NOTES b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/NOTES
new file mode 100644
index 00000000..3d93267e
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/battery/NOTES
@@ -0,0 +1,49 @@
+Eventuell mit einem Menü implementieren, mit Einträgen für:
+Anzeige des kompletten Status und Infos
+Herunterfahren des Systems etc.
+
+apm -a
+ Zeigt den AC line status an
+ 0 = off-line
+ 1 = on-line
+ 2 = backup-power
+
+apm -b
+ Zeigt
+ 0 = high
+ 1 = low
+ 2 = critical
+ 3 = charging
+
+apm -l
+ Zeit die prozentuale Kapazitaet
+ 255 = nicht unterstuetzt
+
+apm -t
+ Zeigt die verbleibende Zeit in Sekunden
+
+Aufruf Systemfunktionen: LUtils.h
+
+mit der Methode:
+QStringList LUtils::getCmdOutput(QString cmd, QStringList args)
+
+Icons:
+/usr/local/share/icons/oxygen/22x22/status
+oder unter:
+/usr/local/share/icons/oxygen/16x16/status
+
+battery-040.png // 40 %
+battery-060.png
+battery-080.png
+battery-100.png
+
+battery-caution.png
+battery-charging.png
+battery-charging-040.png
+battery-charging-060.png
+battery-charging-080.png
+battery-charging-caution.png
+
+battery-charging-log.png
+battery-log.png
+battery-missing.png
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.cpp
new file mode 100644
index 00000000..b370c536
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.cpp
@@ -0,0 +1,230 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LClock.h"
+#include "LSession.h"
+#include <LuminaThemes.h>
+#include <LuminaXDG.h>
+
+LClock::LClock(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ button = new QToolButton(this); //RotateToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonTextOnly);
+ button->setStyleSheet("font-weight: bold;");
+ button->setPopupMode(QToolButton::DelayedPopup); //make sure it runs the update routine first
+ button->setMenu(new QMenu());
+ //button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ //this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+ connect(button, SIGNAL(clicked()), this, SLOT(openMenu()));
+ connect(button->menu(), SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ calendar = new QCalendarWidget(this);
+ calAct = new QWidgetAction(this);
+ calAct->setDefaultWidget(calendar);
+ TZMenu = new QMenu(this);
+ connect(TZMenu, SIGNAL(triggered(QAction*)), this, SLOT(ChangeTZ(QAction*)) );
+
+ //Now assemble the menu
+ button->menu()->addAction(calAct);
+ button->menu()->addMenu(TZMenu);
+
+ this->layout()->setContentsMargins(0,0,0,0); //reserve some space on left/right
+ this->layout()->addWidget(button);
+
+ //Setup the timer
+ timer = new QTimer();
+ //Load all the initial settings
+ updateFormats();
+ LocaleChange();
+ ThemeChange();
+ OrientationChange();
+ //Now connect/start the timer
+ connect(timer,SIGNAL(timeout()), this, SLOT(updateTime()) );
+ connect(QApplication::instance(), SIGNAL(SessionConfigChanged()), this, SLOT(updateFormats()) );
+ timer->start();
+}
+
+LClock::~LClock(){
+ timer->stop();
+ delete timer;
+}
+
+
+void LClock::updateTime(bool adjustformat){
+ QDateTime CT = QDateTime::currentDateTime();
+ //Now update the display
+ QString label;
+ QString timelabel;
+ QString datelabel;
+ if(deftime){ timelabel = CT.time().toString(Qt::DefaultLocaleShortDate) ; }
+ else{ timelabel=CT.toString(timefmt); }
+ if(defdate){ datelabel = CT.date().toString(Qt::DefaultLocaleShortDate); }
+ else{ datelabel = CT.toString(datefmt); }
+ if(datetimeorder == "dateonly"){
+ label = datelabel;
+ button->setToolTip(timelabel);
+ }else if(datetimeorder == "timedate"){
+ label = timelabel + "\n" + datelabel;
+ button->setToolTip("");
+ }else if(datetimeorder == "datetime"){
+ label = datelabel + "\n" + timelabel;
+ button->setToolTip("");
+ }else{
+ label = timelabel;
+ button->setToolTip(datelabel);
+ }
+ if( this->layout()->direction() == QBoxLayout::TopToBottom ){
+ //different routine for vertical text (need newlines instead of spaces)
+ for(int i=0; i<label.count("\n")+1; i++){
+ if(this->size().width() < (this->fontMetrics().width(label.section("\n",i,i))+10 )&& label.section("\n",i,i).contains(" ")){
+ label.replace(label.section("\n",i,i), label.section("\n",i,i).replace(" ", "\n"));
+ i--;
+ }
+ }
+ //label.replace(" ","\n");
+ }else if( this->size().height() < 2*this->fontMetrics().height() ){
+ label.replace("\n",", ");
+ }
+ if(adjustformat){
+ //Check the font/spacing for the display and adjust as necessary
+ /*double efflines = label.count("\n")+1; //effective lines (with wordwrap)
+ if( (button->fontMetrics().height()*efflines) > button->height() ){
+ //Force a pixel metric font size to fit everything
+ int szH = qRound( (button->height() - button->fontMetrics().lineSpacing() )/efflines );
+ //Need to supply a *width* pixel, not a height metric
+ int szW = qRound( (szH*button->fontMetrics().maxWidth())/( (double) button->fontMetrics().height()) );
+ qDebug() << "Change Clock font:" << button->height() << szH << szW << efflines << button->fontMetrics().height() << button->fontMetrics().lineSpacing();
+ button->setStyleSheet("font-weight: bold; font-size: "+QString::number(szW)+"px;");
+ }else{
+ button->setStyleSheet("font-weight: bold;");
+ }*/
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ //horizontal layout
+ this->setFixedWidth( this->sizeHint().width() +6);
+ }else{
+ //vertical layout
+ this->setMaximumWidth(100000);
+ }
+ }
+ button->setText(label);
+}
+
+void LClock::updateFormats(){
+ qDebug() << "Updating clock format";
+ timefmt = LSession::handle()->sessionSettings()->value("TimeFormat","").toString();
+ datefmt = LSession::handle()->sessionSettings()->value("DateFormat","").toString();
+ deftime = timefmt.simplified().isEmpty();
+ defdate = datefmt.simplified().isEmpty();
+ //Adjust the timer interval based on the smallest unit displayed
+ if(deftime){ timer->setInterval(500); } //1/2 second
+ else if(timefmt.contains("z")){ timer->setInterval(1); } //every millisecond (smallest unit)
+ else if(timefmt.contains("s")){ timer->setInterval(500); } //1/2 second
+ else if(timefmt.contains("m")){ timer->setInterval(2000); } //2 seconds
+ else{ timer->setInterval(1000); } //unknown format - use 1 second interval
+ datetimeorder = LSession::handle()->sessionSettings()->value("DateTimeOrder", "timeonly").toString().toLower();
+ //this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ updateTime(true);
+ //Now fix the size of the widget with the new size hint
+ //this->setFixedWidth( this->sizeHint().width() +6);
+}
+
+void LClock::updateMenu(){
+ QDateTime cdt = QDateTime::currentDateTime();
+ TZMenu->setTitle(QString(tr("Time Zone (%1)")).arg(cdt.timeZoneAbbreviation()) );
+ calendar->showToday(); //make sure the current month is visible
+ calendar->setSelectedDate(QDate::currentDate()); //select the actual date for today
+}
+
+void LClock::openMenu(){
+ updateMenu();
+ button->showMenu();
+}
+
+void LClock::closeMenu(){
+ button->menu()->hide();
+}
+
+void LClock::ChangeTZ(QAction *act){
+ LTHEME::setCustomEnvSetting("TZ",act->whatsThis());
+ QTimer::singleShot(500, this, SLOT(updateTime()) );
+}
+
+void LClock::LocaleChange(){
+ //Refresh all the time zone information
+ TZMenu->clear();
+ TZMenu->addAction(tr("Use System Time"));
+ TZMenu->addSeparator();
+ QList<QByteArray> TZList = QTimeZone::availableTimeZoneIds();
+ //Orgnize time zones for smaller menus (Continent/Country/City)
+ // Note: id = Continent/City
+ QStringList info;
+ for(int i=0; i<TZList.length(); i++){
+ QTimeZone tz(TZList[i]);
+ if(!QString(tz.id()).contains("/")){ continue; }
+ info << "::::"+QString(tz.id()).section("/",0,0)+"::::"+QLocale::countryToString(tz.country())+"::::"+QString(tz.id()).section("/",1,100).replace("_"," ")+"::::"+QString(tz.id());
+ }
+ //Now sort alphabetically
+ info.sort();
+ //Now create the menu tree
+ QString continent, country; //current continent/country
+ QMenu *tmpC=0; //continent menu
+ QMenu *tmpCM=0; //country menu
+ for(int i=0; i<info.length(); i++){
+ //Check if different continent
+ if(info[i].section("::::",1,1)!=continent){
+ if(tmpC!=0){
+ if(tmpCM!=0 && !tmpCM->isEmpty()){
+ tmpC->addMenu(tmpCM);
+ }
+ if(!tmpC->isEmpty()){ TZMenu->addMenu(tmpC); }
+ }
+ tmpC = new QMenu(this);
+ tmpC->setTitle(info[i].section("::::",1,1));
+ tmpCM = new QMenu(this);
+ tmpCM->setTitle(info[i].section("::::",2,2));
+ //Check if different country
+ }else if(info[i].section("::::",2,2)!=country){
+ if(tmpC!=0 && tmpCM!=0 && !tmpCM->isEmpty()){
+ tmpC->addMenu(tmpCM);
+ }
+ tmpCM = new QMenu(this);
+ tmpCM->setTitle(info[i].section("::::",2,2));
+ }
+ //Now create the entry within the country menu
+ if(tmpCM!=0){
+ QAction *act = new QAction(info[i].section("::::",3,3), this);
+ act->setWhatsThis(info[i].section("::::",4,4) );
+ tmpCM->addAction(act);
+ }
+ //Save the values for the next run
+ continent = info[i].section("::::",1,1);
+ country = info[i].section("::::",2,2);
+
+ if(i== info.length()-1){
+ //last go through - save all menus
+ if(tmpCM!=0 && tmpC!=0 && !tmpCM->isEmpty()){ tmpC->addMenu(tmpCM); }
+ if(tmpC!=0 && !tmpC->isEmpty()){ TZMenu->addMenu(tmpC); }
+ }
+ }
+
+}
+
+void LClock::ThemeChange(){
+ TZMenu->setIcon(LXDG::findIcon("clock",""));
+}
+
+void LClock::OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){ //horizontal panel
+ //button->setRotation(0); //no rotation of text
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+ button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+ }else{ //vertical panel
+ //button->setRotation(90); //90 degree rotation
+ this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ }
+ updateTime(true); //re-adjust the font/spacings
+ this->layout()->update();
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.h
new file mode 100644
index 00000000..eddf782c
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/clock/LClock.h
@@ -0,0 +1,58 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_CLOCK_H
+#define _LUMINA_DESKTOP_CLOCK_H
+
+#include <QTimer>
+#include <QDateTime>
+#include <QLabel>
+#include <QWidget>
+#include <QString>
+#include <QLocale>
+#include <QTimeZone>
+#include <QCalendarWidget>
+#include <QWidgetAction>
+#include <QAction>
+#include <QToolButton>
+#include <QMenu>
+
+#include "../LPPlugin.h"
+
+//#include "../RotateToolButton.h"
+
+class LClock : public LPPlugin{
+ Q_OBJECT
+public:
+ LClock(QWidget *parent = 0, QString id = "clock", bool horizontal=true);
+ ~LClock();
+
+private:
+ QTimer *timer;
+ QToolButton *button; //RotateToolButton
+ QString timefmt, datefmt, datetimeorder;
+ bool deftime, defdate;
+ QMenu *TZMenu;
+ QCalendarWidget *calendar;
+ QWidgetAction *calAct;
+
+private slots:
+ void updateTime(bool adjustformat = false);
+ void updateFormats();
+
+ void updateMenu();
+ void openMenu();
+ void closeMenu();
+
+ void ChangeTZ(QAction*);
+
+public slots:
+ void LocaleChange();
+ void ThemeChange();
+ void OrientationChange();
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.cpp
new file mode 100644
index 00000000..90d942de
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.cpp
@@ -0,0 +1,207 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LDeskBar.h"
+#include "../../LSession.h"
+
+LDeskBarPlugin::LDeskBarPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->setStyleSheet( "QToolButton::menu-indicator{ image: none; } QToolButton{ padding: 0px; }");
+
+ //initialize the desktop bar items
+ initializeDesktop();
+ //setup the directory watcher
+ QString fav = QString(getenv("XDG_CONFIG_HOME"))+"/lumina-desktop/favorites.list";
+ if(!QFile::exists(fav)){ QProcess::execute("touch \""+fav+"\""); }
+ watcher = new QFileSystemWatcher(this);
+ watcher->addPath( fav );
+ connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(filechanged(QString)) );
+ QTimer::singleShot(1,this, SLOT(updateFiles()) ); //make sure to load it the first time
+ QTimer::singleShot(0,this, SLOT(OrientationChange()) ); //adjust sizes/layout
+ connect(QApplication::instance(), SIGNAL(DesktopFilesChanged()), this, SLOT(updateFiles()) );
+}
+
+LDeskBarPlugin::~LDeskBarPlugin(){
+}
+
+// =======================
+// PRIVATE FUNCTIONS
+// =======================
+void LDeskBarPlugin::initializeDesktop(){
+ //Applications on the desktop
+ appB = new QToolButton(this);
+ appB->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ appB->setAutoRaise(true);
+ appB->setPopupMode(QToolButton::InstantPopup);
+ appM = new QMenu(this);
+ appB->setMenu(appM);
+ this->layout()->addWidget(appB);
+ connect(appM,SIGNAL(triggered(QAction*)),this,SLOT(ActionTriggered(QAction*)) );
+ connect(appM, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ //Directories on the desktop
+ dirB = new QToolButton(this);
+ dirB->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ dirB->setAutoRaise(true);
+ dirB->setPopupMode(QToolButton::InstantPopup);
+ dirM = new QMenu(this);
+ dirB->setMenu(dirM);
+ this->layout()->addWidget(dirB);
+ connect(dirM,SIGNAL(triggered(QAction*)),this,SLOT(ActionTriggered(QAction*)) );
+ connect(dirM, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ //Audio Files on the desktop
+ audioM = new QMenu(this);
+ connect(audioM,SIGNAL(triggered(QAction*)),this,SLOT(ActionTriggered(QAction*)) );
+ //Video Files on the desktop
+ videoM = new QMenu(this);
+ connect(videoM,SIGNAL(triggered(QAction*)),this,SLOT(ActionTriggered(QAction*)) );
+ //Picture Files on the desktop
+ pictureM = new QMenu(this);
+ connect(pictureM,SIGNAL(triggered(QAction*)),this,SLOT(ActionTriggered(QAction*)) );
+ //Other Files on the desktop
+ otherM = new QMenu(this);
+ connect(otherM,SIGNAL(triggered(QAction*)),this,SLOT(ActionTriggered(QAction*)) );
+ docM = new QMenu(this);
+ connect(docM,SIGNAL(triggered(QAction*)), this,SLOT(ActionTriggered(QAction*)) );
+ //All Files Button
+ fileB = new QToolButton(this);
+ fileB->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ fileB->setAutoRaise(true);
+ fileB->setPopupMode(QToolButton::InstantPopup);
+ fileM = new QMenu(this);
+ fileB->setMenu(fileM);
+ this->layout()->addWidget(fileB);
+
+ updateIcons(); //set all the text/icons
+}
+
+QAction* LDeskBarPlugin::newAction(QString filepath, QString name, QString iconpath){
+ return newAction(filepath, name, QIcon(iconpath));
+}
+
+QAction* LDeskBarPlugin::newAction(QString filepath, QString name, QIcon icon){
+ QAction *act = new QAction(icon, name, this);
+ act->setWhatsThis(filepath);
+ return act;
+}
+
+// =======================
+// PRIVATE SLOTS
+// =======================
+void LDeskBarPlugin::ActionTriggered(QAction* act){
+ //Open up the file with the appropriate application
+ QString cmd = "lumina-open \""+act->whatsThis()+"\"";
+ qDebug() << "Open File:" << cmd;
+ LSession::LaunchApplication(cmd);
+}
+void LDeskBarPlugin::filechanged(QString file){
+ updateFiles();
+ if(!watcher->files().contains(file)){ watcher->addPath(file); } //make sure the file does not get removed from the watcher
+}
+void LDeskBarPlugin::updateFiles(){
+ QFileInfoList homefiles = LSession::handle()->DesktopFiles();
+ QStringList favitems = LDesktopUtils::listFavorites();
+ //Remember for format for favorites: <name>::::[app/dir/<mimetype>]::::<full path>
+ for(int i=0; i<homefiles.length(); i++){
+ if( !favitems.filter(homefiles[i].canonicalFilePath()).isEmpty() ){ continue; } //duplicate entry
+ QString type;
+ if(homefiles[i].isDir()){ type="dir"; }
+ else if(homefiles[i].fileName().endsWith(".desktop")){ type="app"; }
+ else{ type=LXDG::findAppMimeForFile(homefiles[i].fileName()); }
+ favitems << homefiles[i].fileName()+"::::"+type+"::::"+homefiles[i].absoluteFilePath();
+ //qDebug() << "Desktop Item:" << favitems.last();
+ }
+
+ favitems.sort(); //sort them alphabetically
+ //Now add the items to the lists
+ appM->clear();
+ dirM->clear();
+ audioM->clear();
+ videoM->clear();
+ pictureM->clear();
+ docM->clear();
+ otherM->clear();
+ for(int i=0; i<favitems.length(); i++){
+ QString type = favitems[i].section("::::",1,1);
+ QString name = favitems[i].section("::::",0,0);
+ QString path = favitems[i].section("::::",2,50);
+ if(type=="app"){
+ //Add it to appM
+ bool ok = false;
+ XDGDesktop df(path);
+ if(df.isValid() && !df.isHidden){
+ appM->addAction( newAction(df.filePath, df.name, LXDG::findIcon(df.icon, ":/images/default-application.png")) );
+ }
+ }else if(type=="dir"){
+ //Add it to dirM
+ dirM->addAction( newAction(path, name, LXDG::findIcon("folder","")) );
+ }else if(type.startsWith("audio/")){
+ //Add it to audioM
+ audioM->addAction( newAction(path, name, LXDG::findMimeIcon(type)) );
+ }else if(type.startsWith("video/")){
+ //Add it to videoM
+ videoM->addAction( newAction(path, name, LXDG::findMimeIcon(type)) );
+ }else if(type.startsWith("image/")){
+ //Add it to pictureM
+ if(LUtils::imageExtensions().contains(path.section("/",-1).section(".",-1).toLower()) ){
+ pictureM->addAction( newAction(path, name, QIcon(path)) );
+ }else{
+ pictureM->addAction( newAction(path, name, LXDG::findMimeIcon(type)) );
+ }
+ }else if(type.startsWith("text/")){
+ //Add it to docM
+ docM->addAction( newAction(path, name, LXDG::findMimeIcon(type)) );
+ }else{
+ //Add it to otherM
+ otherM->addAction( newAction(path, name, LXDG::findMimeIcon(type)) );
+ }
+
+ }
+
+ //Now update the file menu as appropriate
+ fileM->clear();
+ if(!audioM->isEmpty()){ fileM->addMenu(audioM); }
+ if(!docM->isEmpty()){ fileM->addMenu(docM); }
+ if(!pictureM->isEmpty()){ fileM->addMenu(pictureM); }
+ if(!videoM->isEmpty()){ fileM->addMenu(videoM); }
+ if(!otherM->isEmpty()){ fileM->addMenu(otherM); }
+ //Check for a single submenu, and skip the main if need be
+ disconnect(fileB->menu(), SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()) );
+ if(fileM->actions().length()==1){
+ if(!audioM->isEmpty()){ fileB->setMenu(audioM); }
+ else if(!pictureM->isEmpty()){ fileB->setMenu(pictureM); }
+ else if(!videoM->isEmpty()){ fileB->setMenu(videoM); }
+ else if(!docM->isEmpty()){ fileB->setMenu(docM); }
+ else if(!otherM->isEmpty()){ fileB->setMenu(otherM); }
+ }else{
+ fileB->setMenu(fileM);
+ }
+ connect(fileB->menu(), SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+
+ //Setup the visibility of the buttons
+ appB->setVisible( !appM->isEmpty() );
+ dirB->setVisible( !dirM->isEmpty() );
+ fileB->setVisible( !fileM->isEmpty() );
+}
+
+void LDeskBarPlugin::updateIcons(){
+ //Set all the text/icons
+ appB->setIcon( LXDG::findIcon("favorites", "") );
+ appB->setToolTip(tr("Favorite Applications"));
+ dirB->setIcon( LXDG::findIcon("folder", "") );
+ dirB->setToolTip(tr("Favorite Folders"));
+ audioM->setTitle( tr("Audio") );
+ audioM->setIcon( LXDG::findIcon("audio-x-generic","") );
+ videoM->setTitle( tr("Video") );
+ videoM->setIcon( LXDG::findIcon("video-x-generic","") );
+ pictureM->setTitle( tr("Pictures") );
+ pictureM->setIcon( LXDG::findIcon("image-x-generic","") );
+ otherM->setTitle( tr("Other Files") );
+ otherM->setIcon( LXDG::findIcon("unknown","") );
+ docM->setTitle( tr("Documents") );
+ docM->setIcon( LXDG::findIcon("x-office-document","") );
+ fileB->setIcon( LXDG::findIcon("document-multiple", "") );
+ fileB->setToolTip(tr("Favorite Files") );
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.h
new file mode 100644
index 00000000..74f41230
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopbar/LDeskBar.h
@@ -0,0 +1,88 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This plugin displays the contents of the user's home directory
+// as organized within a couple buttons on the panel (apps, dirs, files)
+//===========================================
+#ifndef _LUMINA_DESKTOP_DESKBAR_H
+#define _LUMINA_DESKTOP_DESKBAR_H
+
+// Qt includes
+#include <QWidget>
+#include <QString>
+#include <QAction>
+#include <QMenu>
+#include <QProcess>
+#include <QTimer>
+#include <QFileSystemWatcher>
+#include <QHBoxLayout>
+#include <QIcon>
+#include <QToolButton>
+#include <QDebug>
+
+// libLumina includes
+#include <LuminaXDG.h>
+
+// local includes
+//#include "../LTBWidget.h"
+#include "../LPPlugin.h"
+
+class LDeskBarPlugin : public LPPlugin{
+ Q_OBJECT
+public:
+ LDeskBarPlugin(QWidget* parent=0, QString id = "desktopbar", bool horizontal=true);
+ ~LDeskBarPlugin();
+
+private:
+ QFileSystemWatcher *watcher;
+ //Special toolbuttons and menus
+ QToolButton *appB, *fileB, *dirB;
+ QMenu *appM, *dirM, *audioM, *videoM, *pictureM, *fileM, *otherM, *docM;
+ QList<QToolButton*> APPLIST;
+ QDateTime lastHomeUpdate;
+
+ void initializeDesktop();
+ //bool readDesktopFile(QString path, QString &name, QString &iconpath);
+
+ QAction* newAction(QString filepath, QString name, QString iconpath);
+ QAction* newAction(QString filepath, QString name, QIcon icon);
+
+ //void updateMenu(QMenu* menu, QFileInfoList files, bool trim = true);
+
+
+private slots:
+ void ActionTriggered(QAction* act);
+ void filechanged(QString);
+ void updateFiles();
+ void updateIcons();
+
+public slots:
+ void LocaleChange(){
+ updateIcons();
+ updateFiles();
+ }
+
+ void OrientationChange(){
+ QSize sz;
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ sz = QSize(this->height(), this->height());
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ sz = QSize(this->width(), this->width());
+ }
+ appB->setIconSize(sz);
+ fileB->setIconSize(sz);
+ dirB->setIconSize(sz);
+ for(int i=0; i<APPLIST.length(); i++){
+ APPLIST[i]->setIconSize(sz);
+ }
+ this->layout()->update();
+ }
+};
+
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.cpp
new file mode 100644
index 00000000..c51e4b4a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.cpp
@@ -0,0 +1,142 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Susanne Jaeckel
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LDesktopSwitcher.h"
+#include <LSession.h>
+
+LDesktopSwitcher::LDesktopSwitcher(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal) {
+ iconOld = -1;
+ this->setStyleSheet( "QToolButton::menu-indicator{ image: none; } QToolButton{padding: 0px;}");
+ //Setup the widget
+ label = new QToolButton(this);
+ label->setPopupMode(QToolButton::DelayedPopup);
+ label->setAutoRaise(true);
+ label->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ label->setIcon( LXDG::findIcon("preferences-desktop-display-color", "") );
+ label->setToolTip(QString("Workspace 1"));
+ connect(label, SIGNAL(clicked()), this, SLOT(openMenu()));
+ menu = new QMenu(this);
+ connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(menuActionTriggered(QAction*)));
+ connect(menu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ label->setMenu(menu);
+ this->layout()->addWidget(label);
+
+ // Maybe a timer should be set to set the toolTip of the button,
+ // becasue the workspace could be switched via Keyboard-shortcuts ...
+
+ QTimer::singleShot(500, this, SLOT(createMenu()) ); //needs a delay to make sure it works right the first time
+ QTimer::singleShot(0,this, SLOT(OrientationChange()) ); //adjust icon size
+}
+
+LDesktopSwitcher::~LDesktopSwitcher(){
+}
+/* MOVED THESE FUNCTIONS TO LIBLUMINA (LuminaX11.h) -- Ken Moore 5/9/14
+void LDesktopSwitcher::setNumberOfDesktops(int number) {
+ Display *display = QX11Info::display();
+ Window rootWindow = QX11Info::appRootWindow();
+
+ Atom atom = XInternAtom(display, "_NET_NUMBER_OF_DESKTOPS", False);
+ XEvent xevent;
+ xevent.type = ClientMessage;
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.display = display;
+ xevent.xclient.window = rootWindow;
+ xevent.xclient.message_type = atom;
+ xevent.xclient.format = 32;
+ xevent.xclient.data.l[0] = number;
+ xevent.xclient.data.l[1] = CurrentTime;
+ xevent.xclient.data.l[2] = 0;
+ xevent.xclient.data.l[3] = 0;
+ xevent.xclient.data.l[4] = 0;
+ XSendEvent(display, rootWindow, False, SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
+
+ XFlush(display);
+}
+
+void LDesktopSwitcher::setCurrentDesktop(int number) {
+ Display *display = QX11Info::display();
+ Window rootWindow = QX11Info::appRootWindow();
+
+ Atom atom = XInternAtom(display, "_NET_CURRENT_DESKTOP", False);
+ XEvent xevent;
+ xevent.type = ClientMessage;
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.display = display;
+ xevent.xclient.window = rootWindow;
+ xevent.xclient.message_type = atom;
+ xevent.xclient.format = 32;
+ xevent.xclient.data.l[0] = number;
+ xevent.xclient.data.l[1] = CurrentTime;
+ xevent.xclient.data.l[2] = 0;
+ xevent.xclient.data.l[3] = 0;
+ xevent.xclient.data.l[4] = 0;
+ XSendEvent(display, rootWindow, False, SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
+
+ XFlush(display);
+}
+
+int LDesktopSwitcher::getNumberOfDesktops() {
+ int number = -1;
+ Atom a = XInternAtom(QX11Info::display(), "_NET_NUMBER_OF_DESKTOPS", true);
+ Atom realType;
+ int format;
+ unsigned long num, bytes;
+ unsigned char *data = 0;
+ int status = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), a, 0L, (~0L),
+ false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
+ if( (status >= Success) && (num > 0) ){
+ number = *data;
+ XFree(data);
+ }
+ return number;
+}
+
+int LDesktopSwitcher::getCurrentDesktop() {
+ int number = -1;
+ Atom a = XInternAtom(QX11Info::display(), "_NET_CURRENT_DESKTOP", true);
+ Atom realType;
+ int format;
+ unsigned long num, bytes;
+ unsigned char *data = 0;
+ int status = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), a, 0L, (~0L),
+ false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
+ if( (status >= Success) && (num > 0) ){
+ number = *data;
+ XFree(data);
+ }
+ return number;
+} */
+
+void LDesktopSwitcher::openMenu(){
+ //Make sure the menu is refreshed right before it opens
+ createMenu();
+ label->showMenu();
+}
+
+QAction* LDesktopSwitcher::newAction(int what, QString name) {
+ QAction *act = new QAction(LXDG::findIcon("preferences-desktop-display", ""), name, this);
+ act->setWhatsThis(QString::number(what));
+ return act;
+}
+
+void LDesktopSwitcher::createMenu() {
+ int cur = LSession::handle()->XCB->CurrentWorkspace(); //current desktop number
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces(); //total number of desktops
+ //qDebug() << "-- vor getCurrentDesktop SWITCH";
+ qDebug() << "Virtual Desktops:" << tot << cur;
+ menu->clear();
+ for (int i = 0; i < tot; i++) {
+ QString name = QString(tr("Workspace %1")).arg( QString::number(i+1) );
+ if(i == cur){ name.prepend("*"); name.append("*");} //identify which desktop this is currently
+ menu->addAction(newAction(i, name));
+ }
+}
+
+void LDesktopSwitcher::menuActionTriggered(QAction* act) {
+ LSession::handle()->XCB->SetCurrentWorkspace(act->whatsThis().toInt());
+ label->setToolTip(QString(tr("Workspace %1")).arg(act->whatsThis().toInt() +1));
+ QTimer::singleShot(500, this, SLOT(createMenu()) ); //make sure the menu gets updated
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.h
new file mode 100644
index 00000000..af9250b7
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/desktopswitcher/LDesktopSwitcher.h
@@ -0,0 +1,72 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Susanne Jaeckel
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_SWITCHER_H
+#define _LUMINA_DESKTOP_SWITCHER_H
+
+#include <QTimer>
+#include <QWidget>
+#include <QString>
+//#include <QX11Info>
+#include <QMenu>
+#include <QToolButton>
+
+#include <LUtils.h>
+#include <LuminaXDG.h>
+#include <LuminaX11.h>
+
+//#include "../LTBWidget.h"
+#include "../LPPlugin.h"
+
+//#include <X11/Xlib.h>
+//#include <X11/Xutil.h>
+//#include <X11/Xatom.h>
+
+class LDesktopSwitcher : public LPPlugin{
+ Q_OBJECT
+public:
+ LDesktopSwitcher(QWidget *parent = 0, QString id = "desktopswitcher", bool horizontal=true);
+ ~LDesktopSwitcher();
+
+private:
+ QTimer *timer;
+ QToolButton *label;
+ QMenu *menu;
+ int iconOld;
+
+ //void setNumberOfDesktops(int);
+ //void setCurrentDesktop(int);
+ //int getNumberOfDesktops();
+ //int getCurrentDesktop();
+
+
+ QAction* newAction(int, QString);
+
+private slots:
+ void openMenu();
+ void createMenu();
+ void menuActionTriggered(QAction*);
+
+public slots:
+ void LocaleChange(){
+ createMenu();
+ }
+
+ void OrientationChange(){
+ QSize sz;
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ sz = QSize(this->height(), this->height());
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ sz = QSize(this->width(), this->width());
+ }
+ label->setIconSize(sz);
+ this->layout()->update();
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.cpp
new file mode 100644
index 00000000..14880f9b
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.cpp
@@ -0,0 +1,35 @@
+//===========================================
+// Lumina Desktop source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "PPJsonMenu.h"
+#include "../../JsonMenu.h"
+
+LPJsonMenu::LPJsonMenu(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ //Setup the button
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ button->setPopupMode(QToolButton::InstantPopup); //make sure it runs the update routine first
+ //connect(button, SIGNAL(clicked()), this, SLOT(openMenu()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+ //Parse the id and get the extra information needed for the plugin
+ QStringList info = id.section("---",0,0).split("::::"); //FORMAT:[ "jsonmenu---<number>",exec,name, icon(optional)]
+ if(info.length()>=3){
+ qDebug() << "Custom JSON Menu Loaded:" << info;
+ JsonMenu *menu = new JsonMenu(info[1], button);
+ button->setText(info[2]);
+ //connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(SystemApplication(QAction*)) );
+ if(info.length()>=4){ button->setIcon( LXDG::findIcon(info[3],"run-build") ); }
+ else{ button->setIcon( LXDG::findIcon("run-build","") ); }
+ button->setMenu(menu);
+ }
+ //Now start up the widgets
+ QTimer::singleShot(0,this,SLOT(OrientationChange()) ); //update the sizing/icon
+}
+
+LPJsonMenu::~LPJsonMenu(){
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.h
new file mode 100644
index 00000000..d0827fd2
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/jsonmenu/PPJsonMenu.h
@@ -0,0 +1,42 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2016, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_PANEL_JSON_PLUGIN_H
+#define _LUMINA_PANEL_JSON_PLUGIN_H
+
+#include "../../Globals.h"
+#include "../LPPlugin.h"
+
+
+class LPJsonMenu : public LPPlugin{
+ Q_OBJECT
+public:
+ LPJsonMenu(QWidget *parent = 0, QString id = "jsonmenu", bool horizontal=true);
+ ~LPJsonMenu();
+
+private:
+ QToolButton *button;
+
+private slots:
+ //void SystemApplication(QAction*);
+
+public slots:
+ void LocaleChange(){
+ }
+
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ this->layout()->update();
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/line/LLine.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/line/LLine.h
new file mode 100644
index 00000000..94de486e
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/line/LLine.h
@@ -0,0 +1,40 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a generic line separator for the panel
+//===========================================
+#ifndef _LUMINA_DESKTOP_PANEL_PLUGIN_LINE_H
+#define _LUMINA_DESKTOP_PANEL_PLUGIN_LINE_H
+
+#include "../LPPlugin.h"
+#include <QFrame>
+
+class LLinePlugin : public LPPlugin{
+ Q_OBJECT
+private:
+ QFrame *line;
+
+public:
+ LLinePlugin(QWidget *parent=0, QString id="spacer", bool horizontal=true) : LPPlugin(parent, id, horizontal){
+ line = new QFrame(this);
+ line->setObjectName("LuminaPanelLine");
+ this->layout()->addWidget(line);
+ OrientationChange();
+ }
+ ~LLinePlugin(){}
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){ //horizontal
+ line->setFrameShape(QFrame::VLine);
+ }else{ //vertical
+ line->setFrameShape(QFrame::HLine);
+ }
+ }
+};
+
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/panel-plugins.pri b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/panel-plugins.pri
new file mode 100644
index 00000000..c40c4725
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/panel-plugins.pri
@@ -0,0 +1,57 @@
+#Lumina Desktop Panel Plugin files
+
+SOURCES += $$PWD/userbutton/LUserButton.cpp \
+ $$PWD/userbutton/UserWidget.cpp \
+ $$PWD/userbutton/UserItemWidget.cpp \
+ $$PWD/desktopbar/LDeskBar.cpp \
+ $$PWD/taskmanager/LTaskManagerPlugin.cpp \
+ $$PWD/taskmanager/LTaskButton.cpp \
+ $$PWD/systemtray/LSysTray.cpp \
+ $$PWD/systemtray/TrayIcon.cpp \
+ $$PWD/clock/LClock.cpp \
+ $$PWD/battery/LBattery.cpp \
+ $$PWD/desktopswitcher/LDesktopSwitcher.cpp \
+ $$PWD/systemdashboard/LSysDashboard.cpp \
+ $$PWD/systemdashboard/SysMenuQuick.cpp \
+ $$PWD/showdesktop/LHomeButton.cpp \
+ $$PWD/appmenu/LAppMenuPlugin.cpp \
+ $$PWD/applauncher/AppLaunchButton.cpp \
+ $$PWD/systemstart/LStartButton.cpp \
+ $$PWD/systemstart/StartMenu.cpp \
+ $$PWD/systemstart/ItemWidget.cpp \
+ $$PWD/audioplayer/LPAudioPlayer.cpp \
+ $$PWD/audioplayer/PPlayerWidget.cpp \
+ $$PWD/jsonmenu/PPJsonMenu.cpp
+
+HEADERS += $$PWD/RotateToolButton.h \
+ $$PWD/userbutton/LUserButton.h \
+ $$PWD/userbutton/UserWidget.h \
+ $$PWD/userbutton/UserItemWidget.h \
+ $$PWD/desktopbar/LDeskBar.h \
+ $$PWD/systemtray/LSysTray.h \
+ $$PWD/systemtray/TrayIcon.h \
+ $$PWD/spacer/LSpacer.h \
+ $$PWD/line/LLine.h \
+ $$PWD/clock/LClock.h \
+ $$PWD/battery/LBattery.h \
+ $$PWD/desktopswitcher/LDesktopSwitcher.h \
+ $$PWD/taskmanager/LTaskManagerPlugin.h \
+ $$PWD/taskmanager/LTaskButton.h \
+ $$PWD/systemdashboard/LSysDashboard.h \
+ $$PWD/systemdashboard/SysMenuQuick.h \
+ $$PWD/showdesktop/LHomeButton.h \
+ $$PWD/appmenu/LAppMenuPlugin.h \
+ $$PWD/applauncher/AppLaunchButton.h \
+ $$PWD/systemstart/LStartButton.h \
+ $$PWD/systemstart/StartMenu.h \
+ $$PWD/systemstart/ItemWidget.h \
+ $$PWD/audioplayer/LPAudioPlayer.h \
+ $$PWD/audioplayer/PPlayerWidget.h \
+ $$PWD/jsonmenu/PPJsonMenu.h
+# $$PWD/quickcontainer/QuickPPlugin.h
+
+FORMS += $$PWD/userbutton/UserWidget.ui \
+ $$PWD/systemdashboard/SysMenuQuick.ui \
+ $$PWD/systemstart/StartMenu.ui \
+ $$PWD/audioplayer/PPlayerWidget.ui
+
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/quickcontainer/QuickPPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/quickcontainer/QuickPPlugin.h
new file mode 100644
index 00000000..6f61c4d5
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/quickcontainer/QuickPPlugin.h
@@ -0,0 +1,43 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a simple container for a QtQuick plugin
+//===========================================
+#ifndef _LUMINA_DESKTOP_PANEL_PLUGIN_QUICK_H
+#define _LUMINA_DESKTOP_PANEL_PLUGIN_QUICK_H
+
+#include <QQuickWidget>
+#include <QVBoxLayout>
+#include "../LPPlugin.h"
+
+#include <LUtils.h>
+#include <QDebug>
+
+class QuickPPlugin : public LPPlugin{
+ Q_OBJECT
+public:
+ QuickPPlugin(QWidget* parent, QString ID, bool horizontal) : LPPlugin(parent, ID){
+ container = new QQuickWidget(this);
+ container->setResizeMode(QQuickWidget::SizeRootObjectToView);
+ this->layout()->addWidget(container);
+ horizontal = true; //just to silence compiler warning
+ container->setSource(QUrl::fromLocalFile( LUtils::findQuickPluginFile(ID.section("---",0,0)) ));
+ }
+
+ ~QuickPPlugin(){}
+
+private:
+ QQuickWidget *container;
+
+private slots:
+ void statusChange(QQuickWidget::Status status){
+ if(status == QQuickWidget::Error){
+ qDebug() << "Quick Widget Error:" << this->type();
+ }
+ }
+
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.cpp
new file mode 100644
index 00000000..6c259b16
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.cpp
@@ -0,0 +1,43 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LHomeButton.h"
+#include "../../LSession.h"
+
+#include <LuminaX11.h>
+
+LHomeButtonPlugin::LHomeButtonPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+
+ connect(button, SIGNAL(clicked()), this, SLOT(showDesktop()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+}
+
+LHomeButtonPlugin::~LHomeButtonPlugin(){
+
+}
+
+void LHomeButtonPlugin::updateButtonVisuals(){
+ button->setIcon( LXDG::findIcon("user-desktop", "") );
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void LHomeButtonPlugin::showDesktop(){
+ QList<WId> wins = LSession::handle()->XCB->WindowList();
+ for(int i=0; i<wins.length(); i++){
+ if( LXCB::INVISIBLE != LSession::handle()->XCB->WindowState(wins[i]) ){
+ LSession::handle()->XCB->MinimizeWindow(wins[i]);
+ }
+ }
+}
+
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.h
new file mode 100644
index 00000000..74aaf4fb
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/showdesktop/LHomeButton.h
@@ -0,0 +1,62 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin is a simple button to hide all windows so the desktop is visible
+//===========================================
+#ifndef _LUMINA_DESKTOP_GO_HOME_PLUGIN_H
+#define _LUMINA_DESKTOP_GO_HOME_PLUGIN_H
+
+// Qt includes
+#include <QToolButton>
+#include <QString>
+#include <QWidget>
+
+
+// Lumina-desktop includes
+#include "../LPPlugin.h" //main plugin widget
+
+// libLumina includes
+#include "LuminaXDG.h"
+
+// PANEL PLUGIN BUTTON
+class LHomeButtonPlugin : public LPPlugin{
+ Q_OBJECT
+
+public:
+ LHomeButtonPlugin(QWidget *parent = 0, QString id = "homebutton", bool horizontal=true);
+ ~LHomeButtonPlugin();
+
+private:
+ QToolButton *button;
+
+ void updateButtonVisuals();
+
+private slots:
+ void showDesktop();
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ this->layout()->update();
+ updateButtonVisuals();
+ }
+
+ void LocaleChange(){
+ updateButtonVisuals();
+ }
+
+ void ThemeChange(){
+ updateButtonVisuals();
+ }
+};
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/spacer/LSpacer.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/spacer/LSpacer.h
new file mode 100644
index 00000000..1e60c519
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/spacer/LSpacer.h
@@ -0,0 +1,34 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This class is a generic invisible spacer for the panel
+//===========================================
+#ifndef _LUMINA_DESKTOP_PANEL_PLUGIN_SPACER_H
+#define _LUMINA_DESKTOP_PANEL_PLUGIN_SPACER_H
+
+#include "../LPPlugin.h"
+
+class LSpacerPlugin : public LPPlugin{
+ Q_OBJECT
+public:
+ LSpacerPlugin(QWidget *parent=0, QString id="spacer", bool horizontal=true) : LPPlugin(parent, id, horizontal){
+ if(horizontal){ this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); }
+ else{ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); }
+ }
+ ~LSpacerPlugin(){}
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){ //horizontal
+ this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ }else{ //vertical
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+ }
+ }
+};
+
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.cpp
new file mode 100644
index 00000000..267a7cb0
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.cpp
@@ -0,0 +1,91 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LSysDashboard.h"
+
+LSysDashboard::LSysDashboard(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ upTimer = new QTimer(this);
+ upTimer->setInterval(10000); //10 second update ping
+ connect(upTimer, SIGNAL(timeout()), this, SLOT(updateIcon()));
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ button->setPopupMode(QToolButton::DelayedPopup); //make sure it runs the update routine first
+ connect(button, SIGNAL(clicked()), this, SLOT(openMenu()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+ menu = new QMenu(this);
+ connect(menu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ sysmenu = new LSysMenuQuick(this);
+ connect(sysmenu, SIGNAL(CloseMenu()), this, SLOT(closeMenu()) );
+ mact = new QWidgetAction(this);
+ mact->setDefaultWidget(sysmenu);
+ menu->addAction(mact);
+
+ button->setMenu(menu);
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+}
+
+LSysDashboard::~LSysDashboard(){
+
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void LSysDashboard::updateIcon(bool force){
+ //For the visual, show battery state only if important
+ static bool batcharging = false;
+ QPixmap pix;
+ button->setToolTip(tr("System Dashboard"));
+ if(LOS::hasBattery()){
+ int bat = LOS::batteryCharge();
+ bool charging = LOS::batteryIsCharging();
+ //Set the icon as necessary
+ if(charging && !batcharging){
+ //Charging and just plugged in
+ if(bat < 15){ button->setIcon( LXDG::findIcon("battery-charging-low","") ); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(bat < 30){ button->setIcon( LXDG::findIcon("battery-charging-caution","") ); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(force || button->icon().isNull()){ resetIcon(); }
+ }else if(!charging){
+ //Not charging (critical level or just unplugged)
+ if(bat<5){ button->setIcon( LXDG::findIcon("battery-missing","") ); }
+ else if(bat < 15){ button->setIcon( LXDG::findIcon("battery-low","") ); QTimer::singleShot(5000, this, SLOT(resetIcon())); }
+ else if(bat < 30 && batcharging){ button->setIcon( LXDG::findIcon("battery-caution","") ); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(bat < 50 && batcharging){ button->setIcon( LXDG::findIcon("battery-040","")); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(bat < 70 && batcharging){ button->setIcon( LXDG::findIcon("battery-060","")); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(bat < 90 && batcharging){ button->setIcon( LXDG::findIcon("battery-080","")); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(batcharging){ button->setIcon( LXDG::findIcon("battery-100","")); QTimer::singleShot(5000, this, SLOT(resetIcon()));}
+ else if(force || button->icon().isNull()){ resetIcon(); }
+ }else if(force || button->icon().isNull()){
+ //Otherwise just use the default icon
+ resetIcon();
+ }
+ //Save the values for comparison later
+ batcharging = charging;
+ if( !upTimer->isActive() ){ upTimer->start(); } //only use the timer if a battery is present
+
+ // No battery - just use/set the normal icon
+ }else if(force || button->icon().isNull()){
+ resetIcon();
+ if(upTimer->isActive() ){ upTimer->stop(); } //no battery available - no refresh timer needed
+ }
+
+}
+
+void LSysDashboard::resetIcon(){
+ button->setIcon( LXDG::findIcon("dashboard-show",""));
+}
+
+void LSysDashboard::openMenu(){
+ sysmenu->UpdateMenu();
+ button->showMenu();
+}
+
+void LSysDashboard::closeMenu(){
+ menu->hide();
+}
+
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.h
new file mode 100644
index 00000000..782fc4e6
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/LSysDashboard.h
@@ -0,0 +1,76 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin allows the user to control different system settings
+// For example: screen brightness, audio volume, workspace, and battery
+//===========================================
+#ifndef _LUMINA_DESKTOP_SYSTEM_DASHBOARD_H
+#define _LUMINA_DESKTOP_SYSTEM_DASHBOARD_H
+
+//Qt includes
+
+#include <QHBoxLayout>
+#include <QDebug>
+#include <QCoreApplication>
+#include <QPainter>
+#include <QPixmap>
+#include <QWidgetAction>
+#include <QMenu>
+#include <QTimer>
+#include <QToolButton>
+
+//libLumina includes
+#include <LuminaOS.h>
+#include <LuminaXDG.h>
+
+//Local includes
+#include "../LPPlugin.h"
+#include "SysMenuQuick.h"
+
+class LSysDashboard : public LPPlugin{
+ Q_OBJECT
+public:
+ LSysDashboard(QWidget *parent = 0, QString id="systemdashboard", bool horizontal=true);
+ ~LSysDashboard();
+
+private:
+ QMenu *menu;
+ QWidgetAction *mact;
+ LSysMenuQuick *sysmenu;
+ QToolButton *button;
+ QTimer *upTimer;
+
+private slots:
+ void updateIcon(bool force = false);
+ void resetIcon();
+ void openMenu();
+ void closeMenu();
+
+public slots:
+ void LocaleChange(){
+ updateIcon(true);
+ sysmenu->UpdateMenu();
+ }
+
+ void ThemeChange(){
+ updateIcon(true);
+ sysmenu->UpdateMenu();
+ }
+
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ updateIcon(true); //force icon refresh
+ this->layout()->update();
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.cpp
new file mode 100644
index 00000000..1d699ea9
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.cpp
@@ -0,0 +1,211 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "SysMenuQuick.h"
+#include "ui_SysMenuQuick.h"
+
+#include "../../LSession.h"
+#include <LuminaX11.h>
+
+LSysMenuQuick::LSysMenuQuick(QWidget *parent) : QWidget(parent), ui(new Ui::LSysMenuQuick){
+ ui->setupUi(this);
+ brighttimer = new QTimer(this);
+ brighttimer->setSingleShot(true);
+ brighttimer->setInterval(50); //50ms delay in setting the new value
+ //Now reset the initial saved settings (this is handles by the LOS/session now - 4/22/15)
+ firstrun = true;
+ UpdateMenu(); //do this once before all the signals/slots are connected below
+ firstrun = false;
+ //Now setup the connections
+ connect(ui->slider_volume, SIGNAL(valueChanged(int)), this, SLOT(volSliderChanged()) );
+ connect(ui->slider_brightness, SIGNAL(valueChanged(int)), this, SLOT(brightSliderChanged()) );
+ connect(ui->tool_wk_prev, SIGNAL(clicked()), this, SLOT(prevWorkspace()) );
+ connect(ui->tool_wk_next, SIGNAL(clicked()), this, SLOT(nextWorkspace()) );
+ connect(ui->tool_logout, SIGNAL(clicked()), this, SLOT(startLogout()) );
+ connect(ui->tool_vol_mixer, SIGNAL(clicked()), this, SLOT(startMixer()) );
+ connect(brighttimer, SIGNAL(timeout()), this, SLOT(setCurrentBrightness()) );
+ connect(ui->combo_locale, SIGNAL(currentIndexChanged(int)), this, SLOT(changeLocale()) );
+ //And setup the default icons
+ ui->label_bright_icon->setPixmap( LXDG::findIcon("preferences-system-power-management","").pixmap(ui->label_bright_icon->maximumSize()) );
+ ui->tool_wk_prev->setIcon( LXDG::findIcon("go-previous-view",""));
+ ui->tool_wk_next->setIcon( LXDG::findIcon("go-next-view","") );
+ ui->tool_logout->setIcon( LXDG::findIcon("system-log-out","") );
+}
+
+LSysMenuQuick::~LSysMenuQuick(){
+
+}
+
+void LSysMenuQuick::UpdateMenu(){
+ ui->retranslateUi(this);
+ //Audio Volume
+ int val = LOS::audioVolume();
+ QIcon ico;
+ if(val > 66){ ico= LXDG::findIcon("audio-volume-high",""); }
+ else if(val > 33){ ico= LXDG::findIcon("audio-volume-medium",""); }
+ else if(val > 0){ ico= LXDG::findIcon("audio-volume-low",""); }
+ else{ ico= LXDG::findIcon("audio-volume-muted",""); }
+ bool hasMixer = LOS::hasMixerUtility();
+ ui->label_vol_icon->setVisible(!hasMixer);
+ ui->tool_vol_mixer->setVisible(hasMixer);
+ if(!hasMixer){ ui->label_vol_icon->setPixmap( ico.pixmap(ui->label_vol_icon->maximumSize()) ); }
+ else{ ui->tool_vol_mixer->setIcon(ico); }
+ QString txt = QString::number(val)+"%";
+ if(val<100){ txt.prepend(" "); } //make sure no widget resizing
+ ui->label_vol_text->setText(txt);
+ if(ui->slider_volume->value()!= val){ ui->slider_volume->setValue(val); }
+ //Screen Brightness
+ val = LOS::ScreenBrightness();
+ if(val < 0){
+ //No brightness control - hide it
+ ui->group_brightness->setVisible(false);
+ }else{
+ ui->group_brightness->setVisible(true);
+ txt = QString::number(val)+"%";
+ if(val<100){ txt.prepend(" "); } //make sure no widget resizing
+ ui->label_bright_text->setText(txt);
+ if(ui->slider_brightness->value()!=val){ ui->slider_brightness->setValue(val); }
+ }
+
+ //Do any one-time checks
+ if(firstrun){
+ hasBat = LOS::hasBattery(); //No need to check this more than once - will not change in the middle of a session
+ //Current Locale
+ QStringList locales = LUtils::knownLocales();
+ ui->combo_locale->clear();
+ QLocale curr;
+ for(int i=0; i<locales.length(); i++){
+ QLocale loc( (locales[i]=="pt") ? "pt_PT" : locales[i] );
+ ui->combo_locale->addItem(loc.nativeLanguageName()+" ("+locales[i]+")", locales[i]); //Make the display text prettier later
+ if(locales[i] == curr.name() || locales[i] == curr.name().section("_",0,0) ){
+ //Current Locale
+ ui->combo_locale->setCurrentIndex(ui->combo_locale->count()-1); //the last item in the list right now
+ }
+ }
+ ui->group_locale->setVisible(locales.length() > 1);
+ }
+
+ //Battery Status
+ if(hasBat){
+ ui->group_battery->setVisible(true);
+ val = LOS::batteryCharge();
+ if(LOS::batteryIsCharging()){
+ if(val < 15){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-charging-low","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 30){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-charging-caution","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 50){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-charging-040","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 70){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-charging-060","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 90){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-charging-080","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else{ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-charging","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ ui->label_bat_text->setText( QString("%1%\n(%2)").arg(QString::number(val), tr("connected")) );
+ }else{
+ if(val < 1){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-missing","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 15){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-low","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 30){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-caution","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 50){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-040","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 70){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-060","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else if(val < 90){ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-080","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ else{ ui->label_bat_icon->setPixmap( LXDG::findIcon("battery-100","").pixmap(ui->label_bat_icon->maximumSize()) ); }
+ ui->label_bat_text->setText( QString("%1%\n(%2)").arg(QString::number(val), getRemainingTime()) );
+ }
+ }else{
+ ui->group_battery->setVisible(false);
+ }
+ //Workspace
+ val = LSession::handle()->XCB->CurrentWorkspace();
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces();
+ ui->group_workspace->setVisible(val>=0 && tot>1);
+ ui->label_wk_text->setText( QString(tr("%1 of %2")).arg(QString::number(val+1), QString::number(tot)) );
+}
+
+void LSysMenuQuick::volSliderChanged(){
+ int val = ui->slider_volume->value();
+ LOS::setAudioVolume(val);
+ QString txt = QString::number(val)+"%";
+ if(val<100){ txt.prepend(" "); } //make sure no widget resizing
+ ui->label_vol_text->setText( txt );
+ if(val > 66){ ui->label_vol_icon->setPixmap( LXDG::findIcon("audio-volume-high","").pixmap(ui->label_vol_icon->maximumSize()) ); }
+ else if(val > 33){ ui->label_vol_icon->setPixmap( LXDG::findIcon("audio-volume-medium","").pixmap(ui->label_vol_icon->maximumSize()) ); }
+ else if(val > 0){ ui->label_vol_icon->setPixmap( LXDG::findIcon("audio-volume-low","").pixmap(ui->label_vol_icon->maximumSize()) ); }
+ else{ ui->label_vol_icon->setPixmap( LXDG::findIcon("audio-volume-muted","").pixmap(ui->label_vol_icon->maximumSize()) ); }
+}
+
+void LSysMenuQuick::startMixer(){
+ emit CloseMenu();
+ LOS::startMixerUtility();
+}
+
+void LSysMenuQuick::brightSliderChanged(){
+ //Brightness controls cannot operate extremely quickly - combine calls as necessary
+ if(brighttimer->isActive()){ brighttimer->stop(); }
+ brighttimer->start();
+ //*DO* update the label right away
+ int val = ui->slider_brightness->value();
+ QString txt = QString::number(val)+"%";
+ if(val<100){ txt.prepend(" "); } //make sure no widget resizing
+ ui->label_bright_text->setText( txt );
+}
+
+void LSysMenuQuick::setCurrentBrightness(){
+ int val = ui->slider_brightness->value();
+ LOS::setScreenBrightness(val);
+ QString txt = QString::number(val)+"%";
+ if(val<100){ txt.prepend(" "); } //make sure no widget resizing
+ ui->label_bright_text->setText( txt );
+}
+
+void LSysMenuQuick::nextWorkspace(){
+ int cur = LSession::handle()->XCB->CurrentWorkspace();
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces();
+ //qDebug()<< "Next Workspace:" << cur << tot;
+ cur++;
+ if(cur>=tot){ cur = 0; } //back to beginning
+ //qDebug() << " - New Current:" << cur;
+ LSession::handle()->XCB->SetCurrentWorkspace(cur);
+ui->label_wk_text->setText( QString(tr("%1 of %2")).arg(QString::number(cur+1), QString::number(tot)) );
+}
+
+void LSysMenuQuick::prevWorkspace(){
+ int cur = LSession::handle()->XCB->CurrentWorkspace();
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces();
+ cur--;
+ if(cur<0){ cur = tot-1; } //back to last
+ LSession::handle()->XCB->SetCurrentWorkspace(cur);
+ ui->label_wk_text->setText( QString(tr("%1 of %2")).arg(QString::number(cur+1), QString::number(tot)) );
+}
+
+QString LSysMenuQuick::getRemainingTime(){
+ int secs = LOS::batterySecondsLeft();
+ if(secs < 0){ return "??"; }
+ QString rem; //remaining
+ if(secs > 3600){
+ int hours = secs/3600;
+ rem.append( QString::number(hours)+"h ");
+ secs = secs - (hours*3600);
+ }
+ if(secs > 60){
+ int min = secs/60;
+ rem.append( QString::number(min)+"m ");
+ secs = secs - (min*60);
+ }
+ if(secs > 0){
+ rem.append( QString::number(secs)+"s");
+ }else{
+ rem.append( "0s" );
+ }
+ return rem;
+}
+
+void LSysMenuQuick::startLogout(){
+ emit CloseMenu();
+ LSession::handle()->systemWindow();
+}
+
+void LSysMenuQuick::changeLocale(){
+ //Get the currently selected Locale
+ QString locale = ui->combo_locale->currentData().toString();
+ emit CloseMenu();
+ LSession::handle()->switchLocale(locale);
+} \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.h
new file mode 100644
index 00000000..a300b5b1
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.h
@@ -0,0 +1,54 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin allows the user to control different system settings
+// For example: screen brightness, audio volume, workspace, and battery
+//===========================================
+#ifndef _LUMINA_PANEL_QUICK_SYSTEM_MENU_H
+#define _LUMINA_PANEL_QUICK_SYSTEM_MENU_H
+
+#include <QWidget>
+#include <QSettings>
+#include <QString>
+
+#include <LuminaOS.h>
+#include <LuminaXDG.h>
+
+namespace Ui{
+ class LSysMenuQuick;
+};
+
+class LSysMenuQuick : public QWidget{
+ Q_OBJECT
+public:
+ LSysMenuQuick(QWidget *parent=0);
+ ~LSysMenuQuick();
+
+ void UpdateMenu();
+
+private:
+ Ui::LSysMenuQuick *ui;
+ QTimer *brighttimer;
+ bool firstrun, hasBat;
+ QString getRemainingTime(); //battery time left
+
+private slots:
+ void volSliderChanged();
+ void brightSliderChanged(); //start the delay/collection timer
+ void setCurrentBrightness(); //perform the change
+ void startMixer();
+ void nextWorkspace();
+ void prevWorkspace();
+ void startLogout();
+ void changeLocale();
+
+
+signals:
+ void CloseMenu();
+
+};
+
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.ui b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.ui
new file mode 100644
index 00000000..26c32c74
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemdashboard/SysMenuQuick.ui
@@ -0,0 +1,400 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LSysMenuQuick</class>
+ <widget class="QWidget" name="LSysMenuQuick">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>171</width>
+ <height>317</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">/*QGroupBox{
+border-radius: 5px;
+border: 1px solid grey;
+margin-top: 1ex;
+}
+QGroupBox::title{
+subcontrol-origin: margin;
+subcontrol-position: top center;
+padding: 0 3px;
+background-color: rgba(255,255,255,255);
+border-radius: 5px;
+font: bold;
+}*/</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>1</number>
+ </property>
+ <property name="topMargin">
+ <number>1</number>
+ </property>
+ <property name="rightMargin">
+ <number>1</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="group_volume">
+ <property name="title">
+ <string>System Volume</string>
+ </property>
+ <property name="flat">
+ <bool>false</bool>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_vol_icon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_vol_mixer">
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Launch Audio Mixer</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="slider_volume">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_vol_text">
+ <property name="text">
+ <string notr="true">100%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="group_brightness">
+ <property name="title">
+ <string>Screen Brightness</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_bright_icon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="slider_brightness">
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="value">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_bright_text">
+ <property name="text">
+ <string notr="true">100%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="group_battery">
+ <property name="title">
+ <string>Battery Status</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_bat_icon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_bat_text">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">100% (Plugged in)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="group_workspace">
+ <property name="title">
+ <string>Workspace</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="tool_wk_prev">
+ <property name="text">
+ <string notr="true">prev</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_wk_text">
+ <property name="text">
+ <string notr="true">1 of 2</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_wk_next">
+ <property name="text">
+ <string notr="true">next</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="group_locale">
+ <property name="title">
+ <string>Locale</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QComboBox" name="combo_locale"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QToolButton" name="tool_logout">
+ <property name="font">
+ <font>
+ <pointsize>9</pointsize>
+ </font>
+ </property>
+ <property name="text">
+ <string>Log Out</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>22</width>
+ <height>22</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.cpp
new file mode 100644
index 00000000..ea074a59
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.cpp
@@ -0,0 +1,279 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "ItemWidget.h"
+#include <LUtils.h>
+#include <QMenu>
+#include "../../LSession.h"
+
+
+ItemWidget::ItemWidget(QWidget *parent, QString itemPath, QString type, bool goback) : QFrame(parent){
+ createWidget();
+ //Now fill it appropriately
+ bool inHome = type.endsWith("-home"); //internal code
+ if(inHome){ type = type.remove("-home"); }
+ if(itemPath.endsWith(".desktop") || type=="app"){
+ XDGDesktop item(itemPath, this);
+ gooditem = item.isValid();
+ //qDebug() << "Good Item:" << gooditem << itemPath;
+ if(gooditem){
+ icon->setPixmap( LXDG::findIcon(item.icon, "preferences-system-windows-actions").pixmap(32,32) );
+ iconPath = item.icon;
+ text = item.name;
+ if(!item.genericName.isEmpty() && item.name!=item.genericName){ text.append("<br><i> -- "+item.genericName+"</i>"); }
+ name->setText(text);
+ name->setToolTip(item.comment);
+ setupActions(&item);
+ }else{
+ return;
+ }
+ }else if(type=="dir"){
+ actButton->setVisible(false);
+ if(itemPath.endsWith("/")){ itemPath.chop(1); }
+ if(goback){
+ icon->setPixmap( LXDG::findIcon("go-previous","").pixmap(64,64) );
+ iconPath = "go-previous";
+ text = tr("Go Back");
+ name->setText( text );
+ }else{
+ icon->setPixmap( LXDG::findIcon("folder","").pixmap(64,64) );
+ iconPath = "folder";
+ name->setText( itemPath.section("/",-1));
+ text = itemPath.section("/",-1);
+ }
+ }else if(type.startsWith("chcat::::")){
+ //Category given
+ actButton->setVisible(false);
+ iconPath = LXDG::DesktopCatToIcon(type.section("::::",1,50));
+ if(goback){ iconPath = "go-previous"; type = "chcat::::"; itemPath = "<B>("+itemPath+")</B>"; }
+ icon->setPixmap( LXDG::findIcon(iconPath,"applications-other").pixmap(64,64) );
+ name->setText(itemPath);
+ text = itemPath;
+ icon->setWhatsThis(type);
+ linkPath = type;
+ }else{
+ actButton->setVisible(false);
+ if(itemPath.endsWith("/")){ itemPath.chop(1); }
+ if(QFileInfo(itemPath).isDir()){
+ type = "dir";
+ icon->setPixmap( LXDG::findIcon("folder","").pixmap(64,64) );
+ iconPath = "folder";
+ }else if(LUtils::imageExtensions().contains(itemPath.section("/",-1).section(".",-1).toLower()) ){
+ icon->setPixmap( QIcon(itemPath).pixmap(64,64) );
+ }else{
+ if( LUtils::isValidBinary(itemPath) ){ icon->setPixmap( LXDG::findIcon(type, "application-x-executable").pixmap(64,64) ); }
+ else{ icon->setPixmap( LXDG::findMimeIcon(itemPath.section("/",-1)).pixmap(64,64) ); }
+ }
+ name->setText( itemPath.section("/",-1) ); //this->fontMetrics().elidedText(itemPath.section("/",-1), Qt::ElideRight, TEXTCUTOFF) );
+ text = itemPath.section("/",-1) ;
+ }
+ icon->setWhatsThis(itemPath);
+ if(!goback){ this->setWhatsThis(name->text()); }
+ isDirectory = (type=="dir"); //save this for later
+ if(LDesktopUtils::isFavorite(itemPath)){
+ linkPath = itemPath;
+ isShortcut=true;
+ }else if( inHome ){//|| itemPath.section("/",0,-2)==QDir::homePath()+"/Desktop" ){
+ isShortcut = true;
+ }else{
+ isShortcut = false;
+ }
+ if(isShortcut && name->toolTip().isEmpty()){
+ name->setToolTip(icon->whatsThis()); //also allow the user to see the full shortcut path
+ }
+ //Now setup the button appropriately
+ setupContextMenu();
+}
+
+// - Application constructor
+ItemWidget::ItemWidget(QWidget *parent, XDGDesktop *item) : QFrame(parent){
+ createWidget();
+ if(item==0){ gooditem = false; return; }
+ isDirectory = false;
+ if(LDesktopUtils::isFavorite(item->filePath)){
+ linkPath = item->filePath;
+ isShortcut=true;
+ }else if( item->filePath.section("/",0,-2)==QDir::homePath()+"/Desktop" ){
+ isShortcut = true;
+ }else{
+ isShortcut = false;
+ }
+ if(isShortcut){
+ name->setToolTip(icon->whatsThis()); //also allow the user to see the full shortcut path
+ }
+ //Now fill it appropriately
+ icon->setPixmap( LXDG::findIcon(item->icon,"preferences-system-windows-actions").pixmap(64,64) );
+ text = item->name;
+ if(!item->genericName.isEmpty() && item->name!=item->genericName){ text.append("<br><i> -- "+item->genericName+"</i>"); }
+ name->setText(text);
+ name->setToolTip(item->comment);
+ this->setWhatsThis(item->name);
+ icon->setWhatsThis(item->filePath);
+ iconPath = item->icon;
+ //Now setup the buttons appropriately
+ setupContextMenu();
+ setupActions(item);
+}
+
+ItemWidget::~ItemWidget(){
+ icon->setPixmap(QPixmap()); //make sure the pixmap is cleared from memory too
+ actButton->deleteLater();
+ contextMenu->clear();
+ contextMenu->deleteLater();
+ if(actButton->menu()!=0){
+ for(int i=0; i<actButton->menu()->actions().length(); i++){
+ actButton->menu()->actions().at(i)->deleteLater();
+ }
+ actButton->menu()->deleteLater();
+ }
+ actButton->deleteLater();
+ icon->deleteLater();
+ name->deleteLater();
+ menureset->deleteLater();
+ linkPath.clear(); iconPath.clear(); text.clear();
+}
+
+void ItemWidget::triggerItem(){
+ ItemClicked();
+}
+
+void ItemWidget::createWidget(){
+ //Initialize the widgets
+ gooditem = true;
+ menuopen = false;
+ menureset = new QTimer(this);
+ menureset->setSingleShot(true);
+ menureset->setInterval(1000); //1 second
+ this->setContentsMargins(0,0,0,0);
+ contextMenu = new QMenu(this);
+ connect(contextMenu, SIGNAL(aboutToShow()), this, SLOT(actionMenuOpen()) );
+ connect(contextMenu, SIGNAL(aboutToHide()), this, SLOT(actionMenuClosed()) );
+ actButton = new QToolButton(this);
+ actButton->setPopupMode(QToolButton::InstantPopup);
+ actButton->setArrowType(Qt::DownArrow);
+ icon = new QLabel(this);
+ name = new QLabel(this);
+ name->setWordWrap(true);
+ name->setTextFormat(Qt::RichText);
+ name->setTextInteractionFlags(Qt::NoTextInteraction);
+ //Add them to the layout
+ this->setLayout(new QHBoxLayout(this));
+ this->layout()->setContentsMargins(1,1,1,1);
+ this->layout()->addWidget(icon);
+ this->layout()->addWidget(actButton);
+ this->layout()->addWidget(name);
+ //Set a custom object name so this can be tied into the Lumina Theme stylesheets
+ this->setObjectName("LuminaItemWidget");
+}
+
+void ItemWidget::setupContextMenu(){
+ //Now refresh the context menu
+ contextMenu->clear();
+ if(!QFile::exists(QDir::homePath()+"/Desktop/"+icon->whatsThis().section("/",-1)) ){
+ //Does not have a desktop link
+ contextMenu->addAction( LXDG::findIcon("preferences-desktop-icons",""), tr("Pin to Desktop"), this, SLOT(PinToDesktop()) );
+ }
+ //Favorite Item
+ if( LDesktopUtils::isFavorite(icon->whatsThis()) ){ //Favorite Item - can always remove this
+ contextMenu->addAction( LXDG::findIcon("edit-delete",""), tr("Remove from Favorites"), this, SLOT(RemoveFavorite()) );
+ }else{
+ //This file does not have a shortcut yet -- allow the user to add it
+ contextMenu->addAction( LXDG::findIcon("bookmark-toolbar",""), tr("Add to Favorites"), this, SLOT(AddFavorite()) );
+ }
+ //QuickLaunch Item
+ if(LSession::handle()->sessionSettings()->value("QuicklaunchApps",QStringList()).toStringList().contains(icon->whatsThis()) ){ //Favorite Item - can always remove this
+ contextMenu->addAction( LXDG::findIcon("edit-delete",""), tr("Remove from Quicklaunch"), this, SLOT(RemoveQL()) );
+ }else{
+ //This file does not have a shortcut yet -- allow the user to add it
+ contextMenu->addAction( LXDG::findIcon("quickopen",""), tr("Add to Quicklaunch"), this, SLOT(AddQL()) );
+ }
+}
+
+void ItemWidget::setupActions(XDGDesktop *app){
+ if(app==0 || app->actions.isEmpty()){ actButton->setVisible(false); return; }
+ //Actions Available - go ahead and list them all
+ actButton->setMenu( new QMenu(this) );
+ for(int i=0; i<app->actions.length(); i++){
+ QAction *act = new QAction(LXDG::findIcon(app->actions[i].icon, app->icon), app->actions[i].name, this);
+ act->setToolTip(app->actions[i].ID);
+ act->setWhatsThis(app->actions[i].ID);
+ actButton->menu()->addAction(act);
+ }
+ connect(actButton->menu(), SIGNAL(triggered(QAction*)), this, SLOT(actionClicked(QAction*)) );
+ connect(actButton->menu(), SIGNAL(aboutToShow()), this, SLOT(actionMenuOpen()) );
+ connect(actButton->menu(), SIGNAL(aboutToHide()), this, SLOT(actionMenuClosed()) );
+ connect(menureset, SIGNAL(timeout()), this, SLOT(resetmenuflag()) );
+}
+
+void ItemWidget::updateItems(){
+ //update the text/icon to match sizes
+ int H = 2.3*name->fontMetrics().height(); //make sure the height is large enough for two lines
+ icon->setFixedSize(QSize(H-4, H-4));
+ actButton->setFixedSize( QSize( (H-4)/2, H-4) );
+ QStringList newname = text.split("<br>");
+ for(int i=0; i<newname.length(); i++){ newname[i] = name->fontMetrics().elidedText(newname[i], Qt::ElideRight, name->width()); }
+ name->setText( newname.join("<br>") );
+ //Now reload the icon if necessary
+ if(icon->pixmap()!=0){
+ if(icon->pixmap()->size().height() < (H-4) ){
+ if(iconPath.isEmpty()){
+ //Use item path (thumbnail or mimetype)
+ if(LUtils::imageExtensions().contains(icon->whatsThis().section("/",-1).section(".",-1).toLower()) ){
+ icon->setPixmap( QIcon(icon->whatsThis()).pixmap(H-4,H-4).scaledToHeight(H-4,Qt::SmoothTransformation) );
+ }else{
+ icon->setPixmap( LXDG::findMimeIcon(icon->whatsThis().section("/",-1)).pixmap(H-4,H-4).scaledToHeight(H-4,Qt::SmoothTransformation) );
+ }
+ }else{
+ icon->setPixmap( LXDG::findIcon(iconPath,"preferences-system-windows-actions").pixmap(H-4,H-4).scaledToHeight(H-4,Qt::SmoothTransformation) );
+ }
+ }else if(icon->pixmap()->size().height() > (H-4) ){
+ icon->setPixmap( icon->pixmap()->scaled(H-4, H-4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation) );
+ }
+ }
+}
+
+void ItemWidget::PinToDesktop(){
+ qDebug() << "Create Link on Desktop:" << icon->whatsThis();
+ bool ok = QFile::link(icon->whatsThis(), QDir::homePath()+"/Desktop/"+icon->whatsThis().section("/",-1));
+ qDebug() << " - " << (ok ? "Success": "Failure");
+}
+
+void ItemWidget::RemoveFavorite(){
+ LDesktopUtils::removeFavorite(icon->whatsThis());
+ linkPath.clear();
+ emit RemovedShortcut();
+}
+
+void ItemWidget::AddFavorite(){
+ if( LDesktopUtils::addFavorite(icon->whatsThis()) ){
+ linkPath = icon->whatsThis();
+ emit NewShortcut();
+ }
+
+}
+void ItemWidget::RemoveQL(){
+ qDebug() << "Remove QuickLaunch Button:" << icon->whatsThis();
+ emit toggleQuickLaunch(icon->whatsThis(), false);
+}
+
+void ItemWidget::AddQL(){
+ qDebug() << "Add QuickLaunch Button:" << icon->whatsThis();
+ emit toggleQuickLaunch(icon->whatsThis(), true);
+}
+
+
+void ItemWidget::ItemClicked(){
+ if(!linkPath.isEmpty()){ emit RunItem(linkPath); }
+ else{ emit RunItem(icon->whatsThis()); }
+}
+
+void ItemWidget::actionClicked(QAction *act){
+ actButton->menu()->hide();
+ QString cmd = "lumina-open -action \""+act->whatsThis()+"\" \"%1\"";
+ if(!linkPath.isEmpty()){ cmd = cmd.arg(linkPath); }
+ else{ cmd = cmd.arg(icon->whatsThis()); }
+ emit RunItem(cmd);
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.h
new file mode 100644
index 00000000..11394dd6
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/ItemWidget.h
@@ -0,0 +1,98 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This item widget manages a single file/directory
+//===========================================
+#ifndef _LUMINA_PANEL_SYSTEM_START_ITEM_WIDGET_H
+#define _LUMINA_PANEL_SYSTEM_START_ITEM_WIDGET_H
+
+#include <QFrame>
+#include <QLabel>
+#include <QToolButton>
+#include <QString>
+#include <QHBoxLayout>
+#include <QSize>
+#include <QDir>
+#include <QFile>
+#include <QMouseEvent>
+#include <QAction>
+#include <QMenu>
+#include <QTimer>
+#include <QResizeEvent>
+
+#include <LuminaXDG.h>
+
+class ItemWidget : public QFrame{
+ Q_OBJECT
+public:
+ //Overloaded Constructors for various uses
+ // - Favorites (path/type)
+ ItemWidget(QWidget *parent=0, QString itemPath="", QString type="unknown", bool goback=false);
+ // - Generic Apps
+ ItemWidget(QWidget *parent=0, XDGDesktop *item= 0);
+
+ ~ItemWidget();
+
+ bool gooditem;
+
+ void triggerItem(); //trigger this item - just as if it was clicked on
+
+private:
+ QToolButton *actButton;
+ QMenu *contextMenu;
+ QLabel *icon, *name;
+ bool isDirectory, isShortcut, menuopen;
+ QString linkPath, iconPath, text;
+ QTimer *menureset;
+
+ void createWidget();
+
+ void setupContextMenu();
+ void setupActions(XDGDesktop*);
+
+ void updateItems(); //update the text/icon to match sizes
+
+private slots:
+ void PinToDesktop();
+ void RemoveFavorite();
+ void AddFavorite();
+ void RemoveQL();
+ void AddQL();
+ void ItemClicked();
+ void actionClicked(QAction*);
+ //Functions to fix the submenu open/close issues
+ void actionMenuOpen(){
+ if(menureset->isActive()){ menureset->stop(); }
+ menuopen = true;
+ }
+ void resetmenuflag(){ menuopen = false; } //tied to the "menureset" timer
+ void actionMenuClosed(){ menureset->start(); }
+
+
+protected:
+ void mouseReleaseEvent(QMouseEvent *event){
+ if(menuopen){ resetmenuflag(); } //skip this event if a submenu was open
+ else if(event->button() == Qt::RightButton && !icon->whatsThis().startsWith("chcat::::") ){
+ menuopen = true;
+ setupContextMenu();
+ contextMenu->popup(event->globalPos());
+ }else if(event->button() != Qt::NoButton){ ItemClicked(); }
+ }
+
+ void resizeEvent(QResizeEvent *ev){
+ updateItems(); //update the sizing of everything
+ QFrame::resizeEvent(ev); // do the normal procedures
+ }
+
+signals:
+ void NewShortcut();
+ void RemovedShortcut();
+ void RunItem(QString cmd);
+ void toggleQuickLaunch(QString path, bool ok);
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.cpp
new file mode 100644
index 00000000..f44add77
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.cpp
@@ -0,0 +1,137 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LStartButton.h"
+#include "../../LSession.h"
+
+#include <LuminaXDG.h>
+#include <LUtils.h> //This contains the "ResizeMenu" class
+
+LStartButtonPlugin::LStartButtonPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonIconOnly);
+ button->setPopupMode(QToolButton::DelayedPopup); //make sure it runs the update routine first
+ connect(button, SIGNAL(clicked()), this, SLOT(openMenu()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+ menu = new ResizeMenu(this);
+ menu->setContentsMargins(1,1,1,1);
+ connect(menu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ connect(menu, SIGNAL(MenuResized(QSize)), this, SLOT(SaveMenuSize(QSize)) );
+ startmenu = new StartMenu(this);
+ connect(startmenu, SIGNAL(CloseMenu()), this, SLOT(closeMenu()) );
+ connect(startmenu, SIGNAL(UpdateQuickLaunch(QStringList)), this, SLOT(updateQuickLaunch(QStringList)));
+ menu->setContents(startmenu);
+ QSize saved = LSession::handle()->DesktopPluginSettings()->value("panelPlugs/"+this->type()+"/MenuSize", QSize(0,0)).toSize();
+ if(!saved.isNull()){ startmenu->setFixedSize(saved); } //re-load the previously saved value
+
+ button->setMenu(menu);
+ connect(menu, SIGNAL(aboutToHide()), this, SLOT(updateButtonVisuals()) );
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+ QTimer::singleShot(0, startmenu, SLOT(ReLoadQuickLaunch()) );
+ //Setup the global shortcut handling for opening the start menu
+ connect(QApplication::instance(), SIGNAL(StartButtonActivated()), this, SLOT(shortcutActivated()) );
+ LSession::handle()->registerStartButton(this->type());
+}
+
+LStartButtonPlugin::~LStartButtonPlugin(){
+ LSession::handle()->unregisterStartButton(this->type());
+}
+
+void LStartButtonPlugin::updateButtonVisuals(){
+ button->setToolTip(tr(""));
+ button->setText( SYSTEM::user() );
+ button->setIcon( LXDG::findIcon("start-here-lumina","Lumina-DE") ); //force icon refresh
+}
+
+void LStartButtonPlugin::updateQuickLaunch(QStringList apps){
+ //First clear any obsolete apps
+ QStringList old;
+ //qDebug() << "Update QuickLaunch Buttons";
+ for(int i=0; i<QUICKL.length(); i++){
+ if( !apps.contains(QUICKL[i]->whatsThis()) ){
+ //App was removed
+ QUICKL.takeAt(i)->deleteLater();
+ i--;
+ }else{
+ //App still listed - update the button
+ old << QUICKL[i]->whatsThis(); //add the list of current buttons
+ LFileInfo info(QUICKL[i]->whatsThis());
+ QUICKL[i]->setIcon( LXDG::findIcon(info.iconfile(),"unknown") );
+ if(info.isDesktopFile()){ QUICKL[i]->setToolTip( info.XDG()->name ); }
+ else{ QUICKL[i]->setToolTip( info.fileName() ); }
+ }
+ }
+ //Now go through and create any new buttons
+ for(int i=0; i<apps.length(); i++){
+ if( !old.contains(apps[i]) ){
+ //New App
+ LQuickLaunchButton *tmp = new LQuickLaunchButton(apps[i], this);
+ QUICKL << tmp;
+ LFileInfo info(apps[i]);
+ tmp->setIcon( LXDG::findIcon( info.iconfile() ) );
+ if(info.isDesktopFile()){ tmp->setToolTip( info.XDG()->name ); }
+ else{ tmp->setToolTip( info.fileName() ); }
+ //Now add the button to the layout and connect the signal/slots
+ this->layout()->insertWidget(i+1,tmp); //"button" is always in slot 0
+ connect(tmp, SIGNAL(Launch(QString)), this, SLOT(LaunchQuick(QString)) );
+ connect(tmp, SIGNAL(Remove(QString)), this, SLOT(RemoveQuick(QString)) );
+ }
+ }
+ //qDebug() << " - Done updateing QuickLaunch Buttons";
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+}
+
+void LStartButtonPlugin::LaunchQuick(QString file){
+ //Need to get which button was clicked
+ //qDebug() << "Quick Launch triggered:" << file;
+ if(!file.isEmpty()){
+ LSession::LaunchApplication("lumina-open \""+file+"\"");
+ emit MenuClosed();
+ }
+}
+
+void LStartButtonPlugin::RemoveQuick(QString file){
+ //qDebug() << "Remove Quicklaunch Button:" << file;
+ if(!file.isEmpty()){
+ startmenu->UpdateQuickLaunch(file, false); //always a removal
+ emit MenuClosed();
+ }
+}
+
+void LStartButtonPlugin::SaveMenuSize(QSize sz){
+ //Save this size for the menu
+ LSession::handle()->DesktopPluginSettings()->setValue("panelPlugs/"+this->type()+"/MenuSize", sz);
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void LStartButtonPlugin::openMenu(){
+ if(menu->isVisible()){ return; } //don't re-show it - already open
+ //TESTING CODE TO SEE IF THIS MAKES IT RECOVER MEMORY
+ /*StartMenu *old = startmenu;
+ startmenu = new StartMenu(this);
+ connect(startmenu, SIGNAL(CloseMenu()), this, SLOT(closeMenu()) );
+ connect(startmenu, SIGNAL(UpdateQuickLaunch(QStringList)), this, SLOT(updateQuickLaunch(QStringList)));
+ menu->setContents(startmenu);
+ if(old!=0){ old->deleteLater(); }*/
+//--------
+ startmenu->UpdateMenu();
+ button->showMenu();
+}
+
+void LStartButtonPlugin::closeMenu(){
+ menu->hide();
+}
+
+void LStartButtonPlugin::shortcutActivated(){
+ if(LSession::handle()->registerStartButton(this->type())){
+ if(menu->isVisible()){ closeMenu(); }
+ else{ this->activateWindow(); openMenu(); }
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.h
new file mode 100644
index 00000000..d46bb1be
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/LStartButton.h
@@ -0,0 +1,113 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin is the main button that allow the user to run
+// applications or logout of the desktop
+//===========================================
+#ifndef _LUMINA_DESKTOP_START_MENU_PLUGIN_H
+#define _LUMINA_DESKTOP_START_MENU_PLUGIN_H
+
+// Qt includes
+#include <QMenu>
+#include <QWidgetAction>
+#include <QToolButton>
+#include <QString>
+#include <QWidget>
+#include <QMenu>
+
+// Lumina-desktop includes
+//#include "../../Globals.h"
+#include "../LPPlugin.h" //main plugin widget
+
+// libLumina includes
+#include <LuminaXDG.h>
+#include <LUtils.h>
+#include <ResizeMenu.h>
+
+#include "StartMenu.h"
+
+//Simple Tool Button For QuickLaunch Items
+class LQuickLaunchButton : public QToolButton{
+ Q_OBJECT
+signals:
+ void Launch(QString);
+ void Remove(QString);
+
+private slots:
+ void rmentry(){
+ emit Remove(this->whatsThis());
+ }
+ void launchentry(){
+ emit Launch(this->whatsThis());
+ }
+
+public:
+ LQuickLaunchButton(QString path, QWidget* parent = 0) : QToolButton(parent){
+ this->setWhatsThis(path);
+ this->setMenu(new QMenu(this));
+ this->setContextMenuPolicy(Qt::CustomContextMenu);
+ this->menu()->addAction( LXDG::findIcon("edit-delete",""), tr("Remove from Quicklaunch"), this, SLOT(rmentry()) );
+ connect(this, SIGNAL(clicked()), this, SLOT(launchentry()) );
+ connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showMenu()) );
+ }
+ ~LQuickLaunchButton(){}
+
+};
+
+// PANEL PLUGIN BUTTON
+class LStartButtonPlugin : public LPPlugin{
+ Q_OBJECT
+
+public:
+ LStartButtonPlugin(QWidget *parent = 0, QString id = "systemstart", bool horizontal=true);
+ ~LStartButtonPlugin();
+
+private:
+ ResizeMenu *menu;
+ //QWidgetAction *mact;
+ StartMenu *startmenu;
+ QToolButton *button;
+ QList<LQuickLaunchButton*> QUICKL;
+
+private slots:
+ void openMenu();
+ void closeMenu();
+ void shortcutActivated();
+
+ void updateButtonVisuals();
+
+ void updateQuickLaunch(QStringList);
+ void LaunchQuick(QString);
+ void RemoveQuick(QString);
+ void SaveMenuSize(QSize);
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ for(int i=0; i<QUICKL.length(); i++){ QUICKL[i]->setIconSize(QSize(this->height(), this->height())); }
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ for(int i=0; i<QUICKL.length(); i++){ QUICKL[i]->setIconSize(QSize(this->width(), this->width())); }
+ }
+ this->layout()->update();
+ updateButtonVisuals();
+ }
+
+ void LocaleChange(){
+ updateButtonVisuals();
+ if(startmenu!=0){ startmenu->UpdateAll(); }
+ }
+
+ void ThemeChange(){
+ updateButtonVisuals();
+ if(startmenu!=0){ startmenu->UpdateAll(); }
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.cpp
new file mode 100644
index 00000000..d05ba22f
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.cpp
@@ -0,0 +1,720 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "StartMenu.h"
+#include "ui_StartMenu.h"
+//#include <QtConcurrent>
+
+#include <LuminaOS.h>
+#include "../../LSession.h"
+#include <QtConcurrent>
+#include <QMessageBox>
+
+#include "ItemWidget.h"
+//#define SSAVER QString("xscreensaver-demo")
+
+StartMenu::StartMenu(QWidget *parent) : QWidget(parent), ui(new Ui::StartMenu){
+ ui->setupUi(this); //load the designer file
+ this->setMouseTracking(true);
+ searchTimer = new QTimer(this);
+ searchTimer->setInterval(300); //~1/3 second
+ searchTimer->setSingleShot(true);
+ connect(searchTimer, SIGNAL(timeout()), this, SLOT(startSearch()) );
+ connect(LSession::handle()->applicationMenu(), SIGNAL(AppMenuUpdated()), this, SLOT(UpdateApps()) );
+ connect(LSession::handle(), SIGNAL(FavoritesChanged()), this, SLOT(UpdateFavs()) );
+ //Need to load the last used setting of the application list
+ QString state = LSession::handle()->DesktopPluginSettings()->value("panelPlugs/systemstart/showcategories", "partial").toString();
+ if(state=="partial"){ui->check_apps_showcats->setCheckState(Qt::PartiallyChecked); }
+ else if(state=="true"){ ui->check_apps_showcats->setCheckState(Qt::Checked); }
+ else{ ui->check_apps_showcats->setCheckState(Qt::Unchecked); }
+ connect(ui->check_apps_showcats, SIGNAL(stateChanged(int)), this, SLOT(catViewChanged()) );
+ UpdateAll();
+ QTimer::singleShot(10, this,SLOT(UpdateApps()));
+ QTimer::singleShot(10, this, SLOT(UpdateFavs()));
+}
+
+StartMenu::~StartMenu(){
+
+}
+
+void StartMenu::UpdateAll(){
+ //Update all the icons/text on all the pages
+
+ // Update Text
+ ui->retranslateUi(this);
+
+ //Update Icons
+ ui->tool_goto_apps->setIcon(LXDG::findIcon("system-run",""));
+ ui->tool_goto_settings->setIcon(LXDG::findIcon("preferences-system",""));
+ ui->tool_launch_fm->setIcon(LXDG::findIcon("user-home",""));
+ ui->tool_launch_desksettings->setIcon(LXDG::findIcon("preferences-desktop",""));
+ ui->tool_lock->setIcon(LXDG::findIcon("system-lock-screen",""));
+ ui->tool_goto_logout->setIcon(LXDG::findIcon("system-log-out",""));
+ ui->tool_back->setIcon(LXDG::findIcon("go-previous",""));
+ ui->tool_launch_deskinfo->setIcon(LXDG::findIcon("system-help",""));
+
+ ui->tool_launch_mixer->setIcon( LXDG::findIcon("preferences-desktop-sound","") );
+ ui->label_bright_icon->setPixmap( LXDG::findIcon("preferences-system-power-management","").pixmap(ui->tool_goto_apps->iconSize()) );
+ ui->label_locale_icon->setPixmap( LXDG::findIcon("preferences-desktop-locale","").pixmap(ui->tool_goto_apps->iconSize()) );
+ ui->tool_set_nextwkspace->setIcon(LXDG::findIcon("go-next-view",""));
+ ui->tool_set_prevwkspace->setIcon(LXDG::findIcon("go-previous-view",""));
+ ui->tool_logout->setIcon(LXDG::findIcon("system-log-out",""));
+ ui->tool_restart->setIcon(LXDG::findIcon("system-reboot",""));
+ ui->tool_shutdown->setIcon(LXDG::findIcon("system-shutdown",""));
+ ui->tool_suspend->setIcon(LXDG::findIcon("system-suspend",""));
+
+ //Update Visibility of system/session/OS options
+ // -- Control Panel
+ QString tmp = LOS::ControlPanelShortcut();
+ if(QFile::exists(tmp)){
+ ui->tool_launch_controlpanel->setWhatsThis(tmp);
+ //Now read the file to see which icon to use
+ XDGDesktop desk(tmp);
+ if(desk.isValid()){
+ ui->tool_launch_controlpanel->setIcon(LXDG::findIcon(desk.icon,"preferences-other"));
+ }else{ ui->tool_launch_controlpanel->setVisible(false); }
+ }else{ ui->tool_launch_controlpanel->setVisible(false); }
+ // -- App Store
+ tmp = LOS::AppStoreShortcut();
+ if(QFile::exists(tmp)){
+ ui->tool_launch_store->setWhatsThis(tmp);
+ //Now read the file to see which icon to use
+ XDGDesktop desk(tmp);
+ if(desk.isValid()){
+ ui->tool_launch_store->setIcon(LXDG::findIcon(desk.icon,"utilities-file-archiver"));
+ }else{ ui->tool_launch_store->setVisible(false); }
+ }else{ ui->tool_launch_store->setVisible(false); }
+ //Audio Controls
+ ui->frame_audio->setVisible( LOS::audioVolume() >=0 );
+ //Brightness controls
+ ui->frame_bright->setVisible( LOS::ScreenBrightness() >=0 );
+ //Shutdown/restart
+ bool ok = LOS::userHasShutdownAccess();
+ ui->frame_leave_system->setWhatsThis(ok ? "allowed": "");
+ ui->frame_leave_system->setVisible(ok);
+ //Battery Availability
+ ok = LOS::hasBattery();
+ ui->label_status_battery->setWhatsThis(ok ? "allowed": "");
+ ui->label_status_battery->setVisible(ok);
+ //Locale availability
+ QStringList locales = LUtils::knownLocales();
+ ui->stackedWidget->setCurrentWidget(ui->page_main); //need to ensure the settings page is not active
+ ui->combo_locale->clear();
+ QString curr = LUtils::currentLocale();
+ //qDebug() << "Update Locales:" << locales;
+ //qDebug() << "Current Locale:" << curr;
+ for(int i=0; i<locales.length(); i++){
+ QLocale loc( (locales[i]=="pt") ? "pt_PT" : locales[i] );
+ ui->combo_locale->addItem(loc.nativeLanguageName() +" ("+locales[i]+")", locales[i]); //Make the display text prettier later
+ if(locales[i] == curr || locales[i] == curr.section("_",0,0) ){
+ //Current Locale
+ ui->combo_locale->setCurrentIndex(ui->combo_locale->count()-1); //the last item in the list right now
+ }
+ }
+ ui->frame_locale->setVisible(locales.length() > 1);
+ //User Name in status bar
+ ui->label_status->setText( QString::fromLocal8Bit(getlogin()) );
+ //Lumina Utilities
+ ui->tool_launch_desksettings->setVisible(LUtils::isValidBinary("lumina-config"));
+ ui->tool_launch_deskinfo->setVisible(LUtils::isValidBinary("lumina-info"));
+
+}
+
+void StartMenu::UpdateMenu(bool forceall){
+ //qDebug() << "Update Menu" << forceall;
+ ui->line_search->clear();
+ if(forceall){ UpdateAll(); }
+ //Quick update routine before the menu is made visible
+ //qDebug() << "update favs";
+ //UpdateFavs();
+ //qDebug() << "check page";
+ if(ui->stackedWidget->currentWidget() != ui->page_main){
+ ui->stackedWidget->setCurrentWidget(ui->page_main); //just show the main page
+ }else{
+ on_stackedWidget_currentChanged(0); //refresh/update the main page every time
+ }
+ //qDebug() << "done";
+}
+
+void StartMenu::ReLoadQuickLaunch(){
+ emit UpdateQuickLaunch( LSession::handle()->sessionSettings()->value("QuicklaunchApps",QStringList()).toStringList() );
+}
+
+void StartMenu::UpdateQuickLaunch(QString path, bool keep){
+ QStringList QL = LSession::handle()->sessionSettings()->value("QuicklaunchApps",QStringList()).toStringList();
+ if(keep){QL << path; }
+ else{ QL.removeAll(path); }
+ QL.removeDuplicates();
+ LSession::handle()->sessionSettings()->setValue("QuicklaunchApps",QL);
+ //LSession::handle()->sessionSettings()->sync();
+ emit UpdateQuickLaunch(QL);
+}
+
+// ==========================
+// PRIVATE FUNCTIONS
+// ==========================
+/*void StartMenu::deleteChildren(QWidget *obj){
+ if(obj->layout()==0){
+ for(int i=0; i<obj->children().count(); i++){
+ obj->children().at(i)->deleteLater();
+ }
+ }else{
+
+ }
+}*/
+
+void StartMenu::ClearScrollArea(QScrollArea *area){
+ //QWidget *old = area->takeWidget();
+ //qDebug() << "Clear Scroll Area:";
+ //if(old->layout()!=0){ qDebug() << " - Number of items in layout:" << old->layout()->count(); }
+ //qDebug() << " - Number of Children:" << old->children().count();
+ //deleteChildren(old); //make sure we *fully* delete these items to save memory
+ //old->deleteLater();
+ if(area == ui->scroll_favs){
+ area->takeWidget()->deleteLater();
+ }
+ if(area->widget()==0){
+ area->setWidget( new QWidget(area) ); //create a new widget in the scroll area
+ }
+ if(area->widget()->layout()==0){
+ QVBoxLayout *layout = new QVBoxLayout(area->widget());
+ layout->setSpacing(2);
+ layout->setContentsMargins(3,1,3,1);
+ layout->setDirection(QBoxLayout::TopToBottom);
+ layout->setAlignment(Qt::AlignTop);
+ area->widget()->setContentsMargins(0,0,0,0);
+ area->widget()->setLayout(layout);
+ }
+ //Now clear the items in the layout
+ while( area->widget()->layout()->count() >0 ){
+ QLayoutItem *it = area->widget()->layout()->takeAt(0);
+ //Need to delete both the widget and the layout item
+ if(it->widget()!=0){ it->widget()->deleteLater(); }
+ delete it;
+ }
+}
+
+void StartMenu::SortScrollArea(QScrollArea *area){
+ //qDebug() << "Sorting Scroll Area:";
+ //Sort all the items in the scroll area alphabetically
+ QLayout *lay = area->widget()->layout();
+ QStringList items;
+ for(int i=0; i<lay->count(); i++){
+ items << lay->itemAt(i)->widget()->whatsThis();
+ }
+
+ items.sort();
+ //qDebug() << " - Sorted Items:" << items;
+ for(int i=0; i<items.length(); i++){
+ if(items[i].isEmpty()){ continue; }
+ //QLayouts are weird in that they can only add items to the end - need to re-insert almost every item
+ for(int j=0; j<lay->count(); j++){
+ //Find this item
+ if(lay->itemAt(j)->widget()->whatsThis()==items[i]){
+ //Found it - now move it if necessary
+ //qDebug() << "Found Item:" << items[i] << i << j;
+ lay->addItem( lay->takeAt(j) );
+ break;
+ }
+ }
+ }
+}
+
+void StartMenu::do_search(QString search, bool force){
+ search = search.simplified(); //remove unneccesary whitespace
+ if(search == CSearch && !force){
+ //nothing new - just ensure the page is visible
+ if(ui->stackedWidget->currentWidget()!=ui->page_search ){ ui->stackedWidget->setCurrentWidget(ui->page_search); }
+ return;
+ }else if(search.isEmpty()){
+ CSearch.clear();
+ if(ui->stackedWidget->currentWidget()==ui->page_search ){ on_tool_back_clicked(); }
+ return;
+ }
+ //Got a search term - check it
+ CSearch = search; //save this for comparison later
+ qDebug() << "Search for term:" << search;
+ ClearScrollArea(ui->scroll_search);
+ topsearch.clear();
+ //Now find any items which match the search
+ QStringList found; //syntax: [<sorter>::::<mimetype>::::<filepath>]
+ QString tmp = search;
+ if(LUtils::isValidBinary(tmp)){ found << "0::::application/x-executable::::"+tmp; }
+ QList<XDGDesktop*> apps = LSession::handle()->applicationMenu()->currentAppHash()->value("All");
+ for(int i=0; i<apps.length(); i++){
+ int priority = -1;
+ if(apps[i]->name.toLower()==search.toLower()){ priority = 10; }
+ else if(apps[i]->name.startsWith(search, Qt::CaseInsensitive)){ priority = 15; }
+ else if(apps[i]->name.contains(search, Qt::CaseInsensitive)){ priority = 19; }
+ else if(apps[i]->genericName.contains(search, Qt::CaseInsensitive)){ priority = 20; }
+ else if(apps[i]->comment.contains(search, Qt::CaseInsensitive)){ priority = 30; }
+ //Can add other filters here later
+
+ if(priority>0){
+ found << QString::number(priority)+"::::app::::"+apps[i]->filePath;
+ }
+ }
+ found.sort(Qt::CaseInsensitive); //sort by priority/type (lower numbers are higher on list)
+ //qDebug() << "Sorted Items:" << found;
+ //Now add the items to the menu in order
+ for(int i=0; i<found.length(); i++){
+ if( !QFile::exists(found[i].section("::::",2,-1)) ){ continue; } //invalid favorite - skip it
+ if(topsearch.isEmpty()){ topsearch = found[i].section("::::",2,-1); }
+ ItemWidget *it = 0;
+ if( found[i].section("::::",2,-1).endsWith(".desktop")){
+ XDGDesktop item(found[i].section("::::",2,-1));
+ if(item.isValid()){ it = new ItemWidget(ui->scroll_favs->widget(), &item); }
+ }else{
+ it = new ItemWidget(ui->scroll_favs->widget(), found[i].section("::::",2,-1), found[i].section("::::",1,1) );
+ }
+ if(it==0){ continue; }
+ if(!it->gooditem){ it->deleteLater(); continue; } //invalid for some reason
+ ui->scroll_search->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(toggleQuickLaunch(QString, bool)), this, SLOT(UpdateQuickLaunch(QString, bool)) );
+ if(i%3==0){
+ QApplication::processEvents();
+ if(searchTimer->isActive()){ return; } //search changed - go ahead and stop here
+ }
+ }
+ ui->stackedWidget->setCurrentWidget(ui->page_search);
+}
+
+bool StartMenu::promptAboutUpdates(bool &skip){
+ QString pending = LOS::systemPendingUpdates();
+ if(pending.isEmpty()){ skip = false; } //continue without skip
+ else{
+ QMessageBox dlg(QMessageBox::Question, tr("Apply Updates?"), tr("You have system updates waiting to be applied! Do you wish to install them now?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, this);
+ dlg.setDetailedText(pending);
+ dlg.setDefaultButton(QMessageBox::Yes);
+ dlg.show();
+ int ret = dlg.exec();
+ if(ret == QMessageBox::Cancel){ return false; } //do not continue
+ else{ skip = (ret==QMessageBox::No); }
+ }
+ return true;
+}
+
+// ========================
+// PRIVATE SLOTS
+// ========================
+void StartMenu::LaunchItem(QString path, bool fix){
+ if(path.startsWith("chcat::::")){
+ ChangeCategory(path.section("::::",1,50));
+ return;
+ }
+ qDebug() << "Launching Item:" << path << fix;
+ if(!path.isEmpty()){
+ qDebug() << "Launch Application:" << path;
+ if( fix && !path.startsWith("lumina-open") ){ LSession::LaunchApplication("lumina-open \""+path+"\""); }
+ else{ LSession::LaunchApplication(path); }
+ emit CloseMenu(); //so the menu container will close
+ }
+}
+
+void StartMenu::ChangeCategory(QString cat){
+ //This only happens on user interaction - make sure to run the update routine in a separate thread
+ CCat = cat;
+ UpdateApps();
+ //QtConcurrent::run(this, &StartMenu::UpdateApps);
+}
+
+//Listing Update routines
+void StartMenu::UpdateApps(){
+ ClearScrollArea(ui->scroll_apps);
+ //Now assemble the apps list
+ //qDebug() << "Update Apps:";// << CCat << ui->check_apps_showcats->checkState();
+ if(ui->check_apps_showcats->checkState() == Qt::PartiallyChecked){
+ //qDebug() << " - Partially Checked";
+ //Show a single page of apps, but still divided up by categories
+ CCat.clear();
+ QStringList cats = LSession::handle()->applicationMenu()->currentAppHash()->keys();
+ cats.sort();
+ cats.removeAll("All");
+ for(int c=0; c<cats.length(); c++){
+ QList<XDGDesktop*> apps = LSession::handle()->applicationMenu()->currentAppHash()->value(cats[c]);
+ if(apps.isEmpty()){ continue; }
+ //Add the category label to the scroll
+ QLabel *catlabel = new QLabel("<b>"+cats[c]+"</b>",ui->scroll_apps->widget());
+ catlabel->setAlignment(Qt::AlignCenter);
+ ui->scroll_apps->widget()->layout()->addWidget(catlabel);
+ //Now add all the apps for this category
+ for(int i=0; i<apps.length(); i++){
+ ItemWidget *it = new ItemWidget(ui->scroll_apps->widget(), apps[i] );
+ if(!it->gooditem){ qDebug() << "Invalid Item:"; it->deleteLater(); continue; } //invalid for some reason
+ ui->scroll_apps->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(toggleQuickLaunch(QString, bool)), this, SLOT(UpdateQuickLaunch(QString, bool)) );
+ }
+ }
+
+ }else if(ui->check_apps_showcats->checkState() == Qt::Checked){
+ //qDebug() << " - Checked";
+ //Only show categories to start with - and have the user click-into a cat to see apps
+ if(CCat.isEmpty()){
+ //No cat selected yet - show cats only
+ QStringList cats = LSession::handle()->applicationMenu()->currentAppHash()->keys();
+ cats.sort();
+ cats.removeAll("All"); //This is not a "real" category
+ for(int c=0; c<cats.length(); c++){
+ ItemWidget *it = new ItemWidget(ui->scroll_apps->widget(), cats[c], "chcat::::"+cats[c] );
+ if(!it->gooditem){ qDebug() << "Invalid Item:";it->deleteLater(); continue; } //invalid for some reason
+ ui->scroll_apps->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ }
+ }else{
+ //qDebug() << "Show Apps For category:" << CCat;
+ //Show the "go back" button
+ ItemWidget *it = new ItemWidget(ui->scroll_apps->widget(), CCat, "chcat::::"+CCat, true);
+ //if(!it->gooditem){ continue; } //invalid for some reason
+ ui->scroll_apps->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ //Show apps for this cat
+ QList<XDGDesktop*> apps = LSession::handle()->applicationMenu()->currentAppHash()->value(CCat);
+ for(int i=0; i<apps.length(); i++){
+ //qDebug() << " - App:" << apps[i].name;
+ ItemWidget *it = new ItemWidget(ui->scroll_apps->widget(), apps[i] );
+ if(!it->gooditem){ qDebug() << "Invalid Item:"; it->deleteLater(); continue; } //invalid for some reason
+ ui->scroll_apps->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(toggleQuickLaunch(QString, bool)), this, SLOT(UpdateQuickLaunch(QString, bool)) );
+ }
+ }
+
+ }else{
+ //qDebug() << " - Not Checked";
+ //No categories at all - just alphabetize all the apps
+ QList<XDGDesktop*> apps = LSession::handle()->applicationMenu()->currentAppHash()->value("All");
+ CCat.clear();
+ //Now add all the apps for this category
+ for(int i=0; i<apps.length(); i++){
+ ItemWidget *it = new ItemWidget(ui->scroll_apps->widget(), apps[i] );
+ if(!it->gooditem){ it->deleteLater(); continue; } //invalid for some reason
+ ui->scroll_apps->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(toggleQuickLaunch(QString, bool)), this, SLOT(UpdateQuickLaunch(QString, bool)) );
+ }
+ }
+
+
+}
+
+void StartMenu::UpdateFavs(){
+ //SYNTAX NOTE: (per-line) "<name>::::[dir/app/<mimetype>]::::<path>"
+ QStringList newfavs = LDesktopUtils::listFavorites();
+ if(favs == newfavs){ return; } //nothing to do - same as before
+ favs = newfavs;
+ ClearScrollArea(ui->scroll_favs);
+ favs.sort();
+ //Iterate over types of favorites
+ QStringList rest = favs;
+ QStringList tmp;
+ for(int type = 0; type<3; type++){
+ if(type==0){ tmp = favs.filter("::::app::::"); } //apps first
+ else if(type==1){ tmp = favs.filter("::::dir::::"); } //dirs next
+ else{ tmp = rest; } //everything left over
+ if(type==1){
+ SortScrollArea(ui->scroll_favs);
+ //Need to run a special routine for sorting the apps (already in the widget)
+ //qDebug() << "Sort App Widgets...";
+ // Since each app actually might have a different name listed within the file
+ /*QLayout *lay = ui->scroll_favs->widget()->layout();
+ QStringList items;
+ for(int i=0; i<lay->count(); i++){
+ items << lay->itemAt(i)->widget()->whatsThis().toLower();
+ }
+
+ items.sort();
+ // qDebug() << " - Sorted Items:" << items;
+ for(int i=0; i<items.length(); i++){
+ if(items[i].isEmpty()){ continue; }
+ //QLayouts are weird in that they can only add items to the end - need to re-insert almost every item
+ for(int j=0; j<lay->count(); j++){
+ //Find this item
+ if(lay->itemAt(j)->widget()->whatsThis().toLower()==items[i]){
+ //Found it - now move it if necessary
+ //qDebug() << "Found Item:" << items[i] << i << j;
+ lay->addItem( lay->takeAt(j) );
+ break;
+ }
+ }
+ }*/
+
+ }//end of special app sorting routine
+ tmp.sort(); //Sort alphabetically by name (dirs/files)
+ for(int i=0; i<tmp.length(); i++){
+ if(type<2){ rest.removeAll(tmp[i]); }
+ if( !QFile::exists(tmp[i].section("::::",2,-1)) ){ continue; } //invalid favorite - skip it
+ ItemWidget *it = 0;
+ if( tmp[i].section("::::",2,-1).endsWith(".desktop")){
+ XDGDesktop item(tmp[i].section("::::",2,-1));
+ if(item.isValid()){ it = new ItemWidget(ui->scroll_favs->widget(), &item); }
+ }else{
+ it = new ItemWidget(ui->scroll_favs->widget(), tmp[i].section("::::",2,-1), tmp[i].section("::::",1,1) );
+ }
+ if(it==0){ continue; }
+ if(!it->gooditem){ it->deleteLater(); continue; } //invalid for some reason
+ ui->scroll_favs->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(UpdateFavs()) );
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(toggleQuickLaunch(QString, bool)), this, SLOT(UpdateQuickLaunch(QString, bool)) );
+ }
+ //QApplication::processEvents();
+ } //end loop over types
+ ui->scroll_favs->update();
+ //qDebug() << "End updateFavs";
+}
+
+// Page update routines
+void StartMenu::on_stackedWidget_currentChanged(int val){
+ QWidget *page = ui->stackedWidget->widget(val);
+ ui->tool_back->setVisible(page != ui->page_main);
+ ui->line_search->setFocus();
+
+ //Now the page specific updates
+ if(page == ui->page_main){
+ if(!ui->label_status_battery->whatsThis().isEmpty()){
+ //Battery available - update the status button
+ int charge = LOS::batteryCharge();
+ QString TT, ICON;
+ if(charge<=5){ ICON="-caution"; }
+ else if(charge<=20){ ICON="-040"; }
+ else if(charge<=70){ ICON="-060"; }
+ else if(charge<=90){ ICON="-080"; }
+ else{ ICON="-100"; }
+ if(LOS::batteryIsCharging()){
+ if(charge>=80){ ICON.clear(); } //for charging, there is no suffix to the icon name over 80%
+ ICON.prepend("battery-charging");
+ TT = QString(tr("%1% (Plugged In)")).arg(QString::number(charge));
+ }else{
+ ICON.prepend("battery");
+ int secs = LOS::batterySecondsLeft();
+ if(secs>1){ TT = QString(tr("%1% (%2 Estimated)")).arg(QString::number(charge), LUtils::SecondsToDisplay(secs)); }
+ else{ TT = QString(tr("%1% Remaining")).arg(QString::number(charge)); }
+ }
+ //qDebug() << " Battery Icon:" << ICON << val << TT
+ ui->label_status_battery->setPixmap( LXDG::findIcon(ICON,"").pixmap(ui->tool_goto_apps->iconSize()/2) );
+ ui->label_status_battery->setToolTip(TT);
+ }
+ //Network Status
+ ui->label_status_network->clear(); //not implemented yet
+ }else if(page == ui->page_apps){
+ //Nothing needed for this page explicitly
+ }else if( page == ui->page_settings){
+ // -- Workspaces
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces();
+ if(tot>1){
+ ui->frame_wkspace->setVisible(true);
+ int cur = LSession::handle()->XCB->CurrentWorkspace();
+ ui->label_wkspace->setText( QString(tr("Workspace %1/%2")).arg(QString::number(cur+1), QString::number(tot)) );
+ }else{
+ ui->frame_wkspace->setVisible(false);
+ }
+ // -- Brightness Controls
+ int tmp = LOS::ScreenBrightness();
+ ui->frame_bright->setVisible(tmp >= 0);
+ if(tmp >= 0){ ui->slider_bright->setValue(tmp); }
+ // -- Audio Controls
+ tmp = LOS::audioVolume();
+ ui->frame_audio->setVisible(tmp >= 0);
+ if(tmp >= 0){ ui->slider_volume->setValue(tmp); }
+ }else if(page == ui->page_leave){
+ if( !ui->frame_leave_system->whatsThis().isEmpty() ){
+ //This frame is allowed/visible - need to adjust the shutdown detection
+ bool updating = LOS::systemPerformingUpdates();
+ ui->tool_restart->setEnabled(!updating);
+ ui->tool_shutdown->setEnabled(!updating);
+ ui->label_updating->setVisible(updating); //to let the user know *why* they can't shutdown/restart right now
+ }
+ ui->frame_leave_suspend->setVisible( LOS::systemCanSuspend() );
+ }
+
+}
+
+void StartMenu::catViewChanged(){
+ QString state;
+ switch(ui->check_apps_showcats->checkState()){
+ case Qt::Checked:
+ state = "true";
+ break;
+ case Qt::PartiallyChecked:
+ state = "partial";
+ break;
+ default:
+ state = "false";
+ }
+ LSession::handle()->DesktopPluginSettings()->setValue("panelPlugs/systemstart/showcategories", state);
+ //Now kick off the reload of the apps list
+ UpdateApps();
+ //QtConcurrent::run(this, &StartMenu::UpdateApps); //this was a direct user change - keep it thread safe
+}
+//Page Change Buttons
+void StartMenu::on_tool_goto_apps_clicked(){
+ ui->stackedWidget->setCurrentWidget(ui->page_apps);
+}
+
+void StartMenu::on_tool_goto_settings_clicked(){
+ ui->stackedWidget->setCurrentWidget(ui->page_settings);
+}
+
+void StartMenu::on_tool_goto_logout_clicked(){
+ ui->stackedWidget->setCurrentWidget(ui->page_leave);
+}
+
+void StartMenu::on_tool_back_clicked(){
+ ui->stackedWidget->setCurrentWidget(ui->page_main);
+}
+
+
+//Launch Buttons
+void StartMenu::on_tool_launch_controlpanel_clicked(){
+ LaunchItem(ui->tool_launch_controlpanel->whatsThis());
+}
+
+void StartMenu::on_tool_launch_fm_clicked(){
+ LaunchItem(QDir::homePath());
+}
+
+void StartMenu::on_tool_launch_store_clicked(){
+ LaunchItem(ui->tool_launch_store->whatsThis());
+}
+
+void StartMenu::on_tool_launch_desksettings_clicked(){
+ LaunchItem("lumina-config", false);
+}
+
+void StartMenu::on_tool_launch_deskinfo_clicked(){
+ LaunchItem("lumina-info",false);
+}
+
+//Logout Buttons
+void StartMenu::on_tool_lock_clicked(){
+ //QProcess::startDetached("xscreensaver-command -lock");
+ LaunchItem("xscreensaver-command -lock",false);
+}
+
+void StartMenu::on_tool_logout_clicked(){
+ emit CloseMenu();
+ LSession::handle()->StartLogout();
+}
+
+void StartMenu::on_tool_restart_clicked(){
+ emit CloseMenu();
+ QCoreApplication::processEvents();
+ bool skipupdates = false;
+ if( !promptAboutUpdates(skipupdates) ){ return; }
+ LSession::handle()->StartReboot(skipupdates);
+}
+
+void StartMenu::on_tool_shutdown_clicked(){
+ emit CloseMenu();
+ QCoreApplication::processEvents();
+ bool skipupdates = false;
+ if( !promptAboutUpdates(skipupdates) ){ return; }
+ LSession::handle()->StartShutdown(skipupdates);
+}
+
+void StartMenu::on_tool_suspend_clicked(){
+ //Make sure to lock the system first (otherwise anybody can access it again)
+ emit CloseMenu();
+ LUtils::runCmd("xscreensaver-command -lock");
+ LOS::systemSuspend();
+}
+
+
+//Audio Volume
+void StartMenu::on_slider_volume_valueChanged(int val){
+ ui->label_vol->setText(QString::number(val)+"%");
+ LOS::setAudioVolume(val);
+ //Also adjust the icon for the volume
+ if(val<1){ ui->tool_mute_audio->setIcon(LXDG::findIcon("audio-volume-muted","")); }
+ else if(val<33){ ui->tool_mute_audio->setIcon(LXDG::findIcon("audio-volume-low","")); }
+ else if(val<66){ ui->tool_mute_audio->setIcon(LXDG::findIcon("audio-volume-medium","")); }
+ else{ ui->tool_mute_audio->setIcon(LXDG::findIcon("audio-volume-high","")); }
+}
+
+void StartMenu::on_tool_launch_mixer_clicked(){
+ if(LOS::hasMixerUtility()){
+ emit CloseMenu();
+ LOS::startMixerUtility();
+ }
+}
+
+void StartMenu::on_tool_mute_audio_clicked(){
+ static int lastvol = 50;
+ if(ui->slider_volume->value()==0){ ui->slider_volume->setValue(lastvol); }
+ else{
+ lastvol = ui->slider_volume->value();
+ ui->slider_volume->setValue(0);
+ }
+}
+
+//Screen Brightness
+void StartMenu::on_slider_bright_valueChanged(int val){
+ ui->label_bright->setText(QString::number(val)+"%");
+ LOS::setScreenBrightness(val);
+}
+
+
+//Workspace
+void StartMenu::on_tool_set_nextwkspace_clicked(){
+ int cur = LSession::handle()->XCB->CurrentWorkspace();
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces();
+ //qDebug()<< "Next Workspace:" << cur << tot;
+ cur++;
+ if(cur>=tot){ cur = 0; } //back to beginning
+ //qDebug() << " - New Current:" << cur;
+ LSession::handle()->XCB->SetCurrentWorkspace(cur);
+ ui->label_wkspace->setText( QString(tr("Workspace %1/%2")).arg(QString::number(cur+1), QString::number(tot)) );
+}
+
+void StartMenu::on_tool_set_prevwkspace_clicked(){
+ int cur = LSession::handle()->XCB->CurrentWorkspace();
+ int tot = LSession::handle()->XCB->NumberOfWorkspaces();
+ //qDebug()<< "Next Workspace:" << cur << tot;
+ cur--;
+ if(cur<0){ cur = tot-1; } //back to end
+ //qDebug() << " - New Current:" << cur;
+ LSession::handle()->XCB->SetCurrentWorkspace(cur);
+ ui->label_wkspace->setText( QString(tr("Workspace %1/%2")).arg(QString::number(cur+1), QString::number(tot)) );
+}
+
+
+//Locale
+void StartMenu::on_combo_locale_currentIndexChanged(int){
+ //Get the currently selected Locale
+ if(ui->stackedWidget->currentWidget()!=ui->page_settings){ return; }
+ QString locale = ui->combo_locale->currentData().toString();
+ emit CloseMenu();
+ LSession::processEvents();
+ LSession::handle()->switchLocale(locale);
+}
+
+
+//Search
+void StartMenu::on_line_search_textEdited(QString){
+ if(searchTimer->isActive()){ searchTimer->stop(); }
+ searchTimer->start();
+}
+
+void StartMenu::startSearch(){
+ if(!this->isVisible()){ return; } //menu closed while timer was active
+ do_search(ui->line_search->text(),false); //auto-launched
+}
+
+void StartMenu::on_line_search_returnPressed(){
+ if(topsearch.isEmpty()){ return; }
+ LaunchItem(topsearch);
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.h
new file mode 100644
index 00000000..8ab04d94
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.h
@@ -0,0 +1,105 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_SYSTEM_START_PANEL_PLUGIN_H
+#define _LUMINA_DESKTOP_SYSTEM_START_PANEL_PLUGIN_H
+
+#include <QWidget>
+#include <QScrollArea>
+#include <QMouseEvent>
+
+#include <LuminaXDG.h>
+
+namespace Ui{
+ class StartMenu;
+};
+
+class StartMenu : public QWidget{
+ Q_OBJECT
+public:
+ StartMenu(QWidget *parent = 0);
+ ~StartMenu();
+
+public slots:
+ void UpdateAll(); //for re-translation/icon changes
+ void UpdateMenu(bool forceall = false); //for item changes
+
+ void ReLoadQuickLaunch();
+ void UpdateQuickLaunch(QString, bool);
+
+private:
+ Ui::StartMenu *ui;
+ QStringList favs;
+ QString CCat, CSearch, topsearch; //current category/search
+ QTimer *searchTimer;
+
+ //Simple utility functions
+ //void deleteChildren(QWidget *obj); //recursive function
+ void ClearScrollArea(QScrollArea *area);
+ void SortScrollArea(QScrollArea *area);
+ void do_search(QString search, bool force);
+
+ bool promptAboutUpdates(bool &skip);
+
+private slots:
+ void LaunchItem(QString path, bool fix = true);
+
+ //Application/Favorite Listings
+ void ChangeCategory(QString cat);
+ void UpdateApps();
+ void UpdateFavs();
+
+ // Page update routines
+ void on_stackedWidget_currentChanged(int); //page changed
+ void catViewChanged(); //application categorization view mode changed
+
+ //Page Change Buttons
+ void on_tool_goto_apps_clicked();
+ void on_tool_goto_settings_clicked();
+ void on_tool_goto_logout_clicked();
+ void on_tool_back_clicked();
+
+ //Launch Buttons
+ void on_tool_launch_controlpanel_clicked();
+ void on_tool_launch_fm_clicked();
+ void on_tool_launch_store_clicked();
+ void on_tool_launch_desksettings_clicked();
+ void on_tool_launch_deskinfo_clicked();
+
+ //Logout Buttons
+ void on_tool_lock_clicked();
+ void on_tool_logout_clicked();
+ void on_tool_restart_clicked();
+ void on_tool_shutdown_clicked();
+ void on_tool_suspend_clicked();
+
+ //Audio Volume
+ void on_slider_volume_valueChanged(int);
+ void on_tool_launch_mixer_clicked();
+ void on_tool_mute_audio_clicked();
+
+ //Screen Brightness
+ void on_slider_bright_valueChanged(int);
+
+ //Workspace
+ void on_tool_set_nextwkspace_clicked();
+ void on_tool_set_prevwkspace_clicked();
+
+ //Locale
+ void on_combo_locale_currentIndexChanged(int);
+
+ //Search
+ void on_line_search_textEdited(QString);
+ void startSearch();
+ void on_line_search_returnPressed();
+
+signals:
+ void CloseMenu();
+ void UpdateQuickLaunch(QStringList);
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.ui b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.ui
new file mode 100644
index 00000000..74f61d7f
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemstart/StartMenu.ui
@@ -0,0 +1,1148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StartMenu</class>
+ <widget class="QWidget" name="StartMenu">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>181</width>
+ <height>405</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QScrollArea{background: transparent; border: none; }</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>1</number>
+ </property>
+ <property name="topMargin">
+ <number>1</number>
+ </property>
+ <property name="rightMargin">
+ <number>1</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="line_search">
+ <property name="placeholderText">
+ <string>Type to search</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="stackedWidget">
+ <property name="currentIndex">
+ <number>4</number>
+ </property>
+ <widget class="QWidget" name="page_main">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label_status_battery">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">bat%</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_status_network">
+ <property name="text">
+ <string notr="true">netstat</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_status">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_14">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scroll_favs">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true"/>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Plain</enum>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAsNeeded</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>179</width>
+ <height>177</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_launch_fm">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Browse Files</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_goto_apps">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Browse Applications</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_launch_controlpanel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Control Panel</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_goto_settings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Preferences</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QToolButton" name="tool_goto_logout">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QToolButton::menu-arrow{ image: rightarrow-icon; }</string>
+ </property>
+ <property name="text">
+ <string>Leave</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::DelayedPopup</enum>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::NoArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_lock">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string notr="true">lock</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_apps">
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="tool_launch_store">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Manage Applications</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_15">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="check_apps_showcats">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Show Categories</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="tristate">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scroll_apps">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents_2">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>179</width>
+ <height>284</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_settings">
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QToolButton" name="tool_launch_desksettings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Configure Desktop</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_launch_deskinfo">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string notr="true">info</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_8">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_audio">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QToolButton" name="tool_mute_audio">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QSlider" name="slider_volume">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_vol">
+ <property name="text">
+ <string notr="true">vol%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_launch_mixer">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="autoRaise">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_7">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_bright">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="label_bright_icon">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="slider_bright">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="minimum">
+ <number>10</number>
+ </property>
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_bright">
+ <property name="text">
+ <string notr="true">br%</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_9">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_wkspace">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QToolButton" name="tool_set_prevwkspace">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string notr="true">prev</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_wkspace">
+ <property name="text">
+ <string notr="true">workspace #</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_set_nextwkspace">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string notr="true">next</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_10">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_locale">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_locale_icon">
+ <property name="text">
+ <string notr="true">Locale:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="combo_locale">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>161</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="Line" name="line_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_leave">
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>185</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_leave_suspend">
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="tool_suspend">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Suspend System</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_11">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QFrame" name="frame_leave_system">
+ <property name="styleSheet">
+ <string notr="true">QFrame#frame_leave_system{border: none; background: transparent; }</string>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="tool_restart">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Restart System</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_shutdown">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Power Off System</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_updating">
+ <property name="text">
+ <string>(System Performing Updates)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_13">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_logout">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Sign Out User</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_12">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="page_search">
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QScrollArea" name="scroll_search">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents_3">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>161</width>
+ <height>332</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_back">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string>Back</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>32</width>
+ <height>32</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::NoArrow</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.cpp
new file mode 100644
index 00000000..a71fd57e
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.cpp
@@ -0,0 +1,167 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LSysTray.h"
+#include "../../LSession.h"
+
+LSysTray::LSysTray(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ frame = new QFrame(this);
+ frame->setContentsMargins(0,0,0,0);
+ //frame->setStyleSheet("QFrame{ background: transparent; border: 1px solid transparent; border-radius: 3px; }");
+ LI = new QBoxLayout( this->layout()->direction());
+ frame->setLayout(LI);
+ LI->setAlignment(Qt::AlignCenter);
+ LI->setSpacing(0);
+ LI->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(frame);
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ //TrayID=0;
+ upTimer = new QTimer(this);
+ upTimer->setInterval(300000); //maximum time between refreshes is 5 minutes
+ connect(upTimer, SIGNAL(timeout()), this, SLOT(checkAll()) );
+ isRunning = false; stopping = false; checking = false; pending = false;
+ QTimer::singleShot(100, this, SLOT(start()) );
+ //Also do one extra check a minute or so after startup (just in case something got missed in the initial flood of registrations)
+ QTimer::singleShot(90000,this, SLOT(checkAll()) );
+ connect(LSession::handle(), SIGNAL(TrayListChanged()), this, SLOT(checkAll()) );
+ connect(LSession::handle(), SIGNAL(TrayIconChanged(WId)), this, SLOT(UpdateTrayWindow(WId)) );
+ connect(LSession::handle(), SIGNAL(VisualTrayAvailable()), this, SLOT(start()) );
+}
+
+LSysTray::~LSysTray(){
+ if(isRunning){
+ this->stop();
+ }
+}
+
+void LSysTray::start(){
+ if(isRunning || stopping){ return; } //already running
+ isRunning = LSession::handle()->registerVisualTray(this->winId());
+ qDebug() << "Visual Tray Started:" << this->type() << isRunning;
+ if(isRunning){
+ //upTimer->start();
+ QTimer::singleShot(0,this, SLOT(checkAll()) );
+ }
+}
+
+void LSysTray::stop(){
+ if(!isRunning){ return; }
+ stopping = true;
+ upTimer->stop();
+ //Now close down the system tray registry
+ qDebug() << "Stop visual system tray:" << this->type();
+ //LX11::closeSystemTray(TrayID);
+ //TrayID = 0;
+ disconnect(this); //remove any signals/slots
+ isRunning = false;
+ //Release all the tray applications and delete the containers
+ if( !LSession::handle()->currentTrayApps(this->winId()).isEmpty() ){
+ qDebug() << " - Remove tray applications";
+ //This overall system tray is not closed down - go ahead and release them here
+ for(int i=(trayIcons.length()-1); i>=0; i--){
+ trayIcons[i]->detachApp();
+ TrayIcon *cont = trayIcons.takeAt(i);
+ LI->removeWidget(cont);
+ cont->deleteLater();
+ }
+ }
+ //Now let some other visual tray take over
+ LSession::handle()->unregisterVisualTray(this->winId());
+ qDebug() << "Done stopping visual tray";
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void LSysTray::checkAll(){
+ if(!isRunning || stopping || checking){ pending = true; return; } //Don't check if not running at the moment
+ checking = true;
+ pending = false;
+ //Make sure this tray should handle the windows (was not disabled in the backend)
+ bool TrayRunning = LSession::handle()->registerVisualTray(this->winId());
+ //qDebug() << "System Tray: Check tray apps";
+ QList<WId> wins = LSession::handle()->currentTrayApps(this->winId());
+ for(int i=0; i<trayIcons.length(); i++){
+ int index = wins.indexOf(trayIcons[i]->appID());
+ if(index < 0){
+ //Tray Icon no longer exists: remove it
+ qDebug() << " - Visual System Tray: Remove Icon:" << trayIcons[i]->appID();
+ TrayIcon *cont = trayIcons.takeAt(i);
+ cont->cleanup();
+ LI->removeWidget(cont);
+ cont->deleteLater();
+ i--; //List size changed
+ //Re-adjust the maximum widget size to account for what is left
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setMaximumSize( trayIcons.length()*this->height(), 10000);
+ }else{
+ this->setMaximumSize(10000, trayIcons.length()*this->width());
+ }
+ }else{
+ //Tray Icon already exists
+ //qDebug() << " - SysTray: Update Icon";
+ trayIcons[i]->update();
+ wins.removeAt(index); //Already found - remove from the list
+ }
+ }
+ //Now go through any remaining windows and add them
+ for(int i=0; i<wins.length() && TrayRunning; i++){
+ qDebug() << " - Visual System Tray: Add Icon:" << wins[i];
+ TrayIcon *cont = new TrayIcon(this);
+ connect(cont, SIGNAL(BadIcon()), this, SLOT(checkAll()) );
+ //LSession::processEvents();
+ trayIcons << cont;
+ LI->addWidget(cont);
+ //qDebug() << " - Update tray layout";
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ int sz = this->height()-2-2*frame->frameWidth();
+ if(sz>64){ sz = 64; }
+ cont->setSizeSquare(sz); //horizontal tray
+ this->setMaximumSize( trayIcons.length()*this->height(), 10000);
+ }else{
+ int sz = this->width()-2-2*frame->frameWidth();
+ if(sz>64){ sz = 64; }
+ cont->setSizeSquare(sz); //vertical tray
+ this->setMaximumSize(10000, trayIcons.length()*this->width());
+ }
+ //LSession::processEvents();
+ //qDebug() << " - Attach tray app";
+ cont->attachApp(wins[i]);
+ if(cont->appID()==0){
+ //could not attach window - remove the widget
+ qDebug() << " - Invalid Tray App: Could Not Embed:";
+ trayIcons.takeAt(trayIcons.length()-1); //Always at the end
+ LI->removeWidget(cont);
+ cont->deleteLater();
+ continue;
+ }
+ LI->update(); //make sure there is no blank space in the layout
+ }
+ /*if(listChanged){
+ //Icons got moved around: be sure to re-draw all of them to fix visuals
+ for(int i=0; i<trayIcons.length(); i++){
+ trayIcons[i]->update();
+ }
+ }*/
+ //qDebug() << " - System Tray: check done";
+ checking = false;
+ if(pending){ QTimer::singleShot(0,this, SLOT(checkAll()) ); }
+}
+
+void LSysTray::UpdateTrayWindow(WId win){
+ if(!isRunning || stopping || checking){ return; }
+ for(int i=0; i<trayIcons.length(); i++){
+ if(trayIcons[i]->appID()==win){
+ //qDebug() << "System Tray: Update Window " << win;
+ trayIcons[i]->repaint(); //don't use update() because we need an instant repaint (not a cached version)
+ return; //finished now
+ }
+ }
+ //Could not find tray in the list, run the checkall routine to make sure we are not missing any
+ //qDebug() << "System Tray: Missing Window - check all";
+ QTimer::singleShot(0,this, SLOT(checkAll()) );
+}
+
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.h
new file mode 100644
index 00000000..7db307c6
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/LSysTray.h
@@ -0,0 +1,74 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2012-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_SYSTRAY_H
+#define _LUMINA_DESKTOP_SYSTRAY_H
+
+//Qt includes
+#include <QFrame>
+#include <QHBoxLayout>
+#include <QDebug>
+#include <QX11Info>
+#include <QCoreApplication>
+
+//Local includes
+#include "../LPPlugin.h"
+#include "TrayIcon.h"
+
+//SYSTEM TRAY STANDARD DEFINITIONS
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+class LSysTray : public LPPlugin{
+ Q_OBJECT
+public:
+ LSysTray(QWidget *parent = 0, QString id="systemtray", bool horizontal=true);
+ ~LSysTray();
+
+ virtual void AboutToClose(){
+ this->stop();
+ }
+
+private:
+ bool isRunning, stopping, checking, pending;
+ QList<TrayIcon*> trayIcons;
+ QFrame *frame;
+ QBoxLayout *LI; //layout items
+ QTimer *upTimer; //manual timer to force refresh of all items
+
+private slots:
+ void checkAll();
+ void UpdateTrayWindow(WId win);
+
+ //void removeTrayIcon(WId win);
+
+public slots:
+ void start();
+ void stop();
+
+ virtual void OrientationChange(){
+ //make sure the internal layout has the same orientation as the main widget
+ LI->setDirection( this->layout()->direction() );
+ //Re-adjust the maximum widget size
+ int sz;
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setMaximumSize( trayIcons.length()*this->height(), 10000);
+ sz = this->height()-2*frame->frameWidth();
+ }else{
+ this->setMaximumSize(10000, trayIcons.length()*this->width());
+ sz = this->width()-2*frame->frameWidth();
+ }
+ if(sz>64){ sz = 64; } //many tray icons can't go larger than this anyway
+ for(int i=0; i<trayIcons.length(); i++){
+ trayIcons[i]->setSizeSquare(sz);
+ trayIcons[i]->repaint();
+ }
+ }
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.cpp
new file mode 100644
index 00000000..9fdbce50
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.cpp
@@ -0,0 +1,128 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "TrayIcon.h"
+
+#include <LSession.h>
+#include <QScreen>
+#include <LuminaX11.h>
+
+TrayIcon::TrayIcon(QWidget *parent) : QWidget(parent){
+ AID = 0; //nothing yet
+ IID = 0;
+ dmgID = 0;
+ badpaints = 0;
+ //this->setLayout(new QHBoxLayout);
+ //this->layout()->setContentsMargins(0,0,0,0);
+}
+
+TrayIcon::~TrayIcon(){
+}
+
+void TrayIcon::cleanup(){
+ AID = IID = 0;
+}
+
+WId TrayIcon::appID(){
+ return AID;
+}
+
+void TrayIcon::attachApp(WId id){
+ if(id==0){ return; } //nothing to attach
+ else if(AID!=0){ qWarning() << "Tray Icon is already attached to a window!"; return; }
+ AID = id;
+ //WIN = QWindow::fromWinId(AID);
+ //connect(WIN, SIGNAL(
+ //this->layout()->addWidget( QWidget::createWindowContainer(WIN, this) );
+ IID = this->winId(); //embed directly into this widget
+ dmgID = LSession::handle()->XCB->EmbedWindow(AID, IID);
+ if( dmgID != 0 ){
+ LSession::handle()->XCB->RestoreWindow(AID); //make it visible
+ //qDebug() << "New System Tray App:" << AID;
+ QTimer::singleShot(1000, this, SLOT(updateIcon()) );
+ }else{
+ //qWarning() << "Could not Embed Tray Application:" << AID;
+ IID = 0;
+ AID = 0;
+ }
+}
+
+void TrayIcon::setSizeSquare(int side){
+ //qDebug() << " Set Fixed Systray size:" << side;
+ this->setFixedSize( QSize(side, side) );
+}
+
+// ==============
+// PUBLIC SLOTS
+// ==============
+void TrayIcon::detachApp(){
+ if(AID==0){ return; } //already detached
+ //qDebug() << "Detach App:" << AID;
+ //Temporarily move the AID, so that internal slots do not auto-run
+ WId tmp = AID;
+ AID = 0;
+ //Now detach the application window and clean up
+ //qDebug() << " - Unembed";
+ //WIN->setParent(0); //Reset parentage back to the main stack
+ LSession::handle()->XCB->UnembedWindow(tmp);
+ //qDebug() << " - finished app:" << tmp;
+ IID = 0;
+}
+
+// ==============
+// PRIVATE SLOTS
+// ==============
+void TrayIcon::updateIcon(){
+ if(AID==0){ return; }
+ //Make sure the icon is square
+ QSize icosize = this->size();
+ LSession::handle()->XCB->ResizeWindow(AID, icosize.width(), icosize.height());
+ QTimer::singleShot(500, this, SLOT(update()) ); //make sure to re-draw the window in a moment
+}
+
+// =============
+// PROTECTED
+// =============
+void TrayIcon::paintEvent(QPaintEvent *event){
+ QWidget::paintEvent(event); //make sure the background is already painted
+ if(AID!=0){
+ //Update the background on the tray app
+ //qDebug() << "Paint Tray Background";
+ //LSession::handle()->XCB->SetWindowBackground(this, this->geometry(), AID);
+ //qDebug() << "Paint Tray:" << AID;
+ QPainter painter(this);
+ //Now paint the tray app on top of the background
+ //qDebug() << " - Draw tray:" << AID << IID << this->winId();
+ //qDebug() << " - - " << event->rect().x() << event->rect().y() << event->rect().width() << event->rect().height();
+ //qDebug() << " - Get image:" << AID;
+ QPixmap pix = LSession::handle()->XCB->TrayImage(AID); //= WIN->icon().pixmap(this->size());
+
+ //qDebug() << " - Pix size:" << pix.size().width() << pix.size().height();
+ //qDebug() << " - Geom:" << this->geometry().x() << this->geometry().y() << this->geometry().width() << this->geometry().height();
+ if(!pix.isNull()){
+ if(this->size() != pix.size()){ QTimer::singleShot(10, this, SLOT(updateIcon())); }
+ painter.drawPixmap(0,0,this->width(), this->height(), pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation) );
+ badpaints = 0; //good paint
+ }else{
+ badpaints++;
+ if(badpaints>5){
+ qWarning() << " - - No Tray Icon/Image found!" << "ID:" << AID;
+ AID = 0; //reset back to nothing
+ IID = 0;
+ emit BadIcon(); //removed/destroyed in some non-valid way?
+ }
+ }
+ //qDebug() << " - Done";
+ }
+}
+
+void TrayIcon::resizeEvent(QResizeEvent *event){
+ //qDebug() << "Resize Event:" << event->size().width() << event->size().height();
+ if(AID!=0){
+ LSession::handle()->XCB->ResizeWindow(AID, event->size());
+ QTimer::singleShot(500, this, SLOT(update()) ); //make sure to re-draw the window in a moment
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.h
new file mode 100644
index 00000000..5d072cc1
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/systemtray/TrayIcon.h
@@ -0,0 +1,55 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// Note: The basic idea behind this class that that it puts the app window
+// in the same spot as the tray icon (to directly pass mouse events and such),
+// while keeping the tray icon visual in sync with the app window
+//===========================================
+#ifndef _LUMINA_PANEL_PLUGIN_SYSTEM_TRAY_ICON_H
+#define _LUMINA_PANEL_PLUGIN_SYSTEM_TRAY_ICON_H
+
+//Qt includes
+#include <QWidget>
+#include <QTimer>
+#include <QPaintEvent>
+#include <QMoveEvent>
+#include <QResizeEvent>
+#include <QPainter>
+#include <QPixmap>
+#include <QImage>
+//#include <QWindow>
+// libLumina includes
+//#include <LuminaX11.h>
+
+class TrayIcon : public QWidget{
+ Q_OBJECT
+public:
+ TrayIcon(QWidget* parent = 0);
+ ~TrayIcon();
+
+ void cleanup(); //about to be removed after window was detroyed
+
+ WId appID(); //the ID for the attached application
+ void attachApp(WId id);
+ void setSizeSquare(int side);
+
+public slots:
+ void detachApp();
+ void updateIcon();
+
+private:
+ WId IID, AID; //icon ID and app ID
+ int badpaints;
+ uint dmgID;
+
+protected:
+ void paintEvent(QPaintEvent *event);
+ void resizeEvent(QResizeEvent *event);
+
+signals:
+ void BadIcon();
+};
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.cpp
new file mode 100644
index 00000000..0dd68bb0
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.cpp
@@ -0,0 +1,271 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LTaskButton.h"
+#include "LSession.h"
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+LTaskButton::LTaskButton(QWidget *parent, bool smallDisplay) : LTBWidget(parent){
+ actMenu = new QMenu(this);
+ winMenu = new QMenu(this);
+ UpdateMenus();
+ showText = !smallDisplay;
+ this->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ this->setAutoRaise(false); //make sure these always look like buttons
+ this->setContextMenuPolicy(Qt::CustomContextMenu);
+ this->setFocusPolicy(Qt::NoFocus);
+ this->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ winMenu->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(openActionMenu()) );
+ connect(this, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
+ connect(winMenu, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(openActionMenu()) );
+ connect(winMenu, SIGNAL(triggered(QAction*)), this, SLOT(winClicked(QAction*)) );
+ connect(winMenu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ connect(actMenu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+}
+
+LTaskButton::~LTaskButton(){
+
+}
+
+//===========
+// PUBLIC
+//===========
+QList<WId> LTaskButton::windows(){
+ QList<WId> list;
+ for(int i=0; i<WINLIST.length(); i++){
+ list << WINLIST[i].windowID();
+ }
+ return list;
+}
+
+QString LTaskButton::classname(){
+ return cname;
+}
+
+bool LTaskButton::isActive(){
+ return cstate == LXCB::ACTIVE;
+}
+
+void LTaskButton::addWindow(WId win){
+ WINLIST << LWinInfo(win);
+ UpdateButton();
+}
+
+void LTaskButton::rmWindow(WId win){
+ for(int i=0; i<WINLIST.length(); i++){
+ if(WINLIST[i].windowID() == win){
+ WINLIST.removeAt(i);
+ break;
+ }
+ }
+ UpdateButton();
+}
+
+//==========
+// PRIVATE
+//==========
+LWinInfo LTaskButton::currentWindow(){
+ if(WINLIST.length() == 1 || cWin.windowID()==0){
+ return WINLIST[0]; //only 1 window - this must be it
+ }else{
+ return cWin;
+ }
+}
+
+//=============
+// PUBLIC SLOTS
+//=============
+void LTaskButton::UpdateButton(){
+ if(winMenu->isVisible()){ return; } //skip this if the window menu is currently visible for now
+ bool statusOnly = (WINLIST.length() == LWINLIST.length());
+ LWINLIST = WINLIST;
+
+ winMenu->clear();
+ LXCB::WINDOWVISIBILITY showstate = LXCB::IGNORE;
+ for(int i=0; i<WINLIST.length(); i++){
+ if(WINLIST[i].windowID() == 0){
+ WINLIST.removeAt(i);
+ i--;
+ continue;
+ }
+ if(i==0 && !statusOnly){
+ //Update the button visuals from the first window
+ this->setIcon(WINLIST[i].icon(noicon));
+ cname = WINLIST[i].Class();
+ if(cname.isEmpty()){
+ //Special case (chrome/chromium does not register *any* information with X except window title)
+ cname = WINLIST[i].text();
+ if(cname.contains(" - ")){ cname = cname.section(" - ",-1); }
+ }
+ this->setToolTip(cname);
+ }
+ bool junk;
+ QAction *tmp = winMenu->addAction( WINLIST[i].icon(junk), WINLIST[i].text() );
+ tmp->setData(i); //save which number in the WINLIST this entry is for
+ LXCB::WINDOWVISIBILITY stat = WINLIST[i].status(true); //update the saved state for the window
+ if(stat<LXCB::ACTIVE && WINLIST[i].windowID() == LSession::handle()->activeWindow()){ stat = LXCB::ACTIVE; }
+ if(stat > showstate){ showstate = stat; } //higher priority
+ }
+ //Now setup the button appropriately
+ // - visibility
+ if(showstate == LXCB::IGNORE || WINLIST.length() < 1){ this->setVisible(false); }
+ else{ this->setVisible(true); }
+ // - functionality
+ if(WINLIST.length() == 1){
+ //single window
+ this->setPopupMode(QToolButton::DelayedPopup);
+ this->setMenu(actMenu);
+ if(showText){
+ QString txt = WINLIST[0].text();
+ if(txt.length()>30){ txt.truncate(27); txt.append("..."); }
+ else if(txt.length()<30){ txt = txt.leftJustified(30, ' '); }
+ this->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); this->setText(txt);
+ }else if(noicon){ this->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); this->setText( cname ); }
+ else{ this->setToolButtonStyle(Qt::ToolButtonIconOnly); this->setText(""); }
+ this->setToolTip(WINLIST[0].text());
+ }else if(WINLIST.length() > 1){
+ //multiple windows
+ this->setPopupMode(QToolButton::InstantPopup);
+ this->setMenu(winMenu);
+ this->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ if(noicon || showText){ "("+QString::number(WINLIST.length())+") "+cname; }
+ else{ this->setText("("+QString::number(WINLIST.length())+")"); }
+ }
+ this->setState(showstate); //Make sure this is after the button setup so that it properly sets the margins/etc
+ cstate = showstate; //save this for later
+}
+
+void LTaskButton::UpdateMenus(){
+ //Action menu should be auto-created for the state of the current window (cWin/cstate)
+ actMenu->clear();
+ if(cstate!=LXCB::ACTIVE){
+ actMenu->addAction( LXDG::findIcon("edit-select",""), tr("Activate Window"), this, SLOT(triggerWindow()) );
+ }
+ if(cstate!=LXCB::INVISIBLE){
+ actMenu->addAction( LXDG::findIcon("view-close",""), tr("Minimize Window"), this, SLOT(minimizeWindow()) );
+ if(LSession::handle()->XCB->WindowIsMaximized(cWin.windowID()) ){
+ actMenu->addAction( LXDG::findIcon("view-restore",""), tr("Restore Window"), this, SLOT(maximizeWindow()) );
+ }else{
+ actMenu->addAction( LXDG::findIcon("view-fullscreen",""), tr("Maximize Window"), this, SLOT(maximizeWindow()) );
+ }
+ }
+ actMenu->addAction( LXDG::findIcon("window-close",""), tr("Close Window"), this, SLOT(closeWindow()) );
+ if(WINLIST.length()>1 && !winMenu->isVisible()){
+ actMenu->addSeparator();
+ actMenu->addAction( LXDG::findIcon("layer-visible-on",""), tr("Show All Windows"), this, SLOT(showAllWindows()) );
+ actMenu->addAction( LXDG::findIcon("layer-visible-off",""), tr("Minimize All Windows"), this, SLOT(hideAllWindows()) );
+ actMenu->addAction( LXDG::findIcon("window-close",""), tr("Close All Windows"), this, SLOT(closeAllWindows()) );
+ }
+}
+
+//=============
+// PRIVATE SLOTS
+//=============
+void LTaskButton::buttonClicked(){
+ if(WINLIST.length() > 1){
+ winMenu->popup(QCursor::pos());
+ }else{
+ triggerWindow();
+ }
+}
+
+void LTaskButton::closeWindow(){
+ if(DEBUG){ qDebug() << "Close Window:" << this->text(); }
+ if(winMenu->isVisible()){ winMenu->hide(); }
+ LWinInfo win = currentWindow();
+ LSession::handle()->XCB->CloseWindow(win.windowID());
+ cWin = LWinInfo(); //clear the current
+}
+
+void LTaskButton::maximizeWindow(){
+ if(DEBUG){ qDebug() << "Maximize Window:" << this->text(); }
+ if(winMenu->isVisible()){ winMenu->hide(); }
+ LWinInfo win = currentWindow();
+ LSession::handle()->XCB->MaximizeWindow(win.windowID());
+ //LSession::handle()->adjustWindowGeom(win.windowID(), true); //run this for now until the WM works properly
+ cWin = LWinInfo(); //clear the current
+}
+
+void LTaskButton::minimizeWindow(){
+ if(DEBUG){ qDebug() << "Minimize Window:" << this->text(); }
+ if(winMenu->isVisible()){ winMenu->hide(); }
+ LWinInfo win = currentWindow();
+ LSession::handle()->XCB->MinimizeWindow(win.windowID());
+ cWin = LWinInfo(); //clear the current
+ QTimer::singleShot(100, this, SLOT(UpdateButton()) ); //make sure to update this button if losing active status
+}
+
+void LTaskButton::showAllWindows(){
+ for(int i=WINLIST.length()-1; i>=0; i--){
+ if(WINLIST[i].status()==LXCB::INVISIBLE){
+ LSession::handle()->XCB->RestoreWindow(WINLIST[i].windowID());
+ }
+ }
+}
+
+void LTaskButton::hideAllWindows(){
+ for(int i=WINLIST.length()-1; i>=0; i--){
+ LXCB::WINDOWVISIBILITY state = WINLIST[i].status();
+ if(state==LXCB::VISIBLE || state==LXCB::ACTIVE){
+ LSession::handle()->XCB->MinimizeWindow(WINLIST[i].windowID());
+ }
+ }
+}
+
+void LTaskButton::closeAllWindows(){
+ for(int i=WINLIST.length()-1; i>=0; i--){
+ LSession::handle()->XCB->CloseWindow(WINLIST[i].windowID());
+ }
+}
+
+void LTaskButton::triggerWindow(){
+ LWinInfo win = currentWindow();
+ //Check which state the window is currently in and flip it to the other
+ //LXCB::WINDOWSTATE state = cstate;
+ //if(DEBUG){ qDebug() << "Window State: " << state; }
+ if(cstate == LXCB::ACTIVE){
+ if(DEBUG){ qDebug() << "Minimize Window:" << this->text(); }
+ LSession::handle()->XCB->MinimizeWindow(win.windowID());
+ QTimer::singleShot(100, this, SLOT(UpdateButton()) ); //make sure to update this button if losing active status
+ }else{
+ if(DEBUG){ qDebug() << "Activate Window:" << this->text(); }
+ LSession::handle()->XCB->ActivateWindow(win.windowID());
+ }
+ cWin = LWinInfo(); //clear the current
+}
+
+void LTaskButton::winClicked(QAction* act){
+ //Get the window from the action
+ if(act->data().toInt() < WINLIST.length()){
+ cWin = WINLIST[act->data().toInt()];
+ cstate = cWin.status();
+ }else{ cWin = LWinInfo(); } //clear it
+ //Now trigger the window
+ triggerWindow();
+}
+
+void LTaskButton::openActionMenu(){
+ //Get the Window the mouse is currently over
+ QPoint curpos = QCursor::pos();
+ QAction *act = winMenu->actionAt(winMenu->mapFromGlobal(curpos));
+ //qDebug() << "Button Context Menu:" << curpos.x() << curpos.y() << winMenu->geometry().x() << winMenu->geometry().y() << winMenu->geometry().width() << winMenu->geometry().height();
+ cWin = WINLIST[0]; //default to the first window
+ if( act != 0 && winMenu->isVisible() ){
+ //Get the window from the action
+ //qDebug() << "Found Action:" << act->data().toInt();
+ if(act->data().toInt() < WINLIST.length()){
+ cWin = WINLIST[act->data().toInt()];
+ }
+ }
+ cstate = cWin.status();
+ //Now show the action menu
+ UpdateMenus();
+ actMenu->popup(QCursor::pos());
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.h
new file mode 100644
index 00000000..6b171c6a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskButton.h
@@ -0,0 +1,73 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_TASK_BUTTON_H
+#define _LUMINA_DESKTOP_TASK_BUTTON_H
+
+// Qt includes
+#include <QWidget>
+#include <QList>
+#include <QIcon>
+#include <QCursor>
+#include <QMenu>
+#include <QEvent>
+#include <QAction>
+
+// libLumina includes
+#include <LuminaXDG.h>
+#include <LuminaX11.h>
+
+// Local includes
+#include "../../LWinInfo.h"
+#include "../LTBWidget.h"
+
+class LTaskButton : public LTBWidget{
+ Q_OBJECT
+public:
+ LTaskButton(QWidget *parent=0, bool smallDisplay = true);
+ ~LTaskButton();
+
+ //Window Information
+ QList<WId> windows();
+ QString classname();
+ bool isActive();
+
+ //Window Management
+ void addWindow(WId win); //Add a window to this button
+ void rmWindow(WId win); //Remove a window from this button
+
+private:
+ QList<LWinInfo> WINLIST;
+ QList<LWinInfo> LWINLIST;
+ QMenu *actMenu; // action menu (custom context menu)
+ QMenu *winMenu; // window menu (if more than 1)
+ LWinInfo cWin;
+ QString cname; //class name for the entire button
+ bool noicon, showText;
+
+ LWinInfo currentWindow(); //For getting the currently-active window
+ LXCB::WINDOWVISIBILITY cstate; //current state of the button
+
+public slots:
+ void UpdateButton(); //re-sync the current window infomation
+ void UpdateMenus(); //re-create the menus (text + icons)
+
+private slots:
+ void buttonClicked();
+ void closeWindow(); //send the signal to close a window
+ void maximizeWindow(); //send the signal to maximize/restore a window
+ void minimizeWindow(); //send the signal to minimize a window (iconify)
+ void showAllWindows();
+ void hideAllWindows();
+ void closeAllWindows();
+ void triggerWindow(); //change b/w visible and invisible
+ void winClicked(QAction*);
+ void openActionMenu();
+
+signals:
+ void MenuClosed();
+};
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.cpp
new file mode 100644
index 00000000..79c5dd36
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.cpp
@@ -0,0 +1,141 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LTaskManagerPlugin.h"
+#include "../../LSession.h"
+
+LTaskManagerPlugin::LTaskManagerPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ timer = new QTimer(this);
+ timer->setSingleShot(true);
+ timer->setInterval(10); // 1/100 second
+ connect(timer, SIGNAL(timeout()), this, SLOT(UpdateButtons()) );
+ usegroups = true; //backwards-compatible default value
+ if(id.contains("-nogroups")){ usegroups = false; }
+ connect(LSession::handle(), SIGNAL(WindowListEvent()), this, SLOT(checkWindows()) );
+ connect(LSession::handle(), SIGNAL(WindowListEvent(WId)), this, SLOT(UpdateButton(WId)) );
+ this->layout()->setContentsMargins(0,0,0,0);
+ QTimer::singleShot(0,this, SLOT(UpdateButtons()) ); //perform an initial sync
+ //QTimer::singleShot(100,this, SLOT(OrientationChange()) ); //perform an initial sync
+}
+
+LTaskManagerPlugin::~LTaskManagerPlugin(){
+
+}
+
+//==============
+// PRIVATE SLOTS
+//==============
+void LTaskManagerPlugin::UpdateButtons(){
+ updating = QDateTime::currentDateTime(); //global time stamp
+ QDateTime ctime = updating; //current thread time stamp
+
+ //Get the current window list
+ QList<WId> winlist = LSession::handle()->XCB->WindowList();
+ // Ignore the windows which don't want to be listed
+ for (int i = 0; i < winlist.length(); i++) {
+ QList<LXCB::WINDOWSTATE> states = LSession::handle()->XCB->WM_Get_Window_States(winlist[i]);
+ for (int j = 0; j < states.length(); j++) {
+ if (states[j] == LXCB::S_SKIP_TASKBAR) {
+ // Skip taskbar window
+ winlist.removeAt(i);
+ i--;
+ break;
+ }
+ }
+ }
+ //Do not change the status of the previously active window if it just changed to a non-visible window
+ //WId activeWin = LSession::handle()->XCB->ActiveWindow();
+ //bool skipActive = !winlist.contains(activeWin);
+ //qDebug() << "Update Buttons:" << winlist;
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ //Now go through all the current buttons first
+ for(int i=0; i<BUTTONS.length(); i++){
+ //Get the windows managed in this button
+ QList<WId> WI = BUTTONS[i]->windows();
+ bool updated=false;
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ //Loop over all the windows for this button
+ for(int w=0; w<WI.length(); w++){
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ if( winlist.contains( WI[w] ) ){
+ //Still current window - update it later
+ winlist.removeAll(WI[w] ); //remove this window from the list since it is done
+ }else{
+ //Window was closed - remove it
+ if(WI.length()==1){
+ //Remove the entire button
+ //qDebug() << "Window Closed: Remove Button" ;
+ this->layout()->takeAt(i); //remove from the layout
+ BUTTONS.takeAt(i)->deleteLater();
+ i--;
+ updated = true; //prevent updating a removed button
+ break; //break out of the button->window loop
+ }else{
+ //qDebug() << "Window Closed: Remove from button:" << WI[w].windowID() << "Button:" << w;
+ BUTTONS[i]->rmWindow(WI[w]); // one of the multiple windows for the button
+ WI.removeAt(w); //remove this window from the list
+ w--;
+ }
+ updated=true; //button already changed
+ }
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ }
+ if(!updated){
+ //qDebug() << "Update Button:" << i;
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ //if(!skipActive || !BUTTONS[i]->isActive()){
+ QTimer::singleShot(1,BUTTONS[i], SLOT(UpdateButton()) ); //keep moving on
+ //}
+ }
+ }
+ //Now go through the remaining windows
+ for(int i=0; i<winlist.length(); i++){
+ //New windows, create buttons for each (add grouping later)
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ //Check for a button that this can just be added to
+ QString ctxt = LSession::handle()->XCB->WindowClass(winlist[i]);
+ bool found = false;
+ for(int b=0; b<BUTTONS.length(); b++){
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ if(BUTTONS[b]->classname()== ctxt && usegroups){
+ //This adds a window to an existing group
+ found = true;
+ //qDebug() << "Add Window to Button:" << b;
+ BUTTONS[b]->addWindow(winlist[i]);
+ break;
+ }
+ }
+ if(!found){
+ if(updating > ctime){ return; } //another thread kicked off already - stop this one
+ //No group, create a new button
+ //qDebug() << "New Button";
+ LTaskButton *but = new LTaskButton(this, usegroups);
+ but->addWindow( winlist[i] );
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ but->setIconSize(QSize(this->height(), this->height()));
+ }else{
+ but->setIconSize(QSize(this->width(), this->width()));
+ }
+ this->layout()->addWidget(but);
+ connect(but, SIGNAL(MenuClosed()), this, SIGNAL(MenuClosed()));
+ BUTTONS << but;
+ }
+ }
+}
+
+void LTaskManagerPlugin::UpdateButton(WId win){
+ for(int i=0; i<BUTTONS.length(); i++){
+ if(BUTTONS[i]->windows().contains(win)){
+ qDebug() << "Update Task Manager Button (single window ping)";
+ QTimer::singleShot(0,BUTTONS[i], SLOT(UpdateButton()) );
+ break;
+ }
+ }
+}
+
+void LTaskManagerPlugin::checkWindows(){
+ timer->start();
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.h
new file mode 100644
index 00000000..e6371f34
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/taskmanager/LTaskManagerPlugin.h
@@ -0,0 +1,71 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#ifndef _LUMINA_DESKTOP_TASK_MANAGER_PLUGIN_H
+#define _LUMINA_DESKTOP_TASK_MANAGER_PLUGIN_H
+
+// Qt includes
+#include <QWidget>
+#include <QList>
+#include <QString>
+#include <QDebug>
+#include <QTimer>
+#include <QEvent>
+#include <QDateTime>
+
+// libLumina includes
+#include <LuminaX11.h>
+
+// Local includes
+#include "LTaskButton.h"
+#include "LWinInfo.h"
+#include "../LPPlugin.h"
+
+class LTaskManagerPlugin : public LPPlugin{
+ Q_OBJECT
+public:
+ LTaskManagerPlugin(QWidget *parent=0, QString id="taskmanager", bool horizontal=true);
+ ~LTaskManagerPlugin();
+
+private:
+ QList<LTaskButton*> BUTTONS; //to keep track of the current buttons
+ QTimer *timer;
+ QDateTime updating; //quick flag for if it is currently working
+ bool usegroups;
+
+private slots:
+ void UpdateButtons();
+ void UpdateButton(WId win);
+ void checkWindows();
+
+public slots:
+ void LocaleChange(){
+ UpdateButtons();
+ }
+ void ThemeChange(){
+ UpdateButtons();
+ }
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){ //horizontal
+ this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ this->layout()->setAlignment(Qt::AlignLeft);
+ QSize sz(this->height(), this->height());
+ for(int i=0; i<BUTTONS.length(); i++){
+ BUTTONS[i]->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ BUTTONS[i]->setIconSize(sz);
+ }
+ }else{ //vertical
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+ this->layout()->setAlignment(Qt::AlignTop);
+ QSize sz(this->width(), this->width());
+ for(int i=0; i<BUTTONS.length(); i++){
+ BUTTONS[i]->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
+ BUTTONS[i]->setIconSize(sz);
+ }
+ }
+ }
+};
+#endif \ No newline at end of file
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.cpp
new file mode 100644
index 00000000..acb27135
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.cpp
@@ -0,0 +1,67 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "LUserButton.h"
+#include "../../LSession.h"
+
+LUserButtonPlugin::LUserButtonPlugin(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){
+ button = new QToolButton(this);
+ button->setAutoRaise(true);
+ button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ button->setPopupMode(QToolButton::DelayedPopup); //make sure it runs the update routine first
+ connect(button, SIGNAL(clicked()), this, SLOT(openMenu()));
+ this->layout()->setContentsMargins(0,0,0,0);
+ this->layout()->addWidget(button);
+ menu = new QMenu(this);
+ menu->setContentsMargins(1,1,1,1);
+ connect(menu, SIGNAL(aboutToHide()), this, SIGNAL(MenuClosed()));
+ usermenu = new UserWidget(this);
+ connect(usermenu, SIGNAL(CloseMenu()), this, SLOT(closeMenu()) );
+ mact = new QWidgetAction(this);
+ mact->setDefaultWidget(usermenu);
+ menu->addAction(mact);
+
+ button->setMenu(menu);
+ connect(menu, SIGNAL(aboutToHide()), this, SLOT(updateButtonVisuals()) );
+ //Setup the global shortcut handling for opening the start menu
+ connect(QApplication::instance(), SIGNAL(StartButtonActivated()), this, SLOT(shortcutActivated()) );
+ LSession::handle()->registerStartButton(this->type());
+
+ QTimer::singleShot(0,this, SLOT(OrientationChange())); //Update icons/sizes
+}
+
+LUserButtonPlugin::~LUserButtonPlugin(){
+
+}
+
+void LUserButtonPlugin::updateButtonVisuals(){
+ button->setToolTip(tr("Quickly launch applications or open files"));
+ button->setText( SYSTEM::user() );
+ if( QFile::exists(QDir::homePath()+"/.loginIcon.png") ){
+ button->setIcon( QIcon(QDir::homePath()+"/.loginIcon.png") );
+ }else{
+ button->setIcon( LXDG::findIcon("user-identity", ":/images/default-user.png") ); //force icon refresh
+ }
+}
+
+// ========================
+// PRIVATE FUNCTIONS
+// ========================
+void LUserButtonPlugin::openMenu(){
+ usermenu->UpdateMenu();
+ button->showMenu();
+}
+
+void LUserButtonPlugin::closeMenu(){
+ menu->hide();
+}
+
+void LUserButtonPlugin::shortcutActivated(){
+ if(LSession::handle()->registerStartButton(this->type())){
+ if(menu->isVisible()){ closeMenu(); }
+ else{ openMenu(); }
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.h
new file mode 100644
index 00000000..8d5e5040
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/LUserButton.h
@@ -0,0 +1,75 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin is the main button that allow the user to run
+// applications or logout of the desktop
+//===========================================
+#ifndef _LUMINA_DESKTOP_USER_MENU_PLUGIN_H
+#define _LUMINA_DESKTOP_USER_MENU_PLUGIN_H
+
+// Qt includes
+#include <QMenu>
+#include <QWidgetAction>
+#include <QToolButton>
+#include <QString>
+#include <QWidget>
+
+
+// Lumina-desktop includes
+//#include "../../Globals.h"
+#include "../LPPlugin.h" //main plugin widget
+
+// libLumina includes
+#include "LuminaXDG.h"
+
+#include "UserWidget.h"
+
+// PANEL PLUGIN BUTTON
+class LUserButtonPlugin : public LPPlugin{
+ Q_OBJECT
+
+public:
+ LUserButtonPlugin(QWidget *parent = 0, QString id = "userbutton", bool horizontal=true);
+ ~LUserButtonPlugin();
+
+private:
+ QMenu *menu;
+ QWidgetAction *mact;
+ UserWidget *usermenu;
+ QToolButton *button;
+
+private slots:
+ void openMenu();
+ void closeMenu();
+ void shortcutActivated();
+
+ void updateButtonVisuals();
+
+public slots:
+ void OrientationChange(){
+ if(this->layout()->direction()==QBoxLayout::LeftToRight){
+ this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
+ button->setIconSize( QSize(this->height(), this->height()) );
+ }else{
+ this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
+ button->setIconSize( QSize(this->width(), this->width()) );
+ }
+ this->layout()->update();
+ updateButtonVisuals();
+ }
+
+ void LocaleChange(){
+ updateButtonVisuals();
+ usermenu->UpdateAll();
+ }
+
+ void ThemeChange(){
+ updateButtonVisuals();
+ usermenu->UpdateAll();
+ }
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.cpp
new file mode 100644
index 00000000..8d7dab7a
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.cpp
@@ -0,0 +1,205 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "UserItemWidget.h"
+#include <LUtils.h>
+#include <LDesktopUtils.h>
+#include <QMenu>
+
+#define TEXTCUTOFF 165
+UserItemWidget::UserItemWidget(QWidget *parent, QString itemPath, QString type, bool goback) : QFrame(parent){
+ createWidget();
+ //Now fill it appropriately
+ bool inHome = type.endsWith("-home"); //internal code
+ if(inHome){ type = type.remove("-home"); }
+ if(itemPath.endsWith(".desktop") || type=="app"){
+ XDGDesktop item(itemPath);
+ if( item.isValid() ){
+ icon->setPixmap( LXDG::findIcon(item.icon, "preferences-system-windows-actions").pixmap(32,32) );
+ name->setText( this->fontMetrics().elidedText(item.name, Qt::ElideRight, TEXTCUTOFF) );
+ setupActions(&item);
+ }else{
+ gooditem = false;
+ return;
+ }
+ }else if(type=="dir"){
+ actButton->setVisible(false);
+ if(itemPath.endsWith("/")){ itemPath.chop(1); }
+ if(goback){
+ icon->setPixmap( LXDG::findIcon("go-previous","").pixmap(32,32) );
+ name->setText( tr("Go Back") );
+ }else{
+ icon->setPixmap( LXDG::findIcon("folder","").pixmap(32,32) );
+ name->setText( this->fontMetrics().elidedText(itemPath.section("/",-1), Qt::ElideRight, TEXTCUTOFF) );
+ }
+ }else{
+ actButton->setVisible(false);
+ if(itemPath.endsWith("/")){ itemPath.chop(1); }
+ if(QFileInfo(itemPath).isDir()){
+ type = "dir";
+ icon->setPixmap( LXDG::findIcon("folder","").pixmap(32,32) );
+ }else if(LUtils::imageExtensions().contains(itemPath.section("/",-1).section(".",-1).toLower()) ){
+ icon->setPixmap( QIcon(itemPath).pixmap(32,32) );
+ }else{
+ icon->setPixmap( LXDG::findMimeIcon(itemPath.section("/",-1)).pixmap(32,32) );
+ }
+ name->setText( this->fontMetrics().elidedText(itemPath.section("/",-1), Qt::ElideRight, TEXTCUTOFF) );
+ }
+ icon->setWhatsThis(itemPath);
+ if(!goback){ this->setWhatsThis(name->text()); }
+ isDirectory = (type=="dir"); //save this for later
+ if(LDesktopUtils::isFavorite(itemPath)){
+ linkPath = itemPath;
+ isShortcut=true;
+ }else if( inHome ){//|| itemPath.section("/",0,-2)==QDir::homePath()+"/Desktop" ){
+ isShortcut = true;
+ }else{
+ isShortcut = false;
+ }
+ //Now setup the button appropriately
+ setupButton(goback);
+}
+
+UserItemWidget::UserItemWidget(QWidget *parent, XDGDesktop *item) : QFrame(parent){
+ if(item==0){ return; }
+ createWidget();
+ isDirectory = false;
+ if(LDesktopUtils::isFavorite(item->filePath)){
+ linkPath = item->filePath;
+ isShortcut=true;
+ }else if( item->filePath.section("/",0,-2)==QDir::homePath()+"/Desktop" ){
+ isShortcut = true;
+ }else{
+ isShortcut = false;
+ }
+ //Now fill it appropriately
+ icon->setPixmap( LXDG::findIcon(item->icon,"preferences-system-windows-actions").pixmap(32,32) );
+ name->setText( this->fontMetrics().elidedText(item->name, Qt::ElideRight, TEXTCUTOFF) );
+ this->setWhatsThis(name->text());
+ icon->setWhatsThis(item->filePath);
+ //Now setup the buttons appropriately
+ setupButton();
+ setupActions(item);
+}
+
+UserItemWidget::~UserItemWidget(){
+ delete button;
+ delete icon;
+ delete name;
+}
+
+
+void UserItemWidget::createWidget(){
+ //Initialize the widgets
+ gooditem = true;
+ menuopen = false;
+ menureset = new QTimer(this);
+ menureset->setSingleShot(true);
+ menureset->setInterval(1000); //1 second
+ this->setContentsMargins(0,0,0,0);
+ button = new QToolButton(this);
+ button->setIconSize( QSize(14,14) );
+ button->setAutoRaise(true);
+ actButton = new QToolButton(this);
+ actButton->setPopupMode(QToolButton::InstantPopup);
+ actButton->setFixedSize( QSize(17,34) );
+ actButton->setArrowType(Qt::DownArrow);
+ icon = new QLabel(this);
+ icon->setFixedSize( QSize(34,34) );
+ name = new QLabel(this);
+ //Add them to the layout
+ this->setLayout(new QHBoxLayout());
+ this->layout()->setContentsMargins(1,1,1,1);
+ this->layout()->addWidget(icon);
+ this->layout()->addWidget(actButton);
+ this->layout()->addWidget(name);
+ this->layout()->addWidget(button);
+ //Set a custom object name so this can be tied into the Lumina Theme stylesheets
+ this->setObjectName("LuminaUserItemWidget");
+ //Install the stylesheet
+ //this->setStyleSheet("UserItemWidget{ background: transparent; border-radius: 5px;} UserItemWidget::hover{ background: rgba(255,255,255,200); border-radius: 5px; }");
+}
+
+void UserItemWidget::setupButton(bool disable){
+ //if(isDirectory){ qDebug() << "Directory Entry:" << isShortcut << linkPath << icon->whatsThis(); }
+
+ if(disable){
+ button->setVisible(false);
+ }else if(isShortcut && !linkPath.isEmpty()){ //Favorite Item - can always remove this
+ button->setWhatsThis("remove");
+ button->setIcon( LXDG::findIcon("list-remove","") );
+ button->setToolTip(tr("Remove Shortcut"));
+ connect(button, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
+ }else if(isShortcut){ //Physical File/Dir - can remove
+ button->setWhatsThis("remove");
+ button->setIcon( LXDG::findIcon("user-trash","") );
+ button->setToolTip(tr("Delete File"));
+ connect(button, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
+ }else if(!isShortcut){// if( !QFile::exists( QDir::homePath()+"/Desktop/"+icon->whatsThis().section("/",-1) ) && !LUtils::isFavorite(icon->whatsThis() ) ){
+ //This file does not have a shortcut yet -- allow the user to add it
+ button->setWhatsThis("add");
+ button->setIcon( LXDG::findIcon("bookmark-toolbar","") );
+ button->setToolTip(tr("Create Shortcut"));
+ connect(button, SIGNAL(clicked()), this, SLOT(buttonClicked()) );
+ }else{
+ //This already has a desktop shortcut -- no special actions
+ button->setVisible(false);
+ }
+ if(isShortcut){
+ name->setToolTip(icon->whatsThis()); //also allow the user to see the full shortcut path
+ }
+}
+
+void UserItemWidget::setupActions(XDGDesktop *app){
+ if(app==0 || app->actions.isEmpty()){ actButton->setVisible(false); return; }
+ //Actions Available - go ahead and list them all
+ actButton->setMenu( new QMenu(this) );
+ for(int i=0; i<app->actions.length(); i++){
+ QAction *act = new QAction(LXDG::findIcon(app->actions[i].icon, app->icon), app->actions[i].name, this);
+ act->setToolTip(app->actions[i].ID);
+ act->setWhatsThis(app->actions[i].ID);
+ actButton->menu()->addAction(act);
+ }
+ connect(actButton->menu(), SIGNAL(triggered(QAction*)), this, SLOT(actionClicked(QAction*)) );
+ connect(actButton->menu(), SIGNAL(aboutToShow()), this, SLOT(actionMenuOpen()) );
+ connect(actButton->menu(), SIGNAL(aboutToHide()), this, SLOT(actionMenuClosed()) );
+ connect(menureset, SIGNAL(timeout()), this, SLOT(resetmenuflag()) );
+}
+
+void UserItemWidget::buttonClicked(){
+ button->setVisible(false);
+ if(button->whatsThis()=="add"){
+ LDesktopUtils::addFavorite(icon->whatsThis());
+ //QFile::link(icon->whatsThis(), QDir::homePath()+"/.lumina/favorites/"+icon->whatsThis().section("/",-1) );
+ emit NewShortcut();
+ }else if(button->whatsThis()=="remove"){
+ if(linkPath.isEmpty()){
+ //This is a desktop file
+ if(isDirectory){
+ QProcess::startDetached("rm -r \""+icon->whatsThis()+"\"");
+ }else{
+ QFile::remove(icon->whatsThis());
+ }
+ //Don't emit the RemovedShortcut signal here - the automatic ~/Desktop watcher will see the change when finished
+ }else{
+ LDesktopUtils::removeFavorite(icon->whatsThis()); //This is a favorite
+ emit RemovedShortcut();
+ }
+ }
+}
+
+void UserItemWidget::ItemClicked(){
+ if(!linkPath.isEmpty()){ emit RunItem(linkPath); }
+ else{ emit RunItem(icon->whatsThis()); }
+}
+
+void UserItemWidget::actionClicked(QAction *act){
+ actButton->menu()->hide();
+ QString cmd = "lumina-open -action \""+act->whatsThis()+"\" \"%1\"";
+ if(!linkPath.isEmpty()){ cmd = cmd.arg(linkPath); }
+ else{ cmd = cmd.arg(icon->whatsThis()); }
+ emit RunItem(cmd);
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.h
new file mode 100644
index 00000000..0b212f10
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserItemWidget.h
@@ -0,0 +1,72 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This item widget manages a single file/directory
+//===========================================
+#ifndef _LUMINA_PANEL_USER_ITEM_WIDGET_H
+#define _LUMINA_PANEL_USER_ITEM_WIDGET_H
+
+#include <QFrame>
+#include <QLabel>
+#include <QToolButton>
+#include <QString>
+#include <QHBoxLayout>
+#include <QSize>
+#include <QDir>
+#include <QFile>
+#include <QMouseEvent>
+#include <QAction>
+#include <QMenu>
+#include <QTimer>
+
+#include <LuminaXDG.h>
+
+class UserItemWidget : public QFrame{
+ Q_OBJECT
+public:
+ UserItemWidget(QWidget *parent=0, QString itemPath="", QString type="unknown", bool goback=false);
+ UserItemWidget(QWidget *parent=0, XDGDesktop *item= 0);
+ ~UserItemWidget();
+
+ bool gooditem;
+private:
+ QToolButton *button, *actButton;
+ QLabel *icon, *name;
+ bool isDirectory, isShortcut, menuopen;
+ QString linkPath;
+ QTimer *menureset;
+
+ void createWidget();
+ void setupButton(bool disable = false);
+ void setupActions(XDGDesktop*);
+
+private slots:
+ void buttonClicked();
+ void ItemClicked();
+ void actionClicked(QAction*);
+ //Functions to fix the submenu open/close issues
+ void actionMenuOpen(){
+ if(menureset->isActive()){ menureset->stop(); }
+ menuopen = true;
+ }
+ void resetmenuflag(){ menuopen = false; } //tied to the "menureset" timer
+ void actionMenuClosed(){ menureset->start(); }
+
+
+protected:
+ void mouseReleaseEvent(QMouseEvent *event){
+ if(menuopen){ resetmenuflag(); } //skip this event if a submenu was open
+ else if(event->button() != Qt::NoButton){ ItemClicked(); }
+ }
+
+signals:
+ void NewShortcut();
+ void RemovedShortcut();
+ void RunItem(QString cmd);
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.cpp
new file mode 100644
index 00000000..a0ba8996
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.cpp
@@ -0,0 +1,393 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014-2015, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+#include "UserWidget.h"
+#include "ui_UserWidget.h"
+#include "../../LSession.h"
+#include "../../AppMenu.h"
+
+UserWidget::UserWidget(QWidget* parent) : QTabWidget(parent), ui(new Ui::UserWidget){
+ ui->setupUi(this);
+ updatingfavs = false;
+ if(parent!=0){ parent->setMouseTracking(true); }
+ this->setMouseTracking(true);
+ sysapps = LSession::handle()->applicationMenu()->currentAppHash(); //get the raw info
+
+ //Connect the signals/slots
+ connect(ui->tool_desktopsettings, SIGNAL(clicked()), this, SLOT(openDeskSettings()) );
+ connect(ui->tool_config_screensaver, SIGNAL(clicked()), this, SLOT(openScreenSaverConfig()) );
+ connect(ui->tool_config_screensettings, SIGNAL(clicked()), this, SLOT(openScreenConfig()) );
+ connect(ui->tool_fav_apps, SIGNAL(clicked()), this, SLOT(FavChanged()) );
+ connect(ui->tool_fav_files, SIGNAL(clicked()), this, SLOT(FavChanged()) );
+ connect(ui->tool_fav_dirs, SIGNAL(clicked()), this, SLOT(FavChanged()) );
+ connect(ui->combo_app_cats, SIGNAL(currentIndexChanged(int)), this, SLOT(updateApps()) );
+ connect(ui->tool_home_gohome, SIGNAL(clicked()), this, SLOT(slotGoHome()) );
+ connect(ui->tool_home_browse, SIGNAL(clicked()), this, SLOT(slotOpenDir()) );
+ connect(ui->tool_home_search, SIGNAL(clicked()), this, SLOT(slotOpenSearch()) );
+ connect(ui->tool_config_about, SIGNAL(clicked()), this, SLOT(openLuminaInfo()) );
+
+ //Setup the special buttons
+ connect(ui->tool_app_store, SIGNAL(clicked()), this, SLOT(openStore()) );
+ connect(ui->tool_controlpanel, SIGNAL(clicked()), this, SLOT(openControlPanel()) );
+ //connect(ui->tool_qtconfig, SIGNAL(clicked()), this, SLOT(openQtConfig()) );
+
+ lastUpdate = QDateTime(); //make sure it refreshes
+
+ connect(LSession::handle()->applicationMenu(), SIGNAL(AppMenuUpdated()), this, SLOT(UpdateMenu()) );
+ connect(QApplication::instance(), SIGNAL(DesktopFilesChanged()), this, SLOT(updateFavItems()) );
+ QTimer::singleShot(10,this, SLOT(UpdateAll())); //make sure to load this once after initialization
+}
+
+UserWidget::~UserWidget(){
+}
+
+//===========
+// PRIVATE
+//===========
+void UserWidget::ClearScrollArea(QScrollArea *area){
+ QWidget *wgt = area->takeWidget();
+ wgt->deleteLater(); //delete the widget and all children
+ area->setWidget( new QWidget() ); //create a new widget in the scroll area
+ area->widget()->setContentsMargins(0,0,0,0);
+ QVBoxLayout *layout = new QVBoxLayout;
+ layout->setSpacing(2);
+ layout->setContentsMargins(3,1,3,1);
+ layout->setDirection(QBoxLayout::TopToBottom);
+ layout->setAlignment(Qt::AlignTop);
+ area->widget()->setLayout(layout);
+}
+
+void UserWidget::SortScrollArea(QScrollArea *area){
+ //qDebug() << "Sorting Scroll Area:";
+ //Sort all the items in the scroll area alphabetically
+ QLayout *lay = area->widget()->layout();
+ QStringList items;
+ for(int i=0; i<lay->count(); i++){
+ items << lay->itemAt(i)->widget()->whatsThis().toLower();
+ }
+
+ items.sort();
+ //qDebug() << " - Sorted Items:" << items;
+ for(int i=0; i<items.length(); i++){
+ if(items[i].isEmpty()){ continue; }
+ //QLayouts are weird in that they can only add items to the end - need to re-insert almost every item
+ for(int j=0; j<lay->count(); j++){
+ //Find this item
+ if(lay->itemAt(j)->widget()->whatsThis().toLower()==items[i]){
+ //Found it - now move it if necessary
+ //qDebug() << "Found Item:" << items[i] << i << j;
+ lay->addItem( lay->takeAt(j) );
+ break;
+ }
+ }
+ }
+
+}
+
+QIcon UserWidget::rotateIcon(QIcon ico){
+ //Rotate the given icon to appear vertical in the tab widget
+ QPixmap pix = ico.pixmap(32,32);
+ QTransform tran;
+ tran.rotate(+90); //For tabs on the left/West
+ pix = pix.transformed(tran);
+ ico = QIcon(pix);
+ return ico;
+}
+
+//============
+// PRIVATE SLOTS
+//============
+void UserWidget::UpdateAll(){
+ ui->retranslateUi(this);
+ //Setup the Icons
+ // - favorites tab
+ this->setTabIcon(0, rotateIcon(LXDG::findIcon("folder-favorites","")) );
+ this->setTabText(0,"");
+ // - apps tab
+ this->setTabIcon(1, rotateIcon(LXDG::findIcon("system-run","")) );
+ this->setTabText(1,"");
+ // - home tab
+ this->setTabIcon(2, rotateIcon(LXDG::findIcon("user-home","")) );
+ this->setTabText(2,"");
+ // - config tab
+ this->setTabIcon(3, rotateIcon(LXDG::findIcon("preferences-system","")) );
+ this->setTabText(3,"");
+ ui->tool_fav_apps->setIcon( LXDG::findIcon("system-run","") );
+ ui->tool_fav_dirs->setIcon( LXDG::findIcon("folder","") );
+ ui->tool_fav_files->setIcon( LXDG::findIcon("document-multiple","") );
+ ui->tool_desktopsettings->setIcon( LXDG::findIcon("preferences-desktop","") );
+ ui->tool_config_screensaver->setIcon( LXDG::findIcon("preferences-desktop-screensaver","") );
+ ui->tool_config_screensettings->setIcon( LXDG::findIcon("preferences-other","") );
+ ui->tool_home_gohome->setIcon( LXDG::findIcon("go-home","") );
+ ui->tool_home_browse->setIcon( LXDG::findIcon("document-open","") );
+ ui->tool_home_search->setIcon( LXDG::findIcon("system-search","") );
+ ui->tool_config_about->setIcon( LXDG::findIcon("lumina","") );
+
+ //Setup the special buttons
+ QString APPSTORE = LOS::AppStoreShortcut();
+ if(QFile::exists(APPSTORE) && !APPSTORE.isEmpty()){
+ //Now load the info
+ XDGDesktop store(APPSTORE);
+ bool ok = store.isValid();
+ if(ok){
+ ui->tool_app_store->setIcon( LXDG::findIcon(store.icon, "") );
+ ui->tool_app_store->setText( store.name );
+ }
+ ui->tool_app_store->setVisible(ok); //availability
+ }else{
+ ui->tool_app_store->setVisible(false); //not available
+ }
+ QString CONTROLPANEL = LOS::ControlPanelShortcut();
+ if(QFile::exists(CONTROLPANEL) && !CONTROLPANEL.isEmpty()){
+ //Now load the info
+ XDGDesktop cpan(CONTROLPANEL);
+ bool ok = cpan.isValid();
+ if(ok){
+ ui->tool_controlpanel->setIcon( LXDG::findIcon(cpan.icon, "") );
+ }
+ ui->tool_controlpanel->setVisible(ok); //availability
+ }else{
+ ui->tool_controlpanel->setVisible(false); //not available
+ }
+ /*QString QTCONFIG = LOS::QtConfigShortcut();
+ if(QFile::exists(QTCONFIG) && !QTCONFIG.isEmpty()){
+ ui->tool_qtconfig->setVisible(true);
+ ui->tool_qtconfig->setIcon( LXDG::findIcon("preferences-desktop-theme","") );
+ }else{
+ ui->tool_qtconfig->setVisible(false);
+ }*/
+ //Now update the menus
+ UpdateMenu();
+}
+
+void UserWidget::UpdateMenu(bool forceall){
+ this->setCurrentWidget(ui->tab_fav);
+ ui->tool_fav_apps->setChecked(true);
+ ui->tool_fav_dirs->setChecked(false);
+ ui->tool_fav_files->setChecked(false);
+ cfav = 0; //favorite apps
+ FavChanged();
+ QString cdir = ui->label_home_dir->whatsThis();
+ if(cdir.isEmpty() || !QFile::exists(cdir)){
+ //Directory deleted or nothing loaded yet
+ ui->label_home_dir->setWhatsThis(QDir::homePath());
+ QTimer::singleShot(0,this, SLOT(updateHome()) );
+ }else if( lastUpdate < QFileInfo(cdir).lastModified() || forceall){
+ //Directory contents changed - reload it
+ QTimer::singleShot(0,this, SLOT(updateHome()) );
+ }
+ if(lastUpdate < LSession::handle()->applicationMenu()->lastHashUpdate || lastUpdate.isNull() || forceall){
+ updateAppCategories();
+ QTimer::singleShot(0,this, SLOT(updateApps()) );
+ }
+ lastUpdate = QDateTime::currentDateTime();
+}
+
+void UserWidget::LaunchItem(QString path, bool fix){
+ if(!path.isEmpty()){
+ qDebug() << "Launch Application:" << path;
+ if( fix && !path.startsWith("lumina-open") ){ LSession::LaunchApplication("lumina-open \""+path+"\""); }
+ else{ LSession::LaunchApplication(path); }
+ emit CloseMenu(); //so the menu container will close
+ }
+}
+
+void UserWidget::FavChanged(){
+ //uncheck the current item for a moment
+ int oldfav = cfav;
+ if(cfav==0){ ui->tool_fav_apps->setChecked(false); }
+ else if(cfav==1){ ui->tool_fav_dirs->setChecked(false); }
+ if(cfav==2){ ui->tool_fav_files->setChecked(false); }
+ //Now check what other item is now the only one checked
+ if(ui->tool_fav_apps->isChecked() && !ui->tool_fav_dirs->isChecked() && !ui->tool_fav_files->isChecked() ){
+ cfav = 0;
+ }else if(!ui->tool_fav_apps->isChecked() && ui->tool_fav_dirs->isChecked() && !ui->tool_fav_files->isChecked() ){
+ cfav = 1;
+ }else if(!ui->tool_fav_apps->isChecked() && !ui->tool_fav_dirs->isChecked() && ui->tool_fav_files->isChecked() ){
+ cfav = 2;
+ }else{
+ //Re-check the old item (something invalid)
+ ui->tool_fav_apps->setChecked(cfav==0);
+ ui->tool_fav_dirs->setChecked(cfav==1);
+ ui->tool_fav_files->setChecked(cfav==2);
+ }
+ updateFavItems(oldfav!=cfav);
+}
+
+void UserWidget::updateFavItems(bool newfilter){
+ if(updatingfavs){ return; }
+ updatingfavs = true;
+ //qDebug() << "Updating User Favorite Items";
+ QStringList newfavs = LDesktopUtils::listFavorites();
+ //qDebug() << "Favorites:" << newfavs;
+ if(lastHomeUpdate.isNull() || (QFileInfo(QDir::homePath()+"/Desktop").lastModified() > lastHomeUpdate) || newfavs!=favs ){
+ favs = newfavs;
+
+ homefiles = LSession::handle()->DesktopFiles();
+ lastHomeUpdate = QDateTime::currentDateTime();
+ }else if(!newfilter){ updatingfavs = false; return; } //nothing new to change - stop now
+ //qDebug() << " - Passed Smoke Test...";
+ QStringList favitems;
+ //Remember for format for favorites: <name>::::[app/dir/<mimetype>]::::<full path>
+ if(ui->tool_fav_apps->isChecked()){
+ favitems = favs.filter("::::app::::");
+ for(int i=0; i<homefiles.length(); i++){
+ if(homefiles[i].fileName().endsWith(".desktop") && favitems.filter(homefiles[i].canonicalFilePath()).isEmpty() ){
+ favitems << homefiles[i].fileName()+"::::app-home::::"+homefiles[i].absoluteFilePath();
+ }
+ }
+ }else if(ui->tool_fav_dirs->isChecked()){
+ favitems = favs.filter("::::dir::::");
+ for(int i=0; i<homefiles.length(); i++){
+ if(homefiles[i].isDir() && favitems.filter(homefiles[i].canonicalFilePath()).isEmpty() ){
+ favitems << homefiles[i].fileName()+"::::dir-home::::"+homefiles[i].absoluteFilePath();
+ }
+ }
+ }else{
+ //Files
+ for(int i=0; i<favs.length(); i++){
+ QString type = favs[i].section("::::",1,1);
+ if(type != "app" && type !="dir"){
+ favitems << favs[i];
+ }
+ }
+ for(int i=0; i<homefiles.length(); i++){
+ if(!homefiles[i].isDir() && !homefiles[i].fileName().endsWith(".desktop") && favitems.filter(homefiles[i].canonicalFilePath()).isEmpty() ){
+ favitems << homefiles[i].fileName()+"::::"+LXDG::findAppMimeForFile(homefiles[i].fileName())+"-home::::"+homefiles[i].absoluteFilePath();
+ }
+ }
+ }
+ ClearScrollArea(ui->scroll_fav);
+ //qDebug() << " - Sorting Items";
+ favitems.sort(); //sort them alphabetically
+ //qDebug() << " - Creating Items:" << favitems;
+ for(int i=0; i<favitems.length(); i++){
+ if( !QFile::exists(favitems[i].section("::::",2,50)) ){ continue; } //file does not exist - just skip it
+ UserItemWidget *it = new UserItemWidget(ui->scroll_fav->widget(), favitems[i].section("::::",2,50), favitems[i].section("::::",1,1) );
+ if(!it->gooditem){ it->deleteLater(); continue; }
+ ui->scroll_fav->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(updateFavItems()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(updateFavItems()) );
+ QApplication::processEvents(); //keep the UI snappy - might be a number of these
+ }
+ SortScrollArea(ui->scroll_fav);
+ updatingfavs = false;
+ //qDebug() << " - Done";
+}
+
+//Apps Tab
+void UserWidget::updateAppCategories(){
+ ui->combo_app_cats->clear();
+ QStringList cats = sysapps->keys();
+ cats.sort();
+ for(int i=0; i<cats.length(); i++){
+ QString name, icon;
+ if(cats[i] == "All"){ name = tr("All"); icon = "application-x-executable"; }
+ else if(cats[i] == "Multimedia"){ name = tr("Multimedia"); icon = "applications-multimedia"; }
+ else if(cats[i] == "Development"){ name = tr("Development"); icon = "applications-development"; }
+ else if(cats[i] == "Education"){ name = tr("Education"); icon = "applications-education"; }
+ else if(cats[i] == "Game"){ name = tr("Games"); icon = "applications-games"; }
+ else if(cats[i] == "Graphics"){ name = tr("Graphics"); icon = "applications-graphics"; }
+ else if(cats[i] == "Network"){ name = tr("Network"); icon = "applications-internet"; }
+ else if(cats[i] == "Office"){ name = tr("Office"); icon = "applications-office"; }
+ else if(cats[i] == "Science"){ name = tr("Science"); icon = "applications-science"; }
+ else if(cats[i] == "Settings"){ name = tr("Settings"); icon = "preferences-system"; }
+ else if(cats[i] == "System"){ name = tr("System"); icon = "applications-system"; }
+ else if(cats[i] == "Utility"){ name = tr("Utilities"); icon = "applications-utilities"; }
+ else if(cats[i] == "Wine"){ name = tr("Wine"); icon = "wine"; }
+ else{ name = tr("Unsorted"); icon = "applications-other"; }
+ ui->combo_app_cats->addItem( LXDG::findIcon(icon,""), name, cats[i] );
+ }
+}
+
+void UserWidget::updateApps(){
+ if(ui->combo_app_cats->currentIndex() < 0){ return; } //no cat
+ QString cat = ui->combo_app_cats->itemData( ui->combo_app_cats->currentIndex() ).toString();
+ QList<XDGDesktop*> items = sysapps->value(cat);
+ ClearScrollArea(ui->scroll_apps);
+ for(int i=0; i<items.length(); i++){
+ UserItemWidget *it = new UserItemWidget(ui->scroll_apps->widget(), items[i]);
+ ui->scroll_apps->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(LaunchItem(QString)) );
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(updateFavItems()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(updateFavItems()) );
+ QApplication::processEvents(); //keep the UI snappy - might be a number of these
+ }
+}
+
+//Home Tab
+void UserWidget::updateHome(){
+ qDebug() << "Update Home";
+ ClearScrollArea(ui->scroll_home);
+ qDebug() << " - dir:" << ui->label_home_dir->whatsThis();
+ QDir homedir(ui->label_home_dir->whatsThis());
+ QStringList items;
+ if(QDir::homePath() == homedir.absolutePath()){
+ ui->label_home_dir->setText(tr("Home"));
+ ui->tool_home_gohome->setVisible(false);
+ }else{
+ qDebug() << " - Show the back button";
+ ui->tool_home_gohome->setVisible(true);
+ ui->label_home_dir->setText( this->fontMetrics().elidedText(homedir.dirName(), Qt::ElideRight, ui->label_home_dir->width()));
+ //Now make sure to put a "go back" button at the top of the list
+ QString dir = ui->label_home_dir->whatsThis();
+ if(dir.endsWith("/")){ dir.chop(1); }
+ dir.chop( dir.section("/",-1).length() );
+ items << dir;
+ }
+ qDebug() << " - Load items";
+ ui->label_home_dir->setToolTip(ui->label_home_dir->whatsThis());
+ items << homedir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsFirst);
+ QString type = "";
+ if(homedir.absolutePath() == QDir::homePath()+"/Desktop"){ type.append("-home"); }//internal code
+ for(int i=0; i<items.length(); i++){
+ qDebug() << " - New item:" << homedir.absoluteFilePath(items[i]);
+ UserItemWidget *it;
+ if(items[i].startsWith("/")){ it = new UserItemWidget(ui->scroll_home->widget(), items[i], "dir", true); } //go-back button
+ else{ it = new UserItemWidget(ui->scroll_home->widget(), homedir.absoluteFilePath(items[i]), type, false); }
+ qDebug() << " - Add to layout";
+ ui->scroll_home->widget()->layout()->addWidget(it);
+ connect(it, SIGNAL(RunItem(QString)), this, SLOT(slotGoToDir(QString)) );
+ connect(it, SIGNAL(NewShortcut()), this, SLOT(updateFavItems()) );
+ connect(it, SIGNAL(RemovedShortcut()), this, SLOT(updateFavItems()) );
+ qDebug() << " - process events";
+ QApplication::processEvents(); //keep the UI snappy - may be a lot of these to load
+ }
+ qDebug() << " - Done";
+}
+
+void UserWidget::slotGoToDir(QString dir){
+ if(!QFileInfo(dir).isDir()){
+ LaunchItem(dir);
+ }else{
+ ui->label_home_dir->setWhatsThis(dir);
+ updateHome();
+ }
+}
+
+void UserWidget::slotGoHome(){
+ slotGoToDir(QDir::homePath());
+}
+
+void UserWidget::slotOpenDir(){
+ LaunchItem(ui->label_home_dir->whatsThis());
+}
+
+void UserWidget::slotOpenSearch(){
+ LaunchItem("lumina-search -dir \""+ui->label_home_dir->whatsThis()+"\"", false); //use command as-is
+}
+
+void UserWidget::mouseMoveEvent( QMouseEvent *event){
+ QTabBar *wid = tabBar();
+ if(wid==0){ return; } //invalid widget found
+ QPoint relpos = wid->mapFromGlobal( this->mapToGlobal(event->pos()) );
+ //qDebug() << "Mouse Move Event: " << event->pos().x() << event->pos().y() << relpos.x() << relpos.y() << wid->width() << wid->height();
+ if(wid && wid->tabAt(relpos) != -1){
+ qDebug() << " - Mouse over tab";
+ this->setCurrentIndex( wid->tabAt(relpos) );
+ }
+}
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.h b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.h
new file mode 100644
index 00000000..8b03c489
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.h
@@ -0,0 +1,101 @@
+//===========================================
+// Lumina-DE source code
+// Copyright (c) 2014, Ken Moore
+// Available under the 3-clause BSD license
+// See the LICENSE file for full details
+//===========================================
+// This panel plugin allows the user to quickly access user favorites and applications
+//===========================================
+#ifndef _LUMINA_PANEL_USER_BUTTON_WIDGET_H
+#define _LUMINA_PANEL_USER_BUTTON_WIDGET_H
+
+#include <QWidget>
+#include <QString>
+#include <QList>
+#include <QHash>
+#include <QVBoxLayout>
+#include <QScrollArea>
+#include <QDateTime>
+#include <QTransform>
+#include <QMouseEvent>
+#include <QTabWidget>
+
+#include <LuminaXDG.h>
+#include <LuminaOS.h>
+#include "UserItemWidget.h"
+
+#define SSAVER QString("xscreensaver-demo")
+
+namespace Ui{
+ class UserWidget;
+};
+
+class UserWidget : public QTabWidget{
+ Q_OBJECT
+public:
+ UserWidget(QWidget *parent=0);
+ ~UserWidget();
+
+public slots:
+ void UpdateAll(); //for re-translation/icon changes
+ void UpdateMenu(bool forceall = false); //for item changes
+
+private:
+ Ui::UserWidget *ui;
+ QHash<QString, QList<XDGDesktop*> > *sysapps;
+ QDateTime lastUpdate, lastHomeUpdate;
+ QStringList favs;
+ QFileInfoList homefiles;
+ int cfav; //current favorite category
+ void ClearScrollArea(QScrollArea *area);
+ void SortScrollArea(QScrollArea *area);
+ QIcon rotateIcon(QIcon);
+ bool updatingfavs;
+
+private slots:
+ void LaunchItem(QString path, bool fix = true);
+
+ //Favorites Tab
+ void FavChanged(); //for ensuring radio-button-like behaviour
+ void updateFavItems(bool newfilter = true); //if false, will only update if filesystem changes
+
+ //Apps Tab
+ void updateAppCategories();
+ void updateApps();
+
+ //Home Tab
+ void updateHome();
+ void slotGoToDir(QString dir);
+ void slotGoHome();
+ void slotOpenDir();
+ void slotOpenSearch();
+
+ //Slots for the special buttons
+ void openStore(){
+ LaunchItem(LOS::AppStoreShortcut());
+ }
+ void openControlPanel(){
+ LaunchItem(LOS::ControlPanelShortcut());
+ }
+ void openDeskSettings(){
+ LaunchItem("lumina-config", false);
+ }
+ void openScreenSaverConfig(){
+ LaunchItem(SSAVER, false);
+ }
+ void openScreenConfig(){
+ LaunchItem("lumina-xconfig",false);
+ }
+ void openLuminaInfo(){
+ LaunchItem("lumina-info",false);
+ }
+
+protected:
+ void mouseMoveEvent( QMouseEvent *event);
+
+signals:
+ void CloseMenu();
+
+};
+
+#endif
diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.ui b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.ui
new file mode 100644
index 00000000..9ef5af7e
--- /dev/null
+++ b/src-qt5/core/lumina-desktop-unified/src-DE/panel-plugins/userbutton/UserWidget.ui
@@ -0,0 +1,593 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UserWidget</class>
+ <widget class="QTabWidget" name="UserWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>294</width>
+ <height>289</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>UserWidget</string>
+ </property>
+ <property name="tabPosition">
+ <enum>QTabWidget::West</enum>
+ </property>
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab_fav">
+ <attribute name="title">
+ <string>Favorites</string>
+ </attribute>
+ <attribute name="toolTip">
+ <string>Favorites</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>1</number>
+ </property>
+ <property name="topMargin">
+ <number>1</number>
+ </property>
+ <property name="rightMargin">
+ <number>1</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="tool_fav_apps">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="cursor">
+ <cursorShape>ArrowCursor</cursorShape>
+ </property>
+ <property name="toolTip">
+ <string>Favorite Applications</string>
+ </property>
+ <property name="text">
+ <string>Applications</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_fav_dirs">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Favorite Directories</string>
+ </property>
+ <property name="text">
+ <string>Places</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_fav_files">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Favorite FIles</string>
+ </property>
+ <property name="text">
+ <string>Files</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scroll_fav">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>259</width>
+ <height>247</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_apps">
+ <attribute name="title">
+ <string>Apps</string>
+ </attribute>
+ <attribute name="toolTip">
+ <string>Applications</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>1</number>
+ </property>
+ <property name="topMargin">
+ <number>1</number>
+ </property>
+ <property name="rightMargin">
+ <number>1</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="combo_app_cats">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="maxVisibleItems">
+ <number>12</number>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_app_store">
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scroll_apps">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents_2">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>98</width>
+ <height>28</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_home">
+ <attribute name="title">
+ <string>Home</string>
+ </attribute>
+ <attribute name="toolTip">
+ <string>Home Directory</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <property name="spacing">
+ <number>2</number>
+ </property>
+ <property name="leftMargin">
+ <number>1</number>
+ </property>
+ <property name="topMargin">
+ <number>1</number>
+ </property>
+ <property name="rightMargin">
+ <number>1</number>
+ </property>
+ <property name="bottomMargin">
+ <number>1</number>
+ </property>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="horizontalSpacing">
+ <number>4</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="3">
+ <widget class="QToolButton" name="tool_home_search">
+ <property name="toolTip">
+ <string>Search this Directory</string>
+ </property>
+ <property name="text">
+ <string notr="true"/>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolButton" name="tool_home_browse">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Open Directory</string>
+ </property>
+ <property name="text">
+ <string notr="true">Browse</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonIconOnly</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QToolButton" name="tool_home_gohome">
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="toolTip">
+ <string>Go back to home directory</string>
+ </property>
+ <property name="text">
+ <string notr="true">home</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_home_dir">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>30</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::NoFrame</enum>
+ </property>
+ <property name="text">
+ <string notr="true">&lt;current dir&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="indent">
+ <number>0</number>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::NoTextInteraction</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scroll_home">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents_3">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>98</width>
+ <height>28</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab_config">
+ <attribute name="title">
+ <string>Config</string>
+ </attribute>
+ <attribute name="toolTip">
+ <string>Desktop Preferences</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QToolButton" name="tool_controlpanel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Control Panel</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_desktopsettings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Desktop Appearance/Plugins</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_config_screensettings">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Screen Configuration</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_config_screensaver">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Screensaver Settings</string>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>243</width>
+ <height>162</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="tool_config_about">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>About the Lumina Desktop</string>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextBesideIcon</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
bgstack15