diff options
Diffstat (limited to 'src-qt5/core')
32 files changed, 1565 insertions, 66 deletions
diff --git a/src-qt5/core/libLumina/LuminaOS-Debian.cpp b/src-qt5/core/libLumina/LuminaOS-Debian.cpp index 75aad108..cb8e9cdd 100644 --- a/src-qt5/core/libLumina/LuminaOS-Debian.cpp +++ b/src-qt5/core/libLumina/LuminaOS-Debian.cpp @@ -24,6 +24,8 @@ QString LOS::SysPrefix(){ return "/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return LOS::AppPrefix() + "/share/applications/synaptic.desktop"; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ return QStringList(); } // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp b/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp index b98a36ee..919c88e5 100644 --- a/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp +++ b/src-qt5/core/libLumina/LuminaOS-DragonFly.cpp @@ -74,6 +74,12 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ + QStringList feeds; + feeds << "DragonFly BSD Feed::::http://www.dragonflybsd.org/recentchanges/index.rss"; + return feeds; +} // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp b/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp index fb405cb5..6f03767b 100644 --- a/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp +++ b/src-qt5/core/libLumina/LuminaOS-FreeBSD.cpp @@ -26,6 +26,13 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return "/usr/local/share/applications/pccontrol.desktop"; } //system control panel QString LOS::AppStoreShortcut(){ return "/usr/local/share/applications/appcafe.desktop"; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ + QStringList feeds; + feeds << "FreeBSD News Feed::::https://www.freebsd.org/news/rss.xml"; + feeds << "PC-BSD News Feed::::https://blog.pcbsd.org/?feed=rss2"; + return feeds; + } // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp b/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp index e3d5fe56..3a9c7320 100644 --- a/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp +++ b/src-qt5/core/libLumina/LuminaOS-Gentoo.cpp @@ -24,6 +24,8 @@ QString LOS::SysPrefix(){ return "/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return LOS::AppPrefix() + "/share/applications/porthole.desktop"; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ return QStringList(); } // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-Linux.cpp b/src-qt5/core/libLumina/LuminaOS-Linux.cpp index 5939c9d1..c40cbd60 100644 --- a/src-qt5/core/libLumina/LuminaOS-Linux.cpp +++ b/src-qt5/core/libLumina/LuminaOS-Linux.cpp @@ -24,6 +24,8 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ return QStringList(); } // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp b/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp index 866ccc5c..e1152527 100644 --- a/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp +++ b/src-qt5/core/libLumina/LuminaOS-NetBSD.cpp @@ -20,7 +20,13 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager - +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ + QStringList feeds; + feeds << "NetBSD News::::http://www.netbsd.org/changes/rss-netbsd.xml"; + return feeds; +} + // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ //Returns: QStringList[<type>::::<filesystem>::::<path>] diff --git a/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp b/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp index c0fdafd4..30a02078 100644 --- a/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp +++ b/src-qt5/core/libLumina/LuminaOS-OpenBSD.cpp @@ -23,6 +23,8 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ return QStringList(); } // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp b/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp index 4fe62686..8414db1a 100644 --- a/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp +++ b/src-qt5/core/libLumina/LuminaOS-kFreeBSD.cpp @@ -28,6 +28,8 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ return QStringList(); } // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ diff --git a/src-qt5/core/libLumina/LuminaOS-template.cpp b/src-qt5/core/libLumina/LuminaOS-template.cpp index 5969bf3a..7aea0edd 100644 --- a/src-qt5/core/libLumina/LuminaOS-template.cpp +++ b/src-qt5/core/libLumina/LuminaOS-template.cpp @@ -20,7 +20,9 @@ QString LOS::SysPrefix(){ return "/usr/"; } //Prefix for system //OS-specific application shortcuts (*.desktop files) QString LOS::ControlPanelShortcut(){ return ""; } //system control panel QString LOS::AppStoreShortcut(){ return ""; } //graphical app/pkg manager - +//OS-specific RSS feeds (Format: QStringList[ <name>::::<url> ]; ) +QStringList LOS::RSSFeeds(){ return QStringList(); } + // ==== ExternalDevicePaths() ==== QStringList LOS::ExternalDevicePaths(){ //Returns: QStringList[<type>::::<filesystem>::::<path>] diff --git a/src-qt5/core/libLumina/LuminaOS.h b/src-qt5/core/libLumina/LuminaOS.h index c305277a..a18f2909 100644 --- a/src-qt5/core/libLumina/LuminaOS.h +++ b/src-qt5/core/libLumina/LuminaOS.h @@ -34,6 +34,9 @@ public: static QString ControlPanelShortcut(); static QString AppStoreShortcut(); + //OS-specific RSS feeds + static QStringList RSSFeeds(); //Return Format: QStringList[ <name>::::<url> ]; + //Scan for mounted external devices static QStringList ExternalDevicePaths(); //Returns: QStringList[<type>::::<filesystem>::::<path>] //Note: <type> = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] diff --git a/src-qt5/core/libLumina/LuminaUtils.cpp b/src-qt5/core/libLumina/LuminaUtils.cpp index 12dcf0be..4911b9fa 100644 --- a/src-qt5/core/libLumina/LuminaUtils.cpp +++ b/src-qt5/core/libLumina/LuminaUtils.cpp @@ -211,6 +211,23 @@ QString LUtils::PathToAbsolute(QString path){ } return path; } +QString LUtils::AppToAbsolute(QString path){ + if(path.startsWith("/") || QFile::exists(path)){ return path; } + if(path.endsWith(".desktop")){ + //Look in the XDG dirs + QStringList dirs = LXDG::systemApplicationDirs(); + for(int i=0; i<dirs.length(); i++){ + if(QFile::exists(dirs[i]+"/"+path)){ return (dirs[i]+"/"+path); } + } + }else{ + //Look on $PATH for the binary + QStringList paths = QString(getenv("PATH")).split(":"); + for(int i=0; i<paths.length(); i++){ + if(QFile::exists(paths[i]+"/"+path)){ return (paths[i]+"/"+path); } + } + } + return path; +} QStringList LUtils::imageExtensions(bool wildcards){ //Note that all the image extensions are lowercase!! @@ -472,6 +489,7 @@ QStringList LUtils::listFavorites(){ if(lastRead.isNull() || lastRead<QFileInfo(QDir::homePath()+"/.lumina/favorites/fav.list").lastModified()){ fav = LUtils::readFile(QDir::homePath()+"/.lumina/favorites/fav.list"); fav.removeAll(""); //remove any empty lines + fav.removeDuplicates(); lastRead = cur; if(fav.isEmpty()){ //Make sure the favorites dir exists, and create it if necessary @@ -484,6 +502,7 @@ QStringList LUtils::listFavorites(){ } bool LUtils::saveFavorites(QStringList list){ + list.removeDuplicates(); bool ok = LUtils::writeFile(QDir::homePath()+"/.lumina/favorites/fav.list", list, true); if(ok){ fav = list; } //also save internally in case of rapid write/read of the file return ok; @@ -576,7 +595,7 @@ void LUtils::LoadSystemDefaults(bool skipOS){ } } //Now setup the default "desktopsettings.conf" and "sessionsettings.conf" files - QStringList deskset, sesset, lopenset; + QStringList deskset, sesset;//, lopenset; // -- SESSION SETTINGS -- QStringList tmp = sysDefaults.filter("session_"); @@ -593,38 +612,71 @@ void LUtils::LoadSystemDefaults(bool skipOS){ if(var.contains(".")){ var.replace(".","_"); } //Now parse the variable and put the value in the proper file + if(var.contains("_default_")){ val = AppToAbsolute(val); } //got an application/binary //Special handling for values which need to exist first if(var.endsWith("_ifexists") ){ var = var.remove("_ifexists"); //remove this flag from the variable //Check if the value exists (absolute path only) if(!QFile::exists(val)){ continue; } //skip this line - value/file does not exist } + //Parse/save the value - QString loset, sset; //temporary strings + QString sset; //temporary strings if(var=="session_enablenumlock"){ sset = "EnableNumlock="+ istrue; } else if(var=="session_playloginaudio"){ sset = "PlayStartupAudio="+istrue; } else if(var=="session_playlogoutaudio"){ sset = "PlayLogoutAudio="+istrue; } - else if(var=="session_default_terminal"){ sset = "default-terminal="+val; } - else if(var=="session_default_filemanager"){ - sset = "default-filemanager="+val; - loset = "directory="+val; + else if(var=="session_default_terminal"){ + LXDG::setDefaultAppForMime("application/terminal", val); + //sset = "default-terminal="+val; + }else if(var=="session_default_filemanager"){ + LXDG::setDefaultAppForMime("inode/directory", val); + //sset = "default-filemanager="+val; + //loset = "directory="+val; + }else if(var=="session_default_webbrowser"){ + //loset = "webbrowser="+val; + LXDG::setDefaultAppForMime("x-scheme-handler/http", val); + LXDG::setDefaultAppForMime("x-scheme-handler/https", val); + }else if(var=="session_default_email"){ + LXDG::setDefaultAppForMime("application/email",val); + //loset = "email="+val; } - else if(var=="session_default_webbrowser"){ loset = "webbrowser="+val; } - else if(var=="session_default_email"){ loset = "email="+val; } //Put the line into the file (overwriting any previous assignment as necessary) - if(!loset.isEmpty()){ + /*if(!loset.isEmpty()){ int index = lopenset.indexOf(QRegExp(loset.section("=",0,0)+"=*", Qt::CaseSensitive, QRegExp::Wildcard)); qDebug() << "loset line:" << loset << index << lopenset; if(index<0){ lopenset << loset; } //new line else{ lopenset[index] = loset; } //overwrite the other line - } + }*/ if(!sset.isEmpty()){ int index = sesset.indexOf(QRegExp(sset.section("=",0,0)+"=*", Qt::CaseSensitive, QRegExp::Wildcard)); if(index<0){ sesset << sset; } //new line else{ sesset[index] = sset; } //overwrite the other line } } - if(!lopenset.isEmpty()){ lopenset.prepend("[default]"); } //the session options exist within this set + //if(!lopenset.isEmpty()){ lopenset.prepend("[default]"); } //the session options exist within this set + + // -- MIMETYPE DEFAULTS -- + tmp = sysDefaults.filter("mime_default_"); + for(int i=0; i<tmp.length(); i++){ + if(tmp[i].startsWith("#") || !tmp[i].contains("=") ){ continue; } + QString var = tmp[i].section("=",0,0).toLower().simplified(); + QString val = tmp[i].section("=",1,1).section("#",0,0).simplified(); + if(val.isEmpty()){ continue; } + QString istrue = (val.toLower()=="true") ? "true": "false"; + //Change in 0.8.5 - use "_" instead of "." within variables names - need backwards compat for a little while + if(var.contains(".")){ var.replace(".","_"); } + //Now parse the variable and put the value in the proper file + val = AppToAbsolute(val); + //Special handling for values which need to exist first + if(var.endsWith("_ifexists") ){ + var = var.remove("_ifexists"); //remove this flag from the variable + //Check if the value exists (absolute path only) + if(!QFile::exists(val)){ continue; } //skip this line - value/file does not exist + } + //Now turn this variable into the mimetype only + var = var.section("_default_",1,-1); + LXDG::setDefaultAppForMime(var, val); + } // -- DESKTOP SETTINGS -- //(only works for the primary desktop at the moment) @@ -710,6 +762,7 @@ void LUtils::LoadSystemDefaults(bool skipOS){ if(var.contains(".")){ var.replace(".","_"); } //Now parse the variable and put the value in the proper file qDebug() << "Favorite entry:" << var << val; + val = AppToAbsolute(val); //turn any relative files into absolute if(var=="favorites_add_ifexists" && QFile::exists(val)){ qDebug() << " - Exists/Adding:"; LUtils::addFavorite(val); } else if(var=="favorites_add"){ qDebug() << " - Adding:"; LUtils::addFavorite(val); } else if(var=="favorites_remove"){ qDebug() << " - Removing:"; LUtils::removeFavorite(val); } @@ -780,7 +833,7 @@ void LUtils::LoadSystemDefaults(bool skipOS){ if(setTheme){ LTHEME::setCurrentSettings( themesettings[0], themesettings[1], themesettings[2], themesettings[3], themesettings[4]); } LUtils::writeFile(setdir+"/sessionsettings.conf", sesset, true); LUtils::writeFile(setdir+"/desktopsettings.conf", deskset, true); - LUtils::writeFile(setdir+"/lumina-open.conf", lopenset, true); + //LUtils::writeFile(setdir+"/lumina-open.conf", lopenset, true); } bool LUtils::checkUserFiles(QString lastversion){ diff --git a/src-qt5/core/libLumina/LuminaUtils.h b/src-qt5/core/libLumina/LuminaUtils.h index 32109244..e07363ca 100644 --- a/src-qt5/core/libLumina/LuminaUtils.h +++ b/src-qt5/core/libLumina/LuminaUtils.h @@ -54,8 +54,9 @@ public: static QStringList listSubDirectories(QString dir, bool recursive = true); //Convert an input file/dir path to an absolute file path - static QString PathToAbsolute(QString path); - + static QString PathToAbsolute(QString path); //This is primarily for CLI usage (relative paths) + static QString AppToAbsolute(QString path); //This is for looking up a binary/ *.desktop path + //Get the list of all file extensions which Qt can read (lowercase) static QStringList imageExtensions(bool wildcards = false); diff --git a/src-qt5/core/libLumina/LuminaX11.h b/src-qt5/core/libLumina/LuminaX11.h index b0a5d588..b7310abd 100644 --- a/src-qt5/core/libLumina/LuminaX11.h +++ b/src-qt5/core/libLumina/LuminaX11.h @@ -69,6 +69,14 @@ public: } }; +//simple data structure for passing around the XRANDR information +/*class monitor_info{ +public: + QString ID; + bool active; + QRect geometry; +};*/ + //XCB Library replacement for LX11 (Qt5 uses XCB instead of XLib) class LXCB{ @@ -393,6 +401,12 @@ public: WId WM_Get_CM_Owner(); void WM_Set_CM_Owner(WId win); + //============ + // RANDR Functions (directly reading changing monitor outputs) + //============ + //QList<monitor_info> RR_List_Monitors(); + //void RR_Set_Monitors(QList<monitor_info> monitors); + private: QList<xcb_atom_t> ATOMS; QStringList atoms; diff --git a/src-qt5/core/libLumina/LuminaXDG.cpp b/src-qt5/core/libLumina/LuminaXDG.cpp index bfca4f71..8f6ada37 100644 --- a/src-qt5/core/libLumina/LuminaXDG.cpp +++ b/src-qt5/core/libLumina/LuminaXDG.cpp @@ -854,20 +854,27 @@ QString LXDG::findDefaultAppForMime(QString mime){ //Now go through all the files in order of priority until a default is found QString cdefault; - QStringList white; //lists to keep track of during the search (black unused at the moment) for(int i=0; i<dirs.length() && cdefault.isEmpty(); i++){ if(!QFile::exists(dirs[i])){ continue; } QStringList info = LUtils::readFile(dirs[i]); if(info.isEmpty()){ continue; } - QString workdir = dirs[i].section("/",0,-1); //just the directory + QStringList white; //lists to keep track of during the search (black unused at the moment) + QString workdir = dirs[i].section("/",0,-2); //just the directory + // qDebug() << "Check File:" << mime << dirs[i] << workdir; int def = info.indexOf("[Default Applications]"); //find this line to start on if(def>=0){ for(int d=def+1; d<info.length(); d++){ + //qDebug() << "Check Line:" << info[d]; if(info[d].startsWith("[")){ break; } //starting a new section now - finished with defaults - if(info[d].contains(mime+"=")){ - white << info[d].section("=",1,50).split(";"); - break; - } + if(info[d].contains(mime+"=") ){ + white = info[d].section("=",1,-1).split(";") + white; //exact mime match - put at front of list + break; //already found exact match + }else if(info[d].contains("*") && info[d].contains("=") ){ + QRegExp rg(info[d].section("=",0,0), Qt::CaseSensitive, QRegExp::WildcardUnix); + if(rg.exactMatch(mime)){ + white << info[d].section("=",1,-1).split(";"); //partial mime match - put at end of list + } + } } } // Now check for any white-listed files in this work dir @@ -884,19 +891,10 @@ QString LXDG::findDefaultAppForMime(QString mime){ else if( QFile::exists(workdir+"/"+white[w]) ){ cdefault=workdir+"/"+white[w]; break; } //Now go through the XDG DATA dirs and see if the file is in there else{ - QStringList xdirs; - xdirs << QString(getenv("XDG_DATA_HOME"))+"/applications/"; - tmp = QString(getenv("XDG_DATA_DIRS")).split(":"); - for(int t=0; t<tmp.length(); t++){ xdirs << tmp[t]+"/applications/"; } - //Now scan these dirs - bool found = false; - //qDebug() << "Scan dirs:" << white[w] << xdirs; - for(int x=0; x<xdirs.length() && !found; x++){ - if(QFile::exists(xdirs[x]+white[w])){cdefault=xdirs[x]+white[w]; found = true; } - } - if(found){ break; } + white[w] = LUtils::AppToAbsolute(white[w]); + if(QFile::exists(white[w])){ cdefault = white[w]; } } - } + } /* WRITTEN BUT UNUSED CODE FOR MIMETYPE ASSOCIATIONS //Skip using this because it is simply an alternate/unsupported standard that conflicts with the current mimetype database standards. It is better/faster to parse 1 or 2 database glob files diff --git a/src-qt5/core/lumina-desktop/AppMenu.cpp b/src-qt5/core/lumina-desktop/AppMenu.cpp index 14af988a..0b8aeace 100644 --- a/src-qt5/core/lumina-desktop/AppMenu.cpp +++ b/src-qt5/core/lumina-desktop/AppMenu.cpp @@ -32,6 +32,7 @@ QHash<QString, QList<XDGDesktop> >* AppMenu::currentAppHash(){ // 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","") ); @@ -111,6 +112,7 @@ void AppMenu::updateAppList(){ } this->addMenu(menu); } + watcher->addPaths(LXDG::systemApplicationDirs()); emit AppMenuUpdated(); } diff --git a/src-qt5/core/lumina-desktop/LDesktop.cpp b/src-qt5/core/lumina-desktop/LDesktop.cpp index 113b7efc..11d99581 100644 --- a/src-qt5/core/lumina-desktop/LDesktop.cpp +++ b/src-qt5/core/lumina-desktop/LDesktop.cpp @@ -104,7 +104,7 @@ void LDesktop::SystemLogout(){ void LDesktop::SystemTerminal(){ LSession::handle()->sessionSettings()->sync(); //make sure it is up to date - QString term = LSession::handle()->sessionSettings()->value("default-terminal","xterm").toString(); + QString term = LXDG::findDefaultAppForMime("application/terminal"); //LSession::handle()->sessionSettings()->value("default-terminal","xterm").toString(); if(term.endsWith(".desktop")){ term = "lumina-open \""+term+"\""; } LSession::LaunchApplication(term); } diff --git a/src-qt5/core/lumina-desktop/LWinInfo.cpp b/src-qt5/core/lumina-desktop/LWinInfo.cpp index 3ff0c2d7..6a6cea0b 100644 --- a/src-qt5/core/lumina-desktop/LWinInfo.cpp +++ b/src-qt5/core/lumina-desktop/LWinInfo.cpp @@ -21,7 +21,7 @@ QString LWinInfo::text(){ 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)+"..."; } + //if(nm.length()>40){ nm = nm.left(40)+"..."; } return nm; } @@ -45,4 +45,4 @@ LXCB::WINDOWVISIBILITY LWinInfo::status(bool update){ cstate = LSession::handle()->XCB->WindowState(window); } return cstate; -}
\ No newline at end of file +} diff --git a/src-qt5/core/lumina-desktop/defaults/luminaDesktop.conf b/src-qt5/core/lumina-desktop/defaults/luminaDesktop.conf index 2c973adb..251e8309 100644 --- a/src-qt5/core/lumina-desktop/defaults/luminaDesktop.conf +++ b/src-qt5/core/lumina-desktop/defaults/luminaDesktop.conf @@ -6,12 +6,12 @@ # 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.8.7): -# calendar, applauncher[::absolute path to *.desktop file], desktopview, notepad, audioplayer -# Possible Panel Plugins (Lumina version 0.8.7): +# 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.8.7): +# Possible Menu Plugins (Lumina version 0.9.1): # terminal, filemanager, applications, line, settings, windowlist, app::<absolute path to *.desktop file> #GENERAL SESSION SETTINGS @@ -22,32 +22,50 @@ 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 -#session_default_terminal=xterm -#session_default_filemanager=lumina-fm -#session_default_webbrowser=/usr/local/share/applications/firefox.desktop -#session_default_email=/usr/local/share/applications/thunderbird.desktop +#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=peazip.desktop +mime_default_application/x-compressed-tar_ifexists=peazip.desktop +mime_default_application/x-bzip-compressed-tar_ifexists=peazip.desktop +mime_default_application/x-lrzip-compressed-tar_ifexists=peazip.desktop +mime_default_application/x-lzma-compressed-tar_ifexists=peazip.desktop +mime_default_application/x-xz-compressed-tar_ifexists=peazip.desktop +mime_default_application/x-tar_ifexists=peazip.desktop + #THEME SETTINGS -#theme.themefile=<file path> #Absolute path to the theme template file to use (disable for Lumina-Default) +theme_themefile=Glass #Name of the theme to use (disable for Lumina-Default) theme_colorfile=Black #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/1/2] The number of panels visible by default +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= #list of plugins to be shown on the desktop by default +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=top #[top/bottom/left/right] Screen edge the panel should be on +panel1_location=bottom #[top/bottom/left/right] Screen edge the panel should be on panel1_pixelsize=4%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 #list of plugins for the panel panel1_pinlocation=center #[left/center/right] Note:[left/right] corresponds to [top/bottom] for vertical panels -panel1_edgepercent=90 #[1->100] percentage of the screen edge to use +panel1_edgepercent=99 #[1->100] percentage of the screen edge to use #MENU SETTINGS (right-click menu) menu_plugins=terminal, filemanager, applications, line, settings #list of menu plugins to show @@ -56,3 +74,11 @@ menu_plugins=terminal, filemanager, applications, line, settings #list of menu p #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 diff --git a/src-qt5/core/lumina-desktop/desktop-plugins/NewDP.h b/src-qt5/core/lumina-desktop/desktop-plugins/NewDP.h index a12341bb..e28b8c61 100644 --- a/src-qt5/core/lumina-desktop/desktop-plugins/NewDP.h +++ b/src-qt5/core/lumina-desktop/desktop-plugins/NewDP.h @@ -22,6 +22,7 @@ #include "systemmonitor/MonitorWidget.h" //#include "quickcontainer/QuickDPlugin.h" //#include "messagecenter/MessageCenter.h" +#include "rssreader/RSSFeedPlugin.h" class NewDP{ public: @@ -48,6 +49,8 @@ public: //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"; } diff --git a/src-qt5/core/lumina-desktop/desktop-plugins/calendar/CalendarPlugin.h b/src-qt5/core/lumina-desktop/desktop-plugins/calendar/CalendarPlugin.h index b3a6a8d7..abb138f7 100644 --- a/src-qt5/core/lumina-desktop/desktop-plugins/calendar/CalendarPlugin.h +++ b/src-qt5/core/lumina-desktop/desktop-plugins/calendar/CalendarPlugin.h @@ -31,6 +31,7 @@ public: 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())); } diff --git a/src-qt5/core/lumina-desktop/desktop-plugins/desktop-plugins.pri b/src-qt5/core/lumina-desktop/desktop-plugins/desktop-plugins.pri index 5a631b52..8376316a 100644 --- a/src-qt5/core/lumina-desktop/desktop-plugins/desktop-plugins.pri +++ b/src-qt5/core/lumina-desktop/desktop-plugins/desktop-plugins.pri @@ -2,7 +2,9 @@ SOURCES += $$PWD/applauncher/AppLauncherPlugin.cpp \ $$PWD/desktopview/DesktopViewPlugin.cpp \ $$PWD/notepad/NotepadPlugin.cpp \ $$PWD/audioplayer/PlayerWidget.cpp \ - $$PWD/systemmonitor/MonitorWidget.cpp + $$PWD/systemmonitor/MonitorWidget.cpp \ + $$PWD/rssreader/RSSFeedPlugin.cpp \ + $$PWD/rssreader/RSSObjects.cpp # $$PWD/messagecenter/MessageCenter.cpp HEADERS += $$PWD/calendar/CalendarPlugin.h \ @@ -11,9 +13,12 @@ HEADERS += $$PWD/calendar/CalendarPlugin.h \ $$PWD/desktopview/DesktopViewPlugin.h \ $$PWD/notepad/NotepadPlugin.h \ $$PWD/audioplayer/PlayerWidget.h \ - $$PWD/systemmonitor/MonitorWidget.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
\ No newline at end of file + $$PWD/systemmonitor/MonitorWidget.ui \ + $$PWD/rssreader/RSSFeedPlugin.ui diff --git a/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSFeedPlugin.cpp b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSFeedPlugin.cpp new file mode 100644 index 00000000..23c1ca01 --- /dev/null +++ b/src-qt5/core/lumina-desktop/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 <LuminaUtils.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); + + //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/desktop-plugins/rssreader/RSSFeedPlugin.h b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSFeedPlugin.h new file mode 100644 index 00000000..68b36760 --- /dev/null +++ b/src-qt5/core/lumina-desktop/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/desktop-plugins/rssreader/RSSFeedPlugin.ui b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSFeedPlugin.ui new file mode 100644 index 00000000..45dac405 --- /dev/null +++ b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSFeedPlugin.ui @@ -0,0 +1,545 @@ +<?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><!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="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/desktop-plugins/rssreader/RSSObjects.cpp b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSObjects.cpp new file mode 100644 index 00000000..654a005d --- /dev/null +++ b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSObjects.cpp @@ -0,0 +1,275 @@ +//=========================================== +// 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+" : "+urls[i]; + } + 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){ + if(hash.contains(ID)){ return hash[ID]; } + else{ return RSSchannel(); } +} + +//Initial setup +void RSSReader::addUrls(QStringList urls){ + //qDebug() << "Add URLS:" << urls; + QStringList known = hash.keys(); + int orig = known.length(); + for(int i=0; i<orig; i++){ known << hash[known[i]].originalURL; } + 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(); + if(known.contains(url)){ 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){ + if(hash.contains(ID)){ hash.remove(ID); } + emit newChannelsAvailable(); +} + +//================= +// PUBLIC SLOTS +//================= +void RSSReader::syncNow(){ + QStringList urls = hash.keys(); + for(int i=0; i<urls.length(); i++){ + requestRSS(urls[i]); + } +} + +//================= +// PRIVATE +//================= +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; + 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(url) && !hash.contains(newurl)){ + hash.insert(newurl, hash.take(url) ); //just move the data over to the new url + requestRSS(newurl); + emit newChannelsAvailable(); + handled = true; + } + } + if(!handled && hash.contains(url) ){ + emit rssChanged(url); + } + return; + } + + if(!hash.contains(url)){ + //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(keys[i]); + 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[url].lastBuildDate.isNull() || (hash[url].lastBuildDate < info.lastBuildDate) ); + bool newinfo = false; + if(changed){ newinfo = hash[url].title.isEmpty(); } //no previous info from this URL + info.originalURL = hash[url].originalURL; //make sure this info gets preserved across updates + hash.insert(url, info); + if(newinfo){ emit newChannelsAvailable(); } //new channel + else if(changed){ emit rssChanged(url); } //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/desktop-plugins/rssreader/RSSObjects.h b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSObjects.h new file mode 100644 index 00000000..c0e72e18 --- /dev/null +++ b/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSObjects.h @@ -0,0 +1,101 @@ +//=========================================== +// 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; + + //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/panel-plugins/appmenu/LAppMenuPlugin.cpp b/src-qt5/core/lumina-desktop/panel-plugins/appmenu/LAppMenuPlugin.cpp index 318d03fa..7c9b7ab0 100644 --- a/src-qt5/core/lumina-desktop/panel-plugins/appmenu/LAppMenuPlugin.cpp +++ b/src-qt5/core/lumina-desktop/panel-plugins/appmenu/LAppMenuPlugin.cpp @@ -34,7 +34,7 @@ void LAppMenuPlugin::updateButtonVisuals(){ button->setToolTip( tr("Quickly launch applications or open files")); button->setText( tr("Applications") ); //Use the PC-BSD icon by default (or the Lumina icon for non-PC-BSD systems) - button->setIcon( LXDG::findIcon("pcbsd","Lumina-DE") ); + button->setIcon( LXDG::findIcon("start-here-lumina","Lumina-DE") ); } // ======================== @@ -131,4 +131,3 @@ void LAppMenuPlugin::UpdateMenu(){ tmpact->setWhatsThis("internal::logout"); } - diff --git a/src-qt5/core/lumina-desktop/panel-plugins/systemstart/LStartButton.cpp b/src-qt5/core/lumina-desktop/panel-plugins/systemstart/LStartButton.cpp index e08ef1c8..d57846e2 100644 --- a/src-qt5/core/lumina-desktop/panel-plugins/systemstart/LStartButton.cpp +++ b/src-qt5/core/lumina-desktop/panel-plugins/systemstart/LStartButton.cpp @@ -42,7 +42,7 @@ LStartButtonPlugin::~LStartButtonPlugin(){ void LStartButtonPlugin::updateButtonVisuals(){ button->setToolTip(tr("")); button->setText( SYSTEM::user() ); - button->setIcon( LXDG::findIcon("pcbsd","Lumina-DE") ); //force icon refresh + button->setIcon( LXDG::findIcon("start-here-lumina","Lumina-DE") ); //force icon refresh } void LStartButtonPlugin::updateQuickLaunch(QStringList apps){ @@ -117,4 +117,3 @@ void LStartButtonPlugin::openMenu(){ void LStartButtonPlugin::closeMenu(){ menu->hide(); } - diff --git a/src-qt5/core/lumina-desktop/panel-plugins/taskmanager/LTaskButton.cpp b/src-qt5/core/lumina-desktop/panel-plugins/taskmanager/LTaskButton.cpp index a67ef5d6..7e2e53de 100644 --- a/src-qt5/core/lumina-desktop/panel-plugins/taskmanager/LTaskButton.cpp +++ b/src-qt5/core/lumina-desktop/panel-plugins/taskmanager/LTaskButton.cpp @@ -122,8 +122,12 @@ void LTaskButton::UpdateButton(){ //single window this->setPopupMode(QToolButton::DelayedPopup); this->setMenu(actMenu); - if(showText){ this->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); this->setText( WINLIST[0].text()); } - else if(noicon){ this->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); this->setText( cname ); } + 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){ diff --git a/src-qt5/core/lumina-open/LFileDialog.cpp b/src-qt5/core/lumina-open/LFileDialog.cpp index c0417935..9ec94bf5 100644 --- a/src-qt5/core/lumina-open/LFileDialog.cpp +++ b/src-qt5/core/lumina-open/LFileDialog.cpp @@ -15,8 +15,8 @@ LFileDialog::LFileDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LFileDia appExec.clear(); appPath.clear(); appFile.clear(); - QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, QDir::homePath()+"/.lumina"); - settings = new QSettings("LuminaDE", "lumina-open",this); + //QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, QDir::homePath()+"/.lumina"); + settings = new QSettings("lumina-desktop", "lumina-open", this); //Connect the signals/slots connect(ui->combo_apps, SIGNAL(currentIndexChanged(int)), this, SLOT(updateUI()) ); connect(ui->radio_rec, SIGNAL(toggled(bool)), this, SLOT(radioChanged()) ); @@ -46,6 +46,7 @@ void LFileDialog::setFileInfo(QString filename, QString extension, bool isFile){ //static functions QString LFileDialog::getDefaultApp(QString extension){ + qDebug() << "Get Default App:" << extension; QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, QDir::homePath()+"/.lumina"); if(extension.contains("/")){ return LXDG::findDefaultAppForMime(extension); @@ -66,7 +67,7 @@ void LFileDialog::setDefaultApp(QString extension, QString appFile){ }else{ QSettings("LuminaDE", "lumina-open").setValue("default/"+extension,appFile); } - } + } } // ----------- @@ -283,4 +284,3 @@ void LFileDialog::on_tool_findBin_clicked(){ void LFileDialog::on_line_bin_textChanged(){ updateUI(); } - diff --git a/src-qt5/core/lumina-open/main.cpp b/src-qt5/core/lumina-open/main.cpp index a323e075..7a7fef19 100644 --- a/src-qt5/core/lumina-open/main.cpp +++ b/src-qt5/core/lumina-open/main.cpp @@ -101,6 +101,7 @@ QString cmdFromUser(int argc, char **argv, QString inFile, QString extension, QS //qDebug() << "Matches:" << matches; for(int i=0; i<matches.length(); i++){ defApp = LXDG::findDefaultAppForMime(matches[i]); + //qDebug() << "MimeType:" << matches[i] << defApp; if(!defApp.isEmpty()){ extension = matches[i]; break; } else if(i+1==matches.length()){ extension = matches[0]; } } @@ -226,6 +227,7 @@ void getCMD(int argc, char ** argv, QString& binary, QString& args, QString& pat //Now check what type of file this is if(QFile::exists(inFile)){ isFile=true; } else if(QFile::exists(QDir::currentPath()+"/"+inFile)){isFile=true; inFile = QDir::currentPath()+"/"+inFile;} //account for relative paths + else if(inFile.startsWith("mailto:")){ isUrl= true; } else if(QUrl(inFile).isValid() && !inFile.startsWith("/") ){ isUrl=true; } if( !isFile && !isUrl ){ ShowErrorDialog( argc, argv, QString(QObject::tr("Invalid file or URL: %1")).arg(inFile) ); } //Determing the type of file (extension) @@ -239,7 +241,7 @@ void getCMD(int argc, char ** argv, QString& binary, QString& args, QString& pat else if(info.isExecutable() && extension.isEmpty()){ extension="binary"; } else if(extension!="desktop"){ extension="mimetype"; } //flag to check for mimetype default based on file } - else if(isUrl && inFile.startsWith("mailto:")){ extension = "email"; } + else if(isUrl && inFile.startsWith("mailto:")){ extension = "application/email"; } else if(isUrl && inFile.contains("://") ){ extension = "x-scheme-handler/"+inFile.section("://",0,0); } else if(isUrl && inFile.startsWith("www.")){ extension = "x-scheme-handler/http"; inFile.prepend("http://"); } //this catches partial (but still valid) URL's ("www.<something>" for instance) //qDebug() << "Input:" << inFile << isFile << isUrl << extension; @@ -314,7 +316,7 @@ void getCMD(int argc, char ** argv, QString& binary, QString& args, QString& pat cmd.replace("%F","\""+inFile+"\""); }else if( (cmd.contains("%U") || cmd.contains("%u")) ){ //Apply any special field replacements for the desired format - if(!inFile.contains("://")){ inFile.prepend("file://"); } //local file - add the extra flag + if(!inFile.contains("://") && !isUrl){ inFile.prepend("file://"); } //local file - add the extra flag inFile.replace(" ", "%20"); //Now replace the field codes cmd.replace("%u","\""+inFile+"\""); diff --git a/src-qt5/core/lumina-session/main.cpp b/src-qt5/core/lumina-session/main.cpp index 8f89e95c..27eaf537 100644 --- a/src-qt5/core/lumina-session/main.cpp +++ b/src-qt5/core/lumina-session/main.cpp @@ -40,7 +40,9 @@ int main(int argc, char ** argv) //Start X11 if needed //Configure X11 monitors if needed - + if(LUtils::isValidBinary("lumina-xconfig")){ + QProcess::execute("lumina-xconfig --reset-monitors"); + } //Startup the session QCoreApplication a(argc, argv); LSession sess; |