// -*- mode: c++; c-basic-offset: 2 -*-
/* This file is part of the KDE libraries
   Copyright (C) 2000 Kurt Granroth <granroth@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/
#include <kedittoolbar.h>

#include <qdom.h>
#include <qlayout.h>
#include <qdir.h>
#include <qfile.h>
#include <qheader.h>
#include <qcombobox.h>
#include <qdragobject.h>
#include <qtoolbutton.h>
#include <qlabel.h>
#include <qvaluelist.h>
#include <qapplication.h>
#include <qtextstream.h>

#include <kaction.h>
#include <kstandarddirs.h>
#include <klocale.h>
#include <kicontheme.h>
#include <kiconloader.h>
#include <kinstance.h>
#include <kmessagebox.h>
#include <kxmlguifactory.h>
#include <kseparator.h>
#include <kconfig.h>
#include <klistview.h>
#include <kdebug.h>
#include <kpushbutton.h>
#include <kprocio.h>

static const char * const lineseparatorstring = I18N_NOOP("--- line separator ---");
static const char * const separatorstring = I18N_NOOP("--- separator ---");

#define LINESEPARATORSTRING i18n(lineseparatorstring)
#define SEPARATORSTRING i18n(separatorstring)

static void dump_xml(const QDomDocument& doc)
{
    QString str;
    QTextStream ts(&str, IO_WriteOnly);
    ts << doc;
    kdDebug() << str << endl;
}

typedef QValueList<QDomElement> ToolbarList;

namespace
{
class XmlData
{
public:
  enum XmlType { Shell = 0, Part, Local, Merged };
  XmlData()
  {
    m_isModified = false;
    m_actionCollection = 0;
  }

  QString      m_xmlFile;
  QDomDocument m_document;
  XmlType      m_type;
  bool         m_isModified;
  KActionCollection* m_actionCollection;

  ToolbarList  m_barList;
};

typedef QValueList<XmlData> XmlDataList;

class ToolbarItem : public QListViewItem
{
public:
  ToolbarItem(KListView *parent, const QString& tag = QString::null, const QString& name = QString::null, const QString& statusText = QString::null)
    : QListViewItem(parent),
      m_tag(tag),
      m_name(name),
      m_statusText(statusText)
  {
  }

  ToolbarItem(KListView *parent, QListViewItem *item, const QString &tag = QString::null, const QString& name = QString::null, const QString& statusText = QString::null)
    : QListViewItem(parent, item),
      m_tag(tag),
      m_name(name),
      m_statusText(statusText)
  {
  }

  virtual QString key(int column, bool) const
  {
    QString s = text( column );
    if ( s == LINESEPARATORSTRING )
      return "0";
    if ( s == SEPARATORSTRING )
      return "1";
    return "2" + s;
  }

  void setInternalTag(const QString &tag) { m_tag = tag; }
  void setInternalName(const QString &name) { m_name = name; }
  void setStatusText(const QString &text) { m_statusText = text; }
  QString internalTag() const { return m_tag; }
  QString internalName() const { return m_name; }
  QString statusText() const { return m_statusText; }
private:
  QString m_tag;
  QString m_name;
  QString m_statusText;
};

#define TOOLBARITEMMIMETYPE "data/x-kde.toolbar.item"
class ToolbarItemDrag : public QStoredDrag
{
public:
  ToolbarItemDrag(ToolbarItem *toolbarItem,
                    QWidget *dragSource = 0, const char *name = 0)
    : QStoredDrag( TOOLBARITEMMIMETYPE, dragSource, name )
  {
    if (toolbarItem) {
      QByteArray data;
      QDataStream out(data, IO_WriteOnly);
      out << toolbarItem->internalTag();
      out << toolbarItem->internalName();
      out << toolbarItem->statusText();
      out << toolbarItem->text(1); // separators need this.
      setEncodedData(data);
    }
  }

  static bool canDecode(QMimeSource* e)
  {
    return e->provides(TOOLBARITEMMIMETYPE);
  }

  static bool decode( const QMimeSource* e, ToolbarItem& item )
  {
    if (!e)
      return false;

    QByteArray data = e->encodedData(TOOLBARITEMMIMETYPE);
    if ( data.isEmpty() )
      return false;

    QString internalTag, internalName, statusText, text;
    QDataStream in(data, IO_ReadOnly);
    in >> internalTag;
    in >> internalName;
    in >> statusText;
    in >> text;

    item.setInternalTag( internalTag );
    item.setInternalName( internalName );
    item.setStatusText( statusText );
    item.setText(1, text);

    return true;
  }
};

class ToolbarListView : public KListView
{
public:
  ToolbarListView(QWidget *parent=0, const char *name=0)
    : KListView(parent, name)
  {
  }
protected:
  virtual QDragObject *dragObject()
  {
    ToolbarItem *item = dynamic_cast<ToolbarItem*>(selectedItem());
    if ( item ) {
      ToolbarItemDrag *obj = new ToolbarItemDrag(item,
                                 this, "ToolbarAction drag item");
      const QPixmap *pm = item->pixmap(0);
      if( pm )
        obj->setPixmap( *pm );
      return obj;
    }
    return 0;
  }

  virtual bool acceptDrag(QDropEvent *event) const
  {
    return ToolbarItemDrag::canDecode( event );
  }
};
} // namespace

class KEditToolbarWidgetPrivate
{
public:
    /**
     *
     * @param collection In a non-KParts application, this is the collection passed
     * to the KEditToolbar constructor.
     * In a KParts application we let create a KXMLGUIClient create a dummy one,
     * but it probably isn't used.
     */
  KEditToolbarWidgetPrivate(KInstance *instance, KActionCollection* collection)
      : m_collection( collection )
  {
    m_instance = instance;
    m_isPart   = false;
    m_helpArea = 0L;
    m_kdialogProcess = 0;
  }
  ~KEditToolbarWidgetPrivate()
  {
  }

  QString xmlFile(const QString& xml_file)
  {
    return xml_file.isNull() ? QString(m_instance->instanceName()) + "ui.rc" :
                               xml_file;
  }

  /**
   * Load in the specified XML file and dump the raw xml
   */
  QString loadXMLFile(const QString& _xml_file)
  {
    QString raw_xml;
    QString xml_file = xmlFile(_xml_file);
    //kdDebug() << "loadXMLFile xml_file=" << xml_file << endl;

    if ( !QDir::isRelativePath(xml_file) )
      raw_xml = KXMLGUIFactory::readConfigFile(xml_file);
    else
      raw_xml = KXMLGUIFactory::readConfigFile(xml_file, m_instance);

    return raw_xml;
  }

  /**
   * Return a list of toolbar elements given a toplevel element
   */
  ToolbarList findToolbars(QDomNode n)
  {
    static const QString &tagToolbar = KGlobal::staticQString( "ToolBar" );
    static const QString &attrNoEdit = KGlobal::staticQString( "noEdit" );
    ToolbarList list;

    for( ; !n.isNull(); n = n.nextSibling() )
    {
      QDomElement elem = n.toElement();
      if (elem.isNull())
        continue;

      if (elem.tagName() == tagToolbar && elem.attribute( attrNoEdit ) != "true" )
        list.append(elem);

      list += findToolbars(elem.firstChild());
    }

    return list;
  }

  /**
   * Return the name of a given toolbar
   */
  QString toolbarName( const XmlData& xmlData, const QDomElement& it ) const
  {
      static const QString &tagText = KGlobal::staticQString( "text" );
      static const QString &tagText2 = KGlobal::staticQString( "Text" );
      static const QString &attrName = KGlobal::staticQString( "name" );

      QString name;
      QCString txt( it.namedItem( tagText ).toElement().text().utf8() );
      if ( txt.isEmpty() )
          txt = it.namedItem( tagText2 ).toElement().text().utf8();
      if ( txt.isEmpty() )
          name = it.attribute( attrName );
      else
          name = i18n( txt );

      // the name of the toolbar might depend on whether or not
      // it is in kparts
      if ( ( xmlData.m_type == XmlData::Shell ) ||
           ( xmlData.m_type == XmlData::Part ) )
      {
        QString doc_name(xmlData.m_document.documentElement().attribute( attrName ));
        name += " <" + doc_name + ">";
      }
      return name;
  }
  /**
   * Look for a given item in the current toolbar
   */
  QDomElement findElementForToolbarItem( const ToolbarItem* item ) const
  {
    static const QString &attrName    = KGlobal::staticQString( "name" );
    for(QDomNode n = m_currentToolbarElem.firstChild(); !n.isNull(); n = n.nextSibling())
    {
      QDomElement elem = n.toElement();
      if ((elem.attribute(attrName) == item->internalName()) &&
          (elem.tagName() == item->internalTag()))
        return elem;
    }
    return QDomElement();
  }

#ifndef NDEBUG
  void dump()
  {
    static const char* s_XmlTypeToString[] = { "Shell", "Part", "Local", "Merged" };
    XmlDataList::Iterator xit = m_xmlFiles.begin();
    for ( ; xit != m_xmlFiles.end(); ++xit )
    {
        kdDebug(240) << "XmlData type " << s_XmlTypeToString[(*xit).m_type] << " xmlFile: " << (*xit).m_xmlFile << endl;
        for( QValueList<QDomElement>::Iterator it = (*xit).m_barList.begin();
             it != (*xit).m_barList.end(); ++it ) {
            kdDebug(240) << "    Toolbar: " << toolbarName( *xit, *it ) << endl;
        }
        if ( (*xit).m_actionCollection )
            kdDebug(240) << "    " << (*xit).m_actionCollection->count() << " actions in the collection." << endl;
        else
            kdDebug(240) << "    no action collection." << endl;
    }
  }
#endif

  //QValueList<KAction*> m_actionList;
  KActionCollection* m_collection;
  KInstance         *m_instance;

  XmlData*     m_currentXmlData;
  QDomElement m_currentToolbarElem;

  QString            m_xmlFile;
  QString            m_globalFile;
  QString            m_rcFile;
  QDomDocument       m_localDoc;
  bool               m_isPart;

  ToolbarList        m_barList;

  XmlDataList m_xmlFiles;

  QLabel     *m_comboLabel;
  KSeparator *m_comboSeparator;
  QLabel * m_helpArea;
  KPushButton* m_changeIcon;
  KProcIO* m_kdialogProcess;
  bool m_hasKDialog;
};

class KEditToolbarPrivate {
public:
    bool m_accept;

    // Save parameters for recreating widget after resetting toolbar
    bool m_global;
    KActionCollection* m_collection;
    QString m_file;
    KXMLGUIFactory* m_factory;
};

const char *KEditToolbar::s_defaultToolbar = 0L;

KEditToolbar::KEditToolbar(KActionCollection *collection, const QString& file,
                           bool global, QWidget* parent, const char* name)
  : KDialogBase(Swallow, i18n("Configure Toolbars"), Default|Ok|Apply|Cancel, Ok, parent, name),
    m_widget(new KEditToolbarWidget(QString::fromLatin1(s_defaultToolbar), collection, file, global, this))
{
    init();
    d->m_global = global;
    d->m_collection = collection;
    d->m_file = file;
}

KEditToolbar::KEditToolbar(const QString& defaultToolbar, KActionCollection *collection,
                           const QString& file, bool global,
                           QWidget* parent, const char* name)
  : KDialogBase(Swallow, i18n("Configure Toolbars"), Default|Ok|Apply|Cancel, Ok, parent, name),
    m_widget(new KEditToolbarWidget(defaultToolbar, collection, file, global, this))
{
    init();
    d->m_global = global;
    d->m_collection = collection;
    d->m_file = file;
}

KEditToolbar::KEditToolbar(KXMLGUIFactory* factory, QWidget* parent, const char* name)
    : KDialogBase(Swallow, i18n("Configure Toolbars"), Default|Ok|Apply|Cancel, Ok, parent, name),
      m_widget(new KEditToolbarWidget(QString::fromLatin1(s_defaultToolbar), factory, this))
{
    init();
    d->m_factory = factory;
}

KEditToolbar::KEditToolbar(const QString& defaultToolbar,KXMLGUIFactory* factory,
                           QWidget* parent, const char* name)
    : KDialogBase(Swallow, i18n("Configure Toolbars"), Default|Ok|Apply|Cancel, Ok, parent, name),
      m_widget(new KEditToolbarWidget(defaultToolbar, factory, this))
{
    init();
    d->m_factory = factory;
}

void KEditToolbar::init()
{
    d = new KEditToolbarPrivate();
    d->m_accept = false;
    d->m_factory = 0;

    setMainWidget(m_widget);

    connect(m_widget, SIGNAL(enableOk(bool)), SLOT(acceptOK(bool)));
    connect(m_widget, SIGNAL(enableOk(bool)), SLOT(enableButtonApply(bool)));
    enableButtonApply(false);

    setMinimumSize(sizeHint());
    s_defaultToolbar = 0L;
}

KEditToolbar::~KEditToolbar()
{
    delete d;
}

void KEditToolbar::acceptOK(bool b)
{
    enableButtonOK(b);
    d->m_accept = b;
}

void KEditToolbar::slotDefault()
{
    if ( KMessageBox::warningContinueCancel(this, i18n("Do you really want to reset all toolbars of this application to their default? The changes will be applied immediately."), i18n("Reset Toolbars"),i18n("Reset"))!=KMessageBox::Continue )
        return;

    delete m_widget;
    d->m_accept = false;

    if ( d->m_factory )
    {
        const QString localPrefix = locateLocal("data", "");
        QPtrList<KXMLGUIClient> clients(d->m_factory->clients());
        QPtrListIterator<KXMLGUIClient> it( clients );

        for( ; it.current(); ++it)
        {
            KXMLGUIClient *client = it.current();
            QString file = client->xmlFile();

            if (file.isNull())
                continue;

            if (QDir::isRelativePath(file))
            {
                const KInstance *instance = client->instance() ? client->instance() : KGlobal::instance();
                file = locateLocal("data", QString::fromLatin1( instance->instanceName() + '/' ) + file);
            }
            else
            {
                if (!file.startsWith(localPrefix))
                    continue;
            }

            if ( QFile::exists( file ) )
                if ( !QFile::remove( file ) )
                    kdWarning() << "Could not delete " << file << endl;
        }

        m_widget = new KEditToolbarWidget(QString::null, d->m_factory, this);
        m_widget->rebuildKXMLGUIClients();
    }
    else
    {
        int slash = d->m_file.findRev('/')+1;
        if (slash)
            d->m_file = d->m_file.mid(slash);
        QString xml_file = locateLocal("data", QString::fromLatin1( KGlobal::instance()->instanceName() + '/' ) + d->m_file);

        if ( QFile::exists( xml_file ) )
            if ( !QFile::remove( xml_file ) )
                kdWarning() << "Could not delete " << xml_file << endl;

        m_widget = new KEditToolbarWidget(QString::null, d->m_collection, d->m_file, d->m_global, this);
    }

    setMainWidget(m_widget);
    m_widget->show();

    connect(m_widget, SIGNAL(enableOk(bool)), SLOT(acceptOK(bool)));
    connect(m_widget, SIGNAL(enableOk(bool)), SLOT(enableButtonApply(bool)));

    enableButtonApply(false);
    emit newToolbarConfig();
}

void KEditToolbar::slotOk()
{
  if (!d->m_accept) {
      reject();
      return;
  }

  if (!m_widget->save())
  {
    // some error box here is needed
  }
  else
  {
    emit newToolbarConfig();
    accept();
  }
}

void KEditToolbar::slotApply()
{
    (void)m_widget->save();
    enableButtonApply(false);
    emit newToolbarConfig();
}

void KEditToolbar::setDefaultToolbar(const char *toolbarName)
{
    s_defaultToolbar = toolbarName;
}

KEditToolbarWidget::KEditToolbarWidget(KActionCollection *collection,
                                       const QString& file,
                                       bool global, QWidget *parent)
  : QWidget(parent),
    d(new KEditToolbarWidgetPrivate(instance(), collection))
{
  initNonKPart(collection, file, global);
  // now load in our toolbar combo box
  loadToolbarCombo();
  adjustSize();
  setMinimumSize(sizeHint());
}

KEditToolbarWidget::KEditToolbarWidget(const QString& defaultToolbar,
                                       KActionCollection *collection,
                                       const QString& file, bool global,
                                       QWidget *parent)
  : QWidget(parent),
    d(new KEditToolbarWidgetPrivate(instance(), collection))
{
  initNonKPart(collection, file, global);
  // now load in our toolbar combo box
  loadToolbarCombo(defaultToolbar);
  adjustSize();
  setMinimumSize(sizeHint());
}

KEditToolbarWidget::KEditToolbarWidget( KXMLGUIFactory* factory,
                                        QWidget *parent)
  : QWidget(parent),
    d(new KEditToolbarWidgetPrivate(instance(), KXMLGUIClient::actionCollection() /*create new one*/))
{
  initKPart(factory);
  // now load in our toolbar combo box
  loadToolbarCombo();
  adjustSize();
  setMinimumSize(sizeHint());
}

KEditToolbarWidget::KEditToolbarWidget( const QString& defaultToolbar,
                                        KXMLGUIFactory* factory,
                                        QWidget *parent)
  : QWidget(parent),
    d(new KEditToolbarWidgetPrivate(instance(), KXMLGUIClient::actionCollection() /*create new one*/))
{
  initKPart(factory);
  // now load in our toolbar combo box
  loadToolbarCombo(defaultToolbar);
  adjustSize();
  setMinimumSize(sizeHint());
}

KEditToolbarWidget::~KEditToolbarWidget()
{
    delete d;
}

void KEditToolbarWidget::initNonKPart(KActionCollection *collection,
                                      const QString& file, bool global)
{
  //d->m_actionList = collection->actions();

  // handle the merging
  if (global)
    setXMLFile(locate("config", "ui/ui_standards.rc"));
  QString localXML = d->loadXMLFile(file);
  setXML(localXML, true);

  // reusable vars
  QDomElement elem;

  // first, get all of the necessary info for our local xml
  XmlData local;
  local.m_xmlFile = d->xmlFile(file);
  local.m_type    = XmlData::Local;
  local.m_document.setContent(localXML);
  elem = local.m_document.documentElement().toElement();
  local.m_barList = d->findToolbars(elem);
  local.m_actionCollection = collection;
  d->m_xmlFiles.append(local);

  // then, the merged one (ui_standards + local xml)
  XmlData merge;
  merge.m_xmlFile  = QString::null;
  merge.m_type     = XmlData::Merged;
  merge.m_document = domDocument();
  elem = merge.m_document.documentElement().toElement();
  merge.m_barList  = d->findToolbars(elem);
  merge.m_actionCollection = collection;
  d->m_xmlFiles.append(merge);

#ifndef NDEBUG
  //d->dump();
#endif

  // okay, that done, we concern ourselves with the GUI aspects
  setupLayout();
}

void KEditToolbarWidget::initKPart(KXMLGUIFactory* factory)
{
  // reusable vars
  QDomElement elem;

  setFactory( factory );
  actionCollection()->setWidget( this );

  // add all of the client data
  QPtrList<KXMLGUIClient> clients(factory->clients());
  QPtrListIterator<KXMLGUIClient> it( clients );
  for( ; it.current(); ++it)
  {
    KXMLGUIClient *client = it.current();

    if (client->xmlFile().isNull())
      continue;

    XmlData data;
    data.m_xmlFile = client->localXMLFile();
    if ( it.atFirst() )
      data.m_type = XmlData::Shell;
    else
      data.m_type = XmlData::Part;
    data.m_document.setContent( KXMLGUIFactory::readConfigFile( client->xmlFile(), client->instance() ) );
    elem = data.m_document.documentElement().toElement();
    data.m_barList = d->findToolbars(elem);
    data.m_actionCollection = client->actionCollection();
    d->m_xmlFiles.append(data);

    //d->m_actionList += client->actionCollection()->actions();
  }

#ifndef NDEBUG
  //d->dump();
#endif

  // okay, that done, we concern ourselves with the GUI aspects
  setupLayout();
}

bool KEditToolbarWidget::save()
{
  //kdDebug(240) << "KEditToolbarWidget::save" << endl;
  XmlDataList::Iterator it = d->m_xmlFiles.begin();
  for ( ; it != d->m_xmlFiles.end(); ++it)
  {
    // let's not save non-modified files
    if ( !((*it).m_isModified) )
      continue;

    // let's also skip (non-existent) merged files
    if ( (*it).m_type == XmlData::Merged )
      continue;

    dump_xml((*it).m_document);

    kdDebug(240) << "Saving " << (*it).m_xmlFile << endl;
    // if we got this far, we might as well just save it
    KXMLGUIFactory::saveConfigFile((*it).m_document, (*it).m_xmlFile);
  }

  if ( !factory() )
    return true;

  rebuildKXMLGUIClients();

  return true;
}

void KEditToolbarWidget::rebuildKXMLGUIClients()
{
  if ( !factory() )
    return;

  QPtrList<KXMLGUIClient> clients(factory()->clients());
  //kdDebug(240) << "factory: " << clients.count() << " clients" << endl;

  // remove the elements starting from the last going to the first
  KXMLGUIClient *client = clients.last();
  while ( client )
  {
    //kdDebug(240) << "factory->removeClient " << client << endl;
    factory()->removeClient( client );
    client = clients.prev();
  }

  KXMLGUIClient *firstClient = clients.first();

  // now, rebuild the gui from the first to the last
  //kdDebug(240) << "rebuilding the gui" << endl;
  QPtrListIterator<KXMLGUIClient> cit( clients );
  for( ; cit.current(); ++cit)
  {
    KXMLGUIClient* client = cit.current();
    //kdDebug(240) << "updating client " << client << " " << client->instance()->instanceName() << "  xmlFile=" << client->xmlFile() << endl;
    QString file( client->xmlFile() ); // before setting ui_standards!
    if ( !file.isEmpty() )
    {
        // passing an empty stream forces the clients to reread the XML
        client->setXMLGUIBuildDocument( QDomDocument() );

        // for the shell, merge in ui_standards.rc
        if ( client == firstClient ) // same assumption as in the ctor: first==shell
            client->setXMLFile(locate("config", "ui/ui_standards.rc"));

        // and this forces it to use the *new* XML file
        client->setXMLFile( file, client == firstClient /* merge if shell */ );
    }
  }

  // Now we can add the clients to the factory
  // We don't do it in the loop above because adding a part automatically
  // adds its plugins, so we must make sure the plugins were updated first.
  cit.toFirst();
  for( ; cit.current(); ++cit)
    factory()->addClient( cit.current() );
}

void KEditToolbarWidget::setupLayout()
{
  // the toolbar name combo
  d->m_comboLabel = new QLabel(i18n("&Toolbar:"), this);
  m_toolbarCombo = new QComboBox(this);
  m_toolbarCombo->setEnabled(false);
  d->m_comboLabel->setBuddy(m_toolbarCombo);
  d->m_comboSeparator = new KSeparator(this);
  connect(m_toolbarCombo, SIGNAL(activated(const QString&)),
          this,           SLOT(slotToolbarSelected(const QString&)));

//  QPushButton *new_toolbar = new QPushButton(i18n("&New"), this);
//  new_toolbar->setPixmap(BarIcon("filenew", KIcon::SizeSmall));
//  new_toolbar->setEnabled(false); // disabled until implemented
//  QPushButton *del_toolbar = new QPushButton(i18n("&Delete"), this);
//  del_toolbar->setPixmap(BarIcon("editdelete", KIcon::SizeSmall));
//  del_toolbar->setEnabled(false); // disabled until implemented

  // our list of inactive actions
  QLabel *inactive_label = new QLabel(i18n("A&vailable actions:"), this);
  m_inactiveList = new ToolbarListView(this);
  m_inactiveList->setDragEnabled(true);
  m_inactiveList->setAcceptDrops(true);
  m_inactiveList->setDropVisualizer(false);
  m_inactiveList->setAllColumnsShowFocus(true);
  m_inactiveList->setMinimumSize(180, 250);
  m_inactiveList->header()->hide();
  m_inactiveList->addColumn(""); // icon
  int column2 = m_inactiveList->addColumn(""); // text
  m_inactiveList->setSorting( column2 );
  inactive_label->setBuddy(m_inactiveList);
  connect(m_inactiveList, SIGNAL(selectionChanged(QListViewItem *)),
          this,           SLOT(slotInactiveSelected(QListViewItem *)));
  connect(m_inactiveList, SIGNAL( doubleClicked( QListViewItem *, const QPoint &, int  )),
          this,           SLOT(slotInsertButton()));

  // our list of active actions
  QLabel *active_label = new QLabel(i18n("Curr&ent actions:"), this);
  m_activeList = new ToolbarListView(this);
  m_activeList->setDragEnabled(true);
  m_activeList->setAcceptDrops(true);
  m_activeList->setDropVisualizer(true);
  m_activeList->setAllColumnsShowFocus(true);
  m_activeList->setMinimumWidth(m_inactiveList->minimumWidth());
  m_activeList->header()->hide();
  m_activeList->addColumn(""); // icon
  m_activeList->addColumn(""); // text
  m_activeList->setSorting(-1);
  active_label->setBuddy(m_activeList);

  connect(m_inactiveList, SIGNAL(dropped(KListView*,QDropEvent*,QListViewItem*)),
          this,              SLOT(slotDropped(KListView*,QDropEvent*,QListViewItem*)));
  connect(m_activeList, SIGNAL(dropped(KListView*,QDropEvent*,QListViewItem*)),
          this,            SLOT(slotDropped(KListView*,QDropEvent*,QListViewItem*)));
  connect(m_activeList, SIGNAL(selectionChanged(QListViewItem *)),
          this,         SLOT(slotActiveSelected(QListViewItem *)));
  connect(m_activeList, SIGNAL( doubleClicked( QListViewItem *, const QPoint &, int  )),
          this,           SLOT(slotRemoveButton()));

  // "change icon" button
  d->m_changeIcon = new KPushButton( i18n( "Change &Icon..." ), this );
  QString kdialogExe = KStandardDirs::findExe(QString::fromLatin1("kdialog"));
  d->m_hasKDialog = !kdialogExe.isEmpty();
  d->m_changeIcon->setEnabled( d->m_hasKDialog );

  connect( d->m_changeIcon, SIGNAL( clicked() ),
           this, SLOT( slotChangeIcon() ) );

  // The buttons in the middle
  QIconSet iconSet;

  m_upAction     = new QToolButton(this);
  iconSet = SmallIconSet( "up" );
  m_upAction->setIconSet( iconSet );
  m_upAction->setEnabled(false);
  m_upAction->setAutoRepeat(true);
  connect(m_upAction, SIGNAL(clicked()), SLOT(slotUpButton()));

  m_insertAction = new QToolButton(this);
  iconSet = QApplication::reverseLayout() ? SmallIconSet( "back" ) : SmallIconSet( "forward" );
  m_insertAction->setIconSet( iconSet );
  m_insertAction->setEnabled(false);
  connect(m_insertAction, SIGNAL(clicked()), SLOT(slotInsertButton()));

  m_removeAction = new QToolButton(this);
  iconSet = QApplication::reverseLayout() ? SmallIconSet( "forward" ) : SmallIconSet( "back" );
  m_removeAction->setIconSet( iconSet );
  m_removeAction->setEnabled(false);
  connect(m_removeAction, SIGNAL(clicked()), SLOT(slotRemoveButton()));

  m_downAction   = new QToolButton(this);
  iconSet = SmallIconSet( "down" );
  m_downAction->setIconSet( iconSet );
  m_downAction->setEnabled(false);
  m_downAction->setAutoRepeat(true);
  connect(m_downAction, SIGNAL(clicked()), SLOT(slotDownButton()));

  d->m_helpArea = new QLabel(this);
  d->m_helpArea->setAlignment( Qt::WordBreak );

  // now start with our layouts
  QVBoxLayout *top_layout = new QVBoxLayout(this, 0, KDialog::spacingHint());

  QVBoxLayout *name_layout = new QVBoxLayout(KDialog::spacingHint());
  QHBoxLayout *list_layout = new QHBoxLayout(KDialog::spacingHint());

  QVBoxLayout *inactive_layout = new QVBoxLayout(KDialog::spacingHint());
  QVBoxLayout *active_layout = new QVBoxLayout(KDialog::spacingHint());
  QHBoxLayout *changeIcon_layout = new QHBoxLayout(KDialog::spacingHint());

  QGridLayout *button_layout = new QGridLayout(5, 3, 0);

  name_layout->addWidget(d->m_comboLabel);
  name_layout->addWidget(m_toolbarCombo);
//  name_layout->addWidget(new_toolbar);
//  name_layout->addWidget(del_toolbar);

  button_layout->setRowStretch( 0, 10 );
  button_layout->addWidget(m_upAction, 1, 1);
  button_layout->addWidget(m_removeAction, 2, 0);
  button_layout->addWidget(m_insertAction, 2, 2);
  button_layout->addWidget(m_downAction, 3, 1);
  button_layout->setRowStretch( 4, 10 );

  inactive_layout->addWidget(inactive_label);
  inactive_layout->addWidget(m_inactiveList, 1);

  active_layout->addWidget(active_label);
  active_layout->addWidget(m_activeList, 1);
  active_layout->addLayout(changeIcon_layout);

  changeIcon_layout->addStretch( 1 );
  changeIcon_layout->addWidget( d->m_changeIcon );
  changeIcon_layout->addStretch( 1 );

  list_layout->addLayout(inactive_layout);
  list_layout->addLayout(button_layout);
  list_layout->addLayout(active_layout);

  top_layout->addLayout(name_layout);
  top_layout->addWidget(d->m_comboSeparator);
  top_layout->addLayout(list_layout,10);
  top_layout->addWidget(d->m_helpArea);
  top_layout->addWidget(new KSeparator(this));
}

void KEditToolbarWidget::loadToolbarCombo(const QString& defaultToolbar)
{
  static const QString &attrName = KGlobal::staticQString( "name" );
  // just in case, we clear our combo
  m_toolbarCombo->clear();

  int defaultToolbarId = -1;
  int count = 0;
  // load in all of the toolbar names into this combo box
  XmlDataList::Iterator xit = d->m_xmlFiles.begin();
  for ( ; xit != d->m_xmlFiles.end(); ++xit)
  {
    // skip the local one in favor of the merged
    if ( (*xit).m_type == XmlData::Local )
      continue;

    // each xml file may have any number of toolbars
    ToolbarList::Iterator it = (*xit).m_barList.begin();
    for ( ; it != (*xit).m_barList.end(); ++it)
    {
      QString name = d->toolbarName( *xit, *it );
      m_toolbarCombo->setEnabled( true );
      m_toolbarCombo->insertItem( name );
      if (defaultToolbarId == -1 && (name == defaultToolbar || defaultToolbar == (*it).attribute( attrName )))
          defaultToolbarId = count;
      count++;
    }
  }
  bool showCombo = (count > 1);
  d->m_comboLabel->setShown(showCombo);
  d->m_comboSeparator->setShown(showCombo);
  m_toolbarCombo->setShown(showCombo);
  if (defaultToolbarId == -1)
      defaultToolbarId = 0;
  // we want to the specified item selected and its actions loaded
  m_toolbarCombo->setCurrentItem(defaultToolbarId);
  slotToolbarSelected(m_toolbarCombo->currentText());
}

void KEditToolbarWidget::loadActionList(QDomElement& elem)
{
  static const QString &tagSeparator = KGlobal::staticQString( "Separator" );
  static const QString &tagMerge     = KGlobal::staticQString( "Merge" );
  static const QString &tagActionList= KGlobal::staticQString( "ActionList" );
  static const QString &attrName     = KGlobal::staticQString( "name" );
  static const QString &attrLineSeparator = KGlobal::staticQString( "lineSeparator" );

  int     sep_num = 0;
  QString sep_name("separator_%1");

  // clear our lists
  m_inactiveList->clear();
  m_activeList->clear();
  m_insertAction->setEnabled(false);
  m_removeAction->setEnabled(false);
  m_upAction->setEnabled(false);
  m_downAction->setEnabled(false);

  // We'll use this action collection
  KActionCollection* actionCollection = d->m_currentXmlData->m_actionCollection;

  // store the names of our active actions
  QMap<QString, bool> active_list;

  // see if our current action is in this toolbar
  KIconLoader *loader = KGlobal::instance()->iconLoader();
  QDomNode n = elem.lastChild();
  for( ; !n.isNull(); n = n.previousSibling() )
  {
    QDomElement it = n.toElement();
    if (it.isNull()) continue;
    if (it.tagName() == tagSeparator)
    {
      ToolbarItem *act = new ToolbarItem(m_activeList, tagSeparator, sep_name.arg(sep_num++), QString::null);
      bool isLineSep = ( it.attribute(attrLineSeparator, "true").lower() == QString::fromLatin1("true") );
      if(isLineSep)
        act->setText(1, LINESEPARATORSTRING);
      else
        act->setText(1, SEPARATORSTRING);
      it.setAttribute( attrName, act->internalName() );
      continue;
    }

    if (it.tagName() == tagMerge)
    {
      // Merge can be named or not - use the name if there is one
      QString name = it.attribute( attrName );
      ToolbarItem *act = new ToolbarItem(m_activeList, tagMerge, name, i18n("This element will be replaced with all the elements of an embedded component."));
      if ( name.isEmpty() )
          act->setText(1, i18n("<Merge>"));
      else
          act->setText(1, i18n("<Merge %1>").arg(name));
      continue;
    }

    if (it.tagName() == tagActionList)
    {
      ToolbarItem *act = new ToolbarItem(m_activeList, tagActionList, it.attribute(attrName), i18n("This is a dynamic list of actions. You can move it, but if you remove it you won't be able to re-add it.") );
      act->setText(1, i18n("ActionList: %1").arg(it.attribute(attrName)));
      continue;
    }

    // iterate through this client's actions
    // This used to iterate through _all_ actions, but we don't support
    // putting any action into any client...
    for (unsigned int i = 0;  i < actionCollection->count(); i++)
    {
      KAction *action = actionCollection->action( i );

      // do we have a match?
      if (it.attribute( attrName ) == action->name())
      {
        // we have a match!
        ToolbarItem *act = new ToolbarItem(m_activeList, it.tagName(), action->name(), action->toolTip());
        act->setText(1, action->plainText());
        if (action->hasIcon())
          if (!action->icon().isEmpty())
            act->setPixmap(0, loader->loadIcon(action->icon(), KIcon::Toolbar, 16, KIcon::DefaultState, 0, true) );
          else // Has iconset
            act->setPixmap(0, action->iconSet(KIcon::Toolbar).pixmap());

        active_list.insert(action->name(), true);
        break;
      }
    }
  }

  // go through the rest of the collection
  for (int i = actionCollection->count() - 1; i > -1; --i)
  {
    KAction *action = actionCollection->action( i );

    // skip our active ones
    if (active_list.contains(action->name()))
      continue;

    ToolbarItem *act = new ToolbarItem(m_inactiveList, tagActionList, action->name(), action->toolTip());
    act->setText(1, action->plainText());
    if (action->hasIcon())
      if (!action->icon().isEmpty())
        act->setPixmap(0, loader->loadIcon(action->icon(), KIcon::Toolbar, 16, KIcon::DefaultState, 0, true) );
      else // Has iconset
        act->setPixmap(0, action->iconSet(KIcon::Toolbar).pixmap());
  }

  // finally, add default separators to the inactive list
  ToolbarItem *act = new ToolbarItem(m_inactiveList, tagSeparator, sep_name.arg(sep_num++), QString::null);
  act->setText(1, LINESEPARATORSTRING);
  act = new ToolbarItem(m_inactiveList, tagSeparator, sep_name.arg(sep_num++), QString::null);
  act->setText(1, SEPARATORSTRING);
}

KActionCollection *KEditToolbarWidget::actionCollection() const
{
  return d->m_collection;
}

void KEditToolbarWidget::slotToolbarSelected(const QString& _text)
{
  // iterate through everything
  XmlDataList::Iterator xit = d->m_xmlFiles.begin();
  for ( ; xit != d->m_xmlFiles.end(); ++xit)
  {
    // each xml file may have any number of toolbars
    ToolbarList::Iterator it = (*xit).m_barList.begin();
    for ( ; it != (*xit).m_barList.end(); ++it)
    {
      QString name = d->toolbarName( *xit, *it );
      // is this our toolbar?
      if ( name == _text )
      {
        // save our current settings
        d->m_currentXmlData     = & (*xit);
        d->m_currentToolbarElem = (*it);

        // load in our values
        loadActionList(d->m_currentToolbarElem);

        if ((*xit).m_type == XmlData::Part || (*xit).m_type == XmlData::Shell)
          setDOMDocument( (*xit).m_document );
        return;
      }
    }
  }
}

void KEditToolbarWidget::slotInactiveSelected(QListViewItem *item)
{
  ToolbarItem* toolitem = static_cast<ToolbarItem *>(item);
  if (item)
  {
    m_insertAction->setEnabled(true);
    QString statusText = toolitem->statusText();
    d->m_helpArea->setText( statusText );
  }
  else
  {
    m_insertAction->setEnabled(false);
    d->m_helpArea->setText( QString::null );
  }
}

void KEditToolbarWidget::slotActiveSelected(QListViewItem *item)
{
  ToolbarItem* toolitem = static_cast<ToolbarItem *>(item);
  m_removeAction->setEnabled( item );

  static const QString &tagAction = KGlobal::staticQString( "Action" );
  d->m_changeIcon->setEnabled( item &&
                               d->m_hasKDialog &&
                               toolitem->internalTag() == tagAction );

  if (item)
  {
    if (item->itemAbove())
      m_upAction->setEnabled(true);
    else
      m_upAction->setEnabled(false);

    if (item->itemBelow())
      m_downAction->setEnabled(true);
    else
      m_downAction->setEnabled(false);
    QString statusText = toolitem->statusText();
    d->m_helpArea->setText( statusText );
  }
  else
  {
    m_upAction->setEnabled(false);
    m_downAction->setEnabled(false);
    d->m_helpArea->setText( QString::null );
  }
}

void KEditToolbarWidget::slotDropped(KListView *list, QDropEvent *e, QListViewItem *after)
{
  ToolbarItem *item = new ToolbarItem(m_inactiveList); // needs parent, use inactiveList temporarily
  if(!ToolbarItemDrag::decode(e, *item)) {
    delete item;
    return;
  }

  if (list == m_activeList) {
    if (e->source() == m_activeList) {
      // has been dragged within the active list (moved).
      moveActive(item, after);
    }
    else
      insertActive(item, after, true);
  } else if (list == m_inactiveList) {
    // has been dragged to the inactive list -> remove from the active list.
    removeActive(item);
  }

  delete item; item = 0; // not neded anymore

  // we're modified, so let this change
  emit enableOk(true);

  slotToolbarSelected( m_toolbarCombo->currentText() );
}

void KEditToolbarWidget::slotInsertButton()
{
  ToolbarItem *item = (ToolbarItem*)m_inactiveList->currentItem();
  insertActive(item, m_activeList->currentItem(), false);

  // we're modified, so let this change
  emit enableOk(true);

  // TODO: #### this causes #97572.
  // It would be better to just "delete item; loadActions( ... , ActiveListOnly );" or something.
  slotToolbarSelected( m_toolbarCombo->currentText() );
}

void KEditToolbarWidget::slotRemoveButton()
{
  removeActive( dynamic_cast<ToolbarItem*>(m_activeList->currentItem()) );

  // we're modified, so let this change
  emit enableOk(true);

  slotToolbarSelected( m_toolbarCombo->currentText() );
}

void KEditToolbarWidget::insertActive(ToolbarItem *item, QListViewItem *before, bool prepend)
{
  if (!item)
    return;

  static const QString &tagAction    = KGlobal::staticQString( "Action" );
  static const QString &tagSeparator = KGlobal::staticQString( "Separator" );
  static const QString &attrName     = KGlobal::staticQString( "name" );
  static const QString &attrLineSeparator = KGlobal::staticQString( "lineSeparator" );
  static const QString &attrNoMerge  = KGlobal::staticQString( "noMerge" );

  QDomElement new_item;
  // let's handle the separator specially
  if (item->text(1) == LINESEPARATORSTRING) {
    new_item = domDocument().createElement(tagSeparator);
  } else if (item->text(1) == SEPARATORSTRING) {
    new_item = domDocument().createElement(tagSeparator);
    new_item.setAttribute(attrLineSeparator, "false");
  } else
    new_item = domDocument().createElement(tagAction);
  new_item.setAttribute(attrName, item->internalName());

  if (before)
  {
    // we have the item in the active list which is before the new
    // item.. so let's try our best to add our new item right after it
    ToolbarItem *act_item = (ToolbarItem*)before;
    QDomElement elem = d->findElementForToolbarItem( act_item );
    Q_ASSERT( !elem.isNull() );
    d->m_currentToolbarElem.insertAfter(new_item, elem);
  }
  else
  {
    // simply put it at the beginning or the end of the list.
    if (prepend)
      d->m_currentToolbarElem.insertBefore(new_item, d->m_currentToolbarElem.firstChild());
    else
      d->m_currentToolbarElem.appendChild(new_item);
  }

  // and set this container as a noMerge
  d->m_currentToolbarElem.setAttribute( attrNoMerge, "1");

  // update the local doc
  updateLocal(d->m_currentToolbarElem);
}

void KEditToolbarWidget::removeActive(ToolbarItem *item)
{
  if (!item)
    return;

  static const QString &attrNoMerge = KGlobal::staticQString( "noMerge" );

  // we're modified, so let this change
  emit enableOk(true);

  // now iterate through to find the child to nuke
  QDomElement elem = d->findElementForToolbarItem( item );
  if ( !elem.isNull() )
  {
    // nuke myself!
    d->m_currentToolbarElem.removeChild(elem);

    // and set this container as a noMerge
    d->m_currentToolbarElem.setAttribute( attrNoMerge, "1");

    // update the local doc
    updateLocal(d->m_currentToolbarElem);
  }
}

void KEditToolbarWidget::slotUpButton()
{
  ToolbarItem *item = (ToolbarItem*)m_activeList->currentItem();

  // make sure we're not the top item already
  if (!item->itemAbove())
    return;

  // we're modified, so let this change
  emit enableOk(true);

  moveActive( item, item->itemAbove()->itemAbove() );
  delete item;
}

void KEditToolbarWidget::moveActive( ToolbarItem* item, QListViewItem* before )
{
  QDomElement e = d->findElementForToolbarItem( item );

  if ( e.isNull() )
    return;

  // cool, i found me.  now clone myself
  ToolbarItem *clone = new ToolbarItem(m_activeList,
                                       before,
                                       item->internalTag(),
                                       item->internalName(),
                                       item->statusText());

  clone->setText(1, item->text(1));

  // only set new pixmap if exists
  if( item->pixmap(0) )
    clone->setPixmap(0, *item->pixmap(0));

  // select my clone
  m_activeList->setSelected(clone, true);

  // make clone visible
  m_activeList->ensureItemVisible(clone);

  // and do the real move in the DOM
  if ( !before )
    d->m_currentToolbarElem.insertBefore(e, d->m_currentToolbarElem.firstChild() );
  else
    d->m_currentToolbarElem.insertAfter(e, d->findElementForToolbarItem( (ToolbarItem*)before ));

  // and set this container as a noMerge
  static const QString &attrNoMerge = KGlobal::staticQString( "noMerge" );
  d->m_currentToolbarElem.setAttribute( attrNoMerge, "1");

  // update the local doc
  updateLocal(d->m_currentToolbarElem);
}

void KEditToolbarWidget::slotDownButton()
{
  ToolbarItem *item = (ToolbarItem*)m_activeList->currentItem();

  // make sure we're not the bottom item already
  if (!item->itemBelow())
    return;

  // we're modified, so let this change
  emit enableOk(true);

  moveActive( item, item->itemBelow() );
  delete item;
}

void KEditToolbarWidget::updateLocal(QDomElement& elem)
{
  static const QString &attrName = KGlobal::staticQString( "name" );

  XmlDataList::Iterator xit = d->m_xmlFiles.begin();
  for ( ; xit != d->m_xmlFiles.end(); ++xit)
  {
    if ( (*xit).m_type == XmlData::Merged )
      continue;

    if ( (*xit).m_type == XmlData::Shell ||
         (*xit).m_type == XmlData::Part )
    {
      if ( d->m_currentXmlData->m_xmlFile == (*xit).m_xmlFile )
      {
        (*xit).m_isModified = true;
        return;
      }

      continue;
    }

    (*xit).m_isModified = true;

    ToolbarList::Iterator it = (*xit).m_barList.begin();
    for ( ; it != (*xit).m_barList.end(); ++it)
    {
      QString name( (*it).attribute( attrName ) );
      QString tag( (*it).tagName() );
      if ( (tag != elem.tagName()) || (name != elem.attribute(attrName)) )
        continue;

      QDomElement toolbar = (*xit).m_document.documentElement().toElement();
      toolbar.replaceChild(elem, (*it));
      return;
    }

    // just append it
    QDomElement toolbar = (*xit).m_document.documentElement().toElement();
    toolbar.appendChild(elem);
  }
}

void KEditToolbarWidget::slotChangeIcon()
{
  // We can't use KIconChooser here, since it's in libkio
  // ##### KDE4: reconsider this, e.g. move KEditToolbar to libkio
  d->m_kdialogProcess = new KProcIO;
  QString kdialogExe = KStandardDirs::findExe(QString::fromLatin1("kdialog"));
  (*d->m_kdialogProcess) << kdialogExe;
  (*d->m_kdialogProcess) << "--embed";
  (*d->m_kdialogProcess) << QString::number( (ulong)topLevelWidget()->winId() );
  (*d->m_kdialogProcess) << "--geticon";
  (*d->m_kdialogProcess) << "Toolbar";
  (*d->m_kdialogProcess) << "Actions";
  if ( !d->m_kdialogProcess->start( KProcess::NotifyOnExit ) ) {
    kdError(240) << "Can't run " << kdialogExe << endl;
    delete d->m_kdialogProcess;
    d->m_kdialogProcess = 0;
    return;
  }

  m_activeList->setEnabled( false ); // don't change the current item
  m_toolbarCombo->setEnabled( false ); // don't change the current toolbar

  connect( d->m_kdialogProcess, SIGNAL( processExited( KProcess* ) ),
           this, SLOT( slotProcessExited( KProcess* ) ) );
}

void KEditToolbarWidget::slotProcessExited( KProcess* )
{
  m_activeList->setEnabled( true );
  m_toolbarCombo->setEnabled( true );

  QString icon;
  if ( !d->m_kdialogProcess->normalExit() ||
       d->m_kdialogProcess->exitStatus() ||
       d->m_kdialogProcess->readln(icon, true) <= 0 ) {
    delete d->m_kdialogProcess;
    d->m_kdialogProcess = 0;
    return;
  }

  ToolbarItem *item = (ToolbarItem*)m_activeList->currentItem();
  if(item){
    item->setPixmap(0, BarIcon(icon, 16));

    Q_ASSERT( d->m_currentXmlData->m_type != XmlData::Merged );

    d->m_currentXmlData->m_isModified = true;

    // Get hold of ActionProperties tag
    QDomElement elem = KXMLGUIFactory::actionPropertiesElement( d->m_currentXmlData->m_document );
    // Find or create an element for this action
    QDomElement act_elem = KXMLGUIFactory::findActionByName( elem, item->internalName(), true /*create*/ );
    Q_ASSERT( !act_elem.isNull() );
    act_elem.setAttribute( "icon", icon );

    // we're modified, so let this change
    emit enableOk(true);
  }

  delete d->m_kdialogProcess;
  d->m_kdialogProcess = 0;
}

void KEditToolbar::virtual_hook( int id, void* data )
{ KDialogBase::virtual_hook( id, data ); }

void KEditToolbarWidget::virtual_hook( int id, void* data )
{ KXMLGUIClient::virtual_hook( id, data ); }

#include "kedittoolbar.moc"
