/*********
*
* This file is part of BibleTime's source code, http://www.bibletime.info/.
*
* Copyright 1999-2011 by the BibleTime developers.
* The BibleTime source code is licensed under the GNU General Public License version 2.0.
*
**********/

#include "frontend/bookmarks/cbookmarkindex.h"

#include <QSharedPointer>
#include <QAction>
#include <QApplication>
#include <QCursor>
#include <QDebug>
#include <QDrag>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QInputDialog>
#include <QList>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QPaintEvent>
#include <QTimer>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QToolTip>
#include "backend/config/cbtconfig.h"
#include "backend/drivers/cswordmoduleinfo.h"
#include "backend/managers/referencemanager.h"
#include "frontend/cdragdrop.h"
#include "frontend/cinfodisplay.h"
#include "frontend/cprinter.h"
#include "frontend/bookmarks/btbookmarkitembase.h"
#include "frontend/bookmarks/btbookmarkitem.h"
#include "frontend/bookmarks/btbookmarkfolder.h"
#include "frontend/bookmarks/btbookmarkloader.h"
#include "frontend/searchdialog/csearchdialog.h"
#include "util/cresmgr.h"
#include "util/tool.h"
#include "util/directory.h"
#include "util/dialogutil.h"
#include "bibletime.h"


CBookmarkIndex::CBookmarkIndex(QWidget *parent)
        : QTreeWidget(parent),
        m_magTimer(this),
        m_previousEventItem(0) {
    setMouseTracking(true);
    m_magTimer.setSingleShot(true);
    m_magTimer.setInterval(CBTConfig::get(CBTConfig::magDelay));
    setContextMenuPolicy(Qt::CustomContextMenu);
    initView();
    initConnections();
    initTree();
}

CBookmarkIndex::~CBookmarkIndex() {
    saveBookmarks();
}


/** Initializes the view. */
void CBookmarkIndex::initView() {
    setHeaderHidden(true);

    setFocusPolicy(Qt::WheelFocus);

    //d'n'd related settings
    setDragEnabled( true );
    setAcceptDrops( true );
    setDragDropMode(QAbstractItemView::DragDrop);
    viewport()->setAcceptDrops(true);
    setAutoScroll(true);
    setAutoExpandDelay(800);

    setItemsExpandable(true);
    setRootIsDecorated(true);
    setAllColumnsShowFocus(true);
    setSelectionMode(QAbstractItemView::ExtendedSelection);

    //setup the popup menu
    m_popup = new QMenu(viewport());
    m_popup->setTitle(tr("Bookmarks"));

    m_actions.newFolder = newQAction(tr("New folder"), CResMgr::mainIndex::newFolder::icon, 0, this, SLOT(createNewFolder()), this);
    m_actions.changeFolder = newQAction(tr("Rename folder"), CResMgr::mainIndex::changeFolder::icon, 0, this, SLOT(changeFolder()), this);

    m_actions.editBookmark = newQAction(tr("Edit bookmark..."), CResMgr::mainIndex::editBookmark::icon, 0, this, SLOT(editBookmark()), this);
    /// \todo Add icons for sorting bookmarks
    m_actions.sortFolderBookmarks = newQAction(tr("Sort folder bookmarks..."), QString::null, 0, this, SLOT(sortFolderBookmarks()), this);
    m_actions.sortAllBookmarks = newQAction(tr("Sort all bookmarks..."), QString::null, 0, this, SLOT(sortAllBookmarks()), this);
    m_actions.importBookmarks = newQAction(tr("Import to folder..."), CResMgr::mainIndex::importBookmarks::icon, 0, this, SLOT(importBookmarks()), this);
    m_actions.exportBookmarks = newQAction(tr("Export from folder..."), CResMgr::mainIndex::exportBookmarks::icon, 0, this, SLOT(exportBookmarks()), this);
    m_actions.printBookmarks = newQAction(tr("Print bookmarks..."), CResMgr::mainIndex::printBookmarks::icon, 0, this, SLOT(printBookmarks()), this);

    m_actions.deleteEntries = newQAction(tr("Remove selected items..."), CResMgr::mainIndex::deleteItems::icon, 0, this, SLOT(deleteEntries()), this);


    //fill the popup menu itself
    m_popup->addAction(m_actions.newFolder);
    m_popup->addAction(m_actions.changeFolder);
    QAction* separator = new QAction(this);
    separator->setSeparator(true);
    m_popup->addAction(separator);
    m_popup->addAction(m_actions.editBookmark);
    m_popup->addAction(m_actions.sortFolderBookmarks);
    m_popup->addAction(m_actions.sortAllBookmarks);
    m_popup->addAction(m_actions.importBookmarks);
    m_popup->addAction(m_actions.exportBookmarks);
    m_popup->addAction(m_actions.printBookmarks);
    separator = new QAction(this);
    separator->setSeparator(true);
    m_popup->addAction(separator);
    m_popup->addAction(m_actions.deleteEntries);

    m_bookmarksModified = false;
}

/** Convenience function for creating a new QAction.
* Should be replaced with something better; it was easier to make a new function
* than to modify all QAction constructors.
*/
QAction* CBookmarkIndex::newQAction(const QString& text, const QString& pix, const int /*shortcut*/, const QObject* receiver, const char* slot, QObject* parent) {
    namespace DU = util::directory;
    QAction *action;
    if (pix.isEmpty()) {
        action = new QAction(text, parent);
    } else {
        action = new QAction(DU::getIcon(pix), text, parent);
    }
    QObject::connect(action, SIGNAL(triggered()), receiver, slot);
    return action;
}

/** Initialize the SIGNAL<->SLOT connections */
void CBookmarkIndex::initConnections() {
    bool ok;
    ok = connect(this, SIGNAL(itemActivated(QTreeWidgetItem*, int)), this, SLOT(slotExecuted(QTreeWidgetItem*)));
    Q_ASSERT(ok);
    ok = connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
                 SLOT(contextMenu(const QPoint&)));
    Q_ASSERT(ok);
    ok = connect(&m_magTimer, SIGNAL(timeout()), this, SLOT(magTimeout()));
    Q_ASSERT(ok);
    ok = connect(this, SIGNAL(itemEntered(QTreeWidgetItem*, int)), this, SLOT(slotItemEntered(QTreeWidgetItem*, int)) );
    Q_ASSERT(ok);

    // Connection to detect changes in the items themselves (e.g. renames,
    // description changes) so that we can consider saving the bookmarks.
    connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(needToSaveBookmarks(QTreeWidgetItem*)) );

    // Connect the bookmark saving timer.
    bookmarkSaveTimer.setSingleShot(true);
    connect(&bookmarkSaveTimer, SIGNAL(timeout()), this, SLOT(considerSavingBookmarks()) );
}


/**
* Hack to get single click and selection working. See slotExecuted.
*/
void CBookmarkIndex::mouseReleaseEvent(QMouseEvent* event) {
    m_mouseReleaseEventModifiers = event->modifiers();
    QTreeWidget::mouseReleaseEvent(event);
}

/** Called when an item is clicked with mouse or activated with keyboard. */
void CBookmarkIndex::slotExecuted( QTreeWidgetItem* i ) {
    //HACK: checking the modifier keys from the last mouseReleaseEvent
    //depends on executing order: mouseReleaseEvent first, then itemClicked signal
    int modifiers = m_mouseReleaseEventModifiers;
    m_mouseReleaseEventModifiers = Qt::NoModifier;
    if (modifiers != Qt::NoModifier) {
        return;
    }

    BtBookmarkItemBase* btItem = dynamic_cast<BtBookmarkItemBase*>(i);
    if (!btItem) {
        return;
    }

    BtBookmarkFolder* folderItem = 0;
    BtBookmarkItem* bookmarkItem = 0;
    if ((folderItem = dynamic_cast<BtBookmarkFolder*>(btItem))) {
        i->setExpanded( !i->isExpanded() );
    }
    else if (( bookmarkItem = dynamic_cast<BtBookmarkItem*>(btItem) )) { //clicked on a bookmark
        if (CSwordModuleInfo* mod = bookmarkItem->module()) {
            QList<CSwordModuleInfo*> modules;
            modules.append(mod);
            emit createReadDisplayWindow(modules, bookmarkItem->key());
        }
    }
}

/** Creates a drag mime data object for the current selection. */
QMimeData* CBookmarkIndex::dragObject() {
    BTMimeData::ItemList dndItems;
    BTMimeData* mimeData = new BTMimeData;

    foreach( QTreeWidgetItem* widgetItem, selectedItems() ) {
        if (!widgetItem)
            break;
        if (dynamic_cast<BtBookmarkItemBase*>(widgetItem)) {
            if (BtBookmarkItem* bookmark = dynamic_cast<BtBookmarkItem*>( widgetItem )) {
                //take care of bookmarks which have no valid module any more, e.g. if it was uninstalled
                const QString moduleName = bookmark->module() ? bookmark->module()->name() : QString::null;
                mimeData->appendBookmark(moduleName, bookmark->key(), bookmark->description());
            }
        }
    }
    return mimeData;
}

void CBookmarkIndex::dragEnterEvent( QDragEnterEvent* event ) {
    setState(QAbstractItemView::DraggingState);
    QTreeWidget::dragEnterEvent(event);
    if (event->source() == this || event->mimeData()->hasFormat("BibleTime/Bookmark")) {
        event->acceptProposedAction();
    }
}


void CBookmarkIndex::dragMoveEvent( QDragMoveEvent* event ) {
    // do this first, otherwise the event may be ignored
    QTreeWidget::dragMoveEvent(event);

    event->acceptProposedAction();
    event->accept();

    // do this to paint the arrow
    m_dragMovementPosition = event->pos();
    viewport()->update();

}

void CBookmarkIndex::dragLeaveEvent( QDragLeaveEvent* ) {
    setState(QAbstractItemView::NoState); // not dragging anymore
    viewport()->update(); // clear the arrow
}


void CBookmarkIndex::paintEvent(QPaintEvent* event) {
    namespace DU = util::directory;

    static QPixmap pix;
    static int halfPixHeight;
    static bool arrowInitialized = false;

    // Initialize the static variables, including the arrow pixmap
    if (!arrowInitialized) {
        arrowInitialized = true;
        int arrowSize = util::tool::mWidth(this, 1);
        QString fileName;
        if (DU::getIconDir().exists("pointing_arrow.svg")) {
            fileName = DU::getIconDir().filePath("pointing_arrow.svg");
        }
        else {
            if (DU::getIconDir().exists("pointing_arrow.png")) {
                fileName = DU::getIconDir().filePath("pointing_arrow.png");
            }
            else {
                qWarning() << "Picture file pointing_arrow.svg or .png not found!";
            }
        }

        pix = QPixmap(fileName);
        pix = pix.scaled(arrowSize, arrowSize, Qt::KeepAspectRatioByExpanding);
        halfPixHeight = pix.height() / 2;
    }

    // Do the normal painting first
    QTreeWidget::paintEvent(event);

    // Paint the arrow if the drag is going on
    if (QAbstractItemView::DraggingState == state()) {
        bool rtol = QApplication::isRightToLeft();

        QPainter painter(this->viewport());
        QTreeWidgetItem* item = itemAt(m_dragMovementPosition);
        bool isFolder = dynamic_cast<BtBookmarkFolder*>(item);
        bool isBookmark = dynamic_cast<BtBookmarkItem*>(item);

        // Find the place for the arrow
        QRect rect = visualItemRect(item);
        int xCoord = rtol ? rect.right() : rect.left();
        int yCoord;
        if (isFolder) {
            if (m_dragMovementPosition.y() > rect.bottom() - (2* rect.height() / 3) ) {
                yCoord = rect.bottom() - halfPixHeight; // bottom
                xCoord = rtol ? (xCoord - indentation()) : (xCoord + indentation());
            }
            else {
                yCoord = rect.top() - halfPixHeight - 1; // top
            }

        }
        else {
            if (isBookmark) {
                if (m_dragMovementPosition.y() > rect.bottom() - rect.height() / 2) {
                    yCoord = rect.bottom() - halfPixHeight; // bottom
                }
                else {
                    yCoord = rect.top() - halfPixHeight - 1; // top
                }
            }
            else {
                if (item) { // the extra item
                    yCoord = rect.top() - halfPixHeight - 1;
                }
                else { // empty area
                    rect = visualItemRect(m_extraItem);
                    yCoord = rect.top() - halfPixHeight - 1;
                    xCoord = rtol ? rect.right() : rect.left();
                }
            }
        }

        painter.drawPixmap(xCoord, yCoord, pix);
    }
}


void CBookmarkIndex::dropEvent( QDropEvent* event ) {

    //setState(QAbstractItemView::NoState);
    // Try to prevent annoying timed autocollapsing. Remember to disconnect before return.
    QObject::connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(expandAutoCollapsedItem(QTreeWidgetItem*)));
    QTreeWidgetItem* item = itemAt(event->pos());
    QTreeWidgetItem* parentItem = 0;
    int indexUnderParent = 0;

    // Find the place where the drag is dropped
    if (item) {
        QRect rect = visualItemRect(item);
        bool isFolder = dynamic_cast<BtBookmarkFolder*>(item);
        bool isBookmark = dynamic_cast<BtBookmarkItem*>(item);

        if (isFolder) { // item is a folder
            if (event->pos().y() > rect.bottom() - (2* rect.height() / 3) ) {
                parentItem = item;
            }
            else {
                parentItem = item->parent();
                if (!parentItem) {
                    parentItem = invisibleRootItem();
                }
                indexUnderParent = parentItem->indexOfChild(item); // before the current folder
            }
        }
        else {
            if (isBookmark) { // item is a bookmark
                parentItem = item->parent();
                if (!parentItem) {
                    parentItem = invisibleRootItem();
                }
                indexUnderParent = parentItem->indexOfChild(item); // before the current bookmark
                if (event->pos().y() > rect.bottom() - rect.height() / 2) {
                    indexUnderParent++; // after the current bookmark
                }
            }
            else { // item is the extra item
                parentItem = item->parent();
                if (!parentItem) {
                    parentItem = invisibleRootItem();
                }
                indexUnderParent = parentItem->indexOfChild(item); // before the current bookmark
            }
        }

    }
    else { // no item under event point: drop to the end
        parentItem = invisibleRootItem();
        indexUnderParent = parentItem->childCount() - 1;
    }


    if ( event->source() == this ) {
        event->accept();

        bool bookmarksOnly = true;
        bool targetIncluded = false;
        bool moreThanOneFolder = false;

        QList<QTreeWidgetItem*> newItems = addItemsToDropTree(parentItem, bookmarksOnly, targetIncluded, moreThanOneFolder);

        if (moreThanOneFolder) {
            QToolTip::showText(QCursor::pos(), tr("Can drop only bookmarks or one folder"));
            QObject::disconnect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(expandAutoCollapsedItem(QTreeWidgetItem*)));
            return;
        }
        if (targetIncluded) {
            QToolTip::showText(QCursor::pos(), tr("Can't drop folder into the folder itself or into its subfolder"));
            QObject::disconnect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(expandAutoCollapsedItem(QTreeWidgetItem*)));
            return;
        }
        // Ask whether to copy or move with a popup menu

        QMenu* dropPopupMenu = new QMenu(this);
        QAction* copy = dropPopupMenu->addAction(tr("Copy"));
        QAction* move = dropPopupMenu->addAction(tr("Move"));
        QAction* dropAction = dropPopupMenu->exec(QCursor::pos());
        if (dropAction == copy) {
            parentItem->insertChildren(indexUnderParent, newItems);
            // Need this here because the "move" case goes through
            // "deleteEntries" which has a save call.
            needToSaveBookmarks();
        }
        else {
            if (dropAction == move) {
                parentItem->insertChildren(indexUnderParent, newItems);
                deleteEntries(false);
            }
            else {
                QObject::disconnect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)),
                                    this, SLOT(expandAutoCollapsedItem(QTreeWidgetItem*)));
                return; // user canceled
            }
        }
    }
    else {
        createBookmarkFromDrop(event, parentItem, indexUnderParent);
    }
    QObject::disconnect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(expandAutoCollapsedItem(QTreeWidgetItem*)));
    setState(QAbstractItemView::NoState);
}


void CBookmarkIndex::createBookmarkFromDrop(QDropEvent* event, QTreeWidgetItem* parentItem, int indexInParent) {
    //take the bookmark data from the mime source
    const BTMimeData* mdata = dynamic_cast<const BTMimeData*>(event->mimeData());
    if (mdata) {
        //create the new bookmark
        QString moduleName = mdata->bookmark().module();
        QString keyText = mdata->bookmark().key();
        QString description = mdata->bookmark().description();
        CSwordModuleInfo *minfo = CSwordBackend::instance()->findModuleByName(moduleName);
    QString title;  /// \todo

        QTreeWidgetItem* newItem = new BtBookmarkItem(minfo, keyText, description, title);
        //  connect(newItem, SIGNAL(bookmarkModified()), this, SLOT(needToSaveBookmarks()) );
        parentItem->insertChild(indexInParent, newItem);

        needToSaveBookmarks();
    }
}


/** Load the tree from file */
void CBookmarkIndex::initTree() {
    BtBookmarkLoader loader;
    addTopLevelItems(loader.loadTree());

    // add the invisible extra item at the end
    m_extraItem = new QTreeWidgetItem();
    m_extraItem->setFlags(Qt::ItemIsDropEnabled);
    addTopLevelItem(m_extraItem);
}

void CBookmarkIndex::slotItemEntered(QTreeWidgetItem* item, int) {
    if (item == m_extraItem) {
        m_extraItem->setText(0, tr("Drag references from text views to this view"));
    }
    else {
        m_extraItem->setText(0, QString::null);
    }
}


/** Returns the correct QAction object for the given type of action. */
QAction* CBookmarkIndex::action( BtBookmarkItemBase::MenuAction type ) const {
    switch (type) {
        case BtBookmarkItemBase::NewFolder:
            return m_actions.newFolder;
        case BtBookmarkItemBase::ChangeFolder:
            return m_actions.changeFolder;

        case BtBookmarkItemBase::EditBookmark:
            return m_actions.editBookmark;
        case BtBookmarkItemBase::SortFolderBookmarks:
            return m_actions.sortFolderBookmarks;
        case BtBookmarkItemBase::SortAllBookmarks:
            return m_actions.sortAllBookmarks;
        case BtBookmarkItemBase::ImportBookmarks:
            return m_actions.importBookmarks;
        case BtBookmarkItemBase::ExportBookmarks:
            return m_actions.exportBookmarks;
        case BtBookmarkItemBase::PrintBookmarks:
            return m_actions.printBookmarks;

        case BtBookmarkItemBase::DeleteEntries:
            return m_actions.deleteEntries;

        default:
            return 0;
    }
}

/** Shows the context menu at the given position. */
void CBookmarkIndex::contextMenu(const QPoint& p) {
    //setup menu entries depending on current selection
    QTreeWidgetItem* i = itemAt(p);
    QList<QTreeWidgetItem *> items = selectedItems();
    //The item which was clicked may not be selected
    if (i && !items.contains(i) && i != m_extraItem)
        items.append(i);

    if (items.isEmpty()) {
        //special handling for no selection
        BtBookmarkItemBase::MenuAction actionType;
        for (int index = BtBookmarkItemBase::ActionBegin; index <= BtBookmarkItemBase::ActionEnd; ++index) {
            actionType = static_cast<BtBookmarkItemBase::MenuAction>(index);
            if (QAction* a = action(actionType)) {
                switch (index) {
                        //case BtBookmarkItemBase::ExportBookmarks:
                        //case BtBookmarkItemBase::ImportBookmarks:
                    case BtBookmarkItemBase::NewFolder:
            case BtBookmarkItemBase::SortAllBookmarks:
                        //case BtBookmarkItemBase::PrintBookmarks:
                        a->setEnabled(true);
                        break;
                    default:
                        a->setEnabled(false);
                }
            }
        }
    }
    else if (items.count() == 1) {
        //special handling for one selected item

        BtBookmarkItemBase* item = dynamic_cast<BtBookmarkItemBase*>(items.at(0));
        BtBookmarkItemBase::MenuAction actionType;
        for (int index = BtBookmarkItemBase::ActionBegin; index <= BtBookmarkItemBase::ActionEnd; ++index) {
            actionType = static_cast<BtBookmarkItemBase::MenuAction>(index);
            if (QAction* a = action(actionType))
                a->setEnabled( item->enableAction(actionType) );
        }
    }
    else {
        //first disable all actions
        BtBookmarkItemBase::MenuAction actionType;
        for (int index = BtBookmarkItemBase::ActionBegin; index <= BtBookmarkItemBase::ActionEnd; ++index) {
            actionType = static_cast<BtBookmarkItemBase::MenuAction>(index);
            if (QAction* a = action(actionType))
                a->setEnabled(false);
        }
        //enable the menu items depending on the types of the selected items.
        for (int index = BtBookmarkItemBase::ActionBegin; index <= BtBookmarkItemBase::ActionEnd; ++index) {
            actionType = static_cast<BtBookmarkItemBase::MenuAction>(index);
            bool enableAction = isMultiAction(actionType);
            QListIterator<QTreeWidgetItem *> it(items);
            while (it.hasNext()) {
                BtBookmarkItemBase* i = dynamic_cast<BtBookmarkItemBase*>(it.next());
                enableAction = enableAction && i->enableAction(actionType);
            }
            if (enableAction) {
                QAction* a = action(actionType) ;
                if (i && a)
                    a->setEnabled(enableAction);
            }
        }
    }
    //finally, open the popup
    m_popup->exec(mapToGlobal(p));
}

/** Adds a new subfolder to the current item. */
void CBookmarkIndex::createNewFolder() {
    QList<QTreeWidgetItem*> selected = selectedItems();
    if (selected.count() > 0) {
        BtBookmarkFolder* i = dynamic_cast<BtBookmarkFolder*>(currentItem());
        if (i) {
            i->newSubFolder();
        }
    }
    else {
        // create a top level folder
        BtBookmarkFolder* newFolder = new BtBookmarkFolder(tr("New folder"));
        //parentFolder->addChild(newFolder);
        insertTopLevelItem(topLevelItemCount() - 1, newFolder);
        newFolder->update();
        newFolder->rename();
    }
    needToSaveBookmarks();
}

/** Opens a dialog to change the current folder. */
void CBookmarkIndex::changeFolder() {
    BtBookmarkFolder* i = dynamic_cast<BtBookmarkFolder*>(currentItem());
    Q_ASSERT(i);
    if (i) {
        i->rename();
    }
}

/** Edits the current bookmark. */
void CBookmarkIndex::editBookmark() {
    BtBookmarkItem* i = dynamic_cast<BtBookmarkItem*>(currentItem());
    Q_ASSERT(i);

    if (i) {
        i->rename();
    }
}

/** Sorts the current folder bookmarks. */
void CBookmarkIndex::sortFolderBookmarks() {
    BtBookmarkFolder* i = dynamic_cast<BtBookmarkFolder*>(currentItem());
    Q_ASSERT(i);

    if (i) {
        i->sortChildren(0, Qt::AscendingOrder);
    }
}

/** Sorts all bookmarks. */
void CBookmarkIndex::sortAllBookmarks() {
    sortItems(0, Qt::AscendingOrder);
    int index = indexOfTopLevelItem(m_extraItem);
    if (index >= 0) {
      QTreeWidgetItem* item = takeTopLevelItem(index);
      if (item != 0) {
    addTopLevelItem(m_extraItem);
      }
    }
}

/** Exports the bookmarks being in the selected folder. */
void CBookmarkIndex::exportBookmarks() {
    BtBookmarkFolder* i = dynamic_cast<BtBookmarkFolder*>(currentItem());
    Q_ASSERT(i);

    if (i) {
        i->exportBookmarks();
    }
}

/** Import bookmarks from a file and add them to the selected folder. */
void CBookmarkIndex::importBookmarks() {
    BtBookmarkFolder* i = dynamic_cast<BtBookmarkFolder*>(currentItem());
    Q_ASSERT(i);

    if (i) {
        i->importBookmarks();
    }
    needToSaveBookmarks();
}

/** Prints the selected bookmarks. */
void CBookmarkIndex::printBookmarks() {
    Printing::CPrinter::KeyTree tree;
    Printing::CPrinter::KeyTreeItem::Settings settings;
    settings.keyRenderingFace = Printing::CPrinter::KeyTreeItem::Settings::CompleteShort;

    QList<QTreeWidgetItem*> items;
    BtBookmarkFolder* bf = dynamic_cast<BtBookmarkFolder*>(currentItem());

    if (bf) {
        items = bf->getChildList();
    }
    else {
        items = selectedItems();
    }

    //create a tree of keytreeitems using the bookmark hierarchy.
    QListIterator<QTreeWidgetItem*> it(items);
    while (it.hasNext()) {
        BtBookmarkItem* i = dynamic_cast<BtBookmarkItem*>(it.next());
        if (i) {
            qDebug() << "printBookmarks: add to list" << i->key();
            tree.append( new Printing::CPrinter::KeyTreeItem( i->key(), i->module(), settings ) );
        }
    }

    if (items.isEmpty()) {
        qWarning("Tried to print empty bookmark list.");
        return;
    }
    QSharedPointer<Printing::CPrinter> printer(
        new Printing::CPrinter( this, CBTConfig::getDisplayOptionDefaults(), CBTConfig::getFilterOptionDefaults() )
    );
    printer->printKeyTree(tree);
}

/** Deletes the selected entries. */
void CBookmarkIndex::deleteEntries(bool confirm) {
    if (confirm) {
        if (!selectedItems().count()) {
            BtBookmarkItemBase* f = dynamic_cast<BtBookmarkItemBase*>(currentItem());
            if (f) {
                currentItem()->setSelected(true);
            }
            else {
                return;
            }
        }

        if (util::showQuestion(this, tr("Delete Items"),
                               tr("Do you really want to delete the selected items and child-items?"),
                               QMessageBox::Yes | QMessageBox::No, QMessageBox::No )
                != QMessageBox::Yes) {
            return;
        }
    }

    while (selectedItems().size() > 0) {
        delete selectedItems().at(0); // deleting all does not work because it may cause double deletion
    }
    // Save the bookmarks.  One way would be to signal that the bookmarks have
    // changed emit a signal so that a number of changes may be saved at once.
    // Another way is to simply save the bookmarks after each change, which can
    // be inefficient.
    needToSaveBookmarks();
}


/*
Reimplementation from QAbstractItemView/QTreeWidget. Takes care of movable items.
It's easier to use this than to start drag with mouse event handlers.
The default implementation would drag items, but we don't call it. Instead we create
a BibleTime mimedata object. It can be dragged and dropped to a text view or somewhere else.
The internal drag is handled differently, it doesn't use the mimedata (see dropEvent()).
*/
void CBookmarkIndex::startDrag(Qt::DropActions) {

    QMimeData* mData = dragObject(); // create the data which can be used in other widgets
    QDrag* drag = new QDrag(this);
    drag->setMimeData(mData);
    drag->exec();

    viewport()->update(); // because of the arrow
}






/* Returns true if more than one entry is supported by this action type. Returns false for actions which support only one entry, e.g. about module etc. */
bool CBookmarkIndex::isMultiAction( const BtBookmarkItemBase::MenuAction type ) const {
    switch (type) {
        case BtBookmarkItemBase::NewFolder:
            return false;
        case BtBookmarkItemBase::ChangeFolder:
            return false;

        case BtBookmarkItemBase::EditBookmark:
            return false;
        case BtBookmarkItemBase::SortFolderBookmarks:
            return false;
        case BtBookmarkItemBase::SortAllBookmarks:
            return false;
        case BtBookmarkItemBase::ImportBookmarks:
            return false;
        case BtBookmarkItemBase::ExportBookmarks:
            return false;
        case BtBookmarkItemBase::PrintBookmarks:
            return true;

        case BtBookmarkItemBase::DeleteEntries:
            return true;
    }

    return false;
}

/* Saves the bookmarks to the default bookmarks file. */
void CBookmarkIndex::saveBookmarks() {
    BtBookmarkLoader loader;
    loader.saveTreeFromRootItem(invisibleRootItem());
}

void CBookmarkIndex::mouseMoveEvent(QMouseEvent* event) {

    // Restart the mag timer if we have moved to another item and shift was not pressed.
    QTreeWidgetItem* itemUnderPointer = itemAt(event->pos());
    if (itemUnderPointer && (itemUnderPointer != m_previousEventItem) ) {
        if ( !(event->modifiers() & Qt::ShiftModifier)) {
            m_magTimer.start(); // see the ctor for the timer properties
        }
    }
    m_previousEventItem = itemUnderPointer;

    // Clear the extra item text unless we are on top of it
    if ( (itemUnderPointer != m_extraItem) && !m_extraItem->text(0).isNull()) {
        m_extraItem->setText(0, QString::null);
    }

    QTreeWidget::mouseMoveEvent(event);
}

void CBookmarkIndex::magTimeout() {
    QTreeWidgetItem* itemUnderPointer = 0;
    if (underMouse()) {
        itemUnderPointer = itemAt(mapFromGlobal(QCursor::pos()));
    }
    // if the mouse pointer have been over the same item since the timer was started
    if (itemUnderPointer && (m_previousEventItem == itemUnderPointer)) {
        BtBookmarkItem* bitem = dynamic_cast<BtBookmarkItem*>(itemUnderPointer);
        if (bitem) {
            // Update the mag
            if (bitem->module()) {
                (BibleTime::instance()->infoDisplay())->setInfo(
                    InfoDisplay::CInfoDisplay::CrossReference,
                    bitem->module()->name() + ":" + bitem->key()
                );
            }
            else {
                (BibleTime::instance()->infoDisplay())->setInfo(InfoDisplay::CInfoDisplay::Text, tr("The work to which the bookmark points to is not installed."));
            }

        }
    }
}

/*
Creates a list of new items based on the current selection.
If there are only bookmarks in the selection they are all included.
If there is one folder it's included as a deep copy.
Sets bookmarksOnly=false if it finds a folder.
Sets targetIncluded=true if the target is in the list.
Sets moreThanOneFolder=true if selection includes one folder and something more.
If moreThanOneFolder or targetIncluded is detected the creation of list is stopped
and the list is incomplete.
*/
QList<QTreeWidgetItem*> CBookmarkIndex::addItemsToDropTree(
    QTreeWidgetItem* target, bool& bookmarksOnly, bool& targetIncluded, bool& moreThanOneFolder) {
    QList<QTreeWidgetItem*> selectedList = selectedItems();
    QList<QTreeWidgetItem*> newList;

    foreach(QTreeWidgetItem* item, selectedList) {
        if ( BtBookmarkFolder* folder = dynamic_cast<BtBookmarkFolder*>(item)) {
            bookmarksOnly = false;
            if (selectedList.count() > 1) { // only one item allowed if a folder is selected
                moreThanOneFolder = true;
                break;
            }
            if (folder->hasDescendant(target)) { // dropping to self or descendand not allowed
                targetIncluded = true;
                break;
            }
        }
        else {
            newList.append(new BtBookmarkItem( *(dynamic_cast<BtBookmarkItem*>(item)) ));
        }
    }
    if (!bookmarksOnly && selectedList.count() == 1) {
        BtBookmarkFolder* folder = dynamic_cast<BtBookmarkFolder*>(selectedList.value(0));
        BtBookmarkFolder* copy = folder->deepCopy();
        newList.append(copy);
    }
    if (!bookmarksOnly && selectedList.count() > 1) {
        // wrong amount of items
        moreThanOneFolder = true;
    }
    return newList;
}

/// Bookmark saving code.  To avoid many saves during a short period of time,
/// bookmark modification is first noted.  Then, after a wait (1.5s), if no more
/// modifications are made, the bookmarks are saved.  The timer is reset when a
/// new modification is made.  The timer bookmarkSaveTimer is set to be oneshot.
void CBookmarkIndex::needToSaveBookmarks() {
    m_bookmarksModified = true;
    bookmarkSaveTimer.start(1500); // Only save after 1.5s.
}
void CBookmarkIndex::needToSaveBookmarks(QTreeWidgetItem* treeItem) {
    // Need to test whether the item that changed is not just a display item,
    // but actually a folder or bookmark.
    BtBookmarkItemBase* bookmark = dynamic_cast<BtBookmarkItemBase*>(treeItem);
    if (bookmark) {
        m_bookmarksModified = true;
        bookmarkSaveTimer.start(1500); // Only save after 1.5s.
    }
}

/// Considers saving bookmarks only if they have been modified.  This procedure
/// should be called by the qtimer bookmarkTimer.
void CBookmarkIndex::considerSavingBookmarks() {
    if (m_bookmarksModified) {
        saveBookmarks();
        m_bookmarksModified = false;
    }
}

