/***************************************************************************
 *   Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at>                  *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/

#include "infosidebarpage.h"
#include <assert.h>

#include <tqlayout.h>
#include <tqpixmap.h>
#include <tqlabel.h>
#include <tqtimer.h>
#include <tqpushbutton.h>
#include <tqvbox.h>
#include <tqvgroupbox.h>
#include <tqpopupmenu.h>
#include <tqpainter.h>
#include <tqfontmetrics.h>
#include <tqgrid.h>
#include <tqhgroupbox.h>

#include <kbookmarkmanager.h>
#include <tdelocale.h>
#include <kstandarddirs.h>
#include <tdeio/previewjob.h>
#include <tdefileitem.h>
#include <kdialog.h>
#include <tdeglobalsettings.h>
#include <tdefilemetainfo.h>

#include "dolphin.h"
#include "pixmapviewer.h"
#include "dolphinsettings.h"

InfoSidebarPage::InfoSidebarPage(TQWidget* parent) :
    SidebarPage(parent),
    m_multipleSelection(false),
    m_pendingPreview(false),
    m_timer(0),
    m_preview(0),
    m_name(0),
    m_currInfoLineIdx(0),
    m_infoGrid(0),
    m_actionBox(0)
{
    const int spacing = KDialog::spacingHint();

    m_timer = new TQTimer(this);
    connect(m_timer, TQT_SIGNAL(timeout()),
            this, TQT_SLOT(slotTimeout()));

    TQVBoxLayout* layout = new TQVBoxLayout(this);
    layout->setSpacing(spacing);

    // preview
    m_preview = new PixmapViewer(this);
    m_preview->setMinimumWidth(TDEIcon::SizeEnormous);
    m_preview->setFixedHeight(TDEIcon::SizeEnormous);

    // name
    m_name = new TQLabel(this);
    m_name->setTextFormat(TQt::RichText);
    m_name->setAlignment(m_name->alignment() | TQt::AlignHCenter);
    TQFontMetrics fontMetrics(m_name->font());
    m_name->setMinimumHeight(fontMetrics.height() * 3);
    m_name->setSizePolicy(TQSizePolicy::MinimumExpanding, TQSizePolicy::Maximum);

    TQWidget* sep1 = new TQHGroupBox(this);  // TODO: check whether default widget exist for this?
    sep1->setFixedHeight(1);

    // general information
    m_infoGrid = new TQGrid(2, this);
    m_infoGrid->setSizePolicy(TQSizePolicy::Preferred, TQSizePolicy::Fixed);

    TQWidget* sep2 = new TQHGroupBox(this);  // TODO: check whether default widget exist for this?
    sep2->setFixedHeight(1);

    // actions
    m_actionBox = new TQVBox(this);
    m_actionBox->setSizePolicy(TQSizePolicy::Preferred, TQSizePolicy::Fixed);

    // Add a dummy widget with no restriction regarding a vertical resizing.
    // This assures that information is always top aligned.
    TQWidget* dummy = new TQWidget(this);

    layout->addItem(new TQSpacerItem(spacing, spacing, TQSizePolicy::Preferred, TQSizePolicy::Fixed));
    layout->addWidget(m_preview);
    layout->addWidget(m_name);
    layout->addWidget(sep1);
    layout->addWidget(m_infoGrid);
    layout->addWidget(sep2);
    layout->addWidget(m_actionBox);
    layout->addWidget(dummy);

    connect(&Dolphin::mainWin(), TQT_SIGNAL(selectionChanged()),
            this, TQT_SLOT(showItemInfo()));

    connectToActiveView();
}

InfoSidebarPage::~InfoSidebarPage()
{
}

void InfoSidebarPage::activeViewChanged()
{
    connectToActiveView();
}

void InfoSidebarPage::requestDelayedItemInfo(const KURL& url)
{
    cancelRequest();

    if (!url.isEmpty() && !m_multipleSelection) {
        m_urlCandidate = url;
        m_timer->start(300, true);
    }
}

void InfoSidebarPage::requestItemInfo(const KURL& url)
{
    cancelRequest();

    if (!url.isEmpty() && !m_multipleSelection) {
        m_shownURL = url;
        showItemInfo();
    }
}

void InfoSidebarPage::showItemInfo()
{
    cancelRequest();

    m_multipleSelection = false;

    // show the preview...
    DolphinView* view = Dolphin::mainWin().activeView();
    const KFileItemList* selectedItems = view->selectedItems();
    if ((selectedItems != 0) && selectedItems->count() > 1) {
        m_multipleSelection = true;
    }

    if (m_multipleSelection) {
        TDEIconLoader iconLoader;
        TQPixmap icon = iconLoader.loadIcon("application-x-executable",
                                           TDEIcon::NoGroup,
                                           TDEIcon::SizeEnormous);
        m_preview->setPixmap(icon);
        m_name->setText(i18n("%n items selected", "%n items selected", selectedItems->count()));
    }
    else if (!applyBookmark()) {
        // try to get a preview pixmap from the item...
        KURL::List list;
        list.append(m_shownURL);

        m_pendingPreview = true;
        m_preview->setPixmap(TQPixmap());

        TDEIO::PreviewJob* job = TDEIO::filePreview(list,
                                                m_preview->width(),
                                                TDEIcon::SizeEnormous);
        connect(job, TQT_SIGNAL(gotPreview(const KFileItem*, const TQPixmap&)),
                this, TQT_SLOT(gotPreview(const KFileItem*, const TQPixmap&)));
        connect(job, TQT_SIGNAL(failed(const KFileItem*)),
                this, TQT_SLOT(slotPreviewFailed(const KFileItem*)));

        TQString text("<b>");
        text.append(m_shownURL.fileName());
        text.append("</b>");
        m_name->setText(text);
    }

    createMetaInfo();
    insertActions();
}

void InfoSidebarPage::slotTimeout()
{
    m_shownURL = m_urlCandidate;
    showItemInfo();
}

void InfoSidebarPage::slotPreviewFailed(const KFileItem* item)
{
    m_pendingPreview = false;
    if (!applyBookmark()) {
        m_preview->setPixmap(item->pixmap(TDEIcon::SizeEnormous));
    }
}

void InfoSidebarPage::gotPreview(const KFileItem* /* item */,
                                 const TQPixmap& pixmap)
{
    if (m_pendingPreview) {
        m_preview->setPixmap(pixmap);
        m_pendingPreview = false;
    }
}

void InfoSidebarPage::startService(int index)
{
    DolphinView* view = Dolphin::mainWin().activeView();
    if (view->hasSelection()) {
        KURL::List selectedURLs = view->selectedURLs();
        KDEDesktopMimeType::executeService(selectedURLs, m_actionsVector[index]);
    }
    else {
        KDEDesktopMimeType::executeService(m_shownURL, m_actionsVector[index]);
    }
}

void InfoSidebarPage::connectToActiveView()
{
    cancelRequest();

    DolphinView* view = Dolphin::mainWin().activeView();
    connect(view, TQT_SIGNAL(signalRequestItemInfo(const KURL&)),
            this, TQT_SLOT(requestDelayedItemInfo(const KURL&)));
    connect(view, TQT_SIGNAL(signalURLChanged(const KURL&)),
            this, TQT_SLOT(requestItemInfo(const KURL&)));

    m_shownURL = view->url();
    showItemInfo();
}

bool InfoSidebarPage::applyBookmark()
{
    KBookmarkGroup root = DolphinSettings::instance().bookmarkManager()->root();
    KBookmark bookmark = root.first();
    while (!bookmark.isNull()) {
        if (m_shownURL.equals(bookmark.url(), true)) {
            TQString text("<b>");
            text.append(bookmark.text());
            text.append("</b>");
            m_name->setText(text);

            TDEIconLoader iconLoader;
            TQPixmap icon = iconLoader.loadIcon(bookmark.icon(),
                                               TDEIcon::NoGroup,
                                               TDEIcon::SizeEnormous);
            m_preview->setPixmap(icon);
            return true;
        }
        bookmark = root.next(bookmark);
    }

    return false;
}

void InfoSidebarPage::cancelRequest()
{
    m_timer->stop();
    m_pendingPreview = false;
}

void InfoSidebarPage::createMetaInfo()
{
    // To prevent a flickering it's important to reuse available
    // labels instead of deleting them and recreate them afterwards.
    // The methods beginInfoLines(), addInfoLine() and endInfoLines()
    // take care of this.
    beginInfoLines();
    DolphinView* view = Dolphin::mainWin().activeView();
    if (!view->hasSelection()) {
        KFileItem fileItem(S_IFDIR, KFileItem::Unknown, m_shownURL);
        fileItem.refresh();

        if (fileItem.isDir()) {
            addInfoLine(i18n("Type:"), i18n("Directory"));
        }
        else {
            addInfoLine(i18n("Type:"), fileItem.mimeComment());

            TQString sizeText(TDEIO::convertSize(fileItem.size()));
            addInfoLine(i18n("Size:"), sizeText);
            addInfoLine(i18n("Modified:"), fileItem.timeString());

            const KFileMetaInfo& metaInfo = fileItem.metaInfo();
            if (metaInfo.isValid()) {
                TQStringList keys = metaInfo.supportedKeys();
                for (TQStringList::Iterator it = keys.begin(); it != keys.end(); ++it) {
                    if (showMetaInfo(*it)) {
                        KFileMetaInfoItem metaInfoItem = metaInfo.item(*it);
                        addInfoLine(*it, metaInfoItem.string());
                    }
                }
            }
        }
    }
    endInfoLines();
}

void InfoSidebarPage::beginInfoLines()
{
    m_currInfoLineIdx = 0;
}

void InfoSidebarPage::endInfoLines()
{
    if (m_currInfoLineIdx <= 0) {
        return;
    }

    // remove labels which have not been used
    if (m_currInfoLineIdx < static_cast<int>(m_infoWidgets.count())) {
        TQPtrListIterator<TQLabel> deleteIter(m_infoWidgets);
        deleteIter += m_currInfoLineIdx;

        TQWidget* widget = 0;
        int removeCount = 0;
        while ((widget = deleteIter.current()) != 0) {
            widget->close();
            widget->deleteLater();
            ++deleteIter;
            ++removeCount;
        }
        for (int i = 0; i < removeCount; ++i) {
            m_infoWidgets.removeLast();
        }
    }
}

bool InfoSidebarPage::showMetaInfo(const TQString& key) const
{
    // sorted list of keys, where it's data should be shown
    static const char* keys[] = {
        "Album",
        "Artist",
        "Author",
        "Bitrate",
        "Date",
        "Dimensions",
        "Genre",
        "Length",
        "Lines",
        "Pages",
        "Title",
        "Words"
    };

    // do a binary search for the key...
    int top = 0;
    int bottom = sizeof(keys) / sizeof(char*) - 1;
    while (top < bottom) {
        const int middle = (top + bottom) / 2;
        const int result = key.compare(keys[middle]);
        if (result < 0) {
            bottom = middle - 1;
        }
        else if (result > 0) {
            top = middle + 1;
        }
        else {
            return true;
        }
    }

    return false;
}

void InfoSidebarPage::addInfoLine(const TQString& labelText, const TQString& infoText)
{
    TQString labelStr("<b>");
    labelStr.append(labelText);
    labelStr.append("</b>&nbsp;");

    const int count = m_infoWidgets.count();
    if (m_currInfoLineIdx < count - 1) {
        // reuse available labels
        m_infoWidgets.at(m_currInfoLineIdx++)->setText(labelStr);
        m_infoWidgets.at(m_currInfoLineIdx++)->setText(infoText);
    }
    else {
        // no labels are available anymore, hence create 2 new ones
        TQLabel* label = new TQLabel(labelStr, m_infoGrid);
        label->setTextFormat(TQt::RichText);
        label->setAlignment(TQt::AlignRight |
                            TQt::AlignTop);
        label->show();
        m_infoWidgets.append(label);

        TQLabel* info = new TQLabel(infoText, m_infoGrid);
        info->setTextFormat(TQt::RichText);
        info->setAlignment(TQt::AlignTop | TQt::WordBreak);
        info->show();
        m_infoWidgets.append(info);

        m_currInfoLineIdx += 2;
    }
}

void InfoSidebarPage::insertActions()
{
    // delete all existing action widgets
    // TODO: just use children() from TQObject...
    TQPtrListIterator<TQWidget> deleteIter(m_actionWidgets);
    TQWidget* widget = 0;
    while ((widget = deleteIter.current()) != 0) {
        widget->close();
        widget->deleteLater();
        ++deleteIter;
    }

    m_actionWidgets.clear();
    m_actionsVector.clear();

    int actionsIndex = 0;

    // The algorithm for searching the available actions works on a list
    // of KFileItems. If no selection is given, a temporary KFileItem
    // by the given URL 'url' is created and added to the list.
    KFileItem fileItem(S_IFDIR, KFileItem::Unknown, m_shownURL);
    KFileItemList localList;
    const KFileItemList* itemList = Dolphin::mainWin().activeView()->selectedItems();
    if ((itemList == 0) || itemList->isEmpty()) {
        fileItem.refresh();
        localList.append(&fileItem);
        itemList = &localList;
    }

    // 'itemList' contains now all KFileItems, where an item information should be shown.
    // TODO: the following algorithm is quite equal to DolphinContextMenu::insertActionItems().
    // It's open yet whether they should be merged or whether they have to work slightly different.
    TQStringList dirs = TDEGlobal::dirs()->findDirs("data", "d3lphin/servicemenus/");
    for (TQStringList::ConstIterator dirIt = dirs.begin(); dirIt != dirs.end(); ++dirIt) {
        TQDir dir(*dirIt);
        TQStringList entries = dir.entryList("*.desktop", TQDir::Files);

        for (TQStringList::ConstIterator entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) {
            KSimpleConfig cfg(*dirIt + *entryIt, true);
            cfg.setDesktopGroup();
            if ((cfg.hasKey("Actions") || cfg.hasKey("X-TDE-GetActionMenu")) && cfg.hasKey("X-TDE-ServiceTypes")) {
                const TQStringList types = cfg.readListEntry("X-TDE-ServiceTypes");
                for (TQStringList::ConstIterator it = types.begin(); it != types.end(); ++it) {
                    // check whether the mime type is equal or whether the
                    // mimegroup (e. g. image/*) is supported

                    bool insert = false;
                    if ((*it) == "all/allfiles") {
                        // The service type is valid for all files, but not for directories.
                        // Check whether the selected items only consist of files...
                        KFileItemListIterator mimeIt(*itemList);
                        KFileItem* item = 0;
                        insert = true;
                        while (insert && ((item = mimeIt.current()) != 0)) {
                            insert = !item->isDir();
                            ++mimeIt;
                        }
                    }

                    if (!insert) {
                        // Check whether the MIME types of all selected files match
                        // to the mimetype of the service action. As soon as one MIME
                        // type does not match, no service menu is shown at all.
                        KFileItemListIterator mimeIt(*itemList);
                        KFileItem* item = 0;
                        insert = true;
                        while (insert && ((item = mimeIt.current()) != 0)) {
                            const TQString mimeType((*mimeIt)->mimetype());
                            const TQString mimeGroup(mimeType.left(mimeType.find('/')));

                            insert  = (*it == mimeType) ||
                                      ((*it).right(1) == "*") &&
                                      ((*it).left((*it).find('/')) == mimeGroup);
                            ++mimeIt;
                        }
                    }

                    if (insert) {
                        const TQString submenuName = cfg.readEntry( "X-TDE-Submenu" );
                        TQPopupMenu* popup = 0;
                        if (!submenuName.isEmpty()) {
                            // create a sub menu containing all actions
                            popup = new TQPopupMenu();
                            connect(popup, TQT_SIGNAL(activated(int)),
                                    this, TQT_SLOT(startService(int)));

                            TQPushButton* button = new TQPushButton(submenuName, m_actionBox);
                            button->setFlat(true);
                            button->setPopup(popup);
                            button->show();
                            m_actionWidgets.append(button);
                        }

                        TQValueList<KDEDesktopMimeType::Service> userServices =
                            KDEDesktopMimeType::userDefinedServices(*dirIt + *entryIt, true);

                        // iterate through all actions and add them to a widget
                        TQValueList<KDEDesktopMimeType::Service>::Iterator serviceIt;
                        for (serviceIt = userServices.begin(); serviceIt != userServices.end(); ++serviceIt) {
                            KDEDesktopMimeType::Service service = (*serviceIt);
                            if (popup == 0) {
                                ServiceButton* button = new ServiceButton(SmallIcon(service.m_strIcon),
                                                                          service.m_strName,
                                                                          m_actionBox,
                                                                          actionsIndex);
                                connect(button, TQT_SIGNAL(requestServiceStart(int)),
                                        this, TQT_SLOT(startService(int)));
                                m_actionWidgets.append(button);
                                button->show();
                            }
                            else {
                                popup->insertItem(SmallIcon(service.m_strIcon), service.m_strName, actionsIndex);
                            }

                            m_actionsVector.append(service);
                            ++actionsIndex;
                        }
                    }
                }
            }
        }
    }
}

ServiceButton::ServiceButton(const TQIconSet& icon,
                             const TQString& text,
                             TQWidget* parent,
                             int index) :
    TQPushButton(icon, text, parent),
    m_hover(false),
    m_index(index)
{
    setEraseColor(colorGroup().background());
    setFocusPolicy(TQ_NoFocus);
    connect(this, TQT_SIGNAL(released()),
            this, TQT_SLOT(slotReleased()));
}

ServiceButton::~ServiceButton()
{
}

void ServiceButton::drawButton(TQPainter* painter)
{
    const int buttonWidth  = width();
    const int buttonHeight = height();

    TQColor backgroundColor;
    TQColor foregroundColor;
    if (m_hover) {
        backgroundColor = TDEGlobalSettings::highlightColor();
        foregroundColor = TDEGlobalSettings::highlightedTextColor();
    }
    else {
        backgroundColor = colorGroup().background();
        foregroundColor = TDEGlobalSettings::buttonTextColor();
    }

    // draw button background
    painter->setPen(NoPen);
    painter->setBrush(backgroundColor);
    painter->drawRect(0, 0, buttonWidth, buttonHeight);

    const int spacing = KDialog::spacingHint();

    // draw icon
    int x = spacing;
    const int y = (buttonHeight - TDEIcon::SizeSmall) / 2;
    const TQIconSet* set = iconSet();
    if (set != 0) {
        painter->drawPixmap(x, y, set->pixmap(TQIconSet::Small, TQIconSet::Normal));
    }
    x += TDEIcon::SizeSmall + spacing;

    // draw text
    painter->setPen(foregroundColor);

    const int textWidth = buttonWidth - x;
    TQFontMetrics fontMetrics(font());
    const bool clipped = fontMetrics.width(text()) >= textWidth;
    //const int align = clipped ? TQt::AlignVCenter : TQt::AlignCenter;
    painter->drawText(TQRect(x, 0, textWidth, buttonHeight), TQt::AlignVCenter, text());

    if (clipped) {
        // Blend the right area of the text with the background, as the
        // text is clipped.
        // TODO #1: use alpha blending in TQt4 instead of drawing the text that often
        // TODO #2: same code as in URLNavigatorButton::drawButton() -> provide helper class?
        const int blendSteps = 16;

        TQColor blendColor(backgroundColor);
        const int redInc   = (foregroundColor.red()   - backgroundColor.red())   / blendSteps;
        const int greenInc = (foregroundColor.green() - backgroundColor.green()) / blendSteps;
        const int blueInc  = (foregroundColor.blue()  - backgroundColor.blue())  / blendSteps;
        for (int i = 0; i < blendSteps; ++i) {
            painter->setClipRect(TQRect(x + textWidth - i, 0, 1, buttonHeight));
            painter->setPen(blendColor);
            painter->drawText(TQRect(x, 0, textWidth, buttonHeight), TQt::AlignVCenter, text());

            blendColor.setRgb(blendColor.red()   + redInc,
                              blendColor.green() + greenInc,
                              blendColor.blue()  + blueInc);
        }
    }
}

void ServiceButton::enterEvent(TQEvent* event)
{
    TQPushButton::enterEvent(event);
    m_hover = true;
    update();
}

void ServiceButton::leaveEvent(TQEvent* event)
{
    TQPushButton::leaveEvent(event);
    m_hover = false;
    update();
}

void ServiceButton::slotReleased()
{
    emit requestServiceStart(m_index);
}

#include "infosidebarpage.moc"
