diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 4aed2c8219774f5d797760606b8489a92ddc5163 (patch) | |
tree | 3f8c130f7d269626bf6a9447407ef6c35954426a /kicker/applets/launcher/quicklauncher.cpp | |
download | tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kicker/applets/launcher/quicklauncher.cpp')
-rw-r--r-- | kicker/applets/launcher/quicklauncher.cpp | 1093 |
1 files changed, 1093 insertions, 0 deletions
diff --git a/kicker/applets/launcher/quicklauncher.cpp b/kicker/applets/launcher/quicklauncher.cpp new file mode 100644 index 000000000..abae9efe1 --- /dev/null +++ b/kicker/applets/launcher/quicklauncher.cpp @@ -0,0 +1,1093 @@ +/***************************************************************** + +Copyright (c) 2000 Bill Nagel +Copyright (c) 2004 Dan Bullok <[email protected]> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +******************************************************************/ + +#include <qpainter.h> +#include <qpopupmenu.h> +#include <qslider.h> +#include <qtimer.h> +#include <qtooltip.h> + +#include <dcopclient.h> +#include <kaction.h> +#include <kapplication.h> +#include <kaboutapplication.h> +#include <kaboutdata.h> +#include <kdialogbase.h> +#include <kglobal.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <knuminput.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kurldrag.h> +#include <kdebug.h> + + +#include <algorithm> +#include <list> +#include <math.h> +#include <set> +#include <assert.h> + +#include "configdlg.h" +#include "popularity.h" +#include "quicklauncher.h" +#include "quickbutton.h" +#include "quickaddappsmenu.h" +#include "quickbuttongroup.h" + +typedef ButtonGroup::iterator ButtonIter; +const ButtonGroup::Index NotFound=ButtonGroup::NotFound; +const ButtonGroup::Index Append=ButtonGroup::Append; + +#ifdef DEBUG + #define DEBUGSTR kdDebug() +#else + #define DEBUGSTR kndDebug() +#endif + +extern "C" +{ + KDE_EXPORT KPanelApplet* init(QWidget *parent, const QString& configFile) + { + KGlobal::locale()->insertCatalogue("quicklauncher"); + return new QuickLauncher(configFile, KPanelApplet::Normal, + KPanelApplet::Preferences, + parent, "quicklauncher"); + } +} + +QuickLauncher::QuickLauncher(const QString& configFile, Type type, int actions, + QWidget *parent, const char *name) : + KPanelApplet(configFile, type, actions, parent, name) +{ + DCOPObject::setObjId("QuickLauncherApplet"); + DEBUGSTR << endl << endl << endl << "------------" << flush; + DEBUGSTR << "QuickLauncher::QuickLauncher(" << configFile << ",...)" << + endl << flush; + + m_settings = new Prefs(sharedConfig()); + m_settings->readConfig(); + + m_needsSave = false; + m_needsRefresh = false; + m_refreshEnabled = false; + + m_configDialog = 0; + m_popup = 0; + m_appletPopup = 0; + m_removeAppsMenu = 0; + + m_dragAccepted = false; + + m_buttons = new ButtonGroup; + m_manager = new FlowGridManager; + m_newButtons = 0; + m_oldButtons = 0; + m_dragButtons = 0; + + m_configAction = new KAction(i18n("Configure Quicklauncher..."), "configure", KShortcut(), + this, SLOT(slotConfigure()), this); + + m_saveTimer = new QTimer(this); + connect(m_saveTimer, SIGNAL(timeout()), this, SLOT(saveConfig())); + + m_popularity = new PopularityStatistics(); + + setBackgroundOrigin(AncestorOrigin); + + loadConfig(); + + buildPopupMenu(); + m_minPanelDim = std::max(16, m_settings->iconDimChoices()[1]); + refreshContents(); + setRefreshEnabled(true); + + setAcceptDrops(true); + //QToolTip::add(this, i18n("Drop applications here")); + DEBUGSTR << " QuickLauncher::QuickLauncher(" << configFile << + ",...) END" << endl << flush; + + DCOPClient *dcopClient = KApplication::dcopClient(); + dcopClient->connectDCOPSignal(0, "appLauncher", + "serviceStartedByStorageId(QString,QString)", + "QuickLauncherApplet", + "serviceStartedByStorageId(QString,QString)", + false); + kdDebug() << "Quicklauncher registered DCOP signal" << endl; +} + + +//TODO:? Drag/drop more than one item at a time + +QuickLauncher::~QuickLauncher() +{ + KGlobal::locale()->removeCatalogue("quicklauncher"); + setCustomMenu(0); + delete m_popup; + delete m_appletPopup; + delete m_removeAppsMenu; + delete m_popularity; + clearTempButtons(); + if (m_buttons) + { + m_buttons->deleteContents(); + delete m_buttons; + } +} + +// Builds, connects _popup menu +void QuickLauncher::buildPopupMenu() +{ + QuickAddAppsMenu *addAppsMenu = new QuickAddAppsMenu(this, this); + m_popup = new QPopupMenu(this); + m_popup->insertItem(i18n("Add Application"), addAppsMenu); + m_configAction->plug(m_popup); + + m_appletPopup = new QPopupMenu(this); + m_appletPopup->insertItem(i18n("Add Application"), addAppsMenu); + m_removeAppsMenu = new QPopupMenu(this); + connect(m_removeAppsMenu, SIGNAL(aboutToShow()), + SLOT(fillRemoveAppsMenu())); + connect(m_removeAppsMenu, SIGNAL(activated(int)), + SLOT(removeAppManually(int))); + m_appletPopup->insertItem(i18n("Remove Application"), m_removeAppsMenu); + + m_appletPopup->insertSeparator(); + m_appletPopup->setCheckable( true ); + m_appletPopup->insertItem(i18n("About"), this, SLOT(about())); + setCustomMenu(m_appletPopup); +} + + +// Fill the remove apps menu +void QuickLauncher::fillRemoveAppsMenu() +{ + m_removeAppsMenu->clear(); + ButtonIter iter(m_buttons->begin()); + int i = 0; + while (iter != m_buttons->end()) + { + QString text = QToolTip::textFor(*iter); + if (text.isEmpty()) + { + text = (*iter)->url(); + if (text.isEmpty()) + { + text = i18n("Unknown"); + } + } + m_removeAppsMenu->insertItem((*iter)->icon(), text, i); + ++iter; + ++i; + } +} + +void QuickLauncher::slotSettingsDialogChanged() +{ + // Update conserve space setting + setConserveSpace(m_settings->conserveSpace()); + m_popularity->setHistoryHorizon(m_settings->historyHorizon()/100.0); + slotAdjustToCurrentPopularity(); + kdDebug() << "Icon size: " << m_settings->iconDim() << endl; + refreshContents(); + + saveConfig(); +} + +void QuickLauncher::action(Action a) +{ + if (a == KPanelApplet::Preferences) + { + slotConfigure(); + } + else + { + KPanelApplet::action(a); + } +} + +void QuickLauncher::slotConfigure() +{ + if (!m_configDialog) + { + m_configDialog = new ConfigDlg(this, "configdialog", + m_settings, SIZE_AUTO, KDialogBase::Plain, KDialogBase::Ok | + KDialogBase::Cancel | KDialogBase::Apply | KDialogBase::Default); + connect(m_configDialog, SIGNAL(settingsChanged()), + this, SLOT(slotSettingsDialogChanged())); + } + + m_configDialog->show(); +} + + +int QuickLauncher::findApp(QuickButton *button) +{ + if (m_buttons->empty()) + { + return NotFound; + } + int pos = m_buttons->findValue(button); + return pos; +} + + +int QuickLauncher::findApp(QString url) +{ + if (m_buttons->empty()) + { + return NotFound; + } + int pos=m_buttons->findDescriptor(url); + return pos; +} + +void QuickLauncher::removeAppManually(int index) +{ + removeApp(index, true); +} + +void QuickLauncher::removeApp(int index, bool manuallyRemoved) +{ + if (m_buttons->empty()) + { + return; + } + if (!m_buttons->isValidIndex(index)) + { + kdWarning() << " removeApp (" << index << + ") *******WARNING****** index=" << index << "is out of bounds." << + endl << flush; + return; + } + DEBUGSTR << "Removing button. index=" << index << " url='" << + (*m_buttons)[index]->url() << "'" << endl << flush; + + QString removeAppUrl = (*m_buttons)[index]->url(); + QString removeAppMenuId = (*m_buttons)[index]->menuId(); + + delete (*m_buttons)[index]; + m_buttons->eraseAt(index); + refreshContents(); + + if (int(m_buttons->size()) < m_settings->autoAdjustMinItems() && manuallyRemoved) + { + m_settings->setAutoAdjustMinItems(m_buttons->size()); + } + + if (manuallyRemoved) + { + m_popularity->moveToBottom(removeAppMenuId); + slotAdjustToCurrentPopularity(); + } + + saveConfig(); +} + + +void QuickLauncher::removeApp(QString url, bool manuallyRemoved) +{ + int index = findApp(url); + if (index == NotFound) + { + kdDebug() << "removeApp: Not found: " << url << endl; + return; + } + removeApp(index, manuallyRemoved); +} + + +void QuickLauncher::removeAppManually(QuickButton *button) +{ + int index = findApp(button); + if (index == NotFound) + { + return; + } + removeApp(index, true); +} + + +int QuickLauncher::widthForHeight(int h) const +{ + FlowGridManager temp_manager = *m_manager; + temp_manager.setFrameSize(QSize(h,h)); + temp_manager.setOrientation(Qt::Horizontal); // ??? probably not necessary + if (temp_manager.isValid()) + { + return temp_manager.frameSize().width(); + } + return m_minPanelDim; +} + + +int QuickLauncher::heightForWidth(int w) const +{ + FlowGridManager temp_manager=*m_manager; + temp_manager.setFrameSize(QSize(w,w)); + temp_manager.setOrientation(Qt::Vertical); // ??? probably not necessary + if (temp_manager.isValid()) + { + return temp_manager.frameSize().height(); + } + return m_minPanelDim; +} + + +int QuickLauncher::dimension() const +{ + if (orientation()==Qt::Vertical) + { + return size().width(); + } + return size().height(); +} + +void QuickLauncher::addApp(QString url, bool manuallyAdded) +{ + assert(m_buttons); + QString newButtonId = QuickURL(url).menuId(); + if (m_appOrdering.find(newButtonId) == m_appOrdering.end()) + { + m_appOrdering[newButtonId] = m_appOrdering.size(); + } + uint appPos; + for (appPos = 0; appPos < m_buttons->size(); ++appPos) + { + QString buttonId = (*m_buttons)[appPos]->menuId(); + if (m_appOrdering[buttonId] >= m_appOrdering[newButtonId]) + { + break; + } + } + addApp(url, appPos, manuallyAdded); +} + +QuickButton* QuickLauncher::createButton(QString url) +{ + QuickButton* newButton=new QuickButton(url, m_configAction, this); + connect(newButton, SIGNAL(executed(QString)), + this, SLOT(slotOwnServiceExecuted(QString))); + connect(newButton, SIGNAL(stickyToggled(bool)), + this, SLOT(slotStickyToggled())); + newButton->setPopupDirection(popupDirection()); + return newButton; +} + +void QuickLauncher::addApp(QString url, int index, bool manuallyAdded) +{ + DEBUGSTR << endl <<"About to add: url='" << url << + "' index=" << index << endl << flush; + QuickButton *newButton; + if (!m_buttons->isValidInsertIndex(index)) + { + kdWarning() << " *******WARNING****** index=" << index << + "is out of bounds." << endl << flush; + index = m_buttons->lastIndex(); + } + int old = findApp(QuickURL(url).url()); + if (old != NotFound) + { + if (index == old) + { + return; + } + if (index > old) + { + index--; + } + newButton = (*m_buttons)[old]; + m_buttons->eraseAt(old); + } + else + { + newButton = createButton(url); + } + m_buttons->insertAt(index, newButton); + DEBUGSTR << "Added: url='"<<url<<"' index="<<index<<endl<<endl<<flush; + refreshContents(); + + if (manuallyAdded) + { + newButton->setSticky(true); + if (int(m_buttons->size()) > m_settings->autoAdjustMaxItems()) + { + m_settings->setAutoAdjustMaxItems(m_buttons->size()); + } + } + + updateInsertionPosToStatusQuo(); + saveConfig(); +} + +void QuickLauncher::updateInsertionPosToStatusQuo() +{ + // Update the app ordering map, so that next time, + // addApp(url,manAdded) (without index) will insert the + // item at the same position again. + std::list<QString> appList; + std::set<int> posList; + //kdDebug() << "Rearranging application order. Before:" << endl; + for (uint n = 0; n < m_buttons->size(); ++n) + { + QString buttonId = (*m_buttons)[n]->menuId(); + appList.push_back(buttonId); + if (m_appOrdering.find(buttonId) == m_appOrdering.end()) + { + m_appOrdering[buttonId] = m_appOrdering.size(); + } + posList.insert(m_appOrdering[buttonId]); + //kdDebug() << m_appOrdering[buttonId] << " = " << buttonId << endl; + } + //kdDebug() << "After:" << endl; + while (posList.size() > 0) + { + assert(appList.size() > 0); + m_appOrdering[*appList.begin()] = *posList.begin(); + kdDebug() << *posList.begin() << " = " << *appList.begin() << endl; + posList.erase(posList.begin()); + appList.pop_front(); + } + //kdDebug() << "Done." << endl; +} + +void QuickLauncher::addAppBeforeManually(QString url, QString sender) +{ + if (sender.isNull()) + { + addApp(url, Append, true); + } + int pos = findApp(sender); + if (pos < 0) + { + pos = Append; + } + DEBUGSTR << "QuickLauncher::addAppBefore(" << url << + "," << sender << "): pos=" << pos << endl << flush; + addApp(url, pos, true); +} + + +void QuickLauncher::about() +{ + KAboutData about("quicklauncher", I18N_NOOP("Quick Launcher"), "2.0", + I18N_NOOP("A simple application launcher"), + KAboutData::License_GPL_V2, + "(C) 2000 Bill Nagel\n(C) 2004 Dan Bullok\n(C) 2005 Fred Schaettgen"); + KAboutApplication a(&about, this); + a.exec(); +} + + +void QuickLauncher::mousePressEvent(QMouseEvent *e) +{ + if (e->button() == RightButton) + { + m_popup->popup(e->globalPos()); + } +} + +void QuickLauncher::resizeEvent(QResizeEvent*) +{ + refreshContents(); +} + +void QuickLauncher::dragEnterEvent(QDragEnterEvent *e) +{ + DEBUGSTR << "QuickLauncher::dragEnterEvent(pos=" << e->pos() << + " type=" << e->type() << ")" << endl << flush; + m_dragAccepted=false; + KURL::List kurlList; + if (!isDragEnabled() || !KURLDrag::decode(e, kurlList)) + { + e->accept(false); + return; + } + + if (kurlList.size()<=0) + { + e->accept(false); + return; + } + m_dragButtons=new ButtonGroup; + m_oldButtons=new ButtonGroup(*m_buttons); + + QString url; + KURL::List::ConstIterator it = kurlList.begin(); + for ( ; it != kurlList.end(); ++it ) + { + url = QuickURL((*it).url()).url(); + kdDebug() << " Drag Object='"<<url<<"' " << (*it).url() << endl; + int pos = m_buttons->findDescriptor(url); + if (pos != NotFound) + { + // if it's already in m_buttons, take it out + m_dragButtons->push_back(m_buttons->takeFrom(pos)); + } + else + { + // otherwise, create a new one + QuickButton* button = createButton(url); + button->setSticky(true); + m_dragButtons->push_back(button); + } + } + if (m_dragButtons->size() > 0) + { + //make sure we can drag at least one button. + m_dragAccepted=true; + m_newButtons=new ButtonGroup(*m_buttons); + m_dropPos=NotFound; + e->accept(true); + return; + } + e->accept(false); + clearTempButtons(); +} + + +void QuickLauncher::dragMoveEvent(QDragMoveEvent *e) +{ + if (!m_dragAccepted) + { + kdWarning() << "QuickLauncher::dragMoveEvent: Drag is not accepted." << + m_dragAccepted << endl << flush; + e->accept(false); + return; + } + + e->accept(true); + int pos=m_manager->indexNearest(e->pos()); + if (pos == m_dropPos) + { + return;// Already been inserted here, no need to update + } + + if (m_newButtons->isValidInsertIndex(pos)) + { + mergeButtons(pos); + m_dropPos=pos; + } + refreshContents(); +} + + +void QuickLauncher::dragLeaveEvent(QDragLeaveEvent *e) +{ + DEBUGSTR << "QuickLauncher::dragLeaveEvent(type=" << + e->type() << ")" << endl << flush; + if (!m_dragAccepted) + { + return; + } + + // No drop. Return to starting state. + std::swap(m_buttons,m_oldButtons); + clearTempButtons(); + + refreshContents(); + saveConfig(); +} + + +void QuickLauncher::dropEvent(QDropEvent *e) +{ + DEBUGSTR << "QuickLauncher::dropEvent(pos=" << e->pos() << + " type=" << e->type() << ")" << endl << flush; + if (!m_dragAccepted) + { + e->accept(false); + return; + } + + if (e->source() == 0) + { + for (uint n=0; n<m_dragButtons->size(); ++n) + { + (*m_dragButtons)[n]->setSticky(true); + } + } + + clearTempButtons(); + refreshContents(); + saveConfig(); + updateInsertionPosToStatusQuo(); +} + +// insert dragbuttons at index in m_newButtons. Put result in m_buttons +void QuickLauncher::mergeButtons(int index) +{ + if (!m_newButtons->isValidInsertIndex(index)) + { + index=m_newButtons->size(); + } + + m_buttons->clear(); + (*m_buttons) = (*m_newButtons); + m_buttons->insertAt(index, *m_dragButtons); + refreshContents(); +} + +void QuickLauncher::clearTempButtons() +{ + std::set<QuickButton*> allButtons; + //put all the m_buttons in a set (removes duplicates automatically + if (m_newButtons) + { + allButtons.insert(m_newButtons->begin(),m_newButtons->end()); + } + if (m_oldButtons) + { + allButtons.insert(m_oldButtons->begin(),m_oldButtons->end()); + } + if (m_dragButtons) + { + allButtons.insert(m_dragButtons->begin(),m_dragButtons->end()); + } + + //delete temp ButtonGroups + delete m_newButtons; m_newButtons=0; + delete m_oldButtons; m_oldButtons=0; + delete m_dragButtons; m_dragButtons=0; + + //if an element allButtons is NOT in m_buttons (the ones we keep), delete it + std::set<QuickButton *>::iterator iter = allButtons.begin(); + while (iter != allButtons.end()) + { + if (findApp(*iter) == NotFound) + { + delete *iter; + } + ++iter; + } + m_dragAccepted = false; + m_dropPos = NotFound; +} + +void QuickLauncher::refreshContents() +{ + int idim, d(dimension()); + // determine button size + if (m_settings->iconDim() == SIZE_AUTO) + { + if (d < 18) + { + idim = std::min(16,d); + } + else if (d < 64) + { + idim = 16; + } + else if (d < 80) + { + idim = 20; + } + else if (d < 122) + { + idim = 24; + } + else + { + idim = 28; + } + } + else + { + idim = std::min(m_settings->iconDim(), d - std::max((d/8)-1, 0) * 2); + } + m_space = std::max((idim/8)-1, 0); + m_border = m_space; + m_buttonSize = QSize(idim, idim); + m_manager->setOrientation(orientation()); + m_manager->setNumItems(m_buttons->size()); + m_manager->setFrameSize(size()); + m_manager->setItemSize(m_buttonSize); + m_manager->setSpaceSize(QSize(m_space, m_space)); + m_manager->setBorderSize(QSize(m_border, m_border)); + if (!m_refreshEnabled) + { + m_needsRefresh=true; + return; + } + if (!m_manager->isValid()) + { + kdDebug()<<endl<<"******WARNING****** Layout is invalid."<< + endl << flush; + m_manager->dump(); + return; + } + + unsigned index; + QPoint pos; + setUpdatesEnabled(false); + m_buttons->setUpdatesEnabled(false); + for (index = 0; index < m_buttons->size(); index++) + { + pos = m_manager->pos(index); + QuickButton *button = (*m_buttons)[index]; + button->resize(m_manager->itemSize()); + button->move(pos.x(), pos.y()); + button->setDragging(false); + button->setEnableDrag(isDragEnabled()); + button->setDynamicModeEnabled(m_settings->autoAdjustEnabled()); + } + if (m_newButtons) + { + m_newButtons->setDragging(false); + } + if (m_dragButtons) + { + m_dragButtons->setDragging(true); + } + m_buttons->show(); + setUpdatesEnabled(true); + update(); + m_buttons->setUpdatesEnabled(true); + updateGeometry(); + emit updateLayout(); + updateStickyHighlightLayer(); +} + + +void QuickLauncher::setDragEnabled(bool enable) +{ + m_settings->setDragEnabled(enable); +} + +void QuickLauncher::setConserveSpace(bool conserve_space) +{ + m_manager->setConserveSpace(conserve_space); + if (conserve_space) + { + m_manager->setSlack(FlowGridManager::SpaceSlack, + FlowGridManager::SpaceSlack); + } + else + { + m_manager->setSlack(FlowGridManager::ItemSlack, + FlowGridManager::ItemSlack); + } + refreshContents(); +} + +class SortByPopularity { +public: + bool operator()(const QuickLauncher::PopularityInfo& a, + const QuickLauncher::PopularityInfo& b) + { + return a.popularity < b.popularity; + } +}; + +void QuickLauncher::loadConfig() +{ + DEBUGSTR << "QuickLauncher::loadConfig()" << endl << flush; + //KConfig *c = config(); + //c->setGroup("General"); + setConserveSpace(m_settings->conserveSpace()); + setDragEnabled(m_settings->dragEnabled()); + /*DEBUGSTR << " IconDim="<<m_iconDim << endl << flush; + DEBUGSTR << " ConserveSpace=" << (m_manager->conserveSpace()) << + endl << flush; + DEBUGSTR << " DragEnabled=" << isDragEnabled() << endl << flush;*/ + QStringList volatileButtons = m_settings->volatileButtons(); + QStringList urls = m_settings->buttons(); + kdDebug() << "GetButtons " << urls.join("/") << endl; + QStringList::Iterator iter(urls.begin()); + int n = 0; + while (iter != urls.end()) { + QString url = *iter; + addApp(url, n, false); + ++iter; + ++n; + } + + // Restore sticky state + for (n=0; n<int(m_buttons->size()); ++n) + { + QuickButton* button = (*m_buttons)[n]; + if (volatileButtons.contains(button->menuId()) == false) + { + button->setSticky(true); + } + button->setDynamicModeEnabled(m_settings->autoAdjustEnabled()); + } + + m_popularity->readConfig(m_settings); + m_popularity->setHistoryHorizon(m_settings->historyHorizon()/100.0); + + QStringList serviceNames = m_settings->serviceNames(); + QValueList<int> insPos = m_settings->serviceInspos(); + for (int n=std::min(serviceNames.size(),insPos.size())-1; n>=0; --n) + { + m_appOrdering[serviceNames[n]] = insPos[n]; + } +} + +void QuickLauncher::saveConfig() +{ + if (!m_refreshEnabled) + { + m_needsSave=true; + return; + } + QStringList urls, volatileUrls; + ButtonIter iter = m_buttons->begin(); + while (iter != m_buttons->end()) { + if ((*iter)->sticky() == false) + { + volatileUrls.append((*iter)->menuId()); + } + urls.append((*iter)->menuId()); + ++iter; + } + m_settings->setButtons(urls); + kdDebug() << "SetButtons " << urls.join("/") << endl; + m_settings->setVolatileButtons(volatileUrls); + m_settings->setConserveSpace(m_manager->conserveSpace()); + m_settings->setDragEnabled(isDragEnabled()); + + m_popularity->writeConfig(m_settings); + + // m_popularity must have written the current service list by now + QStringList serviceNames = m_settings->serviceNames(); + QValueList<int> insertionPositions; + for (int n=0; n<int(serviceNames.size()); ++n) + { + if (m_appOrdering.find(serviceNames[n]) != m_appOrdering.end()) + { + insertionPositions.push_back(m_appOrdering[serviceNames[n]]); + } + } + m_settings->setServiceInspos(insertionPositions); + + m_settings->writeConfig(); +} + + +void QuickLauncher::setRefreshEnabled(bool enable) +{ + m_refreshEnabled=enable; + if (m_refreshEnabled) + { + if (m_needsSave) { + saveConfig(); + } + if (m_needsRefresh) { + refreshContents(); + } + } +} + +void QuickLauncher::serviceStartedByStorageId(QString /*starter*/, QString storageId) +{ + KService::Ptr service = KService::serviceByStorageId(storageId); + if (service->icon() == QString::null) + { + kdDebug() << storageId << " has no icon. Makes no sense to add it."; + return; + } + QuickURL url = QuickURL(locate("apps", service->desktopEntryPath())); + QString desktopMenuId(url.menuId()); + kdDebug() << "storageId=" << storageId << " desktopURL=" << desktopMenuId << endl; + // A service was started somwhere else. If the quicklauncher contains + // this service too, we flash the icon + QuickButton *startedButton = 0; + std::set<QString> buttonIdSet; + for (uint n = 0; n < m_buttons->size(); ++n) + { + QuickButton *button = (*m_buttons)[n]; + QString buttonMenuId = button->menuId(); + buttonIdSet.insert(buttonMenuId); + if (desktopMenuId == buttonMenuId) + { + kdDebug() << "QuickLauncher: I know that one: " << storageId << endl; + button->flash(); + startedButton = button; + } + } + + // Update popularity info. + // We do this even if autoadjust is disabled + // so there are sane values to start with if it's turned on. + m_popularity->useService(desktopMenuId); + + if (m_settings->autoAdjustEnabled()) + { + QTimer::singleShot(0, this, SLOT(slotAdjustToCurrentPopularity())); + } +} + +void QuickLauncher::slotAdjustToCurrentPopularity() +{ + // TODO: Shrink immediately if buttons->size() > maxItems + kdDebug() << "Starting popularity update" << endl; + PopularityStatistics* stats = m_popularity; + int minItems = m_settings->autoAdjustMinItems(); + int maxItems = m_settings->autoAdjustMaxItems(); + + static const double hysteresisFactor = 0.90; + double minAddPopularity = 0; + for (int n = 0; n < maxItems; ++n) + { + // All items with a popularity not less than 0.75 of the average + // of the first maxItems apps are included in the list + double belowAvgAllowed = 0.75; + minAddPopularity += (belowAvgAllowed * stats->popularityByRank(n)) / maxItems; + } + double minDelPopularity = minAddPopularity * hysteresisFactor; + std::map<QString, QuickButton*> removeableApps; + std::set<QString> existingApps; + int numApps = m_buttons->size(); + for (int n = 0; n < int(m_buttons->size()); ++n) + { + QuickButton *button = (*m_buttons)[n]; + if (((stats->popularityByRank(stats->rankByService(button->menuId())) < + minDelPopularity) || m_settings->autoAdjustEnabled()==false) && + (button->sticky() == false)) + { + removeableApps[button->menuId()] = button; + --numApps; + } + existingApps.insert(button->menuId()); + } + for (int n = 0; + (numApps < minItems && stats->popularityByRank(n) > 0) || + (numApps < maxItems && stats->popularityByRank(n) > minAddPopularity); + ++n) + { + QString app = m_popularity->serviceByRank(n); + if (existingApps.find(app) == existingApps.end()) + { + addApp(QuickURL(m_popularity->serviceByRank(n)).url(), false); + kdDebug() << "Adding app " << app << endl; + ++numApps; + } + else if (removeableApps.find(app) != removeableApps.end()) + { + removeableApps.erase(app); + ++numApps; + } + } + while (removeableApps.size() > 0) + { + removeApp(findApp(removeableApps.begin()->second), false); + kdDebug() << "Removing app " << removeableApps.begin()->first << endl; + removeableApps.erase(removeableApps.begin()->first); + } + kdDebug() << "done popularity update" << endl; + m_settings->setAutoAdjustMinItems(minItems); + m_settings->setAutoAdjustMaxItems(maxItems); + + // TODO: Think of something better than that: + m_saveTimer->start(10000,true); +} + +void QuickLauncher::slotOwnServiceExecuted(QString serviceMenuId) +{ + m_popularity->useService(serviceMenuId); + if (m_settings->autoAdjustEnabled()) + { + QTimer::singleShot(0, this, SLOT(slotAdjustToCurrentPopularity())); + } +} + +void QuickLauncher::updateStickyHighlightLayer() +{ + // Creates a transparent image which is used + // to highlight those buttons which will never + // be removed automatically from the launcher + QPixmap areaPix(width(), height()); + QPainter areaPixPainter(&areaPix); + areaPixPainter.fillRect(0, 0, width(), height(), QColor(255, 255, 255)); + QSize itemSize = m_manager->itemSize(); + QSize spaceSize = m_manager->spaceSize(); + for (uint n=0; n<m_buttons->size(); ++n) + { + QPoint pos = m_manager->pos(n); + if ((*m_buttons)[n]->sticky() == false) + { + areaPixPainter.fillRect(pos.x()-(spaceSize.width()+1)/2, + pos.y()-(spaceSize.height()+1)/2, + itemSize.width()+spaceSize.width()+1, + itemSize.height()+spaceSize.height()+1, + QColor(0, 0, 0)); + } + } + QImage areaLayer = areaPix.convertToImage(); + m_stickyHighlightLayer = QImage(width(), height(), 32); + m_stickyHighlightLayer.setAlphaBuffer(true); + int pix, tlPix, brPix, w(width()), h(height()); + QRgb transparent(qRgba(0, 0, 0, 0)); + for (int y = h-1; y >= 0; --y) + { + for (int x = w-1; x >= 0; --x) + { + pix = qRed(areaLayer.pixel(x, y)); + if (pix == 0) + { + tlPix = (y>0 && x>0) ? qRed(areaLayer.pixel(x-1,y-1)) : 255; + brPix = (y<h-1 && x<w-1) ? qRed(areaLayer.pixel(x+1,y+1)) : 255; + int c = tlPix-brPix < 0 ? 255 : 0; + int alpha = abs(tlPix-brPix)/2; + m_stickyHighlightLayer.setPixel(x, y, qRgba(c, c, c, alpha)); + } + else + { + m_stickyHighlightLayer.setPixel(x, y, transparent); + } + } + } + repaint(); +} + +void QuickLauncher::paintEvent(QPaintEvent* e) +{ + KPanelApplet::paintEvent(e); + + if (m_settings->autoAdjustEnabled() && + m_settings->showVolatileButtonIndicator()) + { + QPainter p(this); + p.drawImage(0, 0, m_stickyHighlightLayer); + } +} + +void QuickLauncher::slotStickyToggled() +{ + updateStickyHighlightLayer(); + saveConfig(); +} + +void QuickLauncher::positionChange(Position) +{ + for (int n=0; n<int(m_buttons->size()); ++n) + { + (*m_buttons)[n]->setPopupDirection(popupDirection()); + } +} + + +#include "quicklauncher.moc" |