diff options
author | Ken Moore <ken@ixsystems.com> | 2017-01-04 16:44:55 -0500 |
---|---|---|
committer | Ken Moore <ken@ixsystems.com> | 2017-01-04 16:44:55 -0500 |
commit | 25b2e77aa2395ba9143683a5ce1a27b99ee7a211 (patch) | |
tree | bbd732bb72689b9b46dfc619d3d0e1748f7e435b /src-qt5/core | |
parent | Tag version 1.2.1 on the master branch in preparation for new changes from th... (diff) | |
download | lumina-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')
158 files changed, 19954 insertions, 0 deletions
diff --git a/src-qt5/core/lumina-desktop-unified/BootSplash.cpp b/src-qt5/core/lumina-desktop-unified/BootSplash.cpp new file mode 100644 index 00000000..1a648973 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/BootSplash.cpp @@ -0,0 +1,184 @@ +#include "BootSplash.h" +#include "ui_BootSplash.h" + +#include <LuminaXDG.h> +#include <LUtils.h> +#include <LDesktopUtils.h> + +BootSplash::BootSplash() : QWidget(0, Qt::SplashScreen | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus), ui(new Ui::BootSplash){ + ui->setupUi(this); + this->setObjectName("LuminaBootSplash"); //for theme styling + //Center the window on the primary screen + QPoint ctr = QApplication::desktop()->screenGeometry().center(); + this->move( ctr.x()-(this->width()/2), ctr.y()-(this->height()/2) ); + generateTipOfTheDay(); + ui->label_version->setText( QString(tr("Version %1")).arg(LDesktopUtils::LuminaDesktopVersion()) ); +} + +void BootSplash::generateTipOfTheDay(){ + //Try to find a system-defined message of the day for lumina + QStringList dirs; dirs << LOS::AppPrefix()+"/etc/" << LOS::SysPrefix()+"/etc/" << L_ETCDIR+"/"; + QString sysMOTD = "lumina-motd"; + for(int i=0; i<dirs.length(); i++){ + if(QFile::exists(dirs[i]+sysMOTD)){ sysMOTD.prepend(dirs[i]); break; } + } + + QString tip; + if(sysMOTD.contains("/") && LUtils::isValidBinary(sysMOTD)){ + //is binary - run it to generate text + tip = LUtils::getCmdOutput(sysMOTD).join("\n"); + + }else if(QFile::exists(sysMOTD)){ + //text file - read it to generate text + tip = LUtils::readFile(sysMOTD).join("\n"); + + }else{ + int index = qrand()%46; //Make sure this number matches the length of the case below (max value +1) + switch(index){ + case 0: + tip = tr("This desktop is powered by coffee, coffee, and more coffee."); break; + case 1: + tip = tr("Keep up with desktop news!")+"\n\nwww.lumina-desktop.org"; break; + case 2: + tip = tr("There is a full handbook of information about the desktop available online.")+"\n\nwww.lumina-desktop.org/handbook"; break; + case 3: + tip = tr("Want to change the interface? Everything is customizable in the desktop configuration!"); break; + case 4: + tip = tr("Lumina can easily reproduce the interface from most other desktop environments."); break; + case 5: + tip = tr("This desktop is generously sponsored by iXsystems")+"\n\nwww.ixsystems.com"; break; + case 6: + tip = "\""+tr("I have never been hurt by what I have not said")+"\"\n\n- Calvin Coolidge -"; break; + case 7: + tip = "\""+tr("Gotta have more cowbell!")+"\"\n\n- Christopher Walken (SNL) -"; break; + case 8: + tip = "\""+tr("Everything has its beauty but not everyone sees it.")+"\"\n\n- Confucius -"; break; + case 9: + tip = "\""+tr("Before God we are all equally wise - and equally foolish.")+"\"\n\n- Albert Einstein -"; break; + case 10: + tip = "\""+tr("We cannot do everything at once, but we can do something at once.")+"\"\n\n- Calvin Coolidge -"; break; + case 11: + tip = "\""+tr("One with the law is a majority.")+"\"\n\n- Calvin Coolidge -"; break; + case 12: + tip = "\""+tr("Don't expect to build up the weak by pulling down the strong.")+"\"\n\n- Calvin Coolidge -"; break; + case 13: + tip = "\""+tr("You can't know too much, but you can say too much.")+"\"\n\n- Calvin Coolidge -"; break; + case 14: + tip = "\""+tr("Duty is not collective; it is personal.")+"\"\n\n- Calvin Coolidge -"; break; + case 15: + tip = "\""+tr("Any society that would give up a little liberty to gain a little security will deserve neither and lose both.")+"\"\n\n- Benjamin Franklin -"; break; + case 16: + tip = "\""+tr("Never trust a computer you can’t throw out a window.")+"\"\n\n- Steve Wozniak -"; break; + case 17: + tip = "\""+tr("Study the past if you would define the future.")+"\"\n\n- Confucius -"; break; + case 18: + tip = "\""+tr("The way to get started is to quit talking and begin doing.")+"\"\n\n- Walt Disney -"; break; + case 19: + tip = "\""+tr("Ask and it will be given to you; search, and you will find; knock and the door will be opened for you.")+"\"\n\n- Jesus Christ -"; break; + case 20: + tip = "\""+tr("Start where you are. Use what you have. Do what you can.")+"\"\n\n- Arthur Ashe -"; break; + case 21: + tip = "\""+tr("A person who never made a mistake never tried anything new.")+"\"\n\n- Albert Einstein -"; break; + case 22: + tip = "\""+tr("It does not matter how slowly you go as long as you do not stop.")+"\"\n\n- Confucius -"; break; + case 23: + tip = "\""+tr("Do what you can, where you are, with what you have.")+"\"\n\n- Theodore Roosevelt -"; break; + case 24: + tip = "\""+tr("Remember no one can make you feel inferior without your consent.")+"\"\n\n- Eleanor Roosevelt -"; break; + case 25: + tip = "\""+tr("It’s not the years in your life that count. It’s the life in your years.")+"\"\n\n- Abraham Lincoln -"; break; + case 26: + tip = "\""+tr("Either write something worth reading or do something worth writing.")+"\"\n\n- Benjamin Franklin -"; break; + case 27: + tip = "\""+tr("The only way to do great work is to love what you do.")+"\"\n\n- Steve Jobs -"; break; + case 28: + tip = "\""+tr("Political correctness is tyranny with manners.")+"\"\n\n- Charlton Heston -"; break; + case 29: + tip = "\""+tr("Only two things are infinite, the universe and human stupidity, and I'm not sure about the former.")+"\"\n\n- Albert Einstein -"; break; + case 30: + tip = "\""+tr("I find that the harder I work, the more luck I seem to have.")+"\"\n\n- Thomas Jefferson -"; break; + case 31: + tip = "\""+tr("Do, or do not. There is no 'try'.")+"\"\n\n- Yoda -"; break; + case 32: + tip = "\""+tr("A mathematician is a device for turning coffee into theorems.")+"\"\n\n- Paul Erdos -"; break; + case 33: + tip = "\""+tr("Good people do not need laws to tell them to act responsibly, while bad people will find a way around the laws.")+"\"\n\n- Plato -"; break; + case 34: + tip = "\""+tr("Black holes are where God divided by zero.")+"\"\n\n- Steven Wright -"; break; + case 35: + tip = "\""+tr("It's kind of fun to do the impossible.")+"\"\n\n- Walt Disney -"; break; + case 36: + tip = "\""+tr("Knowledge speaks, but wisdom listens.")+"\"\n\n- Jimi Hendrix -"; break; + case 37: + tip = "\""+tr("A witty saying proves nothing.")+"\"\n\n- Voltaire -"; break; + case 38: + tip = "\""+tr("Success usually comes to those who are too busy to be looking for it.")+"\"\n\n- Henry David Thoreau -"; break; + case 39: + tip = "\""+tr("Well-timed silence hath more eloquence than speech.")+"\"\n\n- Martin Fraquhar Tupper -"; break; + case 40: + tip = "\""+tr("I have never let my schooling interfere with my education.")+"\"\n\n- Mark Twain -"; break; + case 41: + tip = "\""+tr("The best way to predict the future is to invent it.")+"\"\n\n- Alan Kay -"; break; + case 42: + tip = "\""+tr("Well done is better than well said.")+"\"\n\n- Benjamin Franklin -"; break; + case 43: + tip = "\""+tr("Sometimes it is not enough that we do our best; we must do what is required.")+"\"\n\n- Sir Winston Churchill -"; break; + case 44: + tip = "\""+tr("The truth is more important than the facts.")+"\"\n\n- Frank Lloyd Wright -"; break; + case 45: + tip = "\""+tr("Better to remain silent and be thought a fool than to speak out and remove all doubt.")+"\"\n\n- Abraham Lincoln -"; break; + } //end of switch for tips + + } //end of fallback tip generation + ui->label_welcome->setText( tip); +} + +void BootSplash::showScreen(QString loading){ //update icon, text, and progress + QString txt, icon; + int per = 0; + if(loading=="init"){ + txt = tr("Initializing Session …"); per = 10; + icon = "preferences-system-login"; + }else if(loading=="settings"){ + txt = tr("Loading System Settings …"); per = 20; + icon = "user-home"; + }else if(loading=="user"){ + txt = tr("Loading User Preferences …"); per = 30; + icon = "preferences-desktop-user"; + }else if(loading=="systray"){ + txt = tr("Preparing System Tray …"); per = 40; + icon = "preferences-plugin"; + }else if(loading=="wm"){ + txt = tr("Starting Window Manager …"); per = 50; + icon = "preferences-system-windows-actions"; + }else if(loading=="apps"){ + txt = tr("Detecting Applications …"); per = 60; + icon = "preferences-desktop-icons"; + }else if(loading=="menus"){ + txt = tr("Preparing Menus …"); per = 70; + icon = "preferences-system-windows"; + }else if(loading=="desktop"){ + txt = tr("Preparing Workspace …"); per = 80; + icon = "preferences-desktop-wallpaper"; + }else if(loading=="final"){ + txt = tr("Finalizing …"); per = 90; + icon = "start-here-lumina"; + }else if(loading.startsWith("app::")){ + txt = QString(tr("Starting App: %1")).arg(loading.section("::",1,50)); per = -1; + } + if(per>0){ ui->progressBar->setValue(per); } + else{ ui->progressBar->setRange(0,0); } //loading indicator + ui->label_text->setText(txt); + if(!icon.isEmpty()){ui->label_icon->setPixmap( LXDG::findIcon(icon, "Lumina-DE").pixmap(64,64) ); } + this->raise(); + this->show(); + this->update(); + QApplication::processEvents(); +} + +void BootSplash::showText(QString txt){ //will only update the text, not the icon/progress + ui->label_text->setText(txt); + this->show(); + this->update(); + QApplication::processEvents(); +} diff --git a/src-qt5/core/lumina-desktop-unified/BootSplash.h b/src-qt5/core/lumina-desktop-unified/BootSplash.h new file mode 100644 index 00000000..f9812ff4 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/BootSplash.h @@ -0,0 +1,31 @@ +#ifndef _LUMINA_DESKTOP_BOOT_SPLASHSCREEN_H +#define _LUMINA_DESKTOP_BOOT_SPLASHSCREEN_H + +#include <QWidget> +#include <QLabel> +#include <QProgressBar> +#include <QPixmap> +#include <QPoint> +#include <QApplication> +#include <QDesktopWidget> + +namespace Ui{ + class BootSplash; +}; + +class BootSplash : public QWidget{ + Q_OBJECT +private: + Ui::BootSplash *ui; + + void generateTipOfTheDay(); + +public: + BootSplash(); + ~BootSplash(){} + + void showScreen(QString loading); //update icon, text, and progress + void showText(QString txt); //will only update the text, not the icon/progress +}; + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/BootSplash.ui b/src-qt5/core/lumina-desktop-unified/BootSplash.ui new file mode 100644 index 00000000..43f4ca67 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/BootSplash.ui @@ -0,0 +1,198 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BootSplash</class> + <widget class="QWidget" name="BootSplash"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>314</width> + <height>194</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>1</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"> + <item row="2" column="0"> + <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 row="1" column="0"> + <widget class="QLabel" name="label_icon"> + <property name="minimumSize"> + <size> + <width>64</width> + <height>64</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>64</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <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 row="0" column="1" rowspan="3"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + <underline>false</underline> + </font> + </property> + <property name="text"> + <string>Starting the Lumina Desktop...</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter|Qt::AlignTop</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_welcome"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_version"> + <property name="font"> + <font> + <pointsize>7</pointsize> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string notr="true">Version</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="format"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_text"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string notr="true">Some loading message</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src-qt5/core/lumina-desktop-unified/LSession.cpp b/src-qt5/core/lumina-desktop-unified/LSession.cpp new file mode 100644 index 00000000..482b33ea --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/LSession.cpp @@ -0,0 +1,317 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2012-2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LSession.h" +#include "global-objects.h" + +#include "BootSplash.h" + +#ifndef DEBUG +#define DEBUG 0 +#endif + +LSession::LSession(int &argc, char ** argv) : LSingleApplication(argc, argv, "lumina-desktop"){ + //Initialize the global objects to null pointers + mediaObj = 0; //private object used for playing login/logout chimes + Lumina::SYSTEM = 0; + if(this->isPrimaryProcess()){ + //Setup the global registrations + this->setApplicationName("Lumina Desktop Environment"); + this->setApplicationVersion( LDesktopUtils::LuminaDesktopVersion() ); + this->setOrganizationName("LuminaDesktopEnvironment"); + this->setQuitOnLastWindowClosed(false); //since the LDesktop's are not necessarily "window"s + //Enable 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 + + //Now initialize the global objects which need instant usage/access + Lumina::SYSTEM = new LXCB(); //need access to XCB data/functions right away + + //Setup the event filter for Qt5 + //this->installNativeEventFilter( new XCBEventFilter(this) ); + } //end check for primary process +} + +LSession::~LSession(){ + //Clean up the global objects as needed + if(Lumina::SYSTEM!=0){ Lumina::SYSTEM->deleteLater(); } + +} + +void LSession::setupSession(){ + 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();} + //Seed random number generator + qsrand( QTime::currentTime().msec() ); + //Connect internal signal/slots + connect(this, SIGNAL(InputsAvailable(QStringList)), this, SLOT(NewCommunication(QStringList)) ); + //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(); +} + +//================ +// PRIVATE +//================ +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(); + //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); +} + +//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"; } +} + +//================ +// PRIVATE SLOTS +//================ +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::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()); + } +} + + +//================== +// PUBLIC SLOTS +//================== +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(); +} + +//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(); +} diff --git a/src-qt5/core/lumina-desktop-unified/LSession.h b/src-qt5/core/lumina-desktop-unified/LSession.h new file mode 100644 index 00000000..c89bc66d --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/LSession.h @@ -0,0 +1,50 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2012-2017, 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 <global-includes.h> + +class LSession : public LSingleApplication{ + Q_OBJECT +public: + LSession(int &argc, char **argv); + ~LSession(); + //Functions to be called during startup + void setupSession(); + + +private: + void CleanupSession(); + + int VersionStringToNumber(QString version); + QMediaObject *mediaObj; + void playAudioFile(QString filepath); + +public slots: + void StartLogout(); + void StartShutdown(bool skipupdates = false); + void StartReboot(bool skipupdates = false); + + void reloadIconTheme(); //will emit the IconThemeChanged signal when ready + void switchLocale(QString localeCode); //will emit the LocaleChanged signal when ready + +private slots: + void NewCommunication(QStringList); + void launchStartupApps(); //used during initialization + + //Internal simplification functions + void checkUserFiles(); + +signals: + //General Signals + void LocaleChanged(); + void IconThemeChanged(); + +}; + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/defaults/compton.conf b/src-qt5/core/lumina-desktop-unified/defaults/compton.conf new file mode 100644 index 00000000..a14fee29 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/defaults/compton.conf @@ -0,0 +1,89 @@ +# Shadow +shadow = true; +no-dnd-shadow = true; +no-dock-shadow = true; +clear-shadow = true; +shadow-radius = 5; +shadow-offset-x = -5; +shadow-offset-y = -5; +# shadow-opacity = 0.7; +# shadow-red = 0.0; +# shadow-green = 0.0; +# shadow-blue = 0.0; +shadow-exclude = [ + "name = 'Notification'", + "class_g = 'Conky'", + "class_g ?= 'Notify-osd'", + "class_g = 'Cairo-clock'", + "_GTK_FRAME_EXTENTS@:c" +]; +# shadow-exclude = "n:e:Notification"; +# shadow-exclude-reg = "x10+0+0"; +# xinerama-shadow-crop = true; + +# Opacity +menu-opacity = 0.9; +inactive-opacity = 0.9; +# active-opacity = 0.8; +frame-opacity = 1.0; +inactive-opacity-override = false; +alpha-step = 0.06; +# inactive-dim = 0.2; +# inactive-dim-fixed = true; +# blur-background = true; +# blur-background-frame = true; +blur-kern = "3x3box" +# blur-kern = "5,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1" +# blur-background-fixed = true; +blur-background-exclude = [ + "window_type = 'dock'", + "window_type = 'desktop'", + "_GTK_FRAME_EXTENTS@:c" +]; +# opacity-rule = [ "80:class_g = 'URxvt'" ]; + +# Fading +fading = true; +# fade-delta = 30; +fade-in-step = 0.2; +fade-out-step = 0.2; +# no-fading-openclose = true; +# no-fading-destroyed-argb = true; +fade-exclude = [ ]; + +# Other +backend = "xrender" +mark-wmwin-focused = true; +mark-ovredir-focused = true; +# use-ewmh-active-win = true; +detect-rounded-corners = true; +detect-client-opacity = true; +refresh-rate = 0; +vsync = "none"; +dbe = false; +paint-on-overlay = true; +# sw-opti = true; +# unredir-if-possible = true; +# unredir-if-possible-delay = 5000; +# unredir-if-possible-exclude = [ ]; +focus-exclude = [ "class_g = 'Cairo-clock'" ]; +detect-transient = true; +detect-client-leader = true; +invert-color-include = [ ]; +# resize-damage = 1; + +# GLX backend +# glx-no-stencil = true; +glx-copy-from-front = false; +# glx-use-copysubbuffermesa = true; +# glx-no-rebind-pixmap = true; +glx-swap-method = "undefined"; +# glx-use-gpushader4 = true; +# xrender-sync = true; +# xrender-sync-fence = true; + +# Window type settings +wintypes: +{ + tooltip = { fade = true; shadow = true; opacity = 0.85; focus = true; }; +}; diff --git a/src-qt5/core/lumina-desktop-unified/defaults/desktop-background-TrueOS.jpg b/src-qt5/core/lumina-desktop-unified/defaults/desktop-background-TrueOS.jpg Binary files differnew file mode 100644 index 00000000..de11074e --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/defaults/desktop-background-TrueOS.jpg diff --git a/src-qt5/core/lumina-desktop-unified/defaults/desktop-background.jpg b/src-qt5/core/lumina-desktop-unified/defaults/desktop-background.jpg Binary files differnew file mode 100644 index 00000000..ddee66ea --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/defaults/desktop-background.jpg diff --git a/src-qt5/core/lumina-desktop-unified/defaults/luminaDesktop-TrueOS.conf b/src-qt5/core/lumina-desktop-unified/defaults/luminaDesktop-TrueOS.conf new file mode 100644 index 00000000..c1f3a194 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/defaults/luminaDesktop-TrueOS.conf @@ -0,0 +1,102 @@ +#This is the configuration file that generates all the default settings files for the Lumina desktop +# For any setting that can take a list of values, each vale needs to be seperated by a comma and a space (", ") +# Example: some_setting=item1, item2, item3 + +#NOTE: To pre-setup default applications for particular mime-types, you need to create *.desktop entries on +# system corresponding to the XDG mime-type specifications for default applications +# See Here for specifications: http://www.freedesktop.org/wiki/Specifications/mime-apps-spec/ + +# Possible Desktop Plugins (Lumina version 0.9.1): +# calendar, applauncher[::absolute path to *.desktop file], desktopview, notepad, audioplayer, rssreader +# Possible Panel Plugins (Lumina version 0.9.1): +# userbutton, desktopbar, spacer, desktopswitcher, battery, clock, systemdashboard, systemstart +# taskmanager[-nogroups], systemtray, homebutton, appmenu, applauncher[::absolute path to *.desktop file] +# Possible Menu Plugins (Lumina version 0.9.1): +# terminal, filemanager, applications, line, settings, windowlist, app::<absolute path to *.desktop file> + +#GENERAL SESSION SETTINGS +session_enablenumlock=false #[true/false] Enable numlock on login using "numlockx" +session_playloginaudio=true #[true/false] Play the audio chimes on log in +session_playlogoutaudio=true #[true/false] Play the audio chimes on log out + +# DEFAULT UTILITIES +# Provide the full path to *.desktop file, or a binary name which exists on PATH +# *.desktop files provide better support for input formats, and are recommended +#Note: the last "ifexists" entry has the highest priority for each session utility +session_default_terminal_ifexists=xterm.desktop +session_default_terminal_ifexists=qterminal.desktop +session_default_terminal_ifexists=lumina-terminal.desktop +session_default_filemanager=lumina-fm.desktop +session_default_webbrowser_ifexists=chromium-browser.desktop +session_default_webbrowser_ifexists=firefox.desktop +session_default_webbrowser_ifexists=qupzilla.desktop +session_default_email_ifexists=trojita.desktop + +#DEFAULT UTILITIES FOR INDIVIDUAL MIME TYPES +# Format: mime_default_<mimetype>[_ifexists]=<*.desktop file> +mime_default_text/*_ifexists=lumina-textedit.desktop +mime_default_audio/*_ifexists=vlc.desktop +mime_default_video/*_ifexists=vlc.desktop +mime_default_image/*_ifexists=phototonic.desktop +mime_default_unknown/*=lumina-textedit.desktop +mime_default_application/x-shellscript=lumina-textedit.desktop +mime_default_application/pdf_ifexists=pc-pdfviewer.desktop +mime_default_application/pdf_ifexists=okular.desktop +mime_default_application/zip_ifexists=lumina-archiver.desktop +mime_default_application/x-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-bzip-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-lrzip-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-lzma-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-xz-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-tar_ifexists=lumina-archiver.desktop + +#THEME SETTINGS +theme_themefile=Glass #Name of the theme to use (disable for Lumina-Default) +theme_colorfile=Lumina-Glass #Name of the color spec file to use for theming +theme_iconset=oxygen #Name of the icon theme to use +theme_font=Arial #Name of the font family to use +theme_fontsize=10pt #Default size of the fonts to use on the desktop (can also use a percentage of the screen height (<number>%) ) + +#DESKTOP SETTINGS (used for the primary screen in multi-screen setups) +desktop_visiblepanels=1 #[0 - 12] The number of panels visible by default +#desktop_backgroundfiles= #list of absolute file paths for image files (disable for Lumina default) +desktop_backgroundrotateminutes=5 #[positive integer] number of minutes between background rotations (if multiple files) +desktop_plugins=rssreader #list of plugins to be shown on the desktop by default +desktop_generate_icons=true #[true/false] Auto-generate launchers for ~/Desktop items + +#PANEL SETTINGS (preface with panel1.<setting> or panel2.<setting>, depending on the number of panels you have visible by default) +panel1_location=bottom #[top/bottom/left/right] Screen edge the panel should be on +panel1_pixelsize=3.5%H #number of pixels wide/high the panel should be (or <number>%[W/H] for a percentage of the screen width/height) +panel1_autohide=false #[true/false] Have the panel become visible on mouse-over +panel1_plugins=systemstart, taskmanager-nogroups, spacer, systemtray, clock, battery #list of plugins for the panel +panel1_pinlocation=center #[left/center/right] Note:[left/right] corresponds to [top/bottom] for vertical panels +panel1_edgepercent=99 #[1->100] percentage of the screen edge to use + +#MENU SETTINGS (right-click menu) +menu_plugins=terminal, filemanager, applications, line, settings, line, lockdesktop#list of menu plugins to show + +#FAVORITES CUSTOMIZATION +#favorites_add=<file/dir path> #Create a favorites entry for this file/dir +#favorites_remove=<file/dir path> #Remove a favorites entry for this file/dir +#favorites_add_ifexists=<file/dir path> #Create a favorites entry for this file/dir if the file/dir exists +favorites_add_ifexists=firefox.desktop +favorites_add_ifexists=chromium-browser.desktop +favorites_add_ifexists=qupzilla.desktop +favorites_add_ifexists=thunderbird.desktop +favorites_add_ifexists=trojita.desktop +favorites_add_ifexists=smplayer.desktop +favorites_add_ifexists=vlc.desktop +favorites_add_ifexists=pithos.desktop +favorites_add_ifexists=~/Documents +favorites_add_ifexists=~/Downloads +favorites_add_ifexists=~/Pictures +favorites_add_ifexists=~/Videos + +#QUICKLAUNCH CUSTOMIZATION (requires the use of the "systemstart" panel plugin) +#quicklaunch_add=<file/dir path> #Create a quicklaunch shortcut for this file/dir +#quicklaunch_add_ifexists=<file/dir path> #Create a quicklaunch shortcut for this file/dir if the file/dir exists + +#Generic scripts/utilities to run for any additional setup procedures +# These are always run after all other settings are saved +#Format: usersetup_run=<generic command to run> +usersetup_run=xdg-user-dirs-update diff --git a/src-qt5/core/lumina-desktop-unified/defaults/luminaDesktop.conf b/src-qt5/core/lumina-desktop-unified/defaults/luminaDesktop.conf new file mode 100644 index 00000000..46d00053 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/defaults/luminaDesktop.conf @@ -0,0 +1,104 @@ +#This is the configuration file that generates all the default settings files for the Lumina desktop +# For any setting that can take a list of values, each vale needs to be seperated by a comma and a space (", ") +# Example: some_setting=item1, item2, item3 + +#NOTE: To pre-setup default applications for particular mime-types, you need to create *.desktop entries on +# system corresponding to the XDG mime-type specifications for default applications +# See Here for specifications: http://www.freedesktop.org/wiki/Specifications/mime-apps-spec/ + +# Possible Desktop Plugins (Lumina version 0.9.1): +# calendar, applauncher[::absolute path to *.desktop file], desktopview, notepad, audioplayer, rssreader +# Possible Panel Plugins (Lumina version 0.9.1): +# userbutton, desktopbar, spacer, desktopswitcher, battery, clock, systemdashboard, systemstart +# taskmanager[-nogroups], systemtray, homebutton, appmenu, applauncher[::absolute path to *.desktop file] +# Possible Menu Plugins (Lumina version 0.9.1): +# terminal, filemanager, applications, line, settings, windowlist, app::<absolute path to *.desktop file> + +#GENERAL SESSION SETTINGS +session_enablenumlock=false #[true/false] Enable numlock on login using "numlockx" +session_playloginaudio=true #[true/false] Play the audio chimes on log in +session_playlogoutaudio=true #[true/false] Play the audio chimes on log out + +# DEFAULT UTILITIES +# Provide the full path to *.desktop file, or a binary name which exists on PATH +# *.desktop files provide better support for input formats, and are recommended +#Note: the last "ifexists" entry has the highest priority for each session utility +session_default_terminal_ifexists=xterm.desktop +session_default_terminal_ifexists=lumina-terminal.desktop +session_default_filemanager=lumina-fm.desktop +session_default_webbrowser_ifexists=chromium-browser.desktop +session_default_webbrowser_ifexists=firefox.desktop +session_default_webbrowser_ifexists=qupzilla.desktop +session_default_email_ifexists=trojita.desktop + +#DEFAULT UTILITIES FOR INDIVIDUAL MIME TYPES +# Format: mime_default_<mimetype>[_ifexists]=<*.desktop file> +mime_default_text/*_ifexists=lumina-textedit.desktop +mime_default_audio/*_ifexists=vlc.desktop +mime_default_video/*_ifexists=vlc.desktop +mime_default_application/zip_ifexists=lumina-archiver.desktop +mime_default_application/x-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-bzip-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-lrzip-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-lzma-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-xz-compressed-tar_ifexists=lumina-archiver.desktop +mime_default_application/x-tar_ifexists=lumina-archiver.desktop +mime_default_unknown/*=lumina-textedit.desktop +mime_default_application/x-shellscript=lumina-textedit.desktop + +#THEME SETTINGS +theme_themefile=Glass #Name of the theme to use (disable for Lumina-Default) +theme_colorfile=Lumina-Glass #Name of the color spec file to use for theming +theme_iconset=oxygen #Name of the icon theme to use +theme_font=Arial #Name of the font family to use +theme_fontsize=10pt #Default size of the fonts to use on the desktop (can also use a percentage of the screen height (<number>%) ) + +#DESKTOP SETTINGS (used for the primary screen in multi-screen setups) +desktop_visiblepanels=2 #[0 - 12] The number of panels visible by default +#desktop_backgroundfiles= #list of absolute file paths for image files (disable for Lumina default) +desktop_backgroundrotateminutes=5 #[positive integer] number of minutes between background rotations (if multiple files) +desktop_plugins=rssreader #list of plugins to be shown on the desktop by default +desktop_generate_icons=true #[true/false] Auto-generate launchers for ~/Desktop items + +#PANEL SETTINGS (preface with panel1.<setting> or panel2.<setting>, depending on the number of panels you have visible by default) +panel1_location=bottom #[top/bottom/left/right] Screen edge the panel should be on +panel1_pixelsize=3.5%H #number of pixels wide/high the panel should be (or <number>%[W/H] for a percentage of the screen width/height) +panel1_autohide=false #[true/false] Have the panel become visible on mouse-over +panel1_plugins=systemstart, taskmanager-nogroups, spacer, systemtray, clock, battery #list of plugins for the panel +panel1_pinlocation=center #[left/center/right] Note:[left/right] corresponds to [top/bottom] for vertical panels +panel1_edgepercent=99 #[1->100] percentage of the screen edge to use + +panel2_location=top +panel2_pixelsize=3%H +panel2_autohide=true +panel2_plugins=spacer, desktopbar, spacer +panel2_pinlocation=center +panel2_edgepercent=10 + +#MENU SETTINGS (right-click menu) +menu_plugins=terminal, filemanager, applications, line, settings #list of menu plugins to show + +#FAVORITES CUSTOMIZATION +#favorites_add=<file/dir path> #Create a favorites entry for this file/dir +#favorites_remove=<file/dir path> #Remove a favorites entry for this file/dir +#favorites_add_ifexists=<file/dir path> #Create a favorites entry for this file/dir if the file/dir exists +favorites_add_ifexists=firefox.desktop +favorites_add_ifexists=chromium-browser.desktop +favorites_add_ifexists=qupzilla.desktop +favorites_add_ifexists=thunderbird.desktop +favorites_add_ifexists=trojita.desktop +favorites_add_ifexists=smplayer.desktop +favorites_add_ifexists=vlc.desktop +favorites_add_ifexists=pithos.desktop +favorites_add_ifexists=~/Documents +favorites_add_ifexists=~/Downloads +favorites_add_ifexists=~/Pictures +favorites_add_ifexists=~/Videos + +#QUICKLAUNCH CUSTOMIZATION (requires the use of the "systemstart" panel plugin) +#quicklaunch_add=<file/dir path> #Create a quicklaunch shortcut for this file/dir +#quicklaunch_add_ifexists=<file/dir path> #Create a quicklaunch shortcut for this file/dir if the file/dir exists + +#Generic scripts/utilities to run for any additional setup procedures +# These are always run after all other settings are saved +#Format: usersetup_run=<generic command to run> diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/Lumina-DE.png b/src-qt5/core/lumina-desktop-unified/extrafiles/Lumina-DE.png Binary files differnew file mode 100644 index 00000000..ce88a252 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/Lumina-DE.png diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/LICENCE b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/LICENCE new file mode 100644 index 00000000..aa601d5e --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/LICENCE @@ -0,0 +1,7 @@ +These audio files are BSD-licensed and were created/owned by the TrueOS Project: + - Login.ogg + - Logout.ogg + +These audio files are freely available on jewelbeat.com: +"Music by JewelBeat. Download your free music and free sound effects at www.jewelbeat.com." + - low-battery.ogg (http://www.jewelbeat.com/free/free-sound-effects/musical%20effects/Tympani_2.mp3 - converted to OGG afterward) diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/Login.ogg b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/Login.ogg Binary files differnew file mode 100644 index 00000000..43a07e27 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/Login.ogg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/Logout.ogg b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/Logout.ogg Binary files differnew file mode 100644 index 00000000..e63ae07f --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/Logout.ogg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/low-battery.ogg b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/low-battery.ogg Binary files differnew file mode 100644 index 00000000..d129a2b3 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/audiofiles/low-battery.ogg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/lumina-desktop.desktop b/src-qt5/core/lumina-desktop-unified/extrafiles/lumina-desktop.desktop new file mode 100644 index 00000000..7d87f93a --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/lumina-desktop.desktop @@ -0,0 +1,34 @@ +[Desktop Entry] +Exec=start-lumina-desktop +TryExec=start-lumina-desktop +Icon=Lumina-DE +Type=Application +Name=Lumina +Name[de]=Lumina +Name[en_GB]=Lumina +Name[en_ZA]=Lumina +Name[et]=Lumina +Name[fr]=Lumina +Name[fr_CA]=Lumina +Name[hi]=ल्यूमिना +Name[ja]=Lumina +Name[mt]=Lumina +Name[pl]=Lumina +Name[pt_BR]=Lumina +Name[ru]=Lumina +Name[uk]=Lumina +Name[vi]=Lumina +Comment=A Lightweight Desktop for FreeBSD +Comment[de]=Eine leichtgewichtige Arbeitsplatzumgebung für FreeBSD +Comment[en_GB]=A Lightweight Desktop for FreeBSD +Comment[en_ZA]=A Lightweight Desktop for FreeBSD +Comment[et]=Minimalistlik töölauakeskkond FreeBSD-le +Comment[fr]=Un environnement bureau léger pour FreeBSD +Comment[fr_CA]=Un environnement bureau léger pour FreeBSD +Comment[hi]=एक हल्का डेस्कटॉप फ्री बी.एस.डी के लिए +Comment[ja]=FreeBSD の為に作られた軽快なデスクトップ環境 +Comment[mt]=A Desktop irqiq għal FreeBSD +Comment[pl]=Lekkie Środowisko graficzne dla FreeBSD +Comment[pt_BR]=Um ambiente de trabalho leve para FreeBSD +Comment[uk]=Легковісне оточення стільниці для FreeBSD +Comment[vi]=Một máy tính để bàn nhẹ cho FreeBSD diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_blue-grey-zoom.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_blue-grey-zoom.jpg Binary files differnew file mode 100644 index 00000000..481ca438 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_blue-grey-zoom.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_blue-grey.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_blue-grey.jpg Binary files differnew file mode 100644 index 00000000..9da67596 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_blue-grey.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_gold.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_gold.jpg Binary files differnew file mode 100644 index 00000000..cba03cee --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_gold.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_green.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_green.jpg Binary files differnew file mode 100644 index 00000000..80b0d3e3 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_green.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_grey-blue-zoom.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_grey-blue-zoom.jpg Binary files differnew file mode 100644 index 00000000..4f753ed5 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_grey-blue-zoom.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_grey-blue.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_grey-blue.jpg Binary files differnew file mode 100644 index 00000000..c214cd78 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_grey-blue.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_purple.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_purple.jpg Binary files differnew file mode 100644 index 00000000..e4c3d7a8 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_purple.jpg diff --git a/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_red.jpg b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_red.jpg Binary files differnew file mode 100644 index 00000000..a092f636 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/extrafiles/wallpapers/Lumina_Wispy_red.jpg diff --git a/src-qt5/core/lumina-desktop-unified/global-includes.h b/src-qt5/core/lumina-desktop-unified/global-includes.h new file mode 100644 index 00000000..0733b232 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/global-includes.h @@ -0,0 +1,63 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2015-2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Global defines and enumerations for external includes +//=========================================== +#ifndef _LUMINA_EXTERNAL_GLOBAL_DEFINES_H +#define _LUMINA_EXTERNAL_GLOBAL_DEFINES_H + +//Qt includes +#include <QObject> +#include <QFrame> +#include <QLabel> +#include <QToolButton> +#include <QMenu> +#include <QHBoxLayout> +#include <QMouseEvent> +#include <QAction> +#include <QPoint> +#include <QFile> +#include <QDir> +#include <QString> +#include <QTextStream> +#include <QUrl> +#include <QDebug> +#include <QStringList> +#include <QAbstractNativeEventFilter> +#include <QList> +#include <QX11Info> +#include <QCoreApplication> +#include <QPropertyAnimation> +#include <QAnimationGroup> +#include <QParallelAnimationGroup> +#include <QWindow> +#include <QWidget> +#include <QBackingStore> +#include <QPaintEvent> +#include <QPainter> +#include <QSettings> +#include <QHostInfo> +#include <QDesktopWidget> +#include <QStyleOption> +#include <QThread> + +// libLumina includes +#include <LuminaX11.h> +#include <LuminaXDG.h> +#include <LuminaOS.h> +#include <LuminaThemes.h> +#include <LUtils.h> +#include <LDesktopUtils.h> +#include <LuminaSingleApplication.h> + +//XCB Includes +/*#include <xcb/xcb.h> +#include <xcb/xproto.h> +#include <xcb/damage.h> +#include <xcb/xcb_atom.h> +#include <xcb/xcb_aux.h> //included in libxcb-util.so*/ + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/global-objects.h b/src-qt5/core/lumina-desktop-unified/global-objects.h new file mode 100644 index 00000000..d712c32b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/global-objects.h @@ -0,0 +1,28 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2015-2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Global defines and enumerations for external includes +//=========================================== +#ifndef _LUMINA_INTERNAL_GLOBAL_OBJECTS_H +#define _LUMINA_INTERNAL_GLOBAL_OBJECTS_H + +#include "Global-includes.h" + +//Any special defines for settings/testing +#define ANIMTIME 80 //animation time in milliseconds + +//Global flags/structures +namespace Lumina{ + //Flags/enumerations + enum WindowAction{MoveResize, Show, Hide, TryClose, Closed, WA_NONE}; + + //Data structures and objects + extern LXCB *SYSTEM; + +}; + + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/lumina-desktop.pro b/src-qt5/core/lumina-desktop-unified/lumina-desktop.pro new file mode 100644 index 00000000..4b725288 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/lumina-desktop.pro @@ -0,0 +1,190 @@ +include($${PWD}/../../OS-detect.pri) + +QT += core gui network +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets x11extras multimedia concurrent svg + + + +TARGET = lumina-desktop +target.path = $${L_BINDIR} + +#include all the special classes from the Lumina tree +include(../libLumina/ResizeMenu.pri) +include(../libLumina/LDesktopUtils.pri) #includes LUtils and LOS +include(../libLumina/LuminaXDG.pri) +include(../libLumina/LuminaX11.pri) +include(../libLumina/LuminaSingleApplication.pri) +include(../libLumina/LuminaThemes.pri) + +#LIBS += -lLuminaUtils -lxcb -lxcb-damage +#DEPENDPATH += ../libLumina + +TEMPLATE = app + +SOURCES += main.cpp \ + WMProcess.cpp \ + LXcbEventFilter.cpp \ + LSession.cpp \ + LDesktop.cpp \ + LDesktopBackground.cpp \ + LDesktopPluginSpace.cpp \ + LPanel.cpp \ + LWinInfo.cpp \ + AppMenu.cpp \ + SettingsMenu.cpp \ + SystemWindow.cpp \ + BootSplash.cpp \ + desktop-plugins/LDPlugin.cpp + + +HEADERS += Globals.h \ + WMProcess.h \ + LXcbEventFilter.h \ + LSession.h \ + LDesktop.h \ + LDesktopBackground.h \ + LDesktopPluginSpace.h \ + LPanel.h \ + LWinInfo.h \ + AppMenu.h \ + SettingsMenu.h \ + SystemWindow.h \ + BootSplash.h \ + panel-plugins/LPPlugin.h \ + panel-plugins/NewPP.h \ + panel-plugins/LTBWidget.h \ + desktop-plugins/LDPlugin.h \ + desktop-plugins/NewDP.h \ + JsonMenu.h + +FORMS += SystemWindow.ui \ + BootSplash.ui + + +#Now include all the files for the various plugins +include(panel-plugins/panel-plugins.pri) +include(desktop-plugins/desktop-plugins.pri) + +RESOURCES+= Lumina-DE.qrc + +desktop.path = $${L_SESSDIR} +desktop.files = Lumina-DE.desktop + +icons.files = Lumina-DE.png \ + Insight-FileManager.png +icons.path = $${L_SHAREDIR}/pixmaps + +fluxconf.files = fluxboxconf/fluxbox-init-rc \ + fluxboxconf/fluxbox-keys +fluxconf.path = $${L_SHAREDIR}/lumina-desktop/ + +wallpapers.files = wallpapers/Lumina_Wispy_gold.jpg \ + wallpapers/Lumina_Wispy_green.jpg \ + wallpapers/Lumina_Wispy_purple.jpg \ + wallpapers/Lumina_Wispy_red.jpg \ + wallpapers/Lumina_Wispy_blue-grey.jpg \ + wallpapers/Lumina_Wispy_blue-grey-zoom.jpg \ + wallpapers/Lumina_Wispy_grey-blue.jpg \ + wallpapers/Lumina_Wispy_grey-blue-zoom.jpg +wallpapers.path = $${L_SHAREDIR}/wallpapers/Lumina-DE + + +defaults.files = defaults/luminaDesktop.conf \ + defaults/compton.conf \ + audiofiles/Logout.ogg \ + audiofiles/Login.ogg \ + audiofiles/low-battery.ogg +defaults.path = $${L_SHAREDIR}/lumina-desktop/ + +conf.path = $${L_ETCDIR} + +#Now do any OS-specific defaults (if available) +#First see if there is a known OS override first +!isEmpty(DEFAULT_SETTINGS){ + message("Installing defaults settings for OS: $${DEFAULT_SETTINGS}") + OS=$${DEFAULT_SETTINGS} +} +exists("defaults/luminaDesktop-$${OS}.conf"){ + message(" -- Found OS-specific system config file: $${OS}"); + conf.extra = cp defaults/luminaDesktop-$${OS}.conf $(INSTALL_ROOT)$${L_ETCDIR}/luminaDesktop.conf.dist +}else{ + conf.extra = cp defaults/luminaDesktop.conf $(INSTALL_ROOT)$${L_ETCDIR}/luminaDesktop.conf.dist +} +exists("defaults/desktop-background-$${OS}.jpg"){ + message(" -- Found OS-specific background image: $${OS}"); + defaults.extra = cp defaults/desktop-background-$${OS}.jpg $(INSTALL_ROOT)$${L_SHAREDIR}/lumina-desktop/desktop-background.jpg +}else{ + defaults.extra = cp defaults/desktop-background.jpg $(INSTALL_ROOT)$${L_SHAREDIR}/lumina-desktop/desktop-background.jpg +} + +TRANSLATIONS = i18n/lumina-desktop_af.ts \ + i18n/lumina-desktop_ar.ts \ + i18n/lumina-desktop_az.ts \ + i18n/lumina-desktop_bg.ts \ + i18n/lumina-desktop_bn.ts \ + i18n/lumina-desktop_bs.ts \ + i18n/lumina-desktop_ca.ts \ + i18n/lumina-desktop_cs.ts \ + i18n/lumina-desktop_cy.ts \ + i18n/lumina-desktop_da.ts \ + i18n/lumina-desktop_de.ts \ + i18n/lumina-desktop_el.ts \ + i18n/lumina-desktop_en_GB.ts \ + i18n/lumina-desktop_en_ZA.ts \ + i18n/lumina-desktop_es.ts \ + i18n/lumina-desktop_et.ts \ + i18n/lumina-desktop_eu.ts \ + i18n/lumina-desktop_fa.ts \ + i18n/lumina-desktop_fi.ts \ + i18n/lumina-desktop_fr.ts \ + i18n/lumina-desktop_fr_CA.ts \ + i18n/lumina-desktop_gl.ts \ + i18n/lumina-desktop_he.ts \ + i18n/lumina-desktop_hi.ts \ + i18n/lumina-desktop_hr.ts \ + i18n/lumina-desktop_hu.ts \ + i18n/lumina-desktop_id.ts \ + i18n/lumina-desktop_is.ts \ + i18n/lumina-desktop_it.ts \ + i18n/lumina-desktop_ja.ts \ + i18n/lumina-desktop_ka.ts \ + i18n/lumina-desktop_ko.ts \ + i18n/lumina-desktop_lt.ts \ + i18n/lumina-desktop_lv.ts \ + i18n/lumina-desktop_mk.ts \ + i18n/lumina-desktop_mn.ts \ + i18n/lumina-desktop_ms.ts \ + i18n/lumina-desktop_mt.ts \ + i18n/lumina-desktop_nb.ts \ + i18n/lumina-desktop_nl.ts \ + i18n/lumina-desktop_pa.ts \ + i18n/lumina-desktop_pl.ts \ + i18n/lumina-desktop_pt.ts \ + i18n/lumina-desktop_pt_BR.ts \ + i18n/lumina-desktop_ro.ts \ + i18n/lumina-desktop_ru.ts \ + i18n/lumina-desktop_sk.ts \ + i18n/lumina-desktop_sl.ts \ + i18n/lumina-desktop_sr.ts \ + i18n/lumina-desktop_sv.ts \ + i18n/lumina-desktop_sw.ts \ + i18n/lumina-desktop_ta.ts \ + i18n/lumina-desktop_tg.ts \ + i18n/lumina-desktop_th.ts \ + i18n/lumina-desktop_tr.ts \ + i18n/lumina-desktop_uk.ts \ + i18n/lumina-desktop_uz.ts \ + i18n/lumina-desktop_vi.ts \ + i18n/lumina-desktop_zh_CN.ts \ + i18n/lumina-desktop_zh_HK.ts \ + i18n/lumina-desktop_zh_TW.ts \ + i18n/lumina-desktop_zu.ts + +dotrans.path=$${L_SHAREDIR}/lumina-desktop/i18n/ +dotrans.extra=cd i18n && $${LRELEASE} -nounfinished *.ts && cp *.qm $(INSTALL_ROOT)$${L_SHAREDIR}/lumina-desktop/i18n/ + +INSTALLS += target desktop icons wallpapers defaults conf fluxconf + +WITH_I18N{ + INSTALLS += dotrans +} diff --git a/src-qt5/core/lumina-desktop-unified/main.cpp b/src-qt5/core/lumina-desktop-unified/main.cpp new file mode 100644 index 00000000..f8be977d --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/main.cpp @@ -0,0 +1,46 @@ +//=========================================== +// Lumina-desktop source code +// Copyright (c) 2012-2017, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== + +#include "global-includes.h" +#include "LSession.h" + +#define DEBUG 0 + +int main(int argc, char ** argv) +{ + if (argc > 1) { + if (QString(argv[1]) == QString("--version")){ + qDebug() << LDesktopUtils::LuminaDesktopVersion(); + return 0; + } + } + if(!QFile::exists(LOS::LuminaShare())){ + qDebug() << "Lumina does not appear to be installed correctly. Cannot find: " << LOS::LuminaShare(); + return 1; + } + //Setup any pre-QApplication initialization values + LTHEME::LoadCustomEnvSettings(); + LXDG::setEnvironmentVars(); + setenv("DESKTOP_SESSION","Lumina",1); + setenv("XDG_CURRENT_DESKTOP","Lumina",1); + unsetenv("QT_QPA_PLATFORMTHEME"); //causes issues with Lumina themes - not many people have this by default... + //Startup the session + LSession a(argc, argv); + if(!a.isPrimaryProcess()){ return 0; } + QTime *timer=0; + if(DEBUG){ timer = new QTime(); timer->start(); } + if(DEBUG){ qDebug() << "Theme Init:" << timer->elapsed(); } + LuminaThemeEngine theme(&a); + QObject::connect(&theme, SIGNAL(updateIcons()), &a, SLOT(reloadIconTheme()) ); + if(DEBUG){ qDebug() << "Session Setup:" << timer->elapsed(); } + a.setupSession(); + theme.refresh(); + if(DEBUG){ qDebug() << "Exec Time:" << timer->elapsed(); delete timer;} + int retCode = a.exec(); + qDebug() << "Finished Closing Down Lumina"; + return retCode; +} 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"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></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"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></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"><current dir></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> diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/DEPENDENCIES b/src-qt5/core/lumina-desktop-unified/src-WM/DEPENDENCIES new file mode 100644 index 00000000..fa0ce486 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/DEPENDENCIES @@ -0,0 +1,17 @@ +Most dependencies required to build Lumina are listed in the +DEPENDENCIES file in the directory above this one. The following +are dependencies specific to Lumina's window manager. + + +FreeBSD/TrueOS +======================= + + + + +Linux (Debian/Ubuntu) +======================= + +libxcb-screensaver0-dev + + diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/GlobalDefines.h b/src-qt5/core/lumina-desktop-unified/src-WM/GlobalDefines.h new file mode 100644 index 00000000..3ec278ac --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/GlobalDefines.h @@ -0,0 +1,74 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Global defines and enumerations for the window manager +//=========================================== +#ifndef _LUMINA_WINDOW_MANAGER_GLOBAL_DEFINES_H +#define _LUMINA_WINDOW_MANAGER_GLOBAL_DEFINES_H + +//Qt includes +#include <QObject> +#include <QFrame> +#include <QLabel> +#include <QToolButton> +#include <QMenu> +#include <QHBoxLayout> +#include <QMouseEvent> +#include <QAction> +#include <QPoint> +#include <QFile> +#include <QDir> +#include <QString> +#include <QTextStream> +#include <QUrl> +#include <QDebug> +#include <QStringList> +#include <QAbstractNativeEventFilter> +#include <QList> +#include <QX11Info> +#include <QCoreApplication> +#include <QPropertyAnimation> +#include <QAnimationGroup> +#include <QParallelAnimationGroup> +#include <QWindow> +#include <QWidget> +#include <QBackingStore> +#include <QPaintEvent> +#include <QPainter> +#include <QSettings> +#include <QHostInfo> +#include <QDesktopWidget> +#include <QStyleOption> +#include <QThread> + +// libLumina includes +#include <LuminaX11.h> +#include <LuminaXDG.h> +#include <LuminaOS.h> +#include <LuminaThemes.h> +#include <LuminaUtils.h> +#include <LuminaSingleApplication.h> + +//XCB Includes +#include <xcb/xcb.h> +#include <xcb/xproto.h> +#include <xcb/damage.h> +#include <xcb/xcb_atom.h> +#include <xcb/xcb_aux.h> //included in libxcb-util.so + +#define ANIMTIME 80 //animation time in milliseconds +//Global flags/structures +namespace LWM{ + //Flags/enumerations + enum WindowAction{MoveResize, Show, Hide, TryClose, Closed, WA_NONE}; + + //Data structures + extern LXCB *SYSTEM; +}; + + + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.cpp new file mode 100644 index 00000000..4cc6d68b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.cpp @@ -0,0 +1,102 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LLockScreen.h" +#include "ui_LLockScreen.h" + +#include <unistd.h> + +#define NUMTRIES 3 +#define WAITMINS 1 +#define DEBUG 1 + +LLockScreen::LLockScreen(QWidget *parent) : QWidget(parent), ui(new Ui::LLockScreen()){ + ui->setupUi(this); + waittime = new QTimer(this); + waittime->setInterval(WAITMINS*60000); //(too many attempts in short time) + waittime->setSingleShot(true); + refreshtime = new QTimer(this); //timer to update the wait time display + refreshtime->setInterval(6000); //6 seconds (1/10 second) + + connect(ui->tool_unlock, SIGNAL(clicked()), this, SLOT(TryUnlock()) ); + connect(ui->line_password, SIGNAL(returnPressed()), this, SLOT(TryUnlock()) ); + connect(ui->line_password, SIGNAL(textEdited(QString)), this, SIGNAL(InputDetected()) ); + connect(ui->line_password, SIGNAL(cursorPositionChanged(int,int)), this, SIGNAL(InputDetected()) ); + connect(waittime, SIGNAL(timeout()), this, SLOT(aboutToShow()) ); + connect(refreshtime, SIGNAL(timeout()), this, SLOT(UpdateLockInfo()) ); +} + +LLockScreen::~LLockScreen(){ + +} + +void LLockScreen::LoadSystemDetails(){ + //Run every time the screen is initially locked + QString user = QString(getlogin()); + ui->label_username->setText( QString(tr("Locked by: %1")).arg(user) ); + ui->label_hostname->setText( QHostInfo::localHostName() ); + ui->tool_unlock->setIcon( LXDG::findIcon("document-decrypt","") ); + attempts = 0; +} + +void LLockScreen::aboutToHide(){ + //auto-hide timeout - clear display + ui->line_password->clear(); + ui->line_password->clearFocus(); + if(refreshtime->isActive()){ refreshtime->stop(); } +} + +void LLockScreen::aboutToShow(){ + if(!waittime->isActive()){ + ui->label_info->clear(); + this->setEnabled(true); + triesleft = NUMTRIES; //back to initial number of tries + if(refreshtime->isActive()){ refreshtime->stop(); } + }else{ + if(!refreshtime->isActive()){ refreshtime->start(); } + } + UpdateLockInfo(); + ui->line_password->clear(); + ui->line_password->setFocus(); +} + +// ================= +// PRIVATE SLOTS +// ================= +void LLockScreen::UpdateLockInfo(){ + QString info; + /*if(triesleft>0 && triesleft<NUMTRIES ){ + if(triesleft==1){info = tr("1 Attempt Left"); } + else{info = QString(tr("%1 Attempts Left")).arg(QString::number(triesleft)); } + }else*/ + if(waittime->isActive()){ + info = tr("Too Many Failures")+"\n"+ QString(tr("Wait %1 Minutes")).arg( QString::number(qRound(waittime->remainingTime()/6000.0)/10.0) ); + }else if(attempts>0){ info.append("\n"+QString(tr("Failed Attempts: %1")).arg(QString::number(attempts)) ); } + ui->label_info->setText(info); +} + +void LLockScreen::TryUnlock(){ + attempts++; + this->setEnabled(false); + QString pass = ui->line_password->text(); + ui->line_password->clear(); + bool ok = (LUtils::runCmd("lumina-checkpass", QStringList() << pass) == 0); + if(ok){ + emit ScreenUnlocked(); + this->setEnabled(true); + }else{ + triesleft--; + if(triesleft>0){ + this->setEnabled(true); + }else{ + waittime->start(); + refreshtime->start(); + } + ui->line_password->setFocus(); + } + UpdateLockInfo(); + +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.h b/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.h new file mode 100644 index 00000000..040499c1 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.h @@ -0,0 +1,42 @@ +//=========================================== +// 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_LOCK_SCREEN_WIDGET_H +#define _LUMINA_DESKTOP_LOCK_SCREEN_WIDGET_H + +#include "GlobalDefines.h" + +namespace Ui{ + class LLockScreen; +}; + +class LLockScreen : public QWidget{ + Q_OBJECT +public: + LLockScreen(QWidget *parent = 0); + ~LLockScreen(); + + void LoadSystemDetails(); //Run right after the screen is initially locked + +public slots: + void aboutToHide(); //auto-hide timeout (can happen multiple times per lock) + void aboutToShow(); //about to be re-shown (can happen multiple times per lock) + +private: + Ui::LLockScreen *ui; + int triesleft, attempts; + QTimer *waittime; + QTimer *refreshtime; + +private slots: + void UpdateLockInfo(); + void TryUnlock(); + +signals: + void ScreenUnlocked(); + void InputDetected(); +}; +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.ui b/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.ui new file mode 100644 index 00000000..7f0b45b8 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LLockScreen.ui @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>LLockScreen</class> + <widget class="QWidget" name="LLockScreen"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>289</width> + <height>188</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <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="QFrame" name="frame_unlock"> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_hostname"> + <property name="font"> + <font> + <weight>50</weight> + <italic>true</italic> + <bold>false</bold> + <underline>true</underline> + </font> + </property> + <property name="text"> + <string notr="true">hostname</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_username"> + <property name="text"> + <string notr="true">Locked by username</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_info"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLineEdit" name="line_password"> + <property name="inputMask"> + <string notr="true"/> + </property> + <property name="echoMode"> + <enum>QLineEdit::Password</enum> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="placeholderText"> + <string>Password</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="tool_unlock"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="text"> + <string>Unlock Session</string> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LScreenSaver.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/LScreenSaver.cpp new file mode 100644 index 00000000..0c92784e --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LScreenSaver.cpp @@ -0,0 +1,181 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LScreenSaver.h" +#include <QScreen> +#include <QApplication> + +#define DEBUG 1 + +LScreenSaver::LScreenSaver() : QWidget(0,Qt::BypassWindowManagerHint | Qt::WindowStaysOnTopHint){ + starttimer = new QTimer(this); + starttimer->setSingleShot(true); + locktimer = new QTimer(this); + locktimer->setSingleShot(true); + hidetimer = new QTimer(this); + hidetimer->setSingleShot(true); + + LOCKER = new LLockScreen(this); + LOCKER->hide(); + settings = new QSettings("lumina-desktop","lumina-screensaver",this); + SSRunning = SSLocked = updating = false; + this->setObjectName("LSCREENSAVERBASE"); + this->setStyleSheet("LScreenSaver#LSCREENSAVERBASE{ background: grey; }"); + this->setMouseTracking(true); + connect(starttimer, SIGNAL(timeout()), this, SLOT(ShowScreenSaver()) ); + connect(locktimer, SIGNAL(timeout()), this, SLOT(LockScreen()) ); + connect(hidetimer, SIGNAL(timeout()), this, SLOT(HideLockScreen()) ); + connect(LOCKER, SIGNAL(ScreenUnlocked()), this, SLOT(SSFinished()) ); + connect(LOCKER, SIGNAL(InputDetected()), this, SLOT(newInputEvent()) ); +} + +LScreenSaver::~LScreenSaver(){ + +} + +bool LScreenSaver::isLocked(){ + return SSLocked; +} + +void LScreenSaver::UpdateTimers(){ + //This is generally used for programmatic changes + if(starttimer->isActive()){ starttimer->stop();} + if(locktimer->isActive()){ locktimer->stop(); } + if(hidetimer->isActive()){ hidetimer->stop(); } + + if(!SSRunning && !SSLocked && (starttimer->interval() > 1000) ){ starttimer->start(); } //time to SS start + else if( SSRunning && !SSLocked && (locktimer->interval() > 1000 ) ){ locktimer->start(); } //time to lock + else if( !SSRunning && SSLocked ){ hidetimer->start(); } //time to hide lock screen +} + +// =========== +// PUBLIC SLOTS +// =========== +void LScreenSaver::start(){ + reloadSettings(); //setup all the initial time frames + starttimer->start(); +} + +void LScreenSaver::reloadSettings(){ + settings->sync(); + starttimer->setInterval( settings->value("timedelaymin",10).toInt() * 60000 ); + locktimer->setInterval( settings->value("lockdelaymin",1).toInt() * 60000 ); + hidetimer->setInterval( settings->value("hidesecs",15).toInt() * 1000 ); +} + +void LScreenSaver::newInputEvent(){ + if(updating){ return; } //in the middle of making changes which could cause an event + if(DEBUG){ qDebug() << "New Input Event"; } + if(SSRunning && SSLocked){ + //Running and locked + // Hide the running setting, and display the lock screen + HideScreenSaver(); + ShowLockScreen(); + }else if(SSRunning){ + //Only running, not locked + HideScreenSaver(); + } + UpdateTimers(); + +} + +void LScreenSaver::LockScreenNow(){ + ShowScreenSaver(); + LockScreen(); +} + +// =========== +// PRIVATE SLOTS +// =========== +void LScreenSaver::ShowScreenSaver(){ + if(DEBUG){ qDebug() << "Showing Screen Saver:" << QDateTime::currentDateTime().toString(); } + SSRunning = true; + updating = true; + //Now remove any current Base widgets (prevent any lingering painting between sessions) + for(int i=0; i<BASES.length(); i++){ + if(DEBUG){ qDebug() << " - Removing SS Base"; } + delete BASES.takeAt(i); i--; + } + //Now go through and create/show all the various widgets + QList<QScreen*> SCREENS = QApplication::screens(); + QRect bounds; + cBright = LOS::ScreenBrightness(); + if(cBright>0){ LOS::setScreenBrightness(cBright/2); } //cut to half while the screensaver is active + for(int i=0; i<SCREENS.length(); i++){ + bounds = bounds.united(SCREENS[i]->geometry()); + if(DEBUG){ qDebug() << " - New SS Base:" << i; } + BASES << new SSBaseWidget(this, settings); + connect(BASES[i], SIGNAL(InputDetected()), this, SLOT(newInputEvent()) ); + //Setup the geometry of the base to match the screen + BASES[i]->setGeometry(SCREENS[i]->geometry()); //match this screen geometry + BASES[i]->setPlugin(settings->value("screenplugin"+QString::number(i+1), settings->value("defaultscreenplugin","random").toString() ).toString() ); + } + //Now set the overall parent widget geometry and show everything + this->setGeometry(bounds); //overall background widget + if(!this->isActiveWindow()){ + this->raise(); + this->show(); + this->activateWindow(); + } + for(int i=0; i<BASES.length(); i++){ + BASES[i]->show(); + BASES[i]->startPainting(); + } + updating = false; + UpdateTimers(); +} + +void LScreenSaver::ShowLockScreen(){ + if(DEBUG){ qDebug() << "Showing Lock Screen:" << QDateTime::currentDateTime().toString(); } + LOCKER->aboutToShow(); + //Move the screen locker to the appropriate spot + QPoint ctr = QApplication::desktop()->screenGeometry(QCursor::pos()).center(); + LOCKER->resize(LOCKER->sizeHint()); + LOCKER->move(ctr - QPoint(LOCKER->width()/2, LOCKER->height()/2) ); + LOCKER->show(); + //Start the timer for hiding the lock screen due to inactivity + UpdateTimers(); +} + +void LScreenSaver::HideScreenSaver(){ + if(DEBUG){ qDebug() << "Hiding Screen Saver:" << QDateTime::currentDateTime().toString(); } + SSRunning = false; + if(cBright>0){ LOS::setScreenBrightness(cBright); } //return to current brightness + if(!SSLocked){ + this->hide(); + emit ClosingScreenSaver(); + } + for(int i=0; i<BASES.length(); i++){ + BASES[i]->hide(); + BASES[i]->stopPainting(); + } + UpdateTimers(); +} + +void LScreenSaver::HideLockScreen(){ + if(DEBUG){ qDebug() << "Hiding Lock Screen:" << QDateTime::currentDateTime().toString(); } + //Leave the Locked flag set (still locked, just not visible) + LOCKER->aboutToHide(); + LOCKER->hide(); + this->repaint(); + if(SSLocked){ ShowScreenSaver(); } + UpdateTimers(); +} + +void LScreenSaver::LockScreen(){ + if(SSLocked){ return; } + if(DEBUG){ qDebug() << "Locking Screen:" << QDateTime::currentDateTime().toString(); } + SSLocked = true; + LOCKER->LoadSystemDetails(); + UpdateTimers(); +} + +void LScreenSaver::SSFinished(){ + if(DEBUG){ qDebug() << "Screensaver Finished:" << QDateTime::currentDateTime().toString(); } + SSLocked = false; + HideLockScreen(); + HideScreenSaver(); +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LScreenSaver.h b/src-qt5/core/lumina-desktop-unified/src-WM/LScreenSaver.h new file mode 100644 index 00000000..5119d8b1 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LScreenSaver.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 +//=========================================== +#ifndef _LUMINA_DESKTOP_SCREEN_SAVER_H +#define _LUMINA_DESKTOP_SCREEN_SAVER_H + +#include "GlobalDefines.h" + +#include "SSBaseWidget.h" +#include "LLockScreen.h" + +class LScreenSaver : public QWidget{ + Q_OBJECT +public: + LScreenSaver(); + ~LScreenSaver(); + + bool isLocked(); + +private: + QTimer *starttimer, *locktimer, *hidetimer; + QSettings *settings; + QList<SSBaseWidget*> BASES; + LLockScreen *LOCKER; + int cBright; + bool SSRunning, SSLocked, updating; + + void UpdateTimers(); + +public slots: + void start(); + void reloadSettings(); + void newInputEvent(); + void LockScreenNow(); + +private slots: + void ShowScreenSaver(); + void ShowLockScreen(); + void HideScreenSaver(); + void HideLockScreen(); + + void LockScreen(); + void SSFinished(); + +signals: + void StartingScreenSaver(); + void ClosingScreenSaver(); + +protected: + void mouseMoveEvent(QMouseEvent*){ + QTimer::singleShot(0,this, SLOT(newInputEvent())); + } + +}; + +#endif
\ No newline at end of file diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LWindow.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/LWindow.cpp new file mode 100644 index 00000000..84ff2ffd --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LWindow.cpp @@ -0,0 +1,474 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LWindow.h" + +LWindowFrame::LWindowFrame(WId client, QWidget *parent) : QFrame(parent, Qt::X11BypassWindowManagerHint){ + activeState = LWindowFrame::Normal; + CID = client; + lastAction = LWM::WA_NONE; + Closing = false; + //qDebug() << "New Window:" << CID << "Frame:" << this->winId(); + this->setMouseTracking(true); //need this to determine mouse location when not clicked + this->setObjectName("LWindowFrame"); + this->setStyleSheet("LWindowFrame#LWindowFrame{ border: 2px solid white; border-radius:3px; } QWidget#TitleBar{background: grey; } QLabel{ color: black; }"); + InitWindow(); //initially create all the child widgets + //LWM::SYSTEM->setupEventsForFrame(this->winId()); + updateAppearance(); //this loads the appearance based on window/theme settings + //QApplication::processEvents(); + //Now set the frame size on this window + SyncSize(); + SyncText(); + this->show(); +} + +LWindowFrame::~LWindowFrame(){ +} + +// ================= +// PRIVATE +// ================= +void LWindowFrame::InitWindow(){ + anim = new QPropertyAnimation(this); //For simple window animations + anim->setTargetObject(this); + anim->setDuration(ANIMTIME); //In milliseconds + connect(anim, SIGNAL(finished()), this, SLOT(finishedAnimation()) ); + titleBar = new QLabel(this); //This is the "container" for all the title buttons/widgets + titleBar->setObjectName("TitleBar"); + titleBar->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); + titleBar->setFocusPolicy(Qt::NoFocus); + titleBar->setCursor(Qt::ArrowCursor); + title = new QLabel(this); //Shows the window title/text + title->setObjectName("Title"); + title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + title->setCursor(Qt::ArrowCursor); + title->setFocusPolicy(Qt::NoFocus); + icon = new QLabel(this); //Contains the window icon + icon->setObjectName("Icon"); + icon->setCursor(Qt::ArrowCursor); + icon->setFocusPolicy(Qt::NoFocus); + minB = new QToolButton(this); //Minimize Button + minB->setObjectName("Minimize"); + minB->setCursor(Qt::ArrowCursor); + minB->setFocusPolicy(Qt::NoFocus); + connect(minB, SIGNAL(clicked()), this, SLOT(minClicked()) ); + maxB = new QToolButton(this); //Maximize Button + maxB->setObjectName("Maximize"); + maxB->setCursor(Qt::ArrowCursor); + maxB->setFocusPolicy(Qt::NoFocus); + connect(maxB, SIGNAL(clicked()), this, SLOT(maxClicked()) ); + closeB = new QToolButton(this); + closeB->setObjectName("Close"); + closeB->setCursor(Qt::ArrowCursor); + closeB->setFocusPolicy(Qt::NoFocus); + connect(closeB, SIGNAL(clicked()), this, SLOT(closeClicked()) ); + otherB = new QToolButton(this); //Button to place any other actions + otherB->setObjectName("Options"); + otherB->setCursor(Qt::ArrowCursor); + otherB->setPopupMode(QToolButton::InstantPopup); + otherB->setStyleSheet("QToolButton::menu-indicator{ image: none; }"); + otherB->setFocusPolicy(Qt::NoFocus); + otherM = new QMenu(this); //menu of "other" actions for the window + otherB->setMenu(otherM); + connect(otherM, SIGNAL(triggered(QAction*)), this, SLOT(otherClicked(QAction*)) ); + //Now assemble the titlebar + QHBoxLayout *HL = new QHBoxLayout(this); + HL->setContentsMargins(0,0,0,0); + HL->addWidget(otherB); + HL->addWidget(icon); + HL->addWidget(title); + HL->addWidget(minB); + HL->addWidget(maxB); + HL->addWidget(closeB); + titleBar->setLayout(HL); + QVBoxLayout *VL = new QVBoxLayout(this); + this->setLayout(VL); + //The WinWidget container appears shifted right/down by 1 pixel for some reason + // Adjust the margins to account for this variation + VL->setContentsMargins(1,1,2,2); + VL->setSpacing(0); + //Have the window take the same initial size of the client window + QRect geom = LWM::SYSTEM->WM_Window_Geom(CID); + qDebug() << " - Load Size Hints" << "initial size:" << geom.size(); + icccm_size_hints SH = LWM::SYSTEM->WM_ICCCM_GetNormalHints(CID); + qDebug() << " - - Got Normal Hints"; + if(!SH.isValid()){ SH = LWM::SYSTEM->WM_ICCCM_GetSizeHints(CID); } + qDebug() << " - - Start resizing..."; + if(SH.base_width>geom.width() && SH.base_height>geom.height()){ this->resize(SH.base_width, SH.base_height); } + else if(SH.min_width>geom.width() && SH.min_height>geom.height()){ this->resize(SH.min_width, SH.min_height); } + else if(SH.width>geom.width() && SH.height>geom.height()){ this->resize(SH.width, SH.height); } + else if(geom.isNull()){ this->resize(100,80); } + else{ this->resize( geom.size() ); } + qDebug() << " - done"; + + //Now embed the native window into the frame + WIN = QWindow::fromWinId(CID); + WinWidget = QWidget::createWindowContainer( WIN, this); + WinWidget->setCursor(Qt::ArrowCursor); //this is just a fallback - the window itself will adjust it + //WINBACK = new QBackingStore(WIN); //create a data backup for the widget + + //Now assemble te initial layout for the window (all while still invisible) + /*VL->addWidget(titleBar); + VL->addWidget(WinWidget); + VL->setStretch(1,1);*/ +} + +LWindowFrame::ModState LWindowFrame::getStateAtPoint(QPoint pt, bool setoffset){ + //Note: pt should be in widget-relative coordinates, not global + if(!this->layout()->geometry().contains(pt)){ + //above the frame itself - need to figure out which quadrant it is in (8-directions) + if(pt.y() < 3){ + //One of the top options + if(pt.x() < 3){ + if(setoffset){ offset.setX(pt.x()); offset.setY(pt.y()); } //difference from top-left corner + return ResizeTopLeft; + }else if(pt.x() > (this->width()-3)){ + if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(pt.y()); } //difference from top-right corner + return ResizeTopRight; + }else{ + if(setoffset){ offset.setX(0); offset.setY(pt.y()); } //difference from top edge (X does not matter) + return ResizeTop; + } + }else if(pt.y() > (this->height()-3) ){ + //One of the bottom options + if(pt.x() < 3){ + if(setoffset){ offset.setX(pt.x()); offset.setY(this->height()-pt.y()); } //difference from bottom-left corner + return ResizeBottomLeft; + }else if(pt.x() > (this->width()-3)){ + if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(this->height()-pt.y()); } //difference from bottom-right corner + return ResizeBottomRight; + }else{ + if(setoffset){ offset.setX(0); offset.setY(this->height() - pt.y()); } //difference from bottom edge (X does not matter) + return ResizeBottom; + } + }else{ + //One of the side options + if(pt.x() < 3){ + if(setoffset){ offset.setX(pt.x()); offset.setY(0); } //difference from left edge (Y does not matter) + return ResizeLeft; + }else if(pt.x() > (this->width()-3) ){ + if(setoffset){ offset.setX(this->width()-pt.x()); offset.setY(0); } //difference from right edge (Y does not matter) + return ResizeRight; + }else{ + return Normal; + } + } + } + return Normal; +} + +void LWindowFrame::setMouseCursor(ModState state, bool override){ + Qt::CursorShape shape; + switch(state){ + case Normal: + shape = Qt::ArrowCursor; + break; + case Move: + shape = Qt::SizeAllCursor; + break; + case ResizeTop: + shape = Qt::SizeVerCursor; + break; + case ResizeTopRight: + shape = Qt::SizeBDiagCursor; + break; + case ResizeRight: + shape = Qt::SizeHorCursor; + break; + case ResizeBottomRight: + shape = Qt::SizeFDiagCursor; + break; + case ResizeBottom: + shape = Qt::SizeVerCursor; + break; + case ResizeBottomLeft: + shape = Qt::SizeBDiagCursor; + break; + case ResizeLeft: + shape = Qt::SizeHorCursor; + break; + case ResizeTopLeft: + shape = Qt::SizeFDiagCursor; + break; + } + if(override){ + QApplication::setOverrideCursor(QCursor(shape)); + }else{ + this->setCursor(shape); + } +} + +// ========================== +// WINDOW INTERACTIONS +//========================== +void LWindowFrame::SyncSize(bool fromwin){ + //sync the window/frame geometries (generally only done before embedding the client window) + int frame = this->frameWidth(); + int TH = titleBar->height(); + //Now load the information about the window and adjust the frame to match + if(fromwin){ lastGeom = LWM::SYSTEM->WM_Window_Geom(CID); } + else{ lastGeom = this->geometry(); } + qDebug() << "Initial Size:" << lastGeom << frame << TH; + //Add in the frame size + lastGeom.moveTop(lastGeom.y()-frame-TH); + lastGeom.setHeight(lastGeom.height()+(2*frame)+TH); + lastGeom.moveLeft(lastGeom.x()-frame); + lastGeom.setWidth( lastGeom.width()+(2*frame)); + QList<unsigned int> margins; + margins << frame << frame << frame+TH << frame; //L/R/Top/Bottom + qDebug() << " - With Frame:" << lastGeom; + //Now adjust for a out-of-bounds location + if(lastGeom.x() < 0){ lastGeom.moveLeft(0); } + if(lastGeom.y() < 0){ lastGeom.moveTop(0); } + qDebug() << " - Adjusted:" << lastGeom; + this->setGeometry(lastGeom); + LWM::SYSTEM->WM_Set_Frame_Extents(CID, margins); +} + +void LWindowFrame::SyncText(){ + QString txt = WIN->title(); + if(txt.isEmpty()){ txt = LWM::SYSTEM->WindowName(CID); } + if(txt.isEmpty()){ txt = LWM::SYSTEM->OldWindowName(CID); } + if(txt.isEmpty()){ txt = LWM::SYSTEM->WindowVisibleName(CID); } + if(txt.isEmpty()){ txt = LWM::SYSTEM->WindowIconName(CID); } + if(txt.isEmpty()){ txt = LWM::SYSTEM->WindowVisibleIconName(CID); } + if(txt.isEmpty()){ txt = LWM::SYSTEM->WM_ICCCM_GetClass(CID); } + title->setText(txt); +} + +// SIMPLE ANIMATIONS +void LWindowFrame::showAnimation(LWM::WindowAction act){ + bool useanimation = (act!=lastAction); + if(anim->state()==QAbstractAnimation::Running){ + qDebug() << "New Animation Event:" << act; + return; + } + //Setup the animation routine + if(act==LWM::Show){ + if(useanimation){ + lastGeom = this->geometry(); + //Expand out from center point + anim->setPropertyName("geometry"); + anim->setStartValue( QRect(lastGeom.center(), QSize(0,0) ) ); + anim->setEndValue( this->geometry() ); + //Fade in gradually + //anim->setPropertyName("windowOpacity"); + //anim->setStartValue( 0.0 ); + //anim->setEndValue( 1.0 ); + }else{ + ShowClient(true); + this->raise(); + this->show(); //just show it right away + } + + }else if(act==LWM::Hide){ + if(useanimation){ + //Collapse in on center point + lastGeom = this->geometry(); + anim->setPropertyName("geometry"); + anim->setStartValue( QRect(this->geometry()) ); + anim->setEndValue( QRect(this->geometry().center(), QSize(0,0) ) ); + }else{ + this->hide(); //just hide it right away + } + }else if(act==LWM::Closed){ + //Need to clean up the container widget first to prevent XCB errors + //qDebug() << "Window Closed:" << WIN->winId() << CID; + if(useanimation){ + //Collapse in on center line + lastGeom = this->geometry(); + anim->setPropertyName("geometry"); + anim->setStartValue( QRect(this->geometry()) ); + anim->setEndValue( QRect(this->geometry().x(), this->geometry().center().y(), this->width(), 0 ) ); + }else{ + CloseAll(); //just hide it right away + } + } + if(useanimation){ + ShowClient(false); + this->show(); + qDebug() << " - Starting Animation:" << act; + lastAction = act; + anim->start(); + }; +} + +void LWindowFrame::ShowClient(bool show){ + if(show && this->layout()->indexOf(WinWidget)<0 && !Closing){ + while(this->layout()->count()>0){ this->layout()->removeItem(0); } + this->layout()->addWidget(titleBar); + this->layout()->setAlignment(titleBar, Qt::AlignTop); + this->layout()->addWidget(WinWidget); + static_cast<QVBoxLayout*>(this->layout())->setStretch(1,1); + LWM::SYSTEM->WM_ShowWindow(CID); + }else if( !show && this->layout()->indexOf(WinWidget)>=0){ + LWM::SYSTEM->WM_HideWindow(CID); + this->layout()->removeWidget(WinWidget); + } +} + +void LWindowFrame::finishedAnimation(){ + //Also set any final values + qDebug() << " - Finished Animation:" << lastAction; + switch(lastAction){ + case LWM::Show: + ShowClient(true); + break; + case LWM::Closed: + case LWM::Hide: + this->lower(); + this->hide(); + LWM::SYSTEM->WM_HideWindow(this->winId()); + default: + break; + } + if(Closing){ + qDebug() << "Emitting finished signal"; + emit Finished(); + } +} + +// ================= +// PUBLIC SLOTS +// ================= +void LWindowFrame::updateAppearance(){ + //Reload any button icons and such + minB->setIcon(LXDG::findIcon("window-suppressed","")); + maxB->setIcon(LXDG::findIcon("view-fullscreen","")); + closeB->setIcon(LXDG::findIcon("application-exit","")); + otherB->setIcon(LXDG::findIcon("configure","")); +} + +void LWindowFrame::windowChanged(LWM::WindowAction act){ + //A window property was changed - update accordingly + switch(act){ + case LWM::Closed: + Closing = true; + case LWM::Hide: + case LWM::Show: + showAnimation(act); + break; + case LWM::MoveResize: + //Re-adjust to the new position/size of the window + SyncSize(true); + break; + default: + break; //do nothing + } +} +// ================= +// PRIVATE SLOTS +// ================= +void LWindowFrame::closeClicked(){ + qDebug() << "Closing Window" << LWM::SYSTEM->WM_ICCCM_GetClass(CID); + //First try the close event to let the client app do cleanup/etc + LWM::SYSTEM->WM_CloseWindow(CID); +} + +void LWindowFrame::minClicked(){ + qDebug() << "Minimize Window"; + windowChanged(LWM::Hide); +} + +void LWindowFrame::maxClicked(){ + if(normalGeom.isNull()){ + qDebug() << "Maximize Window"; + normalGeom = this->geometry(); //save for later + this->showMaximized(); + }else{ + qDebug() << "Restore Window"; + this->showNormal(); + this->setGeometry(normalGeom); + normalGeom = QRect(); //clear it + } +} + +void LWindowFrame::otherClicked(QAction* act){ + QString action = act->whatsThis(); +} + +void LWindowFrame::CloseAll(){ + qDebug() << " - Closing Frame"; + this->hide(); + emit Finished(); +} +// ===================== +// PROTECTED +// ===================== +void LWindowFrame::mousePressEvent(QMouseEvent *ev){ + qDebug() << "Frame Mouse Press Event"; + offset.setX(0); offset.setY(0); + if(activeState != Normal){ return; } // do nothing - already in a state of grabbed mouse + this->activateWindow(); + LWM::SYSTEM->WM_Set_Active_Window(CID); + if(this->childAt(ev->pos())!=0){ + //Check for any non-left-click event and skip it + if(ev->button()!=Qt::LeftButton){ return; } + activeState = Move; + offset.setX(ev->pos().x()); offset.setY(ev->pos().y()); + }else{ + //Clicked on the frame somewhere + activeState = getStateAtPoint(ev->pos(), true); //also have it set the offset variable + } + setMouseCursor(activeState, true); //this one is an override cursor + +} + +void LWindowFrame::mouseMoveEvent(QMouseEvent *ev){ + ev->accept(); + if(activeState == Normal){ + setMouseCursor( getStateAtPoint(ev->pos()) ); //just update the mouse cursor + + }else{ + //Currently in a modification state + QRect geom = this->geometry(); + switch(activeState){ + case Move: + geom.moveTopLeft(ev->globalPos()-offset); //will not change size + break; + case ResizeTop: + geom.setTop(ev->globalPos().y()-offset.y()); + break; + case ResizeTopRight: + geom.setTopRight(ev->globalPos()-offset); + break; + case ResizeRight: + geom.setRight(ev->globalPos().x()-offset.x()); + break; + case ResizeBottomRight: + geom.setBottomRight(ev->globalPos()-offset); + break; + case ResizeBottom: + geom.setBottom(ev->globalPos().y()-offset.y()); + break; + case ResizeBottomLeft: + geom.setBottomLeft(ev->globalPos()-offset); + break; + case ResizeLeft: + geom.setLeft(ev->globalPos().x()-offset.x()); + break; + case ResizeTopLeft: + geom.setTopLeft(ev->globalPos()-offset); + break; + default: + break; + } + this->setGeometry(geom); + } +} + +void LWindowFrame::mouseReleaseEvent(QMouseEvent *ev){ + //Check for a right-click event + qDebug() << "Frame Mouse Release Event"; + ev->accept(); + if( (activeState==Normal) && (this->childAt(ev->pos())==titleBar) && (ev->button()==Qt::RightButton) ){ + otherM->popup(ev->globalPos()); + return; + } + activeState = Normal; + QApplication::restoreOverrideCursor(); + setMouseCursor( getStateAtPoint(ev->pos()) ); +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LWindow.h b/src-qt5/core/lumina-desktop-unified/src-WM/LWindow.h new file mode 100644 index 00000000..ceefca83 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LWindow.h @@ -0,0 +1,114 @@ +//=========================================== +// 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_WINDOW_FRAME_H +#define _LUMINA_DESKTOP_WINDOW_FRAME_H + +#include "GlobalDefines.h" + +class LWindowFrame : public QFrame{ + Q_OBJECT +public: + LWindowFrame(WId client, QWidget *parent = 0); //MUST have a valid client window + ~LWindowFrame(); + +private: + void InitWindow(); //Initialize all the internal widgets + + //Window status + enum ModState{Normal, Move, ResizeTop, ResizeTopRight, ResizeRight, ResizeBottomRight, ResizeBottom, ResizeBottomLeft, ResizeLeft, ResizeTopLeft}; + ModState activeState; + QPoint offset; //needed for movement calculations (offset from mouse click to movement point) + //Functions for getting/setting state + ModState getStateAtPoint(QPoint pt, bool setoffset = false); //generally used for mouse location detection + void setMouseCursor(ModState, bool override = false); //Update the mouse cursor based on state + + //General Properties/Modifications + WId CID; //Client ID + QWindow *WIN; //Embedded window container + QWidget *WinWidget; + bool Closing; + LWM::WindowAction lastAction; + //QBackingStore *WINBACK; + void SyncSize(bool fromwin = false); //sync the window/frame geometries + void SyncText(); + + //Window Frame Widgets/Items + QLabel *titleBar, *title, *icon; + QToolButton *minB, *maxB, *closeB, *otherB; + QMenu *otherM; //menu of "other" actions for the window + QRect normalGeom; //used for restoring back to original size after maximization/fullscreen + + //Animations + QPropertyAnimation *anim; //used for appear/disappear animations + QRect lastGeom; //used for appear/disappear animations + void showAnimation(LWM::WindowAction); //sets lastAction + void ShowClient(bool show); + +public slots: + //These slots are generally used for the outside event watcher to prod for changes + void updateAppearance(); //reload the theme and change styling as necessary + void windowChanged(LWM::WindowAction); + +private slots: + void finishedAnimation(); //uses lastAction + void closeClicked(); + void minClicked(); + void maxClicked(); + void otherClicked(QAction*); + + void CloseAll(); + +protected: + void mousePressEvent(QMouseEvent*); + void mouseMoveEvent(QMouseEvent*); + void mouseReleaseEvent(QMouseEvent*); + +signals: + void Finished(); //This means the window is completely finished (with animations and such) and should be removed from any lists + +}; + +class LWindow : public QObject{ + Q_OBJECT +signals: + void Finished(WId client); //ready to be removed +private: + WId CID; + LWindowFrame *FID; + bool needsFrame(QList<LXCB::WINDOWTYPE> list){ + if(list.isEmpty()){ return !LWM::SYSTEM->WM_ICCCM_GetClass(CID).contains("Lumina-DE"); } //assume a normal window (fallback) + return !(list.contains(LXCB::T_DESKTOP) || list.contains(LXCB::T_DOCK) || list.contains(LXCB::T_TOOLBAR) \ + || list.contains(LXCB::T_SPLASH) || list.contains(LXCB::T_DROPDOWN_MENU) \ + || list.contains(LXCB::T_TOOLTIP) || list.contains(LXCB::T_POPUP_MENU) || list.contains(LXCB::T_TOOLTIP) \ + || list.contains(LXCB::T_COMBO) || list.contains(LXCB::T_DND) ); + } +private slots: + void frameclosed(){ + qDebug() << " - Window got frame closed signal"; + //FID->close(); + //delete FID; + emit Finished(CID); + } +public: + LWindow(WId client){ + FID= 0; + CID = client; + if( needsFrame(LWM::SYSTEM->WM_Get_Window_Type(CID)) ){ + FID = new LWindowFrame(CID); + connect(FID, SIGNAL(Finished()), this, SLOT(frameclosed()) ); + } + } + ~LWindow(){ + if(FID!=0){delete FID;} + } + + WId clientID(){ return CID; } + bool hasFrame(){ return FID!=0; } + LWindowFrame* frame(){ return FID; } + +}; +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LWindowManager.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/LWindowManager.cpp new file mode 100644 index 00000000..14ce6897 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LWindowManager.cpp @@ -0,0 +1,186 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LWindowManager.h" + +#define DEBUG 1 + +LWindowManager::LWindowManager(){ + +} + +LWindowManager::~LWindowManager(){ + +} + +bool LWindowManager::start(){ + //Setup the initial screen/session values + LWM::SYSTEM->WM_Set_Root_Supported(); + LWM::SYSTEM->WM_SetNumber_Desktops(1); + LWM::SYSTEM->WM_Set_Current_Desktop(0); + LWM::SYSTEM->WM_Set_Desktop_Names(QStringList() << "one"); + QRect totgeom; + QList<QPoint> viewports; + QList<QRect> geoms; + for(int i=0; i<QApplication::desktop()->screenCount(); i++){ + geoms << QApplication::desktop()->screen(i)->geometry(); + viewports << QPoint(0,0); + totgeom = QApplication::desktop()->screen(i)->geometry(); + } + LWM::SYSTEM->WM_Set_Desktop_Geometry(totgeom.size()); + LWM::SYSTEM->WM_Set_Desktop_Viewport(viewports); + LWM::SYSTEM->WM_Set_Workarea(geoms); + //Should probably do a quick loop over any existing windows with the root as parent (just in case) + QList<WId> initial = LWM::SYSTEM->WM_RootWindows(); + for(int i=0; i<initial.length(); i++){ + NewWindow(initial[i], false); //These ones did not explicitly request to be mapped + } + RestackWindows(); + return true; +} + +void LWindowManager::stop(){ + for(int i=0; i<WINS.length(); i++){ + if(WINS[i]->hasFrame()){ + LWM::SYSTEM->UnembedWindow(WINS[i]->clientID()); + WINS[i]->frame()->close(); + } + } +} +//=============== +// PUBLIC SLOTS +//=============== +void LWindowManager::NewWindow(WId win, bool requested){ + //Verify that this window can/should be managed first + //if(DEBUG){ qDebug() << "New Window:" << LWM::SYSTEM->WM_ICCCM_GetClass(win); } + QString wclass = LWM::SYSTEM->WM_ICCCM_GetClass(win); + if( wclass.contains("lumina-wm",Qt::CaseInsensitive) ){ return; } //just in case: prevent recursion + else{ + bool ok = (wclass.isEmpty() ? false : LWM::SYSTEM->WM_ManageWindow(win, requested) ); + if(!ok){ + //See if this window is just a transient pointing to some other window + WId tran = LWM::SYSTEM->WM_ICCCM_GetTransientFor(win); + if(tran!=win && tran!=0){ + win = tran; + ok = LWM::SYSTEM->WM_ManageWindow(win); + } + } + if(!ok){ return; } + } + if(DEBUG){ qDebug() << "New Managed Window:" << LWM::SYSTEM->WM_ICCCM_GetClass(win); } + LWM::SYSTEM->WM_Set_Active_Window(win); + LWindow *lwin = new LWindow(win); + connect(lwin, SIGNAL(Finished(WId)), this, SLOT(FinishedWindow(WId)) ); + WINS << lwin; + if(lwin->hasFrame()){ + lwin->frame()->windowChanged(LWM::Show); //Make sure to show it right away + }else{ + LWM::SYSTEM->WM_ShowWindow(win); //just map the window right now + } +} + +void LWindowManager::ClosedWindow(WId win){ + for(int i=0; i<WINS.length(); i++){ + if(WINS[i]->clientID()==win){ + qDebug() << " - Closed Window"; + if(WINS[i]->hasFrame()){ WINS[i]->frame()->windowChanged(LWM::Closed); } //do any animations/cleanup + else{ FinishedWindow(win); } + break; + } + } +} + +void LWindowManager::ModifyWindow(WId win, LWM::WindowAction act){ + for(int i=0; i<WINS.length(); i++){ + if(WINS[i]->clientID()==win){ + if(WINS[i]->hasFrame()){ WINS[i]->frame()->windowChanged(act); } + return; + } + } + //If it gets this far - it is an unmanaged window + if(act==LWM::Show){ + NewWindow(win); + } + RestackWindows(); +} + +void LWindowManager::RestackWindows(){ + Stack_Desktop.clear(); Stack_Below.clear(); Stack_Normal.clear(); Stack_Above.clear(); Stack_Fullscreen.clear(); + QList<WId> currwins; + int cwork = LWM::SYSTEM->WM_Get_Current_Desktop(); + int winwork = -1; + QList<LXCB::WINDOWSTATE> states; + QList<LXCB::WINDOWTYPE> types; + for(int i=0; i<WINS.length(); i++){ + //Only show windows on the current desktop + winwork = LWM::SYSTEM->WM_Get_Desktop(WINS[i]->clientID()); + states = LWM::SYSTEM->WM_Get_Window_States(WINS[i]->clientID()); + types = LWM::SYSTEM->WM_Get_Window_Type(WINS[i]->clientID()); + WId id = WINS[i]->clientID(); + if(WINS[i]->hasFrame()){ id = WINS[i]->frame()->winId(); } + if(winwork<0 || winwork == cwork || states.contains(LXCB::S_STICKY) ){ + //Now check the state/type and put it in the proper stack + currwins << WINS[i]->clientID(); //add this to the overall "age" list + //Now add it to the proper stack + if(types.contains(LXCB::T_DESKTOP)){ Stack_Desktop << id; } + else if(states.contains(LXCB::S_BELOW)){ Stack_Below << id; } + else if(types.contains(LXCB::T_DOCK) || states.contains(LXCB::S_ABOVE) ){ Stack_Above << id; } + else if(states.contains(LXCB::S_FULLSCREEN)){ Stack_Fullscreen << id; } + else{ Stack_Normal << id; } + } + } + //Active Window management + WId active = LWM::SYSTEM->WM_Get_Active_Window(); + if(Stack_Desktop.contains(active)){ Stack_Desktop.removeAll(active); Stack_Desktop << active; } + else if(Stack_Below.contains(active)){ Stack_Below.removeAll(active); Stack_Below << active; } + else if(Stack_Normal.contains(active)){ Stack_Normal.removeAll(active); Stack_Normal << active; } + else if(Stack_Above.contains(active)){ Stack_Above.removeAll(active); Stack_Above << active; } + else if(Stack_Fullscreen.contains(active)){ Stack_Fullscreen.removeAll(active); Stack_Fullscreen << active; } + + //Now set the root properties for these lists + LWM::SYSTEM->WM_Set_Client_List(currwins, false); //age-ordered version + LWM::SYSTEM->WM_Set_Client_List(QList<WId>() << Stack_Desktop << Stack_Below << Stack_Normal << Stack_Above << Stack_Fullscreen, true); //stacking order version + //Now re-paint (in order) the windows + RepaintWindows(); +} + +void LWindowManager::RepaintWindows(){ + //Go through all the current windows (in stacking order) and re-paint them + for(int i=0; i<Stack_Desktop.length(); i++){ + LWM::SYSTEM->WM_ShowWindow(Stack_Desktop[i]); + } + for(int i=0; i<Stack_Below.length(); i++){ + LWM::SYSTEM->WM_ShowWindow(Stack_Below[i]); + } + for(int i=0; i<Stack_Normal.length(); i++){ + LWM::SYSTEM->WM_ShowWindow(Stack_Normal[i]); + } + for(int i=0; i<Stack_Above.length(); i++){ + LWM::SYSTEM->WM_ShowWindow(Stack_Above[i]); + } + for(int i=0; i<Stack_Fullscreen.length(); i++){ + LWM::SYSTEM->WM_ShowWindow(Stack_Fullscreen[i]); + } +} + +//================= +// PRIVATE SLOTS +//================= +void LWindowManager::FinishedWindow(WId win){ + for(int i=0; i<WINS.length(); i++){ + if(WINS[i]->clientID() == win){ + qDebug() << " - Finished Window"; + if(win == LWM::SYSTEM->WM_Get_Active_Window()){ + if(i==0 && WINS.length()>1){ LWM::SYSTEM->WM_Set_Active_Window(WINS[i+1]->clientID()); } + else if(i>0){ LWM::SYSTEM->WM_Set_Active_Window(WINS[i-1]->clientID()); } + else{ LWM::SYSTEM->WM_Set_Active_Window( QX11Info::appRootWindow()); } + } + delete WINS.takeAt(i); break; + } + } + //Now update the list of clients + RestackWindows(); +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LWindowManager.h b/src-qt5/core/lumina-desktop-unified/src-WM/LWindowManager.h new file mode 100644 index 00000000..203efa1b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LWindowManager.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 +//=========================================== +#ifndef _LUMINA_DESKTOP_WINDOW_MANAGER_MAIN_CLASS_H +#define _LUMINA_DESKTOP_WINDOW_MANAGER_MAIN_CLASS_H + +#include "GlobalDefines.h" +#include "LWindow.h" + +class LWindowManager : public QObject{ + Q_OBJECT +public: + LWindowManager(); + ~LWindowManager(); + + bool start(); + void stop(); + +private: + QList<LWindow*> WINS; + QList<WId> Stack_Desktop, Stack_Below, Stack_Normal, Stack_Above, Stack_Fullscreen; +public slots: + void NewWindow(WId win, bool requested = true); + void ClosedWindow(WId win); + void ModifyWindow(WId win, LWM::WindowAction act); + + void RestackWindows(); + void RepaintWindows(); + +private slots: + void FinishedWindow(WId win); //This is used for LWindow connections/animations + +signals: + void NewFullScreenWindows(QList<WId>); +}; + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LXcbEventFilter.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/LXcbEventFilter.cpp new file mode 100644 index 00000000..abbe5a5a --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LXcbEventFilter.cpp @@ -0,0 +1,204 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LXcbEventFilter.h" + +//================================================== +// NOTE: All the XCB interactions and atoms are accessed via: +// LWM::SYSTEM->EWMH.(atom name) +// LWM::SYSTEM->(do something) +// (LWM::SYSTEM is the global XCB structure) +//================================================== + +#include <LuminaX11.h> +#include <QDebug> + +//#include <xcb/screensaver.h> + +#define DEBUG 1 +// Also keep the root window/screen around for use in the filters +namespace L_XCB{ + xcb_screen_t *root_screen; + xcb_window_t root; +} + +//Constructor for the Event Filter wrapper +EventFilter::EventFilter() : QObject(){ + EF = new XCBEventFilter(this); + L_XCB::root_screen = xcb_aux_get_screen(QX11Info::connection(), QX11Info::appScreen()); + L_XCB::root = L_XCB::root_screen->root; + SSLocked = false; + WMFlag = 0; +} + +void EventFilter::start(){ + if(DEBUG){ qDebug() << " - Install event filter..."; } + QCoreApplication::instance()->installNativeEventFilter(EF); + if(DEBUG){ qDebug() << " - Run request check..."; } + if(!LWM::SYSTEM->setupEventsForRoot()){ + qCritical() << "[ERROR] Unable to setup WM event retrieval. Is another WM running?"; + exit(1); + } + if(DEBUG){ qDebug() << " - Create WM ID Window"; } + WMFlag = LWM::SYSTEM->WM_CreateWindow(); + LWM::SYSTEM->setupEventsForRoot(WMFlag); + LWM::SYSTEM->WM_Set_Supporting_WM(WMFlag); + QCoreApplication::instance()->flush(); +} + +//Constructor for the XCB event filter +XCBEventFilter::XCBEventFilter(EventFilter *parent) : QAbstractNativeEventFilter(){ + obj = parent; + InitAtoms(); +} + +//This function format taken directly from the Qt5.3 documentation +bool XCBEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *) Q_DECL_OVERRIDE +{ + //if(stopping){ return false; } //don't do any parsing + //qDebug() << "New Event"; + bool stopevent = false; + if(eventType=="xcb_generic_event_t"){ + //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){ +//============================== +// INTERACTIVITY EVENTS +//============================== + case XCB_KEY_PRESS: + //This is a keyboard key press + //qDebug() << "Key Press Event"; + obj->emit NewInputEvent(); + stopevent = BlockInputEvent( ((xcb_key_press_event_t *) ev)->root ); //use the main "root" window - not the child widget + break; + case XCB_KEY_RELEASE: + //This is a keyboard key release + //qDebug() << "Key Release Event"; + obj->emit NewInputEvent(); + stopevent = BlockInputEvent( ((xcb_key_release_event_t *) ev)->root ); //use the main "root" window - not the child widget + break; + case XCB_BUTTON_PRESS: + //This is a mouse button press + //qDebug() << "Button Press Event"; + obj->emit NewInputEvent(); + stopevent = BlockInputEvent( ((xcb_button_press_event_t *) ev)->root ); //use the main "root" window - not the child widget + if(!stopevent){ + //Activate the window right now if needed + if(LWM::SYSTEM->WM_Get_Active_Window()!=((xcb_button_press_event_t *) ev)->root){ + LWM::SYSTEM->WM_Set_Active_Window( ((xcb_button_press_event_t *) ev)->root); + } + } + break; + case XCB_BUTTON_RELEASE: + //This is a mouse button release + //qDebug() << "Button Release Event"; + //xcb_button_release_event_t *tmp = (xcb_button_release_event_t *)ev; + stopevent = BlockInputEvent( ((xcb_button_release_event_t *) ev)->root ); //use the main "root" window - not the child widget + break; + case XCB_MOTION_NOTIFY: + //This is a mouse movement event + //qDebug() << "Motion Notify Event"; + obj->emit NewInputEvent(); + stopevent = BlockInputEvent( ((xcb_motion_notify_event_t *) ev)->root ); //use the main "root" window - not the child widget); + break; + case XCB_ENTER_NOTIFY: + //This is a mouse movement event when mouse goes over a new window + //qDebug() << "Enter Notify Event"; + obj->emit NewInputEvent(); + stopevent = BlockInputEvent( ((xcb_enter_notify_event_t *) ev)->root ); + break; + case XCB_LEAVE_NOTIFY: + //This is a mouse movement event when mouse goes leaves a window + //qDebug() << "Leave Notify Event"; + obj->emit NewInputEvent(); + stopevent = BlockInputEvent(); + break; +//============================== + case XCB_EXPOSE: + //qDebug() << "Expose Notify Event:"; + //qDebug() << " - Given Window:" << ((xcb_property_notify_event_t*)ev)->window; + break; +//============================== + case XCB_MAP_NOTIFY: + break; //This is just a notification that a window was mapped - nothing needs to change here + case XCB_MAP_REQUEST: + qDebug() << "Window Map Request Event"; + obj->emit ModifyWindow( ((xcb_map_request_event_t *) ev)->window, LWM::Show); + break; +//============================== + case XCB_CREATE_NOTIFY: + qDebug() << "Window Create Event"; + break; +//============================== + case XCB_UNMAP_NOTIFY: + qDebug() << "Window Unmap Event"; + obj->emit ModifyWindow( ((xcb_unmap_notify_event_t *)ev)->window, LWM::Hide); + break; +//============================== + case XCB_DESTROY_NOTIFY: + qDebug() << "Window Closed Event"; + obj->emit WindowClosed( ((xcb_destroy_notify_event_t *) ev)->window ); + break; +//============================== + case XCB_FOCUS_IN: + //qDebug() << "Focus In Event:"; + break; +//============================== + case XCB_FOCUS_OUT: + //qDebug() << "Focus Out Event:"; + break; +//============================== + case XCB_PROPERTY_NOTIFY: + //qDebug() << "Property Notify Event:"; + //qDebug() << " - Given Window:" << ((xcb_property_notify_event_t*)ev)->window; + break; +//============================== + case XCB_CLIENT_MESSAGE: + //qDebug() << "Client Message Event"; + //qDebug() << " - Given Window:" << ((xcb_client_message_event_t*)ev)->window; + break; +//============================== + case XCB_CONFIGURE_NOTIFY: + //qDebug() << "Configure Notify Event"; + break; +//============================== + case XCB_CONFIGURE_REQUEST: + //qDebug() << "Configure Request Event"; + break; +//============================== + case XCB_SELECTION_CLEAR: + //qDebug() << "Selection Clear Event"; + break; +//============================== + case 85: //not sure what event this is - but it seems to come up very often (just hide the notice) + case 0: + case XCB_GE_GENERIC: + break; //generic event - don't do anything special + default: + qDebug() << "Default Event:" << (ev->response_type & ~0x80); +//============================== + } + } + return false; + //never stop event handling (this will not impact the X events themselves - just the internal screensaver/WM/widgets) +} + +bool XCBEventFilter::BlockInputEvent(WId win){ + //Checks the current state of the WM and sets the stop flag as needed + // - Always let the screensaver know about the event first (need to reset timers and such) + obj->emit NewInputEvent(); + // - Check the state of the screensaver + if(obj->SSLocked){ qDebug() << "SS Locked"; return true; } + // - Check the state of any fullscreen apps + else if( win!=0 && !obj->FS_WINS.isEmpty()){ + if(!obj->FS_WINS.contains(win) ){ + //If this event is for an app underneath a fullscreen window - stop it + if(obj->FS_WINS.length() == QApplication::desktop()->screenCount()){ qDebug() << "Screens Covered"; return true; } //all screens covered right now + } + } + return false; +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/LXcbEventFilter.h b/src-qt5/core/lumina-desktop-unified/src-WM/LXcbEventFilter.h new file mode 100644 index 00000000..b68eedf5 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/LXcbEventFilter.h @@ -0,0 +1,130 @@ +//=========================================== +// 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 "GlobalDefines.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 EventFilter : public QObject{ + Q_OBJECT +private: + QAbstractNativeEventFilter* EF; + WId WMFlag; //used to flag a running WM process + +public: + EventFilter(); + ~EventFilter(){} + + void start(); + + //Public variables for the event filter to use/check + QList<WId> FS_WINS; //Full-screen windows (1 per monitor) - used for hiding non-app events as needed + bool SSLocked; + +public slots: + void StartedSS(){ SSLocked = true; } + void StoppedSS(){ SSLocked = false; } + void FullScreenChanged(QList<WId> fslist){ FS_WINS = fslist; } + +signals: + void NewInputEvent(); + void NewManagedWindow(WId); + void WindowClosed(WId); + void ModifyWindow(WId win, LWM::WindowAction); +}; + +class XCBEventFilter : public QAbstractNativeEventFilter{ +public: + XCBEventFilter(EventFilter *parent); + ~XCBEventFilter(){} + + virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *); + +private: + EventFilter *obj; + QList<xcb_atom_t> WinNotifyAtoms, SysNotifyAtoms; + + void InitAtoms(){ + //Initialize any special atoms that we need to save/use regularly + //NOTE: All the EWMH atoms are already saved globally in LWM::SYSTEM->EWMH + WinNotifyAtoms.clear(); + WinNotifyAtoms << LWM::SYSTEM->EWMH._NET_WM_NAME \ + << LWM::SYSTEM->EWMH._NET_WM_VISIBLE_NAME \ + << LWM::SYSTEM->EWMH._NET_WM_ICON_NAME \ + << LWM::SYSTEM->EWMH._NET_WM_VISIBLE_ICON_NAME \ + << LWM::SYSTEM->EWMH._NET_WM_ICON \ + << LWM::SYSTEM->EWMH._NET_WM_ICON_GEOMETRY; + + SysNotifyAtoms.clear(); + SysNotifyAtoms << LWM::SYSTEM->EWMH._NET_CLIENT_LIST \ + << LWM::SYSTEM->EWMH._NET_CLIENT_LIST_STACKING \ + << LWM::SYSTEM->EWMH._NET_CURRENT_DESKTOP \ + << LWM::SYSTEM->EWMH._NET_WM_STATE \ + << LWM::SYSTEM->EWMH._NET_ACTIVE_WINDOW \ + << LWM::SYSTEM->EWMH._NET_WM_ICON \ + << LWM::SYSTEM->EWMH._NET_WM_ICON_GEOMETRY; + + } + + bool BlockInputEvent(WId win = 0); //Checks the current state of the WM and sets the stop flag as needed + + //Longer Event handling functions + //bool ParseKeyPressEvent(); + //bool ParseKeyReleaseEvent(); + //bool ParseButtonPressEvent(); + //bool ParseButtonReleaseEvent(); + //bool ParseMotionEvent(); + //bool ParsePropertyEvent(); + //bool ParseClientMessageEvent(); + //bool ParseDestroyEvent(); + //bool ParseConfigureEvent(); + //bool ParseKeySelectionClearEvent(); + + + + +}; + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/SSBaseWidget.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/SSBaseWidget.cpp new file mode 100644 index 00000000..83b82ff8 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/SSBaseWidget.cpp @@ -0,0 +1,83 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== + +#include "SSBaseWidget.h" + +#define DEBUG 1 + +static QStringList validPlugs; +// ======== +// PUBLIC +// ======== +SSBaseWidget::SSBaseWidget(QWidget *parent, QSettings *set) : QWidget(parent){ + if(validPlugs.isEmpty()){ validPlugs << "none"; } //add more later + settings = set; //needed to pass along for plugins to read any special options/settings + this->setObjectName("LuminaBaseSSWidget"); + ANIM = 0; + this->setMouseTracking(true); +} + +SSBaseWidget::~SSBaseWidget(){ + if(ANIM!=0){ this->stopPainting(); } +} + +void SSBaseWidget::setPlugin(QString plug){ + plug = plug.toLower(); + if(validPlugs.contains(plug) || plug=="random"){ plugType = plug; } + else{ plugType = "none"; } +} + +// ============= +// PUBLIC SLOTS +// ============= +void SSBaseWidget::startPainting(){ + cplug = plugType; + //free up any old animation instance + if(ANIM!=0){ + ANIM->stop(); ANIM->clear(); + delete ANIM; ANIM = 0; + } + //If a random plugin - grab one of the known plugins + if(cplug=="random"){ + QStringList valid = BaseAnimGroup::KnownAnimations(); + if(valid.isEmpty()){ cplug = "none"; } //no known plugins + else{ cplug = valid[ qrand()%valid.length() ]; } //grab a random plugin + } + if(DEBUG){ qDebug() << " - Screen Saver:" << cplug; } + //Now list all the various plugins and start them appropriately + QString style; + if(cplug=="none"){ + style = "background: transparent;"; //show the underlying black parent widget + }else{ + style = "background: black;"; + } + this->setStyleSheet("QWidget#LuminaBaseSSWidget{ "+style+"}"); + this->repaint(); + //If not a stylesheet-based plugin - set it here + if(cplug!="none"){ + ANIM = BaseAnimGroup::NewAnimation(cplug, this, settings); + connect(ANIM, SIGNAL(finished()), this, SLOT(startPainting()) ); //repeat the plugin as needed + ANIM->LoadAnimations(); + } + //Now start the animation(s) + if(ANIM!=0){ + //if(DEBUG){ qDebug() << " - Starting SS Plugin:" << cplug << ANIM->animationCount() << ANIM->duration() << ANIM->loopCount(); } + if(ANIM->animationCount()>0){ + if(DEBUG){ qDebug() << " - Starting SS Plugin:" << cplug << ANIM->animationCount() << ANIM->duration() << ANIM->loopCount(); } + ANIM->start(); + } + } +} + +void SSBaseWidget::stopPainting(){ + if(ANIM!=0){ + ANIM->stop(); + ANIM->clear(); + delete ANIM; + ANIM = 0; + } +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/SSBaseWidget.h b/src-qt5/core/lumina-desktop-unified/src-WM/SSBaseWidget.h new file mode 100644 index 00000000..a6574679 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/SSBaseWidget.h @@ -0,0 +1,55 @@ +//=========================================== +// 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 the widget which provides the screensaver painting/plugin functionality +//=========================================== +#ifndef _LUMINA_DESKTOP_SCREEN_SAVER_BASE_WIDGET_H +#define _LUMINA_DESKTOP_SCREEN_SAVER_BASE_WIDGET_H + +#include "GlobalDefines.h" +#include "animations/BaseAnimGroup.h" + +class SSBaseWidget : public QWidget{ + Q_OBJECT +public: + SSBaseWidget(QWidget *parent, QSettings *set); + ~SSBaseWidget(); + + void setPlugin(QString); + +public slots: + void startPainting(); + void stopPainting(); + +private: + QString plugType, cplug; //type of custom painting to do + BaseAnimGroup *ANIM; + QSettings *settings; + +private slots: + +signals: + void InputDetected(); //just in case no event handling setup at the WM level + +protected: + void mouseMoveEvent(QMouseEvent *ev){ + ev->accept(); + emit InputDetected(); + } + void keyPressEvent(QKeyEvent *ev){ + ev->accept(); + emit InputDetected(); + } + void paintEvent(QPaintEvent*){ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + } + +}; + +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/WMSession.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/WMSession.cpp new file mode 100644 index 00000000..4a7c6e02 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/WMSession.cpp @@ -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 +//=========================================== +#include "WMSession.h" + +#define DEBUG 1 +// ========== +// PUBLIC +// ========== +WMSession::WMSession(){ + if(DEBUG){ qDebug() << "Creating Event Filter..."; } + EFILTER = new EventFilter(); + if(DEBUG){ qDebug() << "Creating Screen Saver..."; } + SS = new LScreenSaver(); + if(DEBUG){ qDebug() << "Creating Window Manager..."; } + WM = new LWindowManager(); + EVThread = new QThread(); + EFILTER->moveToThread(EVThread); + //Setup connections + connect(EFILTER, SIGNAL(NewInputEvent()), SS, SLOT(newInputEvent()) ); + connect(EFILTER, SIGNAL(NewManagedWindow(WId)), WM, SLOT(NewWindow(WId)) ); + connect(EFILTER, SIGNAL(WindowClosed(WId)), WM, SLOT(ClosedWindow(WId)) ); + connect(EFILTER, SIGNAL(ModifyWindow(WId, LWM::WindowAction)), WM, SLOT(ModifyWindow(WId,LWM::WindowAction)) ); + connect(SS, SIGNAL(StartingScreenSaver()), EFILTER, SLOT(StartedSS()) ); + connect(SS, SIGNAL(ClosingScreenSaver()), EFILTER, SLOT(StoppedSS()) ); + connect(WM, SIGNAL(NewFullScreenWindows(QList<WId>)), EFILTER, SLOT(FullScreenChanged(QList<WId>)) ); +} + +WMSession::~WMSession(){ +} + +void WMSession::start(bool SSONLY){ + //Get the screensaver initialized/ready + if(DEBUG){ qDebug() << "Starting Screen Saver..."; } + SS->start(); + if(SSONLY){ return; } + //Now start pulling/filtering events + if(DEBUG){ qDebug() << "Starting Window Manager..."; } + WM->start(); + if(DEBUG){ qDebug() << "Starting Event Filter..."; } + EVThread->start(); + EFILTER->start(); + if(DEBUG){ qDebug() << "Done Starting WM session..."; } +} + +// ========== +// Public Slots +// ========== +void WMSession::reloadIcons(){ + +} + +void WMSession::newInputsAvailable(QStringList inputs){ + for(int i=0; i<inputs.length(); i++){ + if(inputs[i]=="--lock-now" || inputs[i]=="--test-ss"){ + QTimer::singleShot(0,SS, SLOT(LockScreenNow()) ); + } + } +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/WMSession.h b/src-qt5/core/lumina-desktop-unified/src-WM/WMSession.h new file mode 100644 index 00000000..5b511326 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/WMSession.h @@ -0,0 +1,42 @@ +//=========================================== +// 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_WINDOW_MANAGER_SESSION_H +#define _LUMINA_DESKTOP_WINDOW_MANAGER_SESSION_H + +#include "GlobalDefines.h" + +#include "LScreenSaver.h" +#include "LXcbEventFilter.h" +#include "LWindowManager.h" + +class WMSession : public QObject{ + Q_OBJECT +public: + WMSession(); + ~WMSession(); + + void start(bool SSONLY = false); + +private: + //XCB Event Watcher + EventFilter *EFILTER; + //ScreenSaver + LScreenSaver *SS; + //Window Manager + LWindowManager *WM; + + QThread *EVThread; //X Event thread + +public slots: + void reloadIcons(); + void newInputsAvailable(QStringList); + +private slots: + +}; + +#endif
\ No newline at end of file diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/animations/BaseAnimGroup.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/animations/BaseAnimGroup.cpp new file mode 100644 index 00000000..1e55dc76 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/animations/BaseAnimGroup.cpp @@ -0,0 +1,27 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "BaseAnimGroup.h" + +//Include all the known subclasses here, then add a unique ID for it to the functions at the bottom +#include "SampleAnimation.h" + +//============================== +// PLUGIN LOADING/LISTING +//============================== +BaseAnimGroup* BaseAnimGroup::NewAnimation(QString type, QWidget *parent, QSettings *set){ + //This is where we place all the known plugin ID's, and load the associated subclass + if(type == "sample"){ + return (new SampleAnimation(parent, set)); + }else{ + //Unknown screensaver, return a blank animation group + return (new BaseAnimGroup(parent, set)); + } +} + +QStringList BaseAnimGroup::KnownAnimations(){ + return (QStringList() << "sample"); +}
\ No newline at end of file diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/animations/BaseAnimGroup.h b/src-qt5/core/lumina-desktop-unified/src-WM/animations/BaseAnimGroup.h new file mode 100644 index 00000000..dd7269d4 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/animations/BaseAnimGroup.h @@ -0,0 +1,37 @@ +//=========================================== +// 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 the container which provides the screensaver animations +// and should be subclassed for each of the various animation types +//=========================================== +#ifndef _LUMINA_DESKTOP_SCREEN_SAVER_BASE_ANIMATION_GROUP_H +#define _LUMINA_DESKTOP_SCREEN_SAVER_BASE_ANIMATION_GROUP_H + +#include "GlobalDefines.h" + +class BaseAnimGroup : public QParallelAnimationGroup{ + Q_OBJECT +public: + QWidget *canvas; + QSettings *settings; + + virtual void LoadAnimations(){} //This is the main function which needs to be subclassed + + BaseAnimGroup(QWidget *parent, QSettings *set){ + canvas = parent; + settings = set; + } + ~BaseAnimGroup(){} + + //============================== + // PLUGIN LOADING/LISTING (Change in the .cpp file) + //============================== + static BaseAnimGroup* NewAnimation(QString type, QWidget *parent, QSettings *set); + static QStringList KnownAnimations(); + +}; + +#endif
\ No newline at end of file diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/animations/SampleAnimation.h b/src-qt5/core/lumina-desktop-unified/src-WM/animations/SampleAnimation.h new file mode 100644 index 00000000..e0f11ba5 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/animations/SampleAnimation.h @@ -0,0 +1,45 @@ +//=========================================== +// 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 the sample plugin for a ScreenSaver animation +//=========================================== +#ifndef _LUMINA_DESKTOP_SCREEN_SAVER_SAMPLE_ANIMATION_H +#define _LUMINA_DESKTOP_SCREEN_SAVER_SAMPLE_ANIMATION_H + +#include "GlobalDefines.h" +#include "BaseAnimGroup.h" + +class SampleAnimation : public BaseAnimGroup{ + Q_OBJECT +private: + QWidget *ball; + +public: + SampleAnimation(QWidget *parent, QSettings *set) : BaseAnimGroup(parent, set){} + ~SampleAnimation(){ this->stop(); delete ball; } + + void LoadAnimations(){ + //qDebug() << "Loading Sample Animation"; + ball = new QWidget(canvas); + //This creates a red "ball" on the widget which is going to expand/contract in the center of the screen + ball->setStyleSheet("background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.341, fy:0.796, stop:0.00531915 rgba(107, 10, 10, 255), stop:0.521277 rgba(170, 10, 10, 255), stop:0.957447 rgba(200, 0, 0, 255), stop:0.994681 rgba(0, 0, 0, 225), stop:1 rgba(255, 255, 255, 0));"); + //Now setup the movements + QPropertyAnimation *move = new QPropertyAnimation(ball,"geometry"); + QPoint ctr(canvas->width()/2, canvas->height()/2); + QRect initgeom(ctr-QPoint(12,12), QSize(24,24) ); + move->setKeyValueAt(0, initgeom ); //starting point + move->setKeyValueAt(1, initgeom ); //ending point (same as start for continuity) + int size = canvas->width(); + if(size > canvas->height()){ size = canvas->height(); } + move->setKeyValueAt(0.5, QRect(ctr-QPoint(size/2, size/2), QSize(size,size))); //touch the edge of the screen + move->setDuration(10000); //10 seconds + this->addAnimation(move); + this->setLoopCount(10); //repeat 10 times + ball->show(); + } + +}; +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/animations/animations.pri b/src-qt5/core/lumina-desktop-unified/src-WM/animations/animations.pri new file mode 100644 index 00000000..5473d4e1 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/animations/animations.pri @@ -0,0 +1,6 @@ +SOURCES += $$PWD/BaseAnimGroup.cpp + +HEADERS += $$PWD/BaseAnimGroup.h \ + $$PWD/SampleAnimation.h + +FORMS +=
\ No newline at end of file diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/lumina-wm.pro b/src-qt5/core/lumina-desktop-unified/src-WM/lumina-wm.pro new file mode 100644 index 00000000..928f8744 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/lumina-wm.pro @@ -0,0 +1,107 @@ +include("$${PWD}/../../OS-detect.pri") + +QT += core gui network +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets x11extras + +TARGET = lumina-wm +target.path = $${L_BINDIR} + +LIBS += -lLuminaUtils -lxcb -lxcb-damage -lxcb-composite -lxcb-screensaver -lxcb-util + +DEPENDPATH += ../libLumina + +SOURCES += main.cpp \ + WMSession.cpp \ + LScreenSaver.cpp \ + SSBaseWidget.cpp \ + LLockScreen.cpp \ + LXcbEventFilter.cpp \ + LWindow.cpp \ + LWindowManager.cpp + + +HEADERS += GlobalDefines.h \ + WMSession.h \ + LScreenSaver.h \ + SSBaseWidget.h \ + LLockScreen.h \ + LXcbEventFilter.h \ + LWindow.h \ + LWindowManager.h + +FORMS += LLockScreen.ui + +#Now add in all the screensaver animation plugins +include(animations/animations.pri) + +TRANSLATIONS = i18n/lumina-wm_af.ts \ + i18n/lumina-wm_ar.ts \ + i18n/lumina-wm_az.ts \ + i18n/lumina-wm_bg.ts \ + i18n/lumina-wm_bn.ts \ + i18n/lumina-wm_bs.ts \ + i18n/lumina-wm_ca.ts \ + i18n/lumina-wm_cs.ts \ + i18n/lumina-wm_cy.ts \ + i18n/lumina-wm_da.ts \ + i18n/lumina-wm_de.ts \ + i18n/lumina-wm_el.ts \ + i18n/lumina-wm_en_GB.ts \ + i18n/lumina-wm_en_ZA.ts \ + i18n/lumina-wm_es.ts \ + i18n/lumina-wm_et.ts \ + i18n/lumina-wm_eu.ts \ + i18n/lumina-wm_fa.ts \ + i18n/lumina-wm_fi.ts \ + i18n/lumina-wm_fr.ts \ + i18n/lumina-wm_fr_CA.ts \ + i18n/lumina-wm_gl.ts \ + i18n/lumina-wm_he.ts \ + i18n/lumina-wm_hi.ts \ + i18n/lumina-wm_hr.ts \ + i18n/lumina-wm_hu.ts \ + i18n/lumina-wm_id.ts \ + i18n/lumina-wm_is.ts \ + i18n/lumina-wm_it.ts \ + i18n/lumina-wm_ja.ts \ + i18n/lumina-wm_ka.ts \ + i18n/lumina-wm_ko.ts \ + i18n/lumina-wm_lt.ts \ + i18n/lumina-wm_lv.ts \ + i18n/lumina-wm_mk.ts \ + i18n/lumina-wm_mn.ts \ + i18n/lumina-wm_ms.ts \ + i18n/lumina-wm_mt.ts \ + i18n/lumina-wm_nb.ts \ + i18n/lumina-wm_nl.ts \ + i18n/lumina-wm_pa.ts \ + i18n/lumina-wm_pl.ts \ + i18n/lumina-wm_pt.ts \ + i18n/lumina-wm_pt_BR.ts \ + i18n/lumina-wm_ro.ts \ + i18n/lumina-wm_ru.ts \ + i18n/lumina-wm_sk.ts \ + i18n/lumina-wm_sl.ts \ + i18n/lumina-wm_sr.ts \ + i18n/lumina-wm_sv.ts \ + i18n/lumina-wm_sw.ts \ + i18n/lumina-wm_ta.ts \ + i18n/lumina-wm_tg.ts \ + i18n/lumina-wm_th.ts \ + i18n/lumina-wm_tr.ts \ + i18n/lumina-wm_uk.ts \ + i18n/lumina-wm_uz.ts \ + i18n/lumina-wm_vi.ts \ + i18n/lumina-wm_zh_CN.ts \ + i18n/lumina-wm_zh_HK.ts \ + i18n/lumina-wm_zh_TW.ts \ + i18n/lumina-wm_zu.ts + +dotrans.path=$${L_SHAREDIR}/lumina-desktop/i18n/ +dotrans.extra=cd i18n && $${LRELEASE} -nounfinished *.ts && cp *.qm $(INSTALL_ROOT)$${L_SHAREDIR}/lumina-desktop/i18n/ + +INSTALLS += target + +WITH_I18N{ + INSTALLS += dotrans +} diff --git a/src-qt5/core/lumina-desktop-unified/src-WM/main.cpp b/src-qt5/core/lumina-desktop-unified/src-WM/main.cpp new file mode 100644 index 00000000..02e48b7b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-WM/main.cpp @@ -0,0 +1,56 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2015, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== + +#include "GlobalDefines.h" +//Initialize any global structures here +LXCB *LWM::SYSTEM = 0; + +//Local includes +#include "WMSession.h" +#include "LWindow.h" +#include <QDialog> + + +//#define DEBUG 0 +int main(int argc, char ** argv) +{ + qDebug() << "Starting lumina-wm..."; + LTHEME::LoadCustomEnvSettings(); + LSingleApplication a(argc, argv, "lumina-wm"); + if(!a.isPrimaryProcess()){ return 0; } //Inputs forwarded on to the primary already + LuminaThemeEngine themes(&a); + + //Setup the global structures + LWM::SYSTEM = new LXCB(); + if( a.inputlist.contains("--test-win") ){ + //Simple override to test out the window class + qDebug() << "Starting window test..."; + QLabel dlg(0, Qt::Window | Qt::BypassWindowManagerHint); //this test should be ignored by the current WM + dlg.setText("Sample Window"); + dlg.setWindowTitle("Test"); + dlg.resize(200,100); + dlg.setStyleSheet("background: rgba(255,255,255,100); color: black;"); + dlg.move(100,100); + dlg.show(); + //dlg.move(100,100); + qDebug() << " - Loading window frame..."; + LWindow win(dlg.winId()); //have it wrap around the dialog + qDebug() << " - Show frame..."; + win.frame()->windowChanged(LWM::Show); + qDebug() << " - Start event loop..."; + a.setQuitOnLastWindowClosed(true); + return a.exec(); + } + WMSession w; + w.start(a.inputlist.contains("--test-ss")); + QObject::connect(&themes, SIGNAL(updateIcons()), &w, SLOT(reloadIcons()) ); + QObject::connect(&a, SIGNAL(InputsAvailable(QStringList)), &w, SLOT(newInputsAvailable(QStringList)) ); + if(!a.inputlist.isEmpty()){ w.newInputsAvailable(a.inputlist); } + int retCode = a.exec(); + + return retCode; +} |