You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1094 lines
30 KiB
1094 lines
30 KiB
/*****************************************************************
|
|
|
|
Copyright (c) 2000 Bill Nagel
|
|
Copyright (c) 2004 Dan Bullok <dan.devel@bullok.com>
|
|
|
|
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 <tqpainter.h>
|
|
#include <tqpopupmenu.h>
|
|
#include <tqslider.h>
|
|
#include <tqtimer.h>
|
|
#include <tqtooltip.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(TQWidget *parent, const TQString& configFile)
|
|
{
|
|
KGlobal::locale()->insertCatalogue("quicklauncher");
|
|
return new QuickLauncher(configFile, KPanelApplet::Normal,
|
|
KPanelApplet::Preferences,
|
|
parent, "quicklauncher");
|
|
}
|
|
}
|
|
|
|
QuickLauncher::QuickLauncher(const TQString& configFile, Type type, int actions,
|
|
TQWidget *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, TQT_SLOT(slotConfigure()), this);
|
|
|
|
m_saveTimer = new TQTimer(this, "m_saveTimer");
|
|
connect(m_saveTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(saveConfig()));
|
|
|
|
m_popularity = new PopularityStatistics();
|
|
|
|
setBackgroundOrigin(AncestorOrigin);
|
|
|
|
loadConfig();
|
|
|
|
buildPopupMenu();
|
|
m_minPanelDim = std::max(16, m_settings->iconDimChoices()[1]);
|
|
refreshContents();
|
|
setRefreshEnabled(true);
|
|
|
|
setAcceptDrops(true);
|
|
//TQToolTip::add(this, i18n("Drop applications here"));
|
|
DEBUGSTR << " QuickLauncher::QuickLauncher(" << configFile <<
|
|
",...) END" << endl << flush;
|
|
|
|
DCOPClient *dcopClient = KApplication::dcopClient();
|
|
dcopClient->connectDCOPSignal(0, "appLauncher",
|
|
"serviceStartedByStorageId(TQString,TQString)",
|
|
"QuickLauncherApplet",
|
|
"serviceStartedByStorageId(TQString,TQString)",
|
|
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 TQPopupMenu(this);
|
|
m_popup->insertItem(i18n("Add Application"), addAppsMenu);
|
|
m_configAction->plug(m_popup);
|
|
|
|
m_appletPopup = new TQPopupMenu(this);
|
|
m_appletPopup->insertItem(i18n("Add Application"), addAppsMenu);
|
|
m_removeAppsMenu = new TQPopupMenu(this);
|
|
connect(m_removeAppsMenu, TQT_SIGNAL(aboutToShow()),
|
|
TQT_SLOT(fillRemoveAppsMenu()));
|
|
connect(m_removeAppsMenu, TQT_SIGNAL(activated(int)),
|
|
TQT_SLOT(removeAppManually(int)));
|
|
m_appletPopup->insertItem(i18n("Remove Application"), m_removeAppsMenu);
|
|
|
|
m_appletPopup->insertSeparator();
|
|
m_appletPopup->setCheckable( true );
|
|
m_appletPopup->insertItem(i18n("About"), this, TQT_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())
|
|
{
|
|
TQString text = TQToolTip::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, TQT_SIGNAL(settingsChanged()),
|
|
this, TQT_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(TQString 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;
|
|
|
|
TQString removeAppUrl = (*m_buttons)[index]->url();
|
|
TQString 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(TQString 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(TQSize(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(TQSize(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(TQString url, bool manuallyAdded)
|
|
{
|
|
assert(m_buttons);
|
|
TQString 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)
|
|
{
|
|
TQString buttonId = (*m_buttons)[appPos]->menuId();
|
|
if (m_appOrdering[buttonId] >= m_appOrdering[newButtonId])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
addApp(url, appPos, manuallyAdded);
|
|
}
|
|
|
|
QuickButton* QuickLauncher::createButton(TQString url)
|
|
{
|
|
QuickButton* newButton=new QuickButton(url, m_configAction, this);
|
|
connect(newButton, TQT_SIGNAL(executed(TQString)),
|
|
this, TQT_SLOT(slotOwnServiceExecuted(TQString)));
|
|
connect(newButton, TQT_SIGNAL(stickyToggled(bool)),
|
|
this, TQT_SLOT(slotStickyToggled()));
|
|
newButton->setPopupDirection(popupDirection());
|
|
return newButton;
|
|
}
|
|
|
|
void QuickLauncher::addApp(TQString 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<TQString> appList;
|
|
std::set<int> posList;
|
|
//kdDebug() << "Rearranging application order. Before:" << endl;
|
|
for (uint n = 0; n < m_buttons->size(); ++n)
|
|
{
|
|
TQString 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(TQString url, TQString 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(TQMouseEvent *e)
|
|
{
|
|
if (e->button() == RightButton)
|
|
{
|
|
m_popup->popup(e->globalPos());
|
|
}
|
|
}
|
|
|
|
void QuickLauncher::resizeEvent(TQResizeEvent*)
|
|
{
|
|
refreshContents();
|
|
}
|
|
|
|
void QuickLauncher::dragEnterEvent(TQDragEnterEvent *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);
|
|
|
|
TQString 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(TQDragMoveEvent *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(TQDragLeaveEvent *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(TQDropEvent *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 = TQSize(idim, idim);
|
|
m_manager->setOrientation(orientation());
|
|
m_manager->setNumItems(m_buttons->size());
|
|
m_manager->setFrameSize(size());
|
|
m_manager->setItemSize(m_buttonSize);
|
|
m_manager->setSpaceSize(TQSize(m_space, m_space));
|
|
m_manager->setBorderSize(TQSize(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;
|
|
TQPoint 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;*/
|
|
TQStringList volatileButtons = m_settings->volatileButtons();
|
|
TQStringList urls = m_settings->buttons();
|
|
kdDebug() << "GetButtons " << urls.join("/") << endl;
|
|
TQStringList::Iterator iter(urls.begin());
|
|
int n = 0;
|
|
while (iter != urls.end()) {
|
|
TQString 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);
|
|
|
|
TQStringList serviceNames = m_settings->serviceNames();
|
|
TQValueList<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;
|
|
}
|
|
TQStringList 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
|
|
TQStringList serviceNames = m_settings->serviceNames();
|
|
TQValueList<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(TQString /*starter*/, TQString storageId)
|
|
{
|
|
KService::Ptr service = KService::serviceByStorageId(storageId);
|
|
if (service->icon() == TQString::null)
|
|
{
|
|
kdDebug() << storageId << " has no icon. Makes no sense to add it.";
|
|
return;
|
|
}
|
|
QuickURL url = QuickURL(locate("apps", service->desktopEntryPath()));
|
|
TQString 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<TQString> buttonIdSet;
|
|
for (uint n = 0; n < m_buttons->size(); ++n)
|
|
{
|
|
QuickButton *button = (*m_buttons)[n];
|
|
TQString 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())
|
|
{
|
|
TQTimer::singleShot(0, this, TQT_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<TQString, QuickButton*> removeableApps;
|
|
std::set<TQString> 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)
|
|
{
|
|
TQString 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(TQString serviceMenuId)
|
|
{
|
|
m_popularity->useService(serviceMenuId);
|
|
if (m_settings->autoAdjustEnabled())
|
|
{
|
|
TQTimer::singleShot(0, this, TQT_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
|
|
TQPixmap areaPix(width(), height());
|
|
TQPainter areaPixPainter(&areaPix);
|
|
areaPixPainter.fillRect(0, 0, width(), height(), TQColor(255, 255, 255));
|
|
TQSize itemSize = m_manager->itemSize();
|
|
TQSize spaceSize = m_manager->spaceSize();
|
|
for (uint n=0; n<m_buttons->size(); ++n)
|
|
{
|
|
TQPoint 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,
|
|
TQColor(0, 0, 0));
|
|
}
|
|
}
|
|
TQImage areaLayer = areaPix.convertToImage();
|
|
m_stickyHighlightLayer = TQImage(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(TQPaintEvent* e)
|
|
{
|
|
KPanelApplet::paintEvent(e);
|
|
|
|
if (m_settings->autoAdjustEnabled() &&
|
|
m_settings->showVolatileButtonIndicator())
|
|
{
|
|
TQPainter 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"
|