Logo Search packages:      
Sourcecode: basket version File versions  Download package

basket.cpp

/***************************************************************************
 *   Copyright (C) 2003 by Sébastien Laoût                                 *
 *   sebastien.laout@tuxfamily.org                                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <qlayout.h>
#include <qvbox.h>
#include <qstring.h>
#include <qpixmap.h>
#include <qcolor.h>
#include <kpopupmenu.h>
#include <kurllabel.h>
#include <qcheckbox.h>
#include <qpalette.h>
#include <qcursor.h>
#include <qaction.h>
#include <kstdaccel.h>
#include <kglobalsettings.h>
#include <qevent.h>

#include <kapplication.h>
#include <qinputdialog.h>
#include <qdragobject.h>
#include <kurldrag.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmimetype.h>
#include <kfiledialog.h>
#include <qdir.h>
#include <kiconloader.h>
#include <qregexp.h>
#include <qfileinfo.h>

#include <qstringlist.h>
#include <qdir.h>
#include <kurl.h>
#include <krun.h>
#include <kmessagebox.h>
#include <kdeversion.h>

#include "kdirwatch.h"
#include <qstringlist.h>
#include <klineedit.h>

#include <config.h>
#include <qtextcodec.h>

#include "basket.h"
#include "item.h"
#include "itemfactory.h"
#include "itemedit.h"
#include "additemdialog.h"
#include "linklabel.h"
#include "global.h"
#include "container.h"
#include "xmlwork.h"
#include "settings.h"
#include "popupmenu.h"
#include "debugwindow.h"
#include "clickcursorfeedback.h"
#include "exporterdialog.h"
#include "clipboardpoll.h"

/** DecoratedBasket */

00081 DecoratedBasket::DecoratedBasket(QWidget *parent, const QString &folderName, const char *name, WFlags fl)
 : QWidget(parent, name, fl)
{
      m_layout = new QVBoxLayout(this);
      m_search = new SearchBar(this);
      m_basket = new Basket(this, folderName, name, fl);
      m_layout->addWidget(m_basket);
      setSearchBarPosition(Settings::searchOnTop());

      m_search->setShown(m_basket->showSearchBar());
      m_basket->setFocus(); // To avoid the search bar have focus on load

      connect( m_search, SIGNAL(newSearch(const SearchData&)), m_basket, SLOT(newSearch(const SearchData&)) );
      connect( m_search, SIGNAL(resetSearch()),                m_basket, SLOT(resetSearch())                );
}

DecoratedBasket::~DecoratedBasket()
{
}

void DecoratedBasket::setSearchBarPosition(bool onTop)
{
      m_layout->remove(m_search);
      if (onTop) {
            m_layout->insertWidget(0, m_search);
            setTabOrder(this/*(QWidget*)parent()*/, m_search);
            setTabOrder(m_search, m_basket);
            setTabOrder(m_basket, (QWidget*)parent());
      } else {
            m_layout->addWidget(m_search);
            setTabOrder(this/*(QWidget*)parent()*/, m_basket);
            setTabOrder(m_basket, m_search);
            setTabOrder(m_search, (QWidget*)parent());
      }
}

void DecoratedBasket::setSearchBarShown(bool show, bool switchFocus)
{
      m_basket->setShowSearchBar(show);
      m_basket->save();
      // In this order (m_basket and then m_search) because setShown(false)
      //  will call resetSearch() that will update actions, and then check the
      //  Ctrl+F action whereas it should be unchecked
      //  FIXME: It's very uggly all those things
      m_search->setShown(show);
      if (show) {
            if (switchFocus)
                  m_search->setEditFocus();
      } else if (m_search->hasEditFocus())
            m_basket->setFocus();
}

void DecoratedBasket::resetSearch()
{
      m_search->reset();
}

/** Basket */

const int Basket::c_updateTime = 200;

00142 Basket::Basket(QWidget *parent, const QString &folderName, const char *name, WFlags fl)
 : QScrollView(parent, name, fl)
{
      setFocusPolicy(QWidget::StrongFocus);
      enableClipper(true);
      setAcceptDrops(true);
      setDragAutoScroll(true);

      m_isLoaded                  = false;
      m_firstItem                 = 0L;
      m_lastItem                  = 0L;
      m_firstShownItem            = 0L;
      m_lastShownItem             = 0L;
      m_count                     = 0;
      m_countShown                = 0;
      m_focusedItem               = 0L;
      m_startOfShiftSelectionItem = 0L;
      m_countSelecteds            = 0;
      m_areSelectedItemsChecked   = false;
      m_editor                    = 0L;
      m_isDuringDrag              = false;
      m_clickedToExitEdit         = false;
      m_stackedKeyEvent           = 0L;

      m_ViewFileContent = new bool[TOTAL];
      for (int i = 0; i < TOTAL; ++i)
            m_ViewFileContent[i] = (i != FileText) && (i != FileHTML);

      m_folderName = folderName;
      if ( ! folderName.endsWith("/") )
            m_folderName = folderName + "/";

      m_frameInsertTo = new QFrame(this); // Needed by insertItem() [called by load()]
      m_frameInsertTo->setFrameShape(QFrame::HLine);
      m_frameInsertTo->setFrameShadow(QFrame::Plain);
      m_frameInsertTo->setLineWidth(2);
      m_frameInsertTo->setFixedHeight(2);
      m_frameInsertTo->setPaletteForegroundColor(KApplication::palette().active().dark());
      m_frameInsertTo->hide();

      for (int i = 0; i < 4; ++i) {
            m_framesIT[i] = new QFrame(this); // Needed by insertItem() [called by load()]
            m_framesIT[i]->setFrameShape(QFrame::VLine);
            m_framesIT[i]->setFrameShadow(QFrame::Plain);
            m_framesIT[i]->setLineWidth(1);
            m_framesIT[i]->setFixedWidth(1);
            m_framesIT[i]->setFixedHeight(4 + 2 * (i == 0 || i == 3));
            m_framesIT[i]->setPaletteForegroundColor(KApplication::palette().active().dark());
            m_framesIT[i]->hide();
      }

      m_emptyHelp = new QVBox(viewport()); // Create it now to avoid segfaults

      // Create m_watcher before load() because mirrored files should be added to it
      m_watcher = new KDirWatch(this);
      m_watcher->addDir(fullPath(), true); // Watch all files modifications
      connect( m_watcher,     SIGNAL(dirty(const QString&)),   this, SLOT(slotModifiedFile(const QString&)) );
      connect( m_watcher,     SIGNAL(created(const QString&)), this, SLOT(slotCreatedFile(const QString&))  );
      connect( m_watcher,     SIGNAL(deleted(const QString&)), this, SLOT(slotDeletedFile(const QString&))  );
      connect(&m_updateTimer, SIGNAL(timeout()),               this, SLOT(slotUpdateItems())                );

      m_watcher->stopScan();
      load(); // We disable Dir scan during load, in case files are created (when importing launchers)
      m_watcher->startScan();
      resetSearch();

      ((QVBox*)m_emptyHelp)->setSpacing(6);
      addChild(m_emptyHelp);
      new QLabel(i18n(
            "Use the <b>Insert</b> menu to add items.<br>"
            "You also can <b>drop</b> or <b>paste</b> objects here.").replace(" ", "&nbsp;")/*Do not warp*/, m_emptyHelp);
      KURLLabel *help = new KURLLabel(m_emptyHelp, "URLLabel");
      help->setText(i18n("What is this application?"));
      connect( help, SIGNAL(leftClickedURL()), Global::mainContainer, SLOT(showAppPurpose()) );
      m_dontShowEmptyHelp = new QCheckBox(i18n("&Do not show this message again"), m_emptyHelp);
      m_emptyHelp->setFixedSize(m_emptyHelp->sizeHint());
      connect( m_dontShowEmptyHelp, SIGNAL(toggled(bool)), Global::mainContainer, SLOT(slotDontShowEmptyHelp(bool)) );
}

Basket::~Basket()
{
}

/* Scenario:
 * 1/ The user click the "Do not show this message again"
 *    The setting is remembered but the message is still shown
 * 2/ He can then:
 *    a/ Add an item: message will be hidden and never show again
 *    b/ Switch to another basket:
 *       if this basket was showing the message it will be hidden
 *       And then go back to this basket, the message will be hidden too
 */
void Basket::showEvent(QShowEvent *)
{
      if ( !Settings::showEmptyBasketInfo() )
            m_emptyHelp->hide();
}

void Basket::viewportResizeEvent(QResizeEvent *)
{
      relayoutItems();
//    updateGeometry();
}

void Basket::relayoutItems()
{
      int totalH = 0; // The y position of the current item
      int totalW = 0; // The maximal width of each items
      int h;
      int w;
      int vw = visibleWidth();

      // Browse all SHOWN items :
      for (Item *it = firstShownItem(); it != 0L; it = it->next())
            if (it->isShown()) {
                  moveChild(it, 0, totalH);
                  w = it->sizeHint().width();
                  if (w > totalW)
                        totalW = w;
                  w = (w > vw ? w : vw);
                  it->setFixedWidth(w);
                  h = it->heightForWidth(w);
                  it->setFixedHeight(h);
                  it->setY(totalH);          // Remember x position for future use
                  totalH += h;
                  if (it == lastShownItem())
                        break;
            }

      resizeContents( totalW, totalH );
      m_frameInsertTo->setFixedWidth( visibleWidth() > contentsWidth() ? visibleWidth() : contentsWidth() );
      if (m_frameInsertTo->isShown())
            showFrameInsertTo(); // Re-compute m_frameInsertTo place
      placeEditor();

      if (m_emptyHelp->isShown()) {
            QSize size = m_emptyHelp->size();
            int x = (visibleWidth() - size.width()) / 2;
            if (x < 0) x = 0;
            int y = (visibleHeight() - size.height()) / 2;
            if (y < 0) y = 0;
            moveChild(m_emptyHelp, x, y);
            resizeContents(size.width(), size.height());
      }
}

void Basket::itemSizeChanged(Item *item)
{
      if (item->isShown())
            relayoutItems();
}

void Basket::showFrameInsertTo()
{
      if (isLocked())
            return;

      m_isDuringDrag = true;

      int y;
      if (count() == 0)                                // Show at the middle of the view
            y = visibleHeight() / 2 - 1;
      else if (countShown() == 0)
            y = visibleHeight() / 2 - 1; // FIXME:: is m_insertAtItem was init, and is middle the right place ?
      else if (m_insertAtItem != 0L)
            y = (m_insertAfter ? m_insertAtItem->y() + m_insertAtItem->height()   : m_insertAtItem->y()       ) - 1;
      else
            y = (m_insertAfter ? lastShownItem()->y() + lastShownItem()->height() : 0/*firstShownItem()->y()*/) - 1;

      if (y < 0)
            y = 0;
      if (y + 1 >= visibleHeight())
            y -= 1;

      m_frameInsertTo->show();
      moveChild(m_frameInsertTo, 0, y);
#if 1 // Bleeding edges: eye candy rounded insert line
      for (int i = 0; i < 4; i++)
            m_framesIT[i]->show();
      moveChild(m_framesIT[0], 0,                          y-2);
      moveChild(m_framesIT[1], 1,                          y-1);
      moveChild(m_framesIT[2], m_frameInsertTo->width()-2, y-1);
      moveChild(m_framesIT[3], m_frameInsertTo->width()-1, y-2);
#endif
      if (y < contentsY())
            ensureVisible(contentsX(), y,     0,0);
      else if (y + 1 > contentsY() + visibleHeight())
            ensureVisible(contentsX(), y + 2, 0,0);
}

void Basket::resetInsertTo()
{
      m_isDuringDrag = false;

      m_insertAtItem = (m_insertAtEnd ? lastItem() : firstItem() );
      m_insertAfter  = (m_insertAtEnd ? true       : false       );

      m_frameInsertTo->raise(); // When an item is added, it can be in front of the line, we raise it on top
      m_frameInsertTo->hide();
#if 1 // Bleeding edges: eye candy rounded insert line
      for (int i = 0; i < 4; i++) {
            m_framesIT[i]->raise();
            m_framesIT[i]->hide();
      }
#endif
}

00349 void Basket::setName(const char *name)
{
      QObject::setName(name);

      emit nameChanged(this, QString(name));
}

void Basket::setIcon(const QString &icon)
{
      m_icon = icon;
      emit iconChanged(this, icon);
}

void Basket::setShowCheckBoxes(bool show)
{
      if (show == m_showCheckBoxes)
            return;

      m_showCheckBoxes = show;
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->showCheckBoxesChanged(show);

      relayoutItems();
}

void Basket::setAlign(int hAlign, int vAlign)
{
      if (hAlign == m_hAlign && vAlign == m_vAlign)
            return;

      m_hAlign = hAlign;
      m_vAlign = vAlign;
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->alignChanged(hAlign, vAlign);
}

void Basket::setLocked(bool lock)
{
      if (lock == m_isLocked)
            return;

      if (isDuringEdit())
            closeEditor();

      m_isLocked = lock;
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->lockedChanged(lock);

      m_emptyHelp->setEnabled(!lock);
}

void Basket::clearStack()
{
      if ( ! isAStack() ) // TODO: Feedback
            return;

      selectAll();
      // TODO: Ask a confirmation when Undo/Redo will be done
      delItem();
      unselectAll(); // In case the user canceled the action
}

void Basket::openMirroredFolder()
{
      if ( ! isAMirror() )
            return;

      Global::mainContainer->postStatusbarMessage( i18n("Now openning mirrored folder...") );

      KRun::runURL(folderName(), "inode/directory");
}

void Basket::reloadMirroredFolder()
{
      int loadedItems  = 0;
      int removedItems = 0;

      if ( ! m_isLoaded ) // load() can call setMirrorOnlyNewFiles(bool) to load the propertie
            return;         //  and then reloadMirroredFolder() is called whereas no item was loaded

      if ( ! isAMirror() ) // FIXME: Normal baskets also can call reloadMirroredFolder()
            return;          //   to verify all items are loaded ?

      QDir dir(fullPath(), QString::null, QDir::Name | QDir::IgnoreCase, QDir::Files);
      QStringList list = dir.entryList();

      // Remove items for removed files :
      // (do it before adding items is optimising twice :
      //  - do not browse fresh added items (they aren't deleted ;-) )
      //  - do not browse deleted items to see if theire file name is the same as a new ! )
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if ( it->useFile() && ! dir.exists(it->fileName()) ) {
                  delItem(it, false);
                  removedItems++;
            }

      // Add items for added files :
      for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) // For each folder
            // TODO: Not optimized :
            if ( ! itemForFullPath(fullPath() + (*it)) ) {
                  ItemFactory::loadFile(*it, this);
                  loadedItems++;
            }

      if (loadedItems != 0 && removedItems != 0) {
            save();
            Global::mainContainer->postStatusbarMessage(
                  i18n("e.g. \"Update: 1 loaded item and 3 removed items\"", "Update: %1 and %2")
                        .arg( i18n("%n loaded item",  "%n loaded items",  loadedItems)  )
                        .arg( i18n("%n removed item", "%n removed items", removedItems) )
            );
            // TODO: systemTray : display a list of new files && removed items
      } else {
            Global::mainContainer->postStatusbarMessage(i18n("Up to date basket: no change done."));
      }
}

void Basket::showMirrorOnlyOnceInfo()
{
      KMessageBox::information(this,
            /*i18n*/QString("<p>You can mirror a file only once per basket.</p>"
                 "<p><b>%1</b> is already mirrored here.<br>"
                 "Its content has been copied.</p>").arg(m_mirrorOnlyOnceFileName),
            /*i18n*/("Dropped a Mirrored File"), "mirrorOnlyOnceInfo");
}

// Remove the item from the basket and delete the associated file
// If the item mirror a file, it will ask before deleting or not the file
// But if askForMirroredFile is false, it willn't ask NOR delete the MIRRORED file
//  (it will anyway delete the file if it is not a mirror)
void Basket::delItem(Item *item, bool askForMirroredFile)
{
      if (item->useFile()) {
            bool remove = true;
            if (item->isAMirror() && askForMirroredFile) {
                  int ask = KMessageBox::questionYesNo/*Cancel*/(this,
                        /*i18n*/QString("<p>You are about to delete an item.<br>"
                             "This item mirror the content of <b>%1</b>.</p>"
                             "<p>Do you want to delete the mirrored file too ?</p>").arg(item->fullPath()),
                        /*i18n*/("Delete Item")
#if KDE_IS_VERSION( 3, 2, 90 )   // KDE 3.3.x
                        , KStdGuiItem::del(), KStdGuiItem::cancel());
#else
                                            );
#endif
                  if (ask == KMessageBox::Cancel)
                        return;
                  remove = (ask == KMessageBox::Yes);
            }
            if ( item->isAMirror() && ! askForMirroredFile )
                  remove = false;
            if (item->isAMirror())
                  m_watcher->removeFile(item->fullPath());
            if (remove) {
                  QFile::remove( item->fullPath() ); // FIXME: Directly in Basket ?
            }
      }

      unplugItem(item);

      bool wasShown = item->isShown();
      item->hide();  // We don't delete item because app crash time to time when move items by drag and drop
      //delete item; //  FIXME: I know it is memory unefficient but if anyone find why it crash, it will be
                     //  a very good programer (for that, comment first line and uncomment the second one) !

      if (wasShown) {
            m_countShown--;
            computeShownItems(); // In case we have deleted first shown or last shown item
      }
      if (hasFocus())
            focusAnItem();       // We need item->next() and item->previous() here [BUT deleted item should be hidden]
      if (item->isSelected())
            item->setSelected(false); //removeSelectedItem();

      if ( !count() && Settings::showEmptyBasketInfo() )
            m_emptyHelp->show();

      relayoutItems();
      recolorizeItems();
      resetInsertTo();         // If we delete the first or the last, pointer to it is invalid
      save();

      if (item == m_startOfShiftSelectionItem)
            m_startOfShiftSelectionItem = 0L;

      if (isDuringEdit() && m_editor->editedItem() == item)
            closeEditor(false);

      if (Global::tray)
            Global::tray->updateToolTip();
}

void Basket::plugItem(Item *item, Item *atItem, bool after)
{
      if (count() == 0) {                           // Empty list
            item->plugTo(0L, 0L);
            setFirstItem(item);
            setLastItem(item);
      } else if (atItem != 0) {                     // At middle, begin or at end
            if (after) {                              // after or at end
                  Item *nextItem = atItem->next();
                  item->plugTo(atItem, nextItem);
                  atItem->setNext(item);
                  if (nextItem != 0L) nextItem->setPrevious(item);
                  else                setLastItem(item);
            } else {                                  // before or at begin
                  Item *prevItem = atItem->previous();
                  item->plugTo(prevItem, atItem);
                  atItem->setPrevious(item);
                  if (prevItem != 0L) prevItem->setNext(item);
                  else                setFirstItem(item);
            }
      } else {                                      // At begin or at end
            if (after) {                              // at end
                  item->plugTo(lastItem(), 0L);
                  lastItem()->setNext(item);
                  setLastItem(item);
            } else {                                  // at begin
                  item->plugTo(0L, firstItem());
                  firstItem()->setPrevious(item);
                  setFirstItem(item);
            }
      }

      m_count++;
}

void Basket::unplugItem(Item *item)
{
      Item *prevItem = item->previous();
      Item *nextItem = item->next();
      if (prevItem != 0L) prevItem->setNext(nextItem);
      else                setFirstItem(nextItem);
      if (nextItem != 0L) nextItem->setPrevious(prevItem);
      else                setLastItem(prevItem);

      m_count--;
}

void Basket::changeItemPlace(Item *item)
{
      // Workarround: when dropping an item to the same place,
      // we select/focus the last inserted item.
      // Usualy it's the added/moved item
      // but since we optimized to not move the item we simulate the "moving":
      m_lastInsertedItem = item;

      // If we move the item at the same place it was, abord moving :
      if (m_insertAtItem == item)                                           // Drop at same place
            return;
      if (m_insertAtItem != 0) {
            if (m_insertAfter == true  && item == m_insertAtItem->next()    ) // Drop item just after the item
                  return;
            if (m_insertAfter == false && item == m_insertAtItem->previous()) // Drop item just before the item
                  return;
      } else {
            if (m_insertAfter == true  && item == lastItem()                ) // Drop item at end, but already at end
                  return;
            if (m_insertAfter == false && item == firstItem()               ) // Drop item at begin, but already at begin
                  return;
      }

      if (item->isShown())
            m_countShown--;
      unplugItem(item);
      insertItem(item);
      save();
}

Item* Basket::itemAtPosition(const QPoint &pos)
{
      int y = pos.y();

      // Browse all SHOWN items :
      for (Item *it = firstShownItem(); it != 0L; it = it->next())
            if (it->isShown()) {
                  if ( (it->y() + it->height() >= y) && (it->y() < y) )
                        return it;
                  if (it == lastShownItem())
                        break;
            }

      return 0L;
}

void Basket::contextMenuEvent(QContextMenuEvent *event)
{
      QPopupMenu *menu = Global::mainContainer->popupMenu("item_popup");

      if (event->reason() == QContextMenuEvent::Mouse) {
            Item *itemUnderMouse = itemAtPosition( QPoint(event->pos().x() + contentsX(), event->pos().y() + contentsY()) );
            if (itemUnderMouse == 0L)
                  unselectAll();
            else if ( ! itemUnderMouse->isSelected() )
                  unselectAllBut(itemUnderMouse);
            menu->exec(event->globalPos());
      } else {
            if (countShown() == 0) {
                  QRect basketRect( mapToGlobal(QPoint(0,0)), size() );
                  PopupMenu::execAtRectCenter(*menu, basketRect); // Popup at center or the basket
            } else {
                  if ( ! m_focusedItem->isSelected() )
                        unselectAllBut(m_focusedItem);
                  // Popup at bottom (or top) of the focused item, if visible :
                  PopupMenu::execAtRectBottom(*menu, itemRect(m_focusedItem), true);
            }
      }
}

QRect Basket::itemRect(Item *item) // TODO: QRect Basket::itemVisibleRect(Item *item)
{
//    QRect rect( contentsToViewport(QPoint(0,item->y())),    // Doesn't work
//                QSize(item->width(),item->height())      );
      QRect rect( 0 - contentsX(), item->y() - contentsY(), item->width(), item->height() );
      QPoint basketPoint = mapToGlobal(QPoint(0,0));
      rect.moveTopLeft( rect.topLeft() + basketPoint + QPoint(frameWidth(), frameWidth()) );

      // Now, rect contain the global item rectangle on the screen.
      // We have to clip it by the basket widget :
      if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom
            rect.setBottom( basketPoint.y() + visibleHeight() + 1);
            if (rect.height() <= 0) // Have at least one visible pixel of height
                  rect.setTop(rect.bottom());
      }
      if (rect.top() < basketPoint.y() + frameWidth()) { // Top too... top
            rect.setTop( basketPoint.y() + frameWidth());
            if (rect.height() <= 0)
                  rect.setBottom(rect.top());
      }
      if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right
            rect.setRight( basketPoint.x() + visibleWidth() + 1);
            if (rect.width() <= 0) // Have at least one visible pixel of width
                  rect.setLeft(rect.right());
      }
      if (rect.left() < basketPoint.x() + frameWidth()) { // Left too... left
            rect.setLeft( basketPoint.x() + frameWidth());
            if (rect.width() <= 0)
                  rect.setRight(rect.left());
      }

      return rect;
}

Item* Basket::currentStackItem()
{
      return (stackTakeAtEnd() ? lastItem() : firstItem());
}

void Basket::dragStackItem()
{
      currentStackItem()->dragItem();
}

// After a drag of an stack item from the systray or a paste from global shortcut:
void Basket::consumeStackItem(Item *item)
{
      // if (current->stackAfterDrag() == 0)
      //     Do nothing ;
      if (stackAfterDrag() == 1)
            stackMoveToOppositeSide();
      else if (stackAfterDrag() == 2)
            delItem(item); //QTimer::singleShot(1000, item, SLOT(slotDelete()));
            // Workaround: Delay the deletion, because when dragging from systray to desktop, file should stay while KDE copy/move it
            // FIXME: It create more annoying bugs!
}

void Basket::rotateStack()
{
      // FIXME: Have better feedbacks (in 0.5.0): they aren't accurate at all !
      if ( ! isAStack() ) {
            Global::mainContainer->showPassiveImpossible(i18n("Basket <i>%1</i> is not a stack."));
            return;
      } else if (currentStackItem() == 0) {
            Global::mainContainer->showPassiveImpossible(i18n("Basket <i>%1</i> is empty."));
            return;
      }

      stackMoveToOppositeSide();

      Global::mainContainer->showPassiveContent(); // i18n("Next item to drag from the stack:")
}

// Code from KHotKeys, modified to allow to simulate press of
//  Ctrl+V in the current active window, after having copied
//  the item to recall.
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <kkeynative.h>
#include <kwinmodule.h>
void Basket::insertStackItemInCurrentWindow()
{
      // FIXME: Have better feedbacks (in 0.5.0): they aren't accurate at all !
      if ( ! isAStack() ) {
            Global::mainContainer->showPassiveImpossible(i18n("Basket <i>%1</i> is not a stack."));
            return;
      } else if (currentStackItem() == 0) {
            Global::mainContainer->showPassiveImpossible(i18n("Basket <i>%1</i> is empty."));
            return;
      }

      Item *item = currentStackItem();
      currentStackItem()->slotCopy();

      unsigned int keysym = KKeyNative(KKey("Ctrl+V")).sym();
      KeyCode x_keycode = XKeysymToKeycode( qt_xdisplay(), keysym );
      if( x_keycode == NoSymbol )
            return;
      unsigned int x_mod = KKeyNative(KKey("Ctrl+V")).mod();

      WId activeWindow = KWinModule().activeWindow();

      XKeyEvent ev;
      ev.type = KeyPress;
      ev.display = qt_xdisplay();
      ev.window = activeWindow;
      ev.root = qt_xrootwin();   // I don't know whether these have to be set
      ev.subwindow = None;       // to these values, but it seems to work, hmm
      ev.time = CurrentTime;
      ev.x = 0;
      ev.y = 0;
      ev.x_root = 0;
      ev.y_root = 0;
      ev.keycode = x_keycode;
      ev.state = x_mod;
      ev.same_screen = True;
      bool ret = XSendEvent( qt_xdisplay(), activeWindow, True, KeyPressMask, ( XEvent* )&ev );
#if 1
      ev.type = KeyRelease;  // is this actually really needed ??
      ev.display = qt_xdisplay();
      ev.window = activeWindow;
      ev.root = qt_xrootwin();
      ev.subwindow = None;
      ev.time = CurrentTime;
      ev.x = 0;
      ev.y = 0;
      ev.x_root = 0;
      ev.y_root = 0;
      ev.state = x_mod;
      ev.keycode = x_keycode;
      ev.same_screen = True;
      ret = ret && XSendEvent( qt_xdisplay(), activeWindow, True, KeyReleaseMask, ( XEvent* )&ev );
#endif

      consumeStackItem(item);

      Global::mainContainer->showPassiveContent(); // i18n("Next item to drag from the stack:")
}

void Basket::stackMoveToOppositeSide()
{
      m_insertAtItem = (stackTakeAtEnd() ? firstItem() : lastItem());
      m_insertAfter  = (stackTakeAtEnd() ? false       : true       );
      changeItemPlace( currentStackItem() );
}

// Calculate where to paste or drop
void Basket::computeInsertPlace(const QPoint &cursorPosition)
{
      int y = cursorPosition.y();

      if (countShown() == 0)
            return;

      // TODO: Memorize the last hovered item to avoid a new computation on dragMoveEvent !!
      //       If the mouse is not over the last item, compute which new is :
      // TODO: Optimization : start from m_insertAtItem and compare y position to search before or after (or the same)
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if ( (it->isShown()) && (it->y() + it->height() >= y) && (it->y() < y) ) {
                  int center = it->y() + (it->height() / 2);
                  m_insertAtItem = it;
                  m_insertAfter  = y > center;
                  return;
            }
      // Else, there is at least one shown item but cursor hover NO item, so we are after the last shown item
      m_insertAtItem = lastShownItem();
      m_insertAfter  = true;

      // Code for rectangular items :
      /*QRect globalRect = it->rect();
      globalRect.moveTopLeft(it->pos() + contentsY());
      if ( globalRect.contains(curPos) ) {
            it->doInterestingThing();
      }*/
}

00834 void Basket::dragEnterEvent(QDragEnterEvent*)
{
      Global::mainContainer->setStatusBarDrag();
}

void Basket::dragMoveEvent(QDragMoveEvent* event)
{
//    m_isDuringDrag = true;

      if (isLocked())
            return;

//    FIXME: viewportToContents does NOT work !!!
//    QPoint pos = viewportToContents(event->pos());
      QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() );

      if (insertAtCursorPos())
            computeInsertPlace(pos);

      showFrameInsertTo();
      acceptDropEvent(event);

      // A workarround since QScrollView::dragAutoScroll seem to have no effect :
      ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30);
//    QScrollView::dragMoveEvent(event);
}

void Basket::dragLeaveEvent(QDragLeaveEvent*)
{
      resetInsertTo();
//    m_isDuringDrag = false;
      Global::mainContainer->resetStatusBarHint();
}

void Basket::dropEvent(QDropEvent *event)
{
      m_isDuringDrag = false;
      Global::mainContainer->resetStatusBarHint();

      if (isLocked())
            return;

      ItemFactory::dropItem( event, this, true, event->action(), dynamic_cast<Item*>(event->source()) );
      // TODO: need to know if we really inserted an (or several!!!!) item !!!
      ensureVisibleItem(lastInsertedItem());
      unselectAllBut(lastInsertedItem());
      setFocusedItem(lastInsertedItem());

      resetInsertTo();
}

void Basket::acceptDropEvent(QDropEvent *event, bool preCond)
{
      // We accept all actions !
      // FIXME: Musn't we ?
      //  e.g. when drop an item : link not supported ?!
      event->acceptAction(preCond && 1);
      event->accept(preCond);
}

void Basket::contentsMousePressEvent(QMouseEvent *event)
{
      if (event->button() & Qt::LeftButton) { // Clicked an empty area of the basket
            if ( ! isDuringEdit() ) // Do nothing when clicked in edit mode
                  unselectAll();
            setFocus();
      }
      // if MidButton BUT NOT ALT PRESSED to allow Alt+middleClick to launch actions
      //              Because KWin handle Alt+click : it's allow an alternative to alt+click !
      if ( (event->button() & Qt::MidButton) && (event->state() == 0) ) {
            if (insertAtCursorPos())
                  computeInsertPlace(event->pos());
            pasteItem(QClipboard::Selection);
            event->accept();
      } else if ( (Settings::middleAction() != 0) &&
                  (event->button() & Qt::MidButton) && (event->state() == Qt::ShiftButton) ) {
            if (insertAtCursorPos())
                  computeInsertPlace(event->pos());
            // O:Nothing ; 1:Paste ; 2:Text ; 3:Html ; 4:Image ; 5:Link ; 6:Launcher ; 7:Color
            // TODO: 8:Ask (with a popup menu)
            Item::Type type = (Item::Type)0;
            switch (Settings::middleAction()) {
                  case 1: pasteItem();           break;
                  case 2: type = Item::Text;     break;
                  case 3: type = Item::Html;     break;
                  case 4: type = Item::Image;    break;
                  case 5: type = Item::Link;     break;
                  case 6: type = Item::Launcher; break;
                  case 7: type = Item::Color;    break;
            }
            if (type != 0)
                  Global::mainContainer->insertEmpty(type);
            event->accept();
      }
}

void Basket::pasteItem(QClipboard::Mode mode)
{
      if (m_isLocked)
            return;

      ItemFactory::dropItem( KApplication::clipboard()->data(mode), this );
      // TODO: need to know if we really inserted an (or several!!!!) item !!!
      ensureVisibleItem(lastInsertedItem());
      unselectAllBut(lastInsertedItem());
      setFocusedItem(lastInsertedItem());

      resetInsertTo();
}

00944 void Basket::insertItem(Item *item)
{
      m_emptyHelp->hide();

      plugItem(item, m_insertAtItem, m_insertAfter);

      item->reparent( viewport(), QPoint(0,0), true );
      addChild(item);
      bool match = item->match( ((DecoratedBasket*)parent())->searchData() ); // TODO: Add feedback when dropped and hidden
      item->setShown(match);                                                  //       Or cancel search and ensureVisible(item)
      if (match) {
            m_countShown++;
            computeShownItems();
      } else
            Global::mainContainer->postStatusbarMessage( i18n("The new item do not match the search and isn't shown.") );

      m_lastInsertedItem = item;
      if (item->isAMirror())
            m_watcher->addFile(item->fullPath());

      connect( item, SIGNAL(wantDelete(Item*)),           this, SLOT(delItem(Item*))              );
      connect( item, SIGNAL(wantPaste(QClipboard::Mode)), this, SLOT(pasteItem(QClipboard::Mode)) );

      if (isAClipboard())
            checkClipboard();

      resetInsertTo();
//    if (m_isLoaded) { // Basket::loadItems() will do that ONCE all items are loaded
            emit changedSelectedItems(); // FIXME: use insetad: emit countSelectedChanged(this);
            relayoutItems();
            recolorizeItems();
//    }
      if (hasFocus()) // In case there were no item, we should focus the new one
            focusAnItem();

      if (Global::tray)
            Global::tray->updateToolTip();
}

Item* Basket::lastInsertedItem()
{
      return m_lastInsertedItem;
}

void Basket::focusInEvent(QFocusEvent*)
{
      if (m_editor != 0L) // It arrive toolbar of another editor stay shown... FIXME: Should be solved in 0.5.1
            closeEditor();

      focusAnItem();      // hasFocus() is true at this stage, item will be focused
}

void Basket::focusOutEvent(QFocusEvent*)
{
      if (m_focusedItem != 0L)
            m_focusedItem->setFocused(false);
}

void Basket::processActionAsYouType(QKeyEvent *event)
{
      if (Global::debugWindow)
            *Global::debugWindow << QString("Key press (%1)").arg(event->text());

      if ( (kapp->focusWidget() != this) ||    // Do not receive child keyPress events
           (event->key() == Qt::Key_Escape) || // This key generate a text() !
           (event->text().isEmpty()) ||        // It should be a text (not a modifier...)
           (event->state() & AltButton) ||     // If user pressed an unexisting shortcut
           (event->state() & MetaButton) ||    //  or accelerator key, do nothing
           ((event->state() & ControlButton) && (event->key() != Qt::Key_Backspace)) ) // But allow Ctrl+BreakSpace
            return;                                                                        //  for search bar

      DecoratedBasket *decoration = (DecoratedBasket*)parent();
      SearchBar       *searchBar  = decoration->searchBar();

      int  action         = Settings::writingAction();
      bool takeCareOfText = true;
      if ( event->text().startsWith(",") &&
           (Settings::writingCommaAction() != 0) && // Do the same, so don't process the ',' as a special case!
             (Settings::writingCommaAction() != Settings::writingAction()) ) { // Do the same too!
            action = Settings::writingCommaAction();
            takeCareOfText = false;
      }
      if (decoration->isSearchBarShown()) {
            action = 1;
            takeCareOfText = true;
      }

      // O:Nothing ; 1:Search ; 2:Text ; 3:Html ; 4:Link
      if (action == 1) {
            if ((event->key() == Qt::Key_Backspace) && !decoration->isSearchBarShown())
                  return; // Do not show and then hide bar (avoid two basket savings)
            if ( ! decoration->isSearchBarShown() )
                  Global::mainContainer->showHideSearchBar(true, false);
            if (takeCareOfText)
                  kapp->sendEvent(searchBar->lineEdit(), event);
            if (searchBar->lineEdit()->text().isEmpty() && takeCareOfText) // takeCareOfText: comma just show the (empty) bar
                  Global::mainContainer->showHideSearchBar(false);
      } else if (action > 1) {
            if ( (isLocked()) ||                        // Do not insert item if locked
                 (event->key() == Qt::Key_Backspace) || // Or if key is backspace !
                 (m_stackedKeyEvent != 0L) )            // Sometimes, insert link dialog can take time to appear
                  return;                                 //  and lot of links would be created otherwise.
            Item::Type type = Item::Text;
            if (action == 3)
                  type = Item::Html;
            if (action == 4)
                  type = Item::Link;
            if (takeCareOfText)
                  m_stackedKeyEvent = new QKeyEvent(*event);
            else
                  m_stackedKeyEvent = 0L;
            Global::mainContainer->insertEmpty(type);
      }
}

void Basket::keyPressEvent(QKeyEvent *event)
{
      if (countShown() == 0) {
            processActionAsYouType(event);
            //event->ignore(); // Important !!
            return;
      }

      Item *toFocus = 0L;

      switch (event->key()) {
            case Qt::Key_Down:
                  toFocus = nextShownItemFrom(m_focusedItem, 1);
                  break;
            case Qt::Key_Up:
                  toFocus = nextShownItemFrom(m_focusedItem, -1);
                  break;
            case Qt::Key_PageDown:
                  toFocus = nextShownItemFrom(m_focusedItem, 10);
                  if (toFocus == 0L)
                        toFocus = lastShownItem();
                  break;
            case Qt::Key_PageUp:
                  toFocus = nextShownItemFrom(m_focusedItem, -10);
                  if (toFocus == 0L)
                        toFocus = firstShownItem();
                  break;
            case Qt::Key_Home:
                  toFocus = firstShownItem();
                  break;
            case Qt::Key_End:
                  toFocus = lastShownItem();
                  break;
            case Qt::Key_Left:   // Those cases do not move focus to another item...
                  scrollBy(-30, 0);
                  return;
            case Qt::Key_Right:
                  scrollBy( 30, 0);
                  return;
            case Qt::Key_Space:  // This case do not move focus to another item...
                  if (m_focusedItem != 0L) {
                        m_focusedItem->setSelected( ! m_focusedItem->isSelected() );
                        recolorizeItems();
                        event->accept();
                  } else
                        event->ignore();
                  return;          // ... so we return after the process
            default:
                  processActionAsYouType(event);
                  return;
      }

      if (toFocus == 0L) { // If no direction keys have been pressed OR reached out the begin or end
            event->ignore(); // Important !!
            return;
      }

      if (event->state() & Qt::ShiftButton) { // Shift+arrowKeys selection
            if (m_startOfShiftSelectionItem == 0L)
                  m_startOfShiftSelectionItem = toFocus;
            selectRange(m_startOfShiftSelectionItem, toFocus);
            setFocusedItem(toFocus);
            ensureVisibleItem(toFocus);
            event->accept();
            return;
      } else /*if (toFocus != m_focusedItem)*/ {  // Move focus to ANOTHER item...
            m_startOfShiftSelectionItem = toFocus;
            setFocusedItem(toFocus);
            ensureVisibleItem(toFocus);
            if ( ! (event->state() & Qt::ControlButton) )       // ... select only current item if Control
                  unselectAllBut(m_focusedItem);
            event->accept();
            return;
      }

      event->ignore(); // Important !!
}

void Basket::ensureVisibleItem(Item *item)
{
      if ( ! item->isShown() ) // It's logical.
            return;

      int visibleX = (contentsX() + visibleWidth()) / 2; // Take middle of view, to not scroll in X
      ensureVisible( visibleX, item->y() + item->height(), 0,0 );
      ensureVisible( visibleX, item->y(),                  0,0 );
}

/** Select a range of items and deselect the others.
  * The order between start and end has no importance (end could be before start)
  */
01150 void Basket::selectRange(Item *start, Item *end)
{
      Item *cur;
      Item *realEnd = 0L;

      // Avoid crash when start (or end) is null
      if (start == 0L)
            start = end;
      else if (end == 0L)
            end = start;
      // And if *both* are null
      if (start == 0L) {
            unselectAll();
            return;
      }
      // In case there is only one item to select
      if (start == end) {
            unselectAllBut(start);
            return;
      }

      // Search the REAL first (and deselect the others before it) :
      for (cur = firstItem(); cur != 0L; cur = cur->next()) {
            if (cur == start || cur == end)
                  break;
            cur->setSelected(false);
      }

      // Select the items after REAL start, until REAL end :
      if (cur == start)
            realEnd = end;
      else if (cur == end)
            realEnd = start;

      for (/*cur = cur*/; cur != 0L; cur = cur->next()) {
            cur->setSelected(cur->isShown()); // Select all items in the range, but only if they are shown
            if (cur == realEnd)
                  break;
      }

      // Deselect the leaving items :
      for (cur = cur->next(); cur != 0L; cur = cur->next())
            cur->setSelected(false);

      recolorizeItems();
}

01197 Item* Basket::nextShownItemFrom(Item *item, int step)
{
      if (step > 0) {
            while (step && item) {
                  item = item->next();
                  if (item && item->isShown())
                        step--;
            }
      } else {
            while (step && item) {
                  item = item->previous();
                  if (item && item->isShown())
                        step++;
            }
      }
      return item;
}

Item* Basket::nextCheckedItem(bool next, bool checked)
{
      if (countShown() == 0)
            return 0L;
      focusAnItem();

      // Start from the focused item to the end/begin:
      Item *startFrom = next ? m_focusedItem->next() : m_focusedItem->previous();
      Item *stopTo    = next ? lastShownItem()       : firstShownItem();
      if (m_focusedItem != stopTo)
            for ( Item *it = startFrom; it != 0L; it = (next ? it->next() : it->previous()) )
                  if (it->isShown()) {
                        if (it->isChecked() == checked)
                              return it;
                        if (it == stopTo)
                              break;
                  }
      // If not found, re-start from the begin/end to the current focused item:
      startFrom = next ? firstShownItem() : lastShownItem();
      stopTo    = m_focusedItem;
      if (startFrom != m_focusedItem)
            for ( Item *it = startFrom; it != 0L; it = (next ? it->next() : it->previous()) )
                  if (it->isShown()) {
                        if (it->isChecked() == checked)
                              return it;
                        if (it == stopTo)
                              break;
                  }

      // Not found:
      return 0L;
}

void Basket::gotoNextCheckedItem(bool next, bool checked)
{
      Item *goTo = nextCheckedItem(next, checked);

      if (goTo != 0L) {
            unselectAllBut(goTo);
            setFocusedItem(goTo);
            ensureVisibleItem(goTo);
            setFocus();
      }
}

01260 void Basket::selectAll()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->setSelected(it->isShown());

      recolorizeItems();
}

void Basket::unselectAll()
{
      if (m_countSelecteds == 0)
            return; // They are all unselected : do not spend ressources

      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->setSelected(false);

      recolorizeItems();
}

void Basket::unselectAllBut(Item *toSelect)
{
      if (m_countSelecteds == 0)
            toSelect->setSelected(true); // They are all unselected : do not spend ressources
      else
            for (Item *it = firstItem(); it != 0L; it = it->next())
                  it->setSelected(it == toSelect);

      m_startOfShiftSelectionItem = toSelect;

      recolorizeItems();
}

void Basket::invertSelection()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->setSelected( ! it->isSelected() && it->isShown() ); // Invert selected items, and unselect hidden items

      recolorizeItems();
}

void Basket::selectCheckedItems()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->setSelected( it->isChecked() && it->isShown() );

      recolorizeItems();
}

void Basket::clicked(Item *item, bool controlPressed, bool shiftPressed)
{
      if (shiftPressed) {
            if (m_startOfShiftSelectionItem == 0L)
                  m_startOfShiftSelectionItem = item;
            selectRange(m_startOfShiftSelectionItem, item);
      } else {
            m_startOfShiftSelectionItem = item;
            if (controlPressed) {
                  item->setSelected( ! item->isSelected() );
                  recolorizeItems();
            } else
                  unselectAllBut(item);
      }

      setFocusedItem(item);
}

// TODO: FIXME: URGENT: Review the order of methods calls :
//                      emit changedSelectedItems() at the end because the receiver will ask for checked items

void Basket::addSelectedItem()
{
      m_countSelecteds++;
      emit changedSelectedItems();

      // TODO: Have an Item as parameter and just set to false if the new item is not selected
      if (m_countSelecteds == 1)
            m_areSelectedItemsChecked = true; // First selected item : we will see if it is checked or not
      if (m_areSelectedItemsChecked == false)
            return; // No need to re-compute

      for (Item *it = firstItem(); it != 0L; it = it->next())
            if ( it->isSelected() && ! it->isChecked() ) {
                  m_areSelectedItemsChecked = false;
                  emit areSelectedItemsCheckedChanged(false);
                  return; // That's all : no need to seek all items
            }
      m_areSelectedItemsChecked = true;
      emit areSelectedItemsCheckedChanged(true);
}

void Basket::removeSelectedItem()
{
      m_countSelecteds--;
      emit changedSelectedItems();

      if (m_countSelecteds == 0) { // If no selected items, not checked
            m_areSelectedItemsChecked = false;
            emit areSelectedItemsCheckedChanged(false);
            return;
      }

      // Same code that for addSelectedItem() : make a recomputeIfSelectedItemsAreChecked() ?
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if ( it->isSelected() && ! it->isChecked() ) {
                  m_areSelectedItemsChecked = false;
                  emit areSelectedItemsCheckedChanged(false);
                  return; // That's all : no need to seek all items
            }
      m_areSelectedItemsChecked = true;
      emit areSelectedItemsCheckedChanged(true);
}

void Basket::resetSelectedItem()
{
      m_countSelecteds = 0;
      emit changedSelectedItems();
      m_areSelectedItemsChecked = false;
      emit areSelectedItemsCheckedChanged(false); // Not always needed
}

void Basket::newSearch(const SearchData &data)
{
      m_countShown = 0;
      m_firstShownItem = 0L;
      m_lastShownItem  = 0L;
      for (Item *it = firstItem(); it != 0L; it = it->next()) {
            bool match = it->match(data);
            it->setShown(match);
            it->setSelected(match && it->isSelected());
            if (match) {
                  m_countShown++;
                  if (m_firstShownItem == 0L)
                        m_firstShownItem = it;
                  m_lastShownItem = it;
            }
      }

      relayoutItems();   // FIXME:
      recolorizeItems(); //   Are a lot of events/refreshs/updates sent ?

      if (hasFocus())    // if (!hasFocus()), focusAnItem() will be called at focusInEvent()
            focusAnItem(); //  so, we avoid de-focus an item if it will be re-shown soon
      if (m_focusedItem != 0L)
            ensureVisibleItem(m_focusedItem);

      Global::mainContainer->setSearching(true);

      emit changedSelectedItems();
//    emit areSelectedItemsCheckedChanged(m_areSelectedItemsChecked); // Needed ????????
}

void Basket::resetSearch()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->show();
      m_firstShownItem = firstItem();
      m_lastShownItem  = lastItem();
      m_countShown     = count();

      relayoutItems();
      recolorizeItems();

      if (hasFocus())    // if (!hasFocus()), focusAnItem() will be called at focusInEvent()
            focusAnItem(); //  so, we avoid de-focus an item if it will be re-shown soon
      if (m_focusedItem != 0L)
            ensureVisibleItem(m_focusedItem);

      Global::mainContainer->setSearching(false);

      emit changedSelectedItems();
//    emit areSelectedItemsCheckedChanged(m_areSelectedItemsChecked); // Needed ????????
}

void Basket::clearDirectory(const QString &path)
{
      // Clear the directory:
      QDir dir(path, QString::null, QDir::Name | QDir::IgnoreCase, QDir::All | QDir::Hidden);
      QStringList list = dir.entryList();
      for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
            if ( *it != "." && *it != ".." ) {
                  QString itemPath = path + "/" + *it;
                  DEBUG_WIN << "Found " + itemPath;
                  QFileInfo fInfo(itemPath);
                  if (fInfo.isDir()) {
                        clearDirectory(itemPath);
                        dir.rmdir(itemPath);
                  } else {
                        dir.remove(itemPath);
                  }
            }
}

void Basket::exportToHTML()
{
      // Get the HTML filename:
      ExporterDialog *options = new ExporterDialog(this);
      if (options->exec() == QDialog::Rejected)
            return;

      // Open the file to write:
      QString filePath = options->filePath();
      QFile file(filePath);
      if ( ! file.open(IO_WriteOnly) )
            return;

      // Smarter export: export only things that should be exported
      // (don't include unneeded clases, or javascript hacks if there is no tranparent PNG...):
      bool hasFolder       = false; // If we should create a folder to put all files!
      bool hasPNG          = false;
      // LinkLooks:
      bool hasSound        = false;
      bool hasFiles        = false;
      bool hasLocalLinks   = false;
      bool hasNetworkLinks = false;
      bool hasLaunchers    = false;
      bool hasColors       = false; // If we have at least one Color item, we can put the .color class in the HTML

      if (!icon().isEmpty()) {
            hasFolder = true;
            hasPNG    = true;
      }
      LinkLook *look;
      for (Item *it = firstShownItem(); it != 0L; it = it->next())
            if (it->isShown()) {
                  // Check:
                  switch (it->type()) {
                        case Item::Text:
                        case Item::Html:
                              break;
                        case Item::Image:
                              hasFolder = true;
                              if (it->fileName().endsWith(".png"))
                                    hasPNG = true;
                              break;
                        case Item::Animation:
                              hasFolder = true;
                              break;
                        case Item::Sound:
                              hasFolder = true;
                              if (LinkLook::soundLook->showIcon())
                                    hasPNG = true;
                              hasSound = true;
                              break;
                        case Item::File:
                              hasFolder = true;
                              if (LinkLook::fileLook->showIcon() && true) // We're assuming every files have an icon
                                    hasPNG = true;
                              hasFiles = true;
                              break;
                        case Item::Link:
                              look = LinkLook::lookForURL(it->url());
                              if (look->showIcon() && !it->icon().isEmpty()) {
                                    hasPNG = true;
                                    hasFolder = true;
                              }
                              if (look == LinkLook::localLook) {
                                    hasLocalLinks = true;
                                    if (!hasFiles) { // optimisation: don't verify if already true:
                                          QFileInfo fInfo(it->url().url());
                                          if ( (options->embedLinkedFiles()   && fInfo.isFile()) ||
                                               (options->embedLinkedFolders() && fInfo.isDir())    )
                                                hasFiles = true;
                                    }
                              } else
                                    hasNetworkLinks = true;
                              break;
                        case Item::Launcher:
                              hasFolder = true;
                              if (LinkLook::noUrlLook->showIcon() && !it->service()->icon().isEmpty())
                                    hasPNG = true;
                              hasLaunchers = true;
                              break;
                        case Item::Color:
                              hasColors = true;
                              break;
                        case Item::Unknow:
                              break;
                  }
                  if (it == lastShownItem())
                        break;
            }

      // Create the folder only if files should be created:
      QString folderPath = i18n("HTML exportation folder", "%1_files").arg(filePath) + "/";
      QString folderName = i18n("HTML exportation folder", "%1_files").arg(KURL(filePath).fileName()) + "/";
      // eg.: folderPath == "/home/seb/exportFileName.html_files/"
      //      folderName == "exportFileName.html_files/"

      QDir dir("");
      if (hasFolder) {
            dir.mkdir(folderPath);
            if (options->erasePreviousFiles())
                  clearDirectory(folderPath);
      }

      QString basketIcon16 = copyIcon(icon(), 16, folderPath);
      QString basketIcon32 = copyIcon(icon(), 32, folderPath);
      if ( ! icon().isEmpty() ) {
            basketIcon16 = folderName + basketIcon16;
            basketIcon32 = folderName + basketIcon32;
      }

      QColor foreground = KApplication::palette().active().foreground();
      QTextStream stream(&file);
      stream.setEncoding(QTextStream::UnicodeUTF8);
      stream << QString(
            "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n"
            "         \"http://www.w3.org/TR/html4/strict.dtd\">\n"
            "<html>\n"
            "  <head>\n"
            "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
            "    <meta name=\"Generator\" content=\"BasKet %2 http://basket.kde.org/\">\n"
            "    <style type=\"text/css\">\n"
            "      h1 { text-align: center; }\n"
            "      img { border: none; vertical-align: middle; }\n")
                  // transform eg. "charset=ISO 8859-15" to content="charset=ISO-8859-15"
                  .arg(/*QString(QTextCodec::codecForLocale()->name()).replace(" ", "-"), */VERSION);
      QString colorName = color().name();
      QString textAlign = (hAlign() == 0 ? "left" : (hAlign() == 1 ? "center" : "right" ));
      // Set the default font (for all the basket), and try to get a CSS generic family for it (serif, sans-serif...):
      QFont appFont = kapp->font();
      QString font = QString((appFont.italic() ? "italic " : "")) +
                     QString((appFont.bold()   ? "bold "   : "")) +
                     QString::number(QFontInfo(appFont).pixelSize()) + "px " + appFont.family();
      QString genericFont = "";
      if (font.contains("serif", false) || font.contains("roman", false))
            genericFont = "serif";
      if (font.contains("sans", false) || // No "else if" because "sans serif" must
          font.contains("arial", false) || font.contains("helvetica", false))
            genericFont = "sans-serif"; // be counted as "sans". So, the order between "serif" and "sans" is important
      if (font.contains("mono", false) || font.contains("courier", false) ||
          font.contains("typewriter", false) || font.contains("console", false) ||
          font.contains("terminal", false) || font.contains("news", false))
            genericFont = "monospace";
      if (!genericFont.isEmpty())
            font += QString(", ") + genericFont;

      QString itemExtendedStyle;
      if (options->formatForImpression())
            itemExtendedStyle = " border-bottom: solid black 1px;";

      if (showCheckBoxes())
            stream <<
                  "      .basket { border-collapse: collapse; width: 100%; border: solid black 1px;\n"
                  "                font: " << font << "; color: " << foreground.name() << "; }\n"
                  "      .item { background-color: " << colorName <<
                              "; margin: 0; text-align: " << textAlign << ";" << itemExtendedStyle <<" }\n"
                  "      td, input { padding: 0; margin: 0; }\n"
                  "      input { margin-" << (hAlign() != 2 ? "left" : "right") << ": 2px; }\n"
                  "      .content { width: 100%; padding: 2px; }\n";
      else
            stream <<
                  "      .basket { border: solid black 1px; font: " << font << "; color: " << foreground.name() << "; }\n"
                  "      .item { background-color: " << colorName <<
                              "; margin: 0; padding: 2px; text-align: " << textAlign << ";" << itemExtendedStyle <<" }\n";

      bool needAltClass = false;
      if (color() != altColor() && m_countShown > 1 && !options->formatForImpression()) { // Don't use alternate color for print
            stream << "      .alt { background-color: " << altColor().name() << "; }\n";
            needAltClass = true;
      }

      if (hasColors)       stream << "      .color { font-weight: bold; }\n";
      if (hasSound)        stream << LinkLook::soundLook->toCSS("sound");
      if (hasFiles)        stream << LinkLook::fileLook->toCSS("file");
      if (hasLocalLinks)   stream << LinkLook::localLook->toCSS("local");
      if (hasNetworkLinks) stream << LinkLook::urlLook->toCSS("network");
      if (hasLaunchers)    stream << LinkLook::noUrlLook->toCSS("launcher");
      stream <<
            "      .credits { margin: 0; font-size: 80%; }\n"
            "    </style>\n"
            "    <title>" << textToHTMLWithoutP(name()) << "</title>\n";
      if ( ! icon().isEmpty() )
            stream << "    <link rel=\"shortcut icon\" type=\"image/png\" href=\"" << basketIcon16 << "\">\n";
      // Put JavaScript hack to get transparent PNG working in Internet Explorer:
      if (hasPNG) {
            // Copy a transparent GIF image in the folder, needed for the JavaScript hack:
            QString gifFileName = ItemFactory::fileNameForNewFile("spacer.gif", folderPath);
            QFile transGIF(folderPath + gifFileName);
            if (transGIF.open(IO_WriteOnly)) {
                  QDataStream streamGIF(&transGIF);
                  // This is a 1px*1px transparent GIF image:
                  const uchar blankGIF[] = {
                        0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0a, 0x00, 0x0a, 0x00,
                        0x80, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x21,
                        0xfe, 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20,
                        0x77, 0x69, 0x74, 0x68, 0x20, 0x54, 0x68, 0x65, 0x20, 0x47,
                        0x49, 0x4d, 0x50, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x0a, 0x00,
                        0x01, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a,
                        0x00, 0x00, 0x02, 0x08, 0x8c, 0x8f, 0xa9, 0xcb, 0xed, 0x0f,
                        0x63, 0x2b, 0x00, 0x3b };
                  streamGIF.writeRawBytes((const char*)blankGIF, (unsigned int)74);
                  transGIF.close();

                  stream <<
                        "    <!--[if gte IE 5.5000]>\n"
                        "    <script>\n"
                        "      window.attachEvent(\"onload\", pngFix);\n"
                        "      function pngFix() {\n"
                        "        for (i = document.images.length - 1; i >= 0; i--) {\n"
                        "          x = document.images[i];\n"
//                      "          //if (x.className == 'pngAlpha') {\n"
                        "          if (x.src.substr(x.src.length - 4) == \".png\") {\n"
                        "            x.style.filter = \"progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'\"+x.src+\"')\"\n"
                        "            x.src = \"" << folderName << gifFileName << "\";\n"
                        "          }\n"
                        "        }\n"
                        "      }\n"
                        "    </script>\n"
                        "    <![endif]-->\n";
            } // else: do not provide IE compatibility
      }
      stream << QString(
            "  </head>\n"
            "  <body>\n");
      if ( ! icon().isEmpty() )
            stream << QString(
                  "    <h1><img src=\"%1\" width=\"32\" height=\"32\" alt=\"\"> %2</h1>\n")
                        .arg(basketIcon32, textToHTMLWithoutP(name()));
      else
            stream << QString("    <h1>%1</h1>\n").arg(textToHTMLWithoutP(name()));

      if ( ((DecoratedBasket*)parent())->searchData().isSearching )
            stream << QString("    <p>%1</p>\n").arg("Items matching &quot;%1&quot; search.")
                  .arg(textToHTMLWithoutP( ((DecoratedBasket*)parent())->searchData().string ));

      if (showCheckBoxes())
            stream << "    <table class=\"basket\">\n";
      else
            stream << "    <div class=\"basket\">\n";

      int i = 0;
      LinkLook *linkLook;
      QString   linkURL;
      QString   linkTitle;
      QString   linkIcon;
      // Browse all SHOWN items :
      for (Item *it = firstShownItem(); it != 0L; it = it->next())
            if (it->isShown()) {
                  i++;
                  // Compute item's class ("item color", "item alt color", "item file"...):
                  QString cssClass = "item";
                  if (needAltClass && !(i % 2))
                        cssClass += " alt";
                  if (it->type() == Item::Color)
                        cssClass += " color";
                  else if (it->useLinkLabel()) {
                        if (it->type() == Item::Sound) {
                              linkLook = LinkLook::soundLook;
                              cssClass += " sound";
                        } else if (it->type() == Item::File) {
                              linkLook = LinkLook::fileLook;
                              cssClass += " file";
                        } else if (it->type() == Item::Link) {
                              linkLook = LinkLook::lookForURL(it->url());
                              cssClass += (linkLook == LinkLook::localLook ? " local" : " network");
                        } else if (it->type() == Item::Launcher) {
                              linkLook = LinkLook::noUrlLook;
                              cssClass += " launcher";
                        }
                  }
                  // Compute text item additionnal style:
                  QString textFontType = "";
                  if (it->type() == Item::Text) {
                        switch (it->textFontType()) { // Set font if non-default
                              case 1:  textFontType = "font-family: sans-serif;"; break;
                              case 2:  textFontType = "font-family: serif;";      break;
                              case 3:  textFontType = "font-family: monospace;";  break;
                        }
                        if (it->textColor() != foreground) { // Set/Add color if non-default
                              if (!textFontType.isEmpty())
                                    textFontType += " ";
                              textFontType += QString("color: %1;").arg(it->textColor().name());
                        }
                        if (!textFontType.isEmpty()) // If at least one thing non-default, set the style
                              textFontType = QString(" style=\"%1\"").arg(textFontType);
                  }
                  // Compute annotations:
                  QString attrAnnotations = "";
                  if ( ! it->annotations().isEmpty() )
                        attrAnnotations = QString(" title=\"%1\"")
                              .arg(it->annotations().replace("\"", "''"));
                  // Output checkbox (if items are aligned at left or center):
                  if (showCheckBoxes()) {
                        stream << QString("      <tr class=\"%1\"%2>\n").arg(cssClass, attrAnnotations);
                        if (hAlign() != 2)
                              stream << QString(
                                    "        <td><input type=\"checkbox\"%1 disabled></td>\n")
                                          .arg(it->isChecked() ? " checked" : "");
                  // Output open tag of content:
                        stream << QString("        <td class=\"content\"%1>").arg(textFontType);
                  } else
                        stream << QString("      <p class=\"%1\"%2%3>").arg(cssClass, textFontType, attrAnnotations);
                  // Output item content OR compute values for file oriented items:
                  switch (it->type()) {
                        case Item::Text:
                              stream << textToHTMLWithoutP(it->text());
                              break;
                        case Item::Html:
                              stream << htmlToParagraph(it->html());
                              break;
                        case Item::Image:
                        case Item::Animation:
                              {
                                    QSize size = (it->type() == Item::Image ? it->pixmap()->size() : it->movie()->framePixmap().size() );
                                    stream << QString("<img src=\"%1\" width=\"%2\" height=\"%3\" alt=\"\">")
                                          .arg( folderName + copyFile(it->fullPath(), folderPath, true),
                                                QString::number(size.width()), QString::number(size.height()) );
                              }
                              break;
                        case Item::Sound:
                        case Item::File:
                              linkURL   = folderName + copyFile(it->fullPath(), folderPath, true);
                              linkTitle = it->fileName();
                              linkIcon  = ItemFactory::iconForURL(KURL(it->fullPath()));
                              break;
                        case Item::Launcher:
                              linkURL   = folderName + copyFile(it->fullPath(), folderPath, true);
                              linkTitle = it->service()->name();
                              linkIcon  = it->service()->icon();
                              break;
                        case Item::Link:
                              linkTitle = it->title();
                              linkIcon  = it->icon();
                              {
                                    QFileInfo fInfo(Item::urlWithoutProtocol(it->url()));
                                    DEBUG_WIN << Item::urlWithoutProtocol(it->url())
                                              << "IsFile:" + QString::number(fInfo.isFile())
                                              << "IsDir:"  + QString::number(fInfo.isDir());
                                    if (options->embedLinkedFiles() && fInfo.isFile()) {
                                          DEBUG_WIN << "Embed file";
                                          linkURL = folderName + copyFile(Item::urlWithoutProtocol(it->url()), folderPath, true);
                                    } else if (options->embedLinkedFolders() && fInfo.isDir()) {
                                          DEBUG_WIN << "Embed folder";
                                          linkURL = folderName + copyFile(Item::urlWithoutProtocol(it->url()), folderPath, true);
                                    } else {
                                          DEBUG_WIN << "Embed LINK";
                                          linkURL = it->url().prettyURL();
                                    }
                              }
                              break;
                        case Item::Color:
                              stream << QString("<span style=\"color: %1;\">%2</span>").arg(it->color().name(), it->color().name());
                              break;
                        case Item::Unknow:
                              stream << it->text(); // Since item text label already have formated HTML result
                              break;
                  }
                  // Output content of file oriented items:
                  if (it->type() == Item::Sound || it->type() == Item::File ||
                      it->type() == Item::Link  || it->type() == Item::Launcher) {
                              if ( linkLook->showIcon() && ! linkIcon.isEmpty() ) {
                                    linkIcon = copyIcon(linkIcon, linkLook->iconSize(), folderPath);
                                    linkIcon = folderName + linkIcon;
                                    linkIcon = QString(
                                          "<img src=\"%1\" width=\"%2\" height=\"%2\" alt=\"\">")
                                                .arg(linkIcon, QString::number(linkLook->iconSize()));
                                    if (linkLook->onTopIcon())
                                          linkIcon += "<br>";
                              }
                              linkTitle = textToHTMLWithoutP(linkTitle);
                              // Append address (useful for print version of the page/basket):
                              if (options->formatForImpression() && it->type() == Item::Link &&
                                   (!it->autoTitle() && it->title() != ItemFactory::titleForURL(it->url().prettyURL())) ) {
                                    // The address is on a new line, unless title is empty (empty lines was replaced by &nbsp;):
                                    if (linkTitle == "&nbsp;")
                                          linkTitle = "";
                                    else
                                          linkTitle = linkTitle + "<br>";
                                    linkTitle += "<i>" + it->url().prettyURL() + "</i>";
                              }
                              if (hAlign() == 2 && ! linkLook->onTopIcon())
                                    stream << QString("<a href=\"%1\">%2%3</a>")
                                              .arg(linkURL, linkTitle == "" || linkTitle == " " ?
                                                            QString("") : linkTitle, linkIcon);
                              else
                                    stream << QString("<a href=\"%1\">%2%3</a>")
                                              .arg(linkURL, linkIcon, linkTitle == "" || linkTitle == " " ?
                                                                      QString("") : linkTitle);
                  }
                  // Output end tag of content:
                  if (showCheckBoxes())
                        stream << "</td>\n";
                  else
                        stream << "</p>\n";
                  // Output checkbox (if items are aligned at left):
                  if (showCheckBoxes()) {
                        if (hAlign() == 2)
                              stream << QString(
                                    "        <td><input type=\"checkbox\"%1 disabled></td>\n")
                                          .arg(it->isChecked() ? " checked" : "");
                        stream << "      </tr>\n";
                  }
                  if (it == lastShownItem())
                        break;
            }

      // Ends the HTML file:
      if (showCheckBoxes())
            stream << "    </table>\n";
      else
            stream << "    </div>\n";

      stream << QString(
            "    <p class=\"credits\">%1</p>\n"
            "  </body>\n"
            "</html>\n").arg(
                  i18n("Made with %1, a KDE tool to take notes and keep a full range of data on hand.")
                        .arg("<a href=\"http://basket.kde.org/\">BasKet</a> %1")
                        .arg(VERSION));
      file.close();
}

QString Basket::textToHTML(const QString &text)
{
      if (text.isEmpty() || text == " " || text == "&nbsp;")
            return "<p>&nbsp;</p>";

      // convertFromPlainText() replace "\n\n" by "</p>\n<p>": we don't want that
      QString htmlString = QStyleSheet::convertFromPlainText(text, QStyleSheetItem::WhiteSpaceNormal);
      return htmlString.replace("</p>\n", "<br>\n<br>\n").replace("\n<p>", "\n"); // Don't replace first and last tags
}

QString Basket::textToHTMLWithoutP(const QString &text)
{
      // textToHTML(text) return "<p>HTMLizedText</p>". We remove the strating "<p>" and ending </p>"
      QString HTMLizedText = textToHTML(text);
      return HTMLizedText.mid(3, HTMLizedText.length() - 3 - 4);
}

QString Basket::htmlToParagraph(const QString &html)
{
      QString result = html;
      bool startedBySpan = false;

      // Remove the <html> start tag, all the <head> and the <body> start
      // Because <body> can contain style="..." parameter, we transform it to <span>
      int pos = result.find("<body");
      if (pos != -1) {
            result = "<span" + result.mid(pos + 5);
            startedBySpan = true;
      }

      // Remove the ending "</p>\n</body></html>", each tag can be separated by space characters (%s)
      // "</p>" can be omitted (eg. if the HTML doesn't contain paragraph but tables), as well as "</body>" (optinal)
      pos = result.find(QRegExp("(?:(?:</p>[\\s\\n\\r\\t]*)*</body>[\\s\\n\\r\\t]*)*</html>", false)); // Case unsensitive
      if (pos != -1)
            result = result.left(pos);

      if (startedBySpan)
            result += "</span>";

      return result.replace("</p>", "<br><br>").replace("<p>", "");
}

QString Basket::copyIcon(const QString &iconName, int size, const QString &destFolder)
{
      if (iconName.isEmpty())
            return "";

      // Sometimes icon can be "favicons/www.kde.org", we replace the '/' by a '_'
      QString fileName = iconName; // QString::replace() isn't const, so I must copy the string before
      fileName = "ico" + QString::number(size) + "_" + fileName.replace("/", "_") + ".png";
      QString fullPath = destFolder + fileName;
      DesktopIcon(iconName, size).save(fullPath, "PNG"); // FIXME: I don't check if the file already exists!
      return fileName;
}

/** DONE: Sometimes we can call two times copyFile() with the same srcPath and destFolder
  *       (eg. when exporting basket to HTML with two links to same filename
  *            (but not necesary same path, as in "/home/foo.txt" and "/foo.txt") )
  *       The first copy isn't yet started, so the dest file isn't created and this method
  *       returns the same filename !!!!!!!!!!!!!!!!!!!!
  */

01935 QString Basket::copyFile(const QString &srcPath, const QString &destFolder, bool createIt)
{
      QString fileName = ItemFactory::fileNameForNewFile(KURL(srcPath).fileName(), destFolder);
      QString fullPath = destFolder + fileName;

      if (createIt) {
            // We create the file to be sure another very near call to copyFile() willn't choose the same name:
            QFile file(Item::urlWithoutProtocol(fullPath));
            if ( file.open(IO_WriteOnly) )
                  file.close();
            // And then we copy the file AND overwriting the file we juste created:
            new KIO::FileCopyJob(
                  KURL(srcPath), KURL(fullPath), 0666, /*move=*/false,
                  /*overwrite=*/true, /*resume=*/true, /*showProgress=*/true );
      } else
            /*KIO::CopyJob *copyJob = */KIO::copy(KURL(srcPath), KURL(fullPath)); // Do it as before

      return fileName;
}

/** If an item is deleted, added, moved... firstShownItem() and lastShownItem() can be wrong
  */
01957 void Basket::computeShownItems()
{
      m_firstShownItem = 0L;
      m_lastShownItem  = 0L;

      if (countShown() == 0)
            return; // No need to compute

      // Find the first :
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isShown()) {
                  m_firstShownItem = it;
                  break;
            }

      // Find the last :
      if (m_firstShownItem != 0L) // No need to re-travel all the list if no item is shown
            for (Item *it = lastItem(); it != 0L; it = it->previous())
                  if (it->isShown()) {
                        m_lastShownItem = it;
                        break;
                  }
}

/* Unfocus the previously focused item (unless it was null)
 * and focus the new @param item (unless it is null) if hasFocus()
 * Update m_focusedItem to the new one
 */
void Basket::setFocusedItem(Item *item) // void Basket::changeFocusTo(Item *item)
{
      if (item != 0L && !item->isShown()) // Don't focus a not visible item!
            return;
      if (m_focusedItem != 0L)
            m_focusedItem->setFocused(false);
      if (hasFocus() && item != 0L)
            item->setFocused(true);
      m_focusedItem = item;
}

/* If no shown item is currently focused, try to find a shown item and focus it
 * Also update m_focusedItem to the new one (or null if there isn't)
 */
void Basket::focusAnItem()
{
      if (countShown() == 0) {   // No item to focus
            setFocusedItem(0L);
            return;
      }

      if (m_focusedItem == 0L) { // No focused item yet : focus the first shown
            setFocusedItem(firstShownItem());
            return;
      }

      // Search a visible item to focus if the focused one isn't shown :
      Item *toFocus = m_focusedItem;
      if (!toFocus->isShown())
            toFocus = nextShownItemFrom(m_focusedItem, 1);
      if (toFocus == 0L)
            toFocus = nextShownItemFrom(m_focusedItem, -1);
      setFocusedItem(toFocus);
}

02020 void Basket::editItem(Item *item, bool editAnnotations)
{
      if (isLocked())
            return;

      if (m_editor != 0L) // It arrive toolbar of another editor stay shown...
            closeEditor();

      setFocusedItem(item); // Focus and select item (in case of new inserted item)
      unselectAllBut(item);

      // If possible and if wanted, use inline editor :
      if ( Settings::useInlineEditors() && !editAnnotations && Global::mainContainer->isShown() &&
           (item->type() == Item::Text || item->type() == Item::Html) ) {
            if (item->type() == Item::Text)
                  m_editor = new ItemTextEditor(item, Global::mainContainer, viewport(), m_stackedKeyEvent);
            else
                  m_editor = new ItemHtmlEditor(item, Global::mainContainer, viewport(), m_stackedKeyEvent);
            Global::mainContainer->addDockWindow(m_editor->toolbar());
/*          // DISABLE all SHOWN items:
            for (Item *it = firstShownItem(); it != 0L; it = it->next())
                  if (it->isShown()) {
                        it->setEnabled(false);
                        if (it == lastShownItem())
                              break;
                  }*/
            // The editor :
            m_editor->editorWidget()->setPaletteBackgroundColor(item->isAlternate() ? altColor() : color());
            m_editor->editorWidget()->reparent( viewport(), QPoint(0,0), true );
            addChild(m_editor->editorWidget(), 0, 0);
            placeEditor();
//          ((QFrame*)m_editor->editorWidget())->setFrameShape(QFrame::NoFrame);
//          ((QFrame*)m_editor->editorWidget())->setLineWidth(1);
            // To avoid a one line text with an hozirontal scrollBar that take all the widget :
            if (item->type() == Item::Text || item->type() == Item::Html)
                  ((QScrollView*)m_editor->editorWidget())->setHScrollBarMode(QScrollView::AlwaysOff);
            m_editor->editorWidget()->show();
            m_editor->editorWidget()->raise();
            m_editor->goFirstFocus();
            connect( m_editor, SIGNAL(focusOut()), this, SLOT(closeEditor()) );
            connect( (QTextEdit*)(m_editor->editorWidget()), SIGNAL(textChanged()), this, SLOT(placeEditor()) );
//          if (m_stackedKeyEvent) {
//                kapp->postEvent(m_editor->editorWidget(), m_stackedKeyEvent);
            m_stackedKeyEvent = 0L;
//          }
            kapp->processEvents();   // Show the editor toolbar before ensuring the item is visible
            ensureVisibleItem(item); //  because toolbar can create a new line and then partially hide the item
            Global::mainContainer->setStatusBarEditing();
      } else {
            ItemEditDialog *dialog = new ItemEditDialog(item, editAnnotations, item, m_stackedKeyEvent);
            dialog->exec();
            m_stackedKeyEvent = 0L;
            delete dialog;
      }
}

void Basket::placeEditor()
{
      if (m_editor == 0L)
            return;

      QTextEdit *editor = (QTextEdit*)m_editor->editorWidget();
      Item      *item   = m_editor->editedItem();
      int x = item->realXWithCheckbox();
      int y;

      int maxHeight = QMAX(visibleHeight(), contentsHeight());
      int frame     = editor->frameWidth();
      int height, width;

      // Need to do it 2 times, because it's wrong overwise:
      for (int i = 0; i < 2; i++) {
            // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called:
            //        editor->sync() CRASH!!
            editor->sync();
            y = item->y();
            height = editor->contentsHeight() + 2*frame;
            height = QMAX(height, item->height());
            height = QMIN(height, visibleHeight());
            width = visibleWidth() - x;
            if (y + height > maxHeight)
                  y = maxHeight - height;
            editor->setFixedSize(width, height);
      }
      addChild(editor, x, y);
      // ensureWidgetVisible(editor):
      int visibleX = (contentsX() + visibleWidth()) / 2; // Take middle of view, to not scroll in X
      ensureVisible( visibleX, y + height, 0,0 );
      ensureVisible( visibleX, y,          0,0 );
}

void Basket::closeEditor(bool save)
{
      if (m_editor == 0L)
            return;

      if (save) {
            m_editor->saveChanges();
            if (m_editor->editedItem()->type() == Item::Text)
                  this->save(); // Save font and color
      }
      Global::mainContainer->removeDockWindow(m_editor->toolbar());
      m_editor->editorWidget()->hide();
// Crash :
//    delete m_editor;
      m_editor = 0L;

      Global::mainContainer->resetStatusBarHint();

/*    // RE-ENABLE all SHOWN items:
      for (Item *it = firstShownItem(); it != 0L; it = it->next())
            if (it->isShown()) {
                  it->setEnabled(true);
                  if (it == lastShownItem())
                        break;
            }*/

      setFocus();
}

void Basket::editItem()
{
      if (m_countSelecteds != 1)
            return;

      // Take THE selected item
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected()) {
                  it->slotEdit();
                  return;
            }
}

void Basket::editItemMetaData()
{
      if (m_countSelecteds != 1)
            return;

      // Take THE selected item
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected()) {
                  it->slotEditAnnotations();
                  return;
            }
}

void Basket::delItem()
{
      if (countSelecteds() == 0)
            return;
      else if (countSelecteds() > /*1*/0) {
            int really = KMessageBox::questionYesNo( this,
                  i18n("<qt>Do you really want to delete this item?</qt>",
                       "<qt>Do you really want to delete those <b>%n</b> items?</qt>",
                       countSelecteds()),
                  i18n("Delete Item", "Delete Items", countSelecteds())
#if KDE_IS_VERSION( 3, 2, 90 )   // KDE 3.3.x
                  , KStdGuiItem::del(), KStdGuiItem::cancel());
#else
                                      );
#endif
            if (really == KMessageBox::No)
                  return;
      }

      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected())
                  delItem(it, true);

      resetSelectedItem();
}

void Basket::copyItem()
{
      if (m_countSelecteds != 1) // TODO: Do better for multiple copy / cut...
            return;

      // Take THE selected item
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected()) {
                  it->slotCopy();
                  return;
            }
}

void Basket::cutItem()
{
      if (m_countSelecteds != 1) // TODO: Do better for multiple copy / cut...
            return;

      // Take THE selected item
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected()) {
                  it->slotCut();
                  return;
            }
}
void Basket::openItem()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected())
                  it->slotOpen();
}

void Basket::openItemWith()
{
      if (m_countSelecteds != 1) // TODO: Do better for multiple open with...
            return;

      // Take THE selected item
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected()) {
                  it->slotOpenWith();
                  return;
            }
}

void Basket::saveItemAs()
{
      if (m_countSelecteds != 1) // TODO: Do better for multiple save as...
            return;

      // Take THE selected item
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected()) {
                  it->slotSaveAs();
                  return;
            }
}

void Basket::checkItem()
{
      m_areSelectedItemsChecked = ! m_areSelectedItemsChecked;

      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->isSelected())
                  it->setChecked( m_areSelectedItemsChecked );
      save();
}

void Basket::moveOnTop()
{
      if (m_countSelecteds == 0)
            return;

      Item *endOfBrowse = firstShownItem();
      Item *topItem     = firstItem();
      Item *prev;
      for (Item *it = lastShownItem(); it != 0L; ) {
            prev = it->previous();
            if (it->isSelected()) {
                  m_insertAtItem = topItem;
                  m_insertAfter  = false;
                  changeItemPlace(it);
                  topItem = it;
            }
            if (it == endOfBrowse)
                  break;
            it = prev;
      }
      ensureVisibleItem(firstShownItem());
      ensureVisibleItem(m_focusedItem);
}

void Basket::moveOnBottom()
{
      if (m_countSelecteds == 0)
            return;

      Item *endOfBrowse = lastShownItem();
      Item *bottomItem  = lastItem();
      Item *next;
      for (Item *it = firstShownItem(); it != 0L; ) {
            next = it->next();
            if (it->isSelected()) {
                  m_insertAtItem = bottomItem;
                  m_insertAfter  = true;
                  changeItemPlace(it);
                  bottomItem = it;
            }
            if (it == endOfBrowse)
                  break;
            it = next;
      }
      ensureVisibleItem(lastShownItem());
      ensureVisibleItem(m_focusedItem);
}

void Basket::moveItemUp()
{
      if (m_countSelecteds == 0)
            return;

      // Begin from the top (important move all selected items one item up
      // AND to quit early if a selected item is the first shown one
      for (Item *it = firstShownItem(); it != 0L; it = it->next()) {
            if (it->isSelected() && it->isShown()) { // it->isShown() not necessary, but in case...
                  if (it == firstShownItem())
                        return; // No way...
                  m_insertAtItem = nextShownItemFrom(it, -1); // Previous shown item
                  if (m_insertAtItem == 0L) { // Should not appends, since it's not the first shown item,
                        resetInsertTo();        // there SHOULD be one before
                        return;
                  }
                  m_insertAfter  = false;
                  changeItemPlace(it);
            }
            if (it == lastShownItem())
                  break;
      }
      ensureVisibleItem(m_focusedItem);
}

void Basket::moveItemDown()
{
      if (m_countSelecteds == 0)
            return;

      // Begin from the bottom (important move all selected items one item down
      // AND to quit early if a selected item is the last shown one
      for (Item *it = lastShownItem(); it != 0L; it = it->previous()) {
            if (it->isSelected() && it->isShown()) { // it->isShown() not necessary, but in case...
                  if (it == lastShownItem())
                        return; // No way...
                  m_insertAtItem = nextShownItemFrom(it, 1); // Next shown item
                  if (m_insertAtItem == 0L) { // Should not appends, since it's not the last shown item,
                        resetInsertTo();        // there SHOULD be one before
                        return;
                  }
                  m_insertAfter  = true;
                  changeItemPlace(it);
            }
            if (it == firstShownItem())
                  break;
      }
      ensureVisibleItem(m_focusedItem);
}

void Basket::recolorizeItems()
{
      QColor color    = this->color();
      QColor altColor = this->altColor();

      int i = 0;
      // Browse all SHOWN items :
      for (Item *it = firstShownItem(); it != 0L; it = it->next()) {
            if (it->isShown()) {
                  i++;
                  it->setAlternate( ! (i % 2) );
                  if (it->isSelected())
                        it->setPaletteBackgroundColor( KApplication::palette().active().highlight() );
                  else if (i % 2)
                        it->setPaletteBackgroundColor(color);
                  else  // void QWidget::unsetPalette () ??????????? : no because colors will be personalizbles
                        it->setPaletteBackgroundColor(altColor);  // FIXME: If background picture, it override it :-/
                  if (it == lastShownItem())
                        break;
            }
      }

      m_emptyHelp->setPaletteBackgroundColor(color);

      if (isDuringEdit())
            m_editor->editorWidget()->setPaletteBackgroundColor(
                  m_editor->editedItem()->isAlternate() ? altColor : color );
}

void Basket::setColors(const QColor &color, const QColor &altColor)
{
      if (color == m_color && altColor == m_altColor)
            return;

      m_color    = color;
      m_altColor = altColor;

      // Colorize basket background (in case of 0 item or a free disposition)
      viewport()->setPaletteBackgroundColor(m_color);
      // And then items
      recolorizeItems();
}

void Basket::linkLookChanged()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->linkLookChanged();
}

void Basket::showItemsToolTipChanged()
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            it->updateToolTip();
}

void Basket::setIsAClipboard(bool enable)
{
      m_isAClipboard = enable;

      if (enable) {
            connect( ClipboardPoll::instance(), SIGNAL(clipboardChanged(bool)), this, SLOT(clipboardChanged(bool)) );
            connect( kapp->clipboard(),         SIGNAL(dataChanged()),          this, SLOT(clipboardChanged()) );
            connect( kapp->clipboard(),         SIGNAL(selectionChanged()),     this, SLOT(selectionChanged()) );
      }
}

void Basket::clipboardChanged(bool selectionMode)
{
      DEBUG_WIN << QString(selectionMode ? "Selection" : "Clipboard") + "has changed (" +
                   kapp->clipboard()->text(selectionMode ? QClipboard::Selection : QClipboard::Clipboard);

      if (selectionMode) {
            if (clipboardWhich() == 1 || clipboardWhich() == 2)
                  pasteItem(QClipboard::Selection);
      } else {
            if (clipboardWhich() == 0 || clipboardWhich() == 2)
                  pasteItem();
      }
}

void Basket::selectionChanged()
{
      clipboardChanged(true);
//    if (clipboardWhich() == 1 || clipboardWhich() == 2)
//          pasteItem(QClipboard::Selection);
}

Item* Basket::duplicatedOf(Item *item)
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it != item && it->isDuplicateOf(item))
                  return it;

      return 0L;
}

void Basket::checkClipboard()
{
      // If the last inserted item is a duplicated of an existing one,
      //  keep only the existing one (to keep annotations, etc...) and raise it
      Item *duplicate = duplicatedOf(m_lastInsertedItem);
      if (duplicate != 0L) {
            delItem(m_lastInsertedItem, false/*, false*/);
            resetInsertTo();
            changeItemPlace(duplicate);
      }

      // Keep care of the max number of items:
      while (count() > m_clipboardMaxItems)
            delItem( (insertAtEnd() ? firstItem() : lastItem()), false/*, false*/);

}

bool Basket::viewFileContent(ViewFileContent of)
{
      return m_ViewFileContent[of];
}

void Basket::setViewFileContent(ViewFileContent of, bool val)
{
      m_ViewFileContent[of] = val;
}

/** Save work */

/* Return the folder name. Can be :
 * - Relative (e.g. "basket1/")  : It's a standard basket
 * - Absolute (e.g. "/basket1/") : It's a mirrored folder
 */
02487 QString Basket::folderName()
{
      return m_folderName;
}

/* Return the full path of the basket, by taking care if it's a normal basket or a mirrored folder */
QString Basket::fullPath()
{
      return fullPathForFolderName(m_folderName);
}

// Static version :
QString Basket::fullPathForFolderName(const QString &folderName)
{
      if (folderName.startsWith("/"))
            return folderName;
      else
            return Global::basketsFolder() + folderName;
}

/* The same as Item::fullPath() but for a given fileName (used in setFileName()) */
QString Basket::fullPathForFileName(const QString &fileName)
{
      if (fileName.startsWith("/"))
            return fileName;
      else
            return fullPath() + fileName;
}


bool Basket::isAMirror()
{
      return m_folderName.startsWith("/");
}

// Static version :
bool Basket::isAMirror(const QString &folderName)
{
      return folderName.startsWith("/");
}

02528 void Basket::save()
{
      if (Global::debugWindow)
            *Global::debugWindow << "Basket : save properties";

      // Create document
      QDomDocument doc("basket");
      QDomElement root = doc.createElement("basket");
      doc.appendChild(root);

      // Create properties element and populate it
      QDomElement properties = doc.createElement("properties");
      root.appendChild(properties);

      XMLWork::addElement( doc, properties, "name", name() );
      XMLWork::addElement( doc, properties, "icon", icon() );

      QDomElement background = doc.createElement("background");
      properties.appendChild(background);
      background.setAttribute( "color",    m_color.name()    );
      background.setAttribute( "altColor", m_altColor.name() );

      XMLWork::addElement( doc, properties, "showCheckBoxes", XMLWork::trueOrFalse(showCheckBoxes()) );
      XMLWork::addElement( doc, properties, "showSearchBar",  XMLWork::trueOrFalse(showSearchBar())  );

      QDomElement align = doc.createElement("alignment");
      properties.appendChild(align);
      align.setAttribute( "hor", QString::number(hAlign()) );
      align.setAttribute( "ver", QString::number(vAlign()) );

      XMLWork::addElement( doc, properties, "locked", XMLWork::trueOrFalse(isLocked()) );

      QDomElement additempolicy = doc.createElement("addItemPolicy");
      properties.appendChild(additempolicy);
      additempolicy.setAttribute( "insertAtEnd",       XMLWork::trueOrFalse( m_insertAtEnd       ) );
      additempolicy.setAttribute( "insertAtCursorPos", XMLWork::trueOrFalse( m_insertAtCursorPos ) );

      QDomElement vfc = doc.createElement("viewFileContent");
      properties.appendChild(vfc);
      vfc.setAttribute( "text",  XMLWork::trueOrFalse(viewFileContent(FileText))  );
      vfc.setAttribute( "HTML",  XMLWork::trueOrFalse(viewFileContent(FileHTML))  );
      vfc.setAttribute( "image", XMLWork::trueOrFalse(viewFileContent(FileImage)) );
      vfc.setAttribute( "sound", XMLWork::trueOrFalse(viewFileContent(FileSound)) );

      QDomElement stack = doc.createElement("stack");
      properties.appendChild(stack);
      stack.setAttribute( "activated",      XMLWork::trueOrFalse( m_isAStack            ) );
      stack.setAttribute( "takeOnSameSide", XMLWork::trueOrFalse( m_stackTakeOnSameSide ) );
      stack.setAttribute( "afterDrag",      QString::number(      m_stackAfterDrag      ) );

      if (isAClipboard()) {
            QDomElement clipboard = doc.createElement("clipboard");
            properties.appendChild(clipboard);
            clipboard.setAttribute( "activated", "true"              );
            clipboard.setAttribute( "maxItems",  m_clipboardMaxItems );
            clipboard.setAttribute( "which",     m_clipboardWhich    );
      }

      // Save it, in case of another computer would mirror it ???
      QDomElement mirror = doc.createElement("mirror");
      properties.appendChild(mirror);
      mirror.setAttribute( "onlyNewFiles", XMLWork::trueOrFalse(m_mirrorOnlyNewFiles) );

      QDomElement onClick = doc.createElement("onClick");
      properties.appendChild(onClick);
      onClick.setAttribute( "content", m_contentOnClickAction );
      onClick.setAttribute( "file",    m_fileOnClickAction    );

      // Create items element and populate it
      QDomElement items = doc.createElement("items");

      for (Item *it = firstItem(); it != 0L; it = it->next()) {
            QDomElement content = doc.createElement("content");
            QDomText cont;
            switch (it->type()) {
                  case Item::Text:
                        content.setAttribute( "font", QString::number(it->textFontType()) );
                        content.setAttribute( "color", it->textColor().name() );
                        cont = doc.createTextNode( it->fileName() );
                        break;
                  case Item::Html:
//                      content.setAttribute( "showSource", XMLWork::trueOrFalse(it->showSource()) );
                        cont = doc.createTextNode( it->fileName() );
                        break;
                  case Item::Image:
                  case Item::Animation:
                  case Item::Sound:
                  case Item::File:
                  case Item::Launcher:
                  case Item::Unknow:
                        cont = doc.createTextNode( it->fileName() );
                        break;
                  case Item::Link:
                        content.setAttribute( "title",                          it->title()      );
                        content.setAttribute( "icon",                           it->icon()       );
                        content.setAttribute( "autoTitle", XMLWork::trueOrFalse(it->autoTitle()) );
                        content.setAttribute( "autoIcon",  XMLWork::trueOrFalse(it->autoIcon())  );
                        cont = doc.createTextNode( it->url().prettyURL() );
                        break;
                  case Item::Color:
                        cont = doc.createTextNode( it->color().name() );
                        break;
            }
            content.appendChild(cont);
            QDomElement item = doc.createElement("item");
            items.appendChild(item);
            item.setAttribute( "type", it->lowerTypeName() );
            item.setAttribute( "checked", XMLWork::trueOrFalse(it->isChecked()) );

            item.appendChild(content);

            if ( ! it->annotations().isEmpty() )
                  XMLWork::addElement( doc, item, "annotations", it->annotations() );
      }

      root.appendChild(items);

      QFile file(fullPath() + ".basket");
      if ( file.open(IO_WriteOnly) ) {
            QTextStream stream(&file);
            stream.setEncoding(QTextStream::UnicodeUTF8);
            QString xml = doc.toString();
            stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
            stream << xml;
            file.close();
      }
}

void Basket::load()
{
      if (Global::debugWindow)
            *Global::debugWindow << "Basket : load properties";

      QDomDocument *doc = XMLWork::openFile("basket", fullPath() + "/.basket");
      if (doc == 0)
            return;

      QDomElement docElem = doc->documentElement();
      QDomElement properties = XMLWork::getElement(docElem, "properties");
      setName(                                 XMLWork::getElementText(properties, "name",           name())   );
      setIcon(                                 XMLWork::getElementText(properties, "icon",           "")       );
      setLocked(          XMLWork::trueOrFalse(XMLWork::getElementText(properties, "locked",         "false")) );
      setShowSearchBar(   XMLWork::trueOrFalse(XMLWork::getElementText(properties, "showSearchBar",  "false")) );

      // Load and stay compatible with BasKet < 0.5.0-beta-2:
      setShowCheckBoxes(  XMLWork::trueOrFalse(XMLWork::getElementText(properties, "showcheckboxes", "false")) );
      setShowCheckBoxes(  XMLWork::trueOrFalse(XMLWork::getElementText(properties, "showCheckBoxes",
                                                                       XMLWork::trueOrFalse(showCheckBoxes()) )) );

      QDomElement align = XMLWork::getElement(properties, "alignment");
      setAlign( align.attribute("hor", "0" ).toInt(), 1/*align.attribute("ver", "1" ).toInt()*/ );

      QDomElement background = XMLWork::getElement(properties, "background");
      setColors( QColor(background.attribute( "color",    KGlobalSettings::baseColor().name() )),
                 QColor(background.attribute( "altColor", KGlobalSettings::alternateBackgroundColor().name() )) );

      QDomElement onClick    = XMLWork::getElement(properties, "onClick");
      setContentOnClickAction(                      onClick.attribute("content",          "0"   ).toInt() );
      setFileOnClickAction(                         onClick.attribute("file",             "2"   ).toInt() );

      QDomElement stack      = XMLWork::getElement(properties, "stack");
      setIsAStack(             XMLWork::trueOrFalse(stack.attribute("activated",      "false"))     );
      setStackTakeOnSameSide(  XMLWork::trueOrFalse(stack.attribute("takeOnSameSide", "true"))      );
      setStackAfterDrag(                            stack.attribute("afterDrag",      "2").toInt()  );

      QDomElement clipboard  = XMLWork::getElement(properties, "clipboard");
      setIsAClipboard(         XMLWork::trueOrFalse(clipboard.attribute("activated",  "false"))     );
      setClipboardMaxItems(                         clipboard.attribute("maxItems",   "10").toInt() );
      setClipboardWhich(                            clipboard.attribute("which",      "2").toInt()  );

      QDomElement mirror     = XMLWork::getElement(properties, "mirror");
      setMirrorOnlyNewFiles(   XMLWork::trueOrFalse(mirror.attribute("onlyNewFiles", "false"))      );

      // Be sure loaded items will be appended to end, and load them !
      m_insertAtEnd = true;
      resetInsertTo();

      QDomElement vfc = XMLWork::getElement(properties, "viewFileContent");
      setViewFileContent(FileText,  XMLWork::trueOrFalse(vfc.attribute("text",  "false")) );
      setViewFileContent(FileHTML,  XMLWork::trueOrFalse(vfc.attribute("HTML",  "false")) );
      setViewFileContent(FileImage, XMLWork::trueOrFalse(vfc.attribute("image", "true"))  );
      setViewFileContent(FileSound, XMLWork::trueOrFalse(vfc.attribute("sound", "true"))  );

      loadItems( XMLWork::getElement(docElem, "items") );

      /* Load add item policy AFTER items because else items will be loaded
         in reverse order if user selected "add new items on top" ! */
      QDomElement addItemPolicy = XMLWork::getElement(properties, "addItemPolicy");
      setInsertAtEnd(       XMLWork::trueOrFalse(addItemPolicy.attribute("insertAtEnd",       "true")) );

      // Load and stay compatible with BasKet < 0.5.0-beta-2:
      setInsertAtCursorPos( XMLWork::trueOrFalse(addItemPolicy.attribute("dropatcursorpos", "true")) );
      setInsertAtCursorPos( XMLWork::trueOrFalse(addItemPolicy.attribute("insertAtCursorPos",
                                                           XMLWork::trueOrFalse(insertAtCursorPos()) )) );

      resetInsertTo(); // Since "add item policy" has now good values

      m_isLoaded = true;

      if ( isAMirror() && !mirrorOnlyNewFiles() )
            reloadMirroredFolder();
}

/* Call this if runCommand isn't empty.
 * Return bool if the item (QDomElement content) has been threated
 * or false if it should be loaded (as if it haven't run command).
 */
bool Basket::importLauncher(const QString &type, const QDomElement &content, const QString &runCommand,
                            const QString &annotations, bool checked)
{
      // Prepare the launcher item
      QString title = content.attribute("title", "");
      QString icon  = content.attribute("icon",  "");
      if (title.isEmpty()) title = runCommand;
      if (icon.isEmpty())  icon  = ItemFactory::iconForCommand(runCommand);

      // Import the launcher item
      QString launcherName = ItemFactory::createItemLauncherFile(runCommand, title, icon, this);
      Item *item = new Item(launcherName, Item::Launcher, annotations, checked, this);
      // Yes, the 3 following lines are duplicate, but necessary since we create two items
      //  per tour in this case:
      if (item->useFile())
            item->loadContent();
      insertItem(item);

      // Link without URL: it was a Launcher.
      // Then no other information was associated to: we have threated it.
      // Else, the link/text/other_item should also be loaded, so return false.
      return (type == "link" && content.text().isEmpty());
}

void Basket::loadItems(const QDomElement &items)
{
      Item *item = 0;
      bool imported = false;
      for ( QDomNode n = items.firstChild(); !n.isNull(); n = n.nextSibling() ) {
            QDomElement e = n.toElement();
            if ( (!e.isNull()) && e.tagName() == "item" ) {
                  QString type = e.attribute("type");
                  QDomElement content = XMLWork::getElement(e, "content");
                  QString annotations = XMLWork::getElementText(e, "annotations");
                  bool checked = XMLWork::trueOrFalse(e.attribute("checked"), false);
                  // BEGIN import Launcher items from version < 0.5.0-alpha5
                  QString runCommand = e.attribute("runcommand"); // Keep compatibility with 0.4.0 and 0.5.0-alphas versions
                  runCommand = XMLWork::getElementText(e, "action", runCommand); // Keep compatibility with 0.3.x versions
                  if ( ! runCommand.isEmpty() ) { // An import should be done
                        imported = true;
                        if (importLauncher(type, content, runCommand, annotations, checked))
                              type = ""; // Make sure the item willn't be interpreted
                  }
                  // END
                  if (type == "text") {
                        item = new Item( content.text(), content.attribute("font").toInt(), QColor(content.attribute("color")),
                                         annotations, checked, this );
                  } else if (type == "html") {
//                      bool showSource = XMLWork::trueOrFalse(content.attribute("showSource"), false);
                        item = new Item( content.text(), /*showSource,  */annotations, checked, this );
                  } else if (type == "image") {
                        item = new Item( content.text(), Item::Image,     annotations, checked, this );
                  } else if (type == "animation") {
                        item = new Item( content.text(), Item::Animation, annotations, checked, this );
                  } else if (type == "sound") {
                        item = new Item( content.text(), Item::Sound,     annotations, checked, this );
                  } else if (type == "file") {
                        item = new Item( content.text(), Item::File,      annotations, checked, this );
                  } else if (type == "link") {
                        bool autoTitle = content.attribute("title") == content.text();
                        bool autoIcon  = content.attribute("icon")  == ItemFactory::iconForURL(KURL(content.text()));
                        // BEGIN Stay compatible with BasKet < 0.5.0
                        autoTitle = XMLWork::trueOrFalse( content.attribute("autotitle"), autoTitle);
                        autoIcon  = XMLWork::trueOrFalse( content.attribute("autoicon"),  autoIcon );
                        // END
                        autoTitle = XMLWork::trueOrFalse( content.attribute("autoTitle"), autoTitle);
                        autoIcon  = XMLWork::trueOrFalse( content.attribute("autoIcon"),  autoIcon );
                        item = new Item( KURL(content.text()), content.attribute("title"), content.attribute("icon"),
                                         autoTitle, autoIcon,             annotations, checked, this );
                  } else if (type == "launcher") {
                        item = new Item( content.text(), Item::Launcher,  annotations, checked, this );
                  } else if (type == "color") {
                        item = new Item( QColor(content.text()),          annotations, checked, this );
                  } else if (type == "unknow") {
                        item = new Item( content.text(), Item::Unknow,    annotations, checked, this );
                  } else
                        item = 0L;
                  if (item) {
                        if (item->useFile())
                              item->loadContent();
                        insertItem(item);
                  }
            }
      }
      if (imported) { // Yes, EVERY baskets will warn this during first 0.5.0 launch,
            save();     // but I'm too lazy and don't want to make global var for that.
            KMessageBox::information( this, i18n(
                  "<p>The basket <b>%1</b> was containing application launchers.<br>"
                  "Launchers now have its own item type and have been imported.<p>").arg(name()) );
      }
}

/*********** Work to re-load items when theire files have changed */
// TODO: class BasketUpdater
// FIXME: When a rename (de, new, del), if cutted by two timer intervals ????? Restart with 50 ms ? Yes.
// FIXME: If a timer can't be launched ? Is this happens often ?

// Created :         In :                  Deleted in :
// FileEvent(...)    slot*edFile           event
//   QString(...)

void Basket::dontCareOfCreation(const QString &path) /////////// FIXME: TODO: URGENT: notYetInserted() ???,
{
      m_dontCare.append(path);
      if (Global::debugWindow)
            *Global::debugWindow << "Watcher>Add a <i>don't care creation</i> of file : <font color=violet>" + path + "</font>";
}

void Basket::slotModifiedFile(const QString &path)
{
      m_updateQueue.append( new FileEvent(FileEvent::Modified, path) );
      if ( ! m_updateTimer.isActive() )
            m_updateTimer.start(c_updateTime, true); // 200 ms, only once
      if (Global::debugWindow)
            *Global::debugWindow << "Watcher>Modified : <font color=blue>" + path + "</font>";

}
void Basket::slotCreatedFile(const QString &path)
{
      bool dontCare = false;
      if (m_dontCare.contains(path)) { // Don't care of creation of files we know we have created ourself
            m_dontCare.remove(path);
            dontCare = true;
      } else {
            m_updateQueue.append( new FileEvent(FileEvent::Created, path) );
            if ( ! m_updateTimer.isActive() )
                  m_updateTimer.start(c_updateTime, true); // 200 ms, only once
      }
      if (Global::debugWindow) {
            *Global::debugWindow << "Watcher>Created : <font color=green>" + path + "</font>";
            if (dontCare)
                  *Global::debugWindow << "\tBut don't care (created by the application and we know this) : ignore it";
      }
}
void Basket::slotDeletedFile(const QString &path)
{
      m_updateQueue.append( new FileEvent(FileEvent::Deleted, path) );
      if ( ! m_updateTimer.isActive() )
            m_updateTimer.start(c_updateTime, true); // 200 ms, only once
      if (Global::debugWindow)
            *Global::debugWindow << "Watcher>Deleted : <font color=red>" + path + "</font>";
}

void Basket::slotCopyingDone(KIO::Job *, const KURL &, const KURL &to, bool, bool)
{
      if (Global::debugWindow)
            *Global::debugWindow << "Copy finished, load item : " + Item::urlWithoutProtocol(to);
      Item *item = itemForFullPath(Item::urlWithoutProtocol(to));
      if (Global::debugWindow && item == 0L)
            *Global::debugWindow << "slotCopyingDone(): No corresponding item";
      if (item != 0L) {
            item->loadContent();
            if (m_focusedItem == item)   // When inserting a new item we ensure it visble
                  ensureVisibleItem(item); //  But after loading it has certainly grown and if it was
      }                                //  on bottom of the basket it's not visible entirly anymore
}

void Basket::slotUpdateItems()
{
      /* This functions is called after changes on the disk
       *
       */

//    m_dontCare.clear();

      if (Global::debugWindow)
            *Global::debugWindow << "Updater : Begin";

      /* First, browse the queue and keep only one instance of each files
          (to reload/change only once), placed in lists per actions to do.
          Note : Events was added in m_updateQueue by chronogical order */
      FileEvent   *event;
      QStringList  toCreate;
      QStringList  toModify;
      QStringList  toDelete;
      QStringList  toRename;
      QStringList  renameTo;
      while (m_updateQueue.count() > 0) {
            event = m_updateQueue.take(0);

            int     eventType =   event->event;
            QString eventPath(event->filePath);
            /* The .basket file isn't an item */
            if (eventPath.endsWith("/.basket")) {
                  ;                                             // Do nothing
            /* Item must be created ? */
            } else if (eventType == FileEvent::Created) {
                  if (toCreate.findIndex(eventPath) == -1)      // Create only once (return first index or -1 if not)
                        toCreate.append(eventPath);
            /* Item must be updated ? */
            } else if (eventType == FileEvent::Modified) {
                  if ( toCreate.findIndex(eventPath) == -1 &&   // If created and then modified, do not add
                       toModify.findIndex(eventPath) == -1    ) // If modified several times,    do not add
                        toModify.append(eventPath);
            /* Item must be deleted ? */
            } else if (eventType == FileEvent::Deleted) {
                  // First try to see if it is a file to rename
                  //  In this case, events are : [delete foo], [create bar], [delete foo]
                  if (m_updateQueue.count() >= 2) { // If theire is at least two next events
                        FileEvent *createEvt  = m_updateQueue.take(0);
                        FileEvent *delete2Evt = m_updateQueue.take(0);
                        if (createEvt->event     == FileEvent::Created &&
                            delete2Evt->event    == FileEvent::Deleted &&
                            delete2Evt->filePath == event->filePath       ) {
                              if (toRename.findIndex(eventPath) == -1 ) { // Rename only once
                                    toRename.append(eventPath);
                                    renameTo.append(createEvt->filePath);
                              }
                              delete event;
                              delete createEvt;
                              delete delete2Evt;
                              continue; // Continue on top of the while(){}
                        } else { // It isn't a rename : re-place the events in the stack to parse them the next time
                              m_updateQueue.prepend(delete2Evt); // In the inverse order !
                              m_updateQueue.prepend(createEvt);
                              // It's a delete : perform the corresponding action :
                        }
                  }
                  if (toCreate.findIndex(eventPath) != -1)  // Created and then deleted
                        toCreate.remove(eventPath);           //  => No need to create
                  if (toModify.findIndex(eventPath) != -1)  // Modified and then deleted
                        toModify.remove(eventPath);           //  => No need to modify
                  if (toDelete.findIndex(eventPath) == -1 ) // Delete only once
                        toDelete.append(eventPath);
            }
            delete event;

      }

      if (Global::debugWindow) {
            QValueList<QString>::iterator path;
            QValueList<QString>::iterator name2 = renameTo.begin();
            *Global::debugWindow << "    Create :";
            for (path = toCreate.begin(); path != toCreate.end(); ++path)
                  *Global::debugWindow << "\t" + (*path);
            *Global::debugWindow << "    Modify :";
            for (path = toModify.begin(); path != toModify.end(); ++path)
                  *Global::debugWindow << "\t" + (*path);
            *Global::debugWindow << "    Delete :";
            for (path = toDelete.begin(); path != toDelete.end(); ++path)
                  *Global::debugWindow << "\t" + (*path);
            *Global::debugWindow << "    Rename :";
            for (path = toRename.begin(); path != toRename.end(); ++path) {
                  *Global::debugWindow << "\t" + (*path) + " to " + (*name2);
                  ++name2;
            }
      }

      Item *item;
      QValueList<QString>::iterator path;
      QValueList<QString>::iterator name2 = renameTo.begin();
      for (path = toCreate.begin(); path != toCreate.end(); ++path) {
            // fileNameForFullPath() :
            QString fileName = (*path).mid(fullPath().length()); // fileName is never a mirror
            ItemFactory::loadFile(fileName, this);
      }
      for (path = toModify.begin(); path != toModify.end(); ++path)
            if ( (item = itemForFullPath(*path)) != 0 ) {
                  item->loadContent();
                  itemSizeChanged(item);
            }
      for (path = toDelete.begin(); path != toDelete.end(); ++path)
            if ( (item = itemForFullPath(*path)) != 0 )
                  delItem(item);
      for (path = toRename.begin(); path != toRename.end(); ++path) {
            if ( (item = itemForFullPath(*path)) != 0 )
                  item->fileNameChanged( (*name2).mid(fullPath().length()) ); // Same
            ++name2;
      } // We view renamed files ^^ and save the change:
      if (!toRename.isEmpty())
            save();

      if (Global::debugWindow)
            *Global::debugWindow << "Updater : End";
}

Item* Basket::itemForFullPath(const QString &path)
{
      for (Item *it = firstItem(); it != 0L; it = it->next())
            if (it->fullPath() == path)
                  return it;

      return 0L;
}

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

void Basket::deleteFiles()
{
      m_watcher->stopScan();
      Global::clickCursorFeedback->stopWidget();

      // Delete the items files
      QDir dir(fullPath(), QString::null, QDir::Name | QDir::IgnoreCase, QDir::All | QDir::Hidden);
      QStringList list = dir.entryList();

      for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
            if ( *it != "." && *it != ".." )
                  QFile::remove(fullPath() + *it);

      dir.rmdir(fullPath());
}

void Basket::deleteBasketData()
{
      m_watcher->stopScan();
      Global::clickCursorFeedback->stopWidget();

      QDir dir(fullPath());
      dir.remove(".basket");
}

#include "basket.moc"

Generated by  Doxygen 1.6.0   Back to index