/*****************************************************************

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 <tdeaction.h>
#include <tdeapplication.h>
#include <tdeaboutapplication.h>
#include <tdeaboutdata.h>
#include <kdialogbase.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <knuminput.h>
#include <tdeconfig.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"
{
    TDE_EXPORT KPanelApplet* init(TQWidget *parent, const TQString& configFile)
    {
        TDEGlobal::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 TDEAction(i18n("Configure Quicklauncher..."), "configure", TDEShortcut(),
        this, TQ_SLOT(slotConfigure()), this);

    m_saveTimer = new TQTimer(this, "m_saveTimer");
    connect(m_saveTimer, TQ_SIGNAL(timeout()), this, TQ_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 = TDEApplication::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()
{
    TDEGlobal::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, TQ_SIGNAL(aboutToShow()),
            TQ_SLOT(fillRemoveAppsMenu()));
    connect(m_removeAppsMenu, TQ_SIGNAL(activated(int)), 
            TQ_SLOT(removeAppManually(int)));
    m_appletPopup->insertItem(i18n("Remove Application"), m_removeAppsMenu);

    m_appletPopup->insertSeparator();
    m_appletPopup->setCheckable( true );
    m_appletPopup->insertItem(i18n("About"), this, TQ_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, TQ_SIGNAL(settingsChanged()),
            this, TQ_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();

    if (removeAppUrl == "SPECIAL_BUTTON__SHOW_DESKTOP") {
        m_settings->setShowDesktopEnabled(false);
    }
   
    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(TQt::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(TQt::Vertical); // ??? probably not necessary
    if (temp_manager.isValid())
    {
        return temp_manager.frameSize().height();
    }
    return m_minPanelDim;
}


int QuickLauncher::dimension() const
{
    if (orientation()==TQt::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, TQ_SIGNAL(executed(TQString)), 
            this, TQ_SLOT(slotOwnServiceExecuted(TQString)));
    connect(newButton, TQ_SIGNAL(stickyToggled(bool)), 
            this, TQ_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()
{
    TDEAboutData about("quicklauncher", I18N_NOOP("Quick Launcher"), "2.0",
                     I18N_NOOP("A simple application launcher"),
                     TDEAboutData::License_GPL_V2, 
                     "(C) 2000 Bill Nagel\n(C) 2004 Dan Bullok\n(C) 2005 Fred Schaettgen");
    TDEAboutApplication a(&about, this);
    a.exec();
}


void QuickLauncher::mousePressEvent(TQMouseEvent *e)
{
    if (e->button() == TQt::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());

    // make sure show desktop setting is honored
    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;
    }
    if (m_settings->showDesktopEnabled()) {
        if (!urls.contains("SPECIAL_BUTTON__SHOW_DESKTOP")) {
            urls.prepend("SPECIAL_BUTTON__SHOW_DESKTOP");
            addApp("SPECIAL_BUTTON__SHOW_DESKTOP", 0, true);
        }
    }
    else {
        if (urls.contains("SPECIAL_BUTTON__SHOW_DESKTOP")) {
            urls.remove("SPECIAL_BUTTON__SHOW_DESKTOP");
            removeApp("SPECIAL_BUTTON__SHOW_DESKTOP", true);
        }
    }

    // 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;
    //TDEConfig *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();
    if (m_settings->showDesktopEnabled()) {
        if (!urls.contains("SPECIAL_BUTTON__SHOW_DESKTOP"))
            urls.prepend("SPECIAL_BUTTON__SHOW_DESKTOP");
    }
    else {
        if (urls.contains("SPECIAL_BUTTON__SHOW_DESKTOP"))
            urls.remove("SPECIAL_BUTTON__SHOW_DESKTOP");
    }
    kdDebug() << "GetButtons " << urls.join("/") << endl;
    TQStringList::Iterator iter(urls.begin());
    int n = 0;
    while (iter != urls.end()) {
        TQString url = *iter;
        if(!url.isEmpty()) 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, TQ_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, TQ_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());
    TQRgb transparent(tqRgba(0, 0, 0, 0));
    for (int y = h-1; y >= 0; --y)
    {
        for (int x = w-1; x >= 0; --x)
        {
            pix = tqRed(areaLayer.pixel(x, y));
            if (pix == 0)
            {
                tlPix = (y>0 && x>0) ? tqRed(areaLayer.pixel(x-1,y-1)) : 255;
                brPix = (y<h-1 && x<w-1) ? tqRed(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, tqRgba(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"
