/*************************************************************************** * 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 "dolphindetailsview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "dolphinview.h" #include "viewproperties.h" #include "dolphin.h" #include "kiconeffect.h" #include "dolphinsettings.h" #include "dolphinstatusbar.h" #include "dolphindetailsviewsettings.h" DolphinDetailsView::DolphinDetailsView(DolphinView* parent) : KFileDetailView(parent, 0), m_dolphinView(parent), m_resizeTimer(0), m_scrollTimer(0), m_rubber(0) { m_resizeTimer = new TQTimer(this); connect(m_resizeTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(updateColumnsWidth())); setAcceptDrops(true); setSelectionMode(KFile::Extended); setHScrollBarMode(TQScrollView::AlwaysOff); setColumnAlignment(SizeColumn, TQt::AlignRight); for (int i = DateColumn; i <= GroupColumn; ++i) { setColumnAlignment(i, TQt::AlignHCenter); } Dolphin& dolphin = Dolphin::mainWin(); connect(this, TQT_SIGNAL(onItem(TQListViewItem*)), this, TQT_SLOT(slotOnItem(TQListViewItem*))); connect(this, TQT_SIGNAL(onViewport()), this, TQT_SLOT(slotOnViewport())); connect(this, TQT_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)), this, TQT_SLOT(slotContextMenuRequested(TQListViewItem*, const TQPoint&, int))); connect(this, TQT_SIGNAL(selectionChanged()), &dolphin, TQT_SLOT(slotSelectionChanged())); connect(&dolphin, TQT_SIGNAL(activeViewChanged()), this, TQT_SLOT(slotActivationUpdate())); connect(this, TQT_SIGNAL(itemRenamed(TQListViewItem*, const TQString&, int)), this, TQT_SLOT(slotItemRenamed(TQListViewItem*, const TQString&, int))); connect(this, TQT_SIGNAL(dropped(TQDropEvent*, const KURL::List&, const KURL&)), parent, TQT_SLOT(slotURLListDropped(TQDropEvent*, const KURL::List&, const KURL&))); TQClipboard* clipboard = TQApplication::clipboard(); connect(clipboard, TQT_SIGNAL(dataChanged()), this, TQT_SLOT(slotUpdateDisabledItems())); TQHeader* viewHeader = header(); viewHeader->setResizeEnabled(false); viewHeader->setMovingEnabled(false); connect(viewHeader, TQT_SIGNAL(clicked(int)), this, TQT_SLOT(slotHeaderClicked(int))); setMouseTracking(true); setDefaultRenameAction(TQListView::Accept); refreshSettings(); } DolphinDetailsView::~DolphinDetailsView() { delete m_rubber; m_rubber = 0; } void DolphinDetailsView::beginItemUpdates() { } void DolphinDetailsView::endItemUpdates() { updateDisabledItems(); // Restore the current item. Use the information stored in the history if // available. Otherwise use the first item as current item. const KFileListViewItem* item = static_cast(firstChild()); if (item != 0) { setCurrentItem(item->fileInfo()); } int index = 0; const TQValueList history = m_dolphinView->urlHistory(index); if (!history.isEmpty()) { KFileView* fileView = static_cast(this); fileView->setCurrentItem(history[index].currentFileName()); setContentsPos(history[index].contentsX(), history[index].contentsY()); } updateColumnsWidth(); } void DolphinDetailsView::insertItem(KFileItem* fileItem) { KFileView::insertItem(fileItem); DolphinListViewItem* item = new DolphinListViewItem(static_cast(this), fileItem); TQDir::SortSpec spec = KFileView::sorting(); if (spec & TQDir::Time) { item->setKey(sortingKey(fileItem->time(KIO::UDS_MODIFICATION_TIME), fileItem->isDir(), spec)); } else if (spec & TQDir::Size) { item->setKey(sortingKey(fileItem->size(), fileItem->isDir(), spec)); } else { item->setKey(sortingKey(fileItem->text(), fileItem->isDir(), spec)); } fileItem->setExtraData(this, item); } bool DolphinDetailsView::isOnFilename(const TQListViewItem* item, const TQPoint& pos) const { const TQPoint absPos(mapToGlobal(TQPoint(0, 0))); return (pos.x() - absPos.x()) <= filenameWidth(item); } void DolphinDetailsView::refreshSettings() { const DolphinDetailsViewSettings* settings = DolphinSettings::instance().detailsView(); assert(settings != 0); for (int i = DolphinDetailsView::GroupColumn; i >= DolphinDetailsView::NameColumn; --i) { if (!settings->isColumnEnabled(i)) { removeColumn(i); } } TQFont adjustedFont(font()); adjustedFont.setFamily(settings->fontFamily()); adjustedFont.setPointSize(settings->fontSize()); setFont(adjustedFont); updateView(true); } void DolphinDetailsView::zoomIn() { if (isZoomInPossible()) { DolphinDetailsViewSettings* settings = DolphinSettings::instance().detailsView(); switch (settings->iconSize()) { case KIcon::SizeSmall: settings->setIconSize(KIcon::SizeMedium); break; case KIcon::SizeMedium: settings->setIconSize(KIcon::SizeLarge); break; default: assert(false); break; } ItemEffectsManager::zoomIn(); } } void DolphinDetailsView::zoomOut() { if (isZoomOutPossible()) { DolphinDetailsViewSettings* settings = DolphinSettings::instance().detailsView(); switch (settings->iconSize()) { case KIcon::SizeLarge: settings->setIconSize(KIcon::SizeMedium); break; case KIcon::SizeMedium: settings->setIconSize(KIcon::SizeSmall); break; default: assert(false); break; } ItemEffectsManager::zoomOut(); } } bool DolphinDetailsView::isZoomInPossible() const { DolphinDetailsViewSettings* settings = DolphinSettings::instance().detailsView(); return settings->iconSize() < KIcon::SizeLarge; } bool DolphinDetailsView::isZoomOutPossible() const { DolphinDetailsViewSettings* settings = DolphinSettings::instance().detailsView(); return settings->iconSize() > KIcon::SizeSmall; } void DolphinDetailsView::resizeContents(int width, int height) { KFileDetailView::resizeContents(width, height); // When loading several 1000 items a punch of resize events // drops in. As updating the column width is a quite expensive // operation, this operation will be postponed until there is // no resize event for at least 50 milliseconds. m_resizeTimer->stop(); m_resizeTimer->start(50, true); } void DolphinDetailsView::slotOnItem(TQListViewItem* item) { if (isOnFilename(item, TQCursor::pos())) { activateItem(item); KFileItem* fileItem = static_cast(item)->fileInfo(); m_dolphinView->requestItemInfo(fileItem->url()); } else { resetActivatedItem(); } } void DolphinDetailsView::slotOnViewport() { resetActivatedItem(); m_dolphinView->requestItemInfo(KURL()); } void DolphinDetailsView::setContextPixmap(void* context, const TQPixmap& pixmap) { reinterpret_cast(context)->setPixmap(0, pixmap); } const TQPixmap* DolphinDetailsView::contextPixmap(void* context) { return reinterpret_cast(context)->pixmap(0); } void* DolphinDetailsView::firstContext() { return reinterpret_cast(firstChild()); } void* DolphinDetailsView::nextContext(void* context) { KFileListViewItem* listViewItem = reinterpret_cast(context); return reinterpret_cast(listViewItem->nextSibling()); } KFileItem* DolphinDetailsView::contextFileInfo(void* context) { return reinterpret_cast(context)->fileInfo(); } void DolphinDetailsView::contentsDragMoveEvent(TQDragMoveEvent* event) { KFileDetailView::contentsDragMoveEvent(event); // If a dragging is done above a directory, show the icon as 'active' for // a visual feedback KFileListViewItem* item = static_cast(itemAt(event->pos())); bool showActive = false; if (item != 0) { const KFileItem* fileInfo = item->fileInfo(); showActive = (fileInfo != 0) && fileInfo->isDir(); } if (showActive) { slotOnItem(item); } else { slotOnViewport(); } } void DolphinDetailsView::resizeEvent(TQResizeEvent* event) { KFileDetailView::resizeEvent(event); // When loading several 1000 items a punch of resize events // drops in. As updating the column width is a quite expensive // operation, this operation will be postponed until there is // no resize event for at least 50 milliseconds. m_resizeTimer->stop(); m_resizeTimer->start(50, true); } bool DolphinDetailsView::acceptDrag(TQDropEvent* event) const { bool accept = KURLDrag::canDecode(event) && (event->action() == TQDropEvent::Copy || event->action() == TQDropEvent::Move || event->action() == TQDropEvent::Link); if (accept) { if (static_cast(event->source()) == this) { KFileListViewItem* item = static_cast(itemAt(event->pos())); accept = (item != 0); if (accept) { KFileItem* fileItem = item->fileInfo(); accept = fileItem->isDir(); } } } return accept; } void DolphinDetailsView::contentsDropEvent(TQDropEvent* event) { // KFileDetailView::contentsDropEvent does not care whether the mouse // cursor is above a filename or not, the destination URL is always // the URL of the item. This is fixed here in a way that the destination // URL is only the URL of the item if the cursor is above the filename. const TQPoint pos(TQCursor::pos()); const TQPoint viewportPos(viewport()->mapToGlobal(TQPoint(0, 0))); TQListViewItem* item = itemAt(TQPoint(pos.x() - viewportPos.x(), pos.y() - viewportPos.y())); if ((item == 0) || ((item != 0) && isOnFilename(item, pos))) { // dropping is done on the viewport or directly above a filename KFileDetailView::contentsDropEvent(event); return; } // Dropping is done above an item, but the mouse cursor is not above the file name. // In this case the signals of the base implementation will be blocked and send // in a corrected manner afterwards. assert(item != 0); const bool block = signalsBlocked(); blockSignals(true); KFileDetailView::contentsDropEvent(event); blockSignals(block); if (!acceptDrag(event)) { return; } emit dropped(event, 0); KURL::List urls; if (KURLDrag::decode(event, urls) && !urls.isEmpty()) { emit dropped(event, urls, KURL()); sig->dropURLs(0, event, urls); } } void DolphinDetailsView::contentsMousePressEvent(TQMouseEvent* event) { if (m_rubber != 0) { drawRubber(); delete m_rubber; m_rubber = 0; } // Swallow the base implementation of the mouse press event // if the mouse cursor is not above the filename. This prevents // that the item gets selected and simulates an equal usability // like in the icon view. const TQPoint pos(TQCursor::pos()); const TQPoint viewportPos(viewport()->mapToGlobal(TQPoint(0, 0))); TQListViewItem* item = itemAt(TQPoint(pos.x() - viewportPos.x(), pos.y() - viewportPos.y())); if ((item != 0) && isOnFilename(item, pos)) { KFileDetailView::contentsMousePressEvent(event); } else if (event->button() == Qt::LeftButton) { const ButtonState keyboardState = TDEApplication::keyboardMouseState(); const bool isSelectionActive = (keyboardState & ShiftButton) || (keyboardState & ControlButton); if (!isSelectionActive) { clearSelection(); } assert(m_rubber == 0); m_rubber = new TQRect(event->x(), event->y(), 0, 0); } resetActivatedItem(); emit signalRequestActivation(); m_dolphinView->statusBar()->clear(); } void DolphinDetailsView::contentsMouseMoveEvent(TQMouseEvent* event) { if (m_rubber != 0) { slotAutoScroll(); return; } KFileDetailView::contentsMouseMoveEvent(event); const TQPoint& pos = event->globalPos(); const TQPoint viewportPos = viewport()->mapToGlobal(TQPoint(0, 0)); TQListViewItem* item = itemAt(TQPoint(pos.x() - viewportPos.x(), pos.y() - viewportPos.y())); if ((item != 0) && isOnFilename(item, pos)) { activateItem(item); } else { resetActivatedItem(); } } void DolphinDetailsView::contentsMouseReleaseEvent(TQMouseEvent* event) { if (m_rubber != 0) { drawRubber(); delete m_rubber; m_rubber = 0; } if (m_scrollTimer != 0) { disconnect(m_scrollTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotAutoScroll())); m_scrollTimer->stop(); delete m_scrollTimer; m_scrollTimer = 0; } KFileDetailView::contentsMouseReleaseEvent(event); } void DolphinDetailsView::paintEmptyArea(TQPainter* painter, const TQRect& rect) { if (m_dolphinView->isActive()) { KFileDetailView::paintEmptyArea(painter, rect); } else { const TQBrush brush(colorGroup().background()); painter->fillRect(rect, brush); } } void DolphinDetailsView::drawRubber() { // Parts of the following code have been taken // from the class KonqBaseListViewWidget located in // konqueror/listview/konq_listviewwidget.h of Konqueror. // (Copyright (C) 1998, 1999 Torben Weis // 2001, 2002, 2004 Michael Brade ) if (m_rubber == 0) { return; } TQPainter p; p.begin(viewport()); p.setRasterOp(NotROP); p.setPen(TQPen(color0, 1)); p.setBrush(NoBrush); TQPoint point(m_rubber->x(), m_rubber->y()); point = contentsToViewport(point); style().tqdrawPrimitive(TQStyle::PE_FocusRect, &p, TQRect(point.x(), point.y(), m_rubber->width(), m_rubber->height()), colorGroup(), TQStyle::Style_Default, colorGroup().base()); p.end(); } void DolphinDetailsView::viewportPaintEvent(TQPaintEvent* paintEvent) { drawRubber(); KFileDetailView::viewportPaintEvent(paintEvent); drawRubber(); } void DolphinDetailsView::leaveEvent(TQEvent* event) { KFileDetailView::leaveEvent(event); slotOnViewport(); } void DolphinDetailsView::slotActivationUpdate() { update(); // TODO: there must be a simpler way to say // "update all children" const TQObjectList list = childrenListObject(); if (list.isEmpty()) { return; } TQObjectListIterator it(list); TQObject* object = 0; while ((object = it.current()) != 0) { if (object->inherits(TQWIDGET_OBJECT_NAME_STRING)) { TQWidget* widget = TQT_TQWIDGET(object); widget->update(); } ++it; } } void DolphinDetailsView::slotContextMenuRequested(TQListViewItem* item, const TQPoint& pos, int /* col */) { KFileItem* fileInfo = 0; if ((item != 0) && isOnFilename(item, pos)) { fileInfo = static_cast(item)->fileInfo(); } m_dolphinView->openContextMenu(fileInfo, pos); } void DolphinDetailsView::slotUpdateDisabledItems() { updateDisabledItems(); } void DolphinDetailsView::slotAutoScroll() { // Parts of the following code have been taken // from the class KonqBaseListViewWidget located in // konqueror/listview/konq_listviewwidget.h of Konqueror. // (Copyright (C) 1998, 1999 Torben Weis // 2001, 2002, 2004 Michael Brade ) const TQPoint pos(viewport()->mapFromGlobal(TQCursor::pos())); const TQPoint vc(viewportToContents(pos)); if (vc == m_rubber->bottomRight()) { return; } drawRubber(); m_rubber->setBottomRight(vc); TQListViewItem* item = itemAt(TQPoint(0,0)); const bool block = signalsBlocked(); blockSignals(true); const TQRect rubber(m_rubber->normalize()); const int bottom = contentsY() + visibleHeight() - 1; // select all items which intersect with the rubber, deselect all others bool bottomReached = false; while ((item != 0) && !bottomReached) { TQRect rect(itemRect(item)); rect.setWidth(filenameWidth(item)); rect = TQRect(viewportToContents(rect.topLeft()), viewportToContents(rect.bottomRight())); if (rect.isValid() && (rect.top() <= bottom)) { const KFileItem* fileItem = static_cast(item)->fileInfo(); setSelected(fileItem, rect.intersects(rubber)); item = item->itemBelow(); } else { bottomReached = true; } } blockSignals(block); emit selectionChanged(); drawRubber(); // scroll the viewport if the top or bottom margin is reached const int scrollMargin = 40; ensureVisible(vc.x(), vc.y(), scrollMargin, scrollMargin); const bool scroll = !TQRect(scrollMargin, scrollMargin, viewport()->width() - 2 * scrollMargin, viewport()->height() - 2 * scrollMargin).contains(pos); if (scroll) { if (m_scrollTimer == 0) { m_scrollTimer = new TQTimer( this ); connect(m_scrollTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotAutoScroll())); m_scrollTimer->start(100, false); } } else if (m_scrollTimer != 0) { disconnect(m_scrollTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotAutoScroll())); m_scrollTimer->stop(); delete m_scrollTimer; m_scrollTimer = 0; } } void DolphinDetailsView::updateColumnsWidth() { const int columnCount = columns(); int requiredWidth = 0; for (int i = 1; i < columnCount; ++i) { // When a directory contains no items, a minimum width for // the column must be available, so that the header is readable. // TODO: use header data instead of the hardcoded 64 value... int columnWidth = 64; TQFontMetrics fontMetrics(font()); for (TQListViewItem* item = firstChild(); item != 0; item = item->nextSibling()) { const int width = item->width(fontMetrics, this, i); if (width > columnWidth) { columnWidth = width; } } columnWidth += 16; // add custom margin setColumnWidth(i, columnWidth); requiredWidth += columnWidth; } // resize the first column in a way that the // whole available width is used int firstColumnWidth = visibleWidth() - requiredWidth; if (firstColumnWidth < 128) { firstColumnWidth = 128; } setColumnWidth(0, firstColumnWidth); } void DolphinDetailsView::slotItemRenamed(TQListViewItem* item, const TQString& name, int /* column */) { KFileItem* fileInfo = static_cast(item)->fileInfo(); m_dolphinView->rename(KURL(fileInfo->url()), name); } void DolphinDetailsView::slotHeaderClicked(int /* section */) { // The sorting has already been changed in TQListView if this slot is // invoked, but Dolphin was not informed about this (no signal is available // which indicates a change of the sorting). This is bypassed by changing // the sorting and sort order to a temporary other value and readjust it again. const int column = sortColumn(); if (column <= DateColumn) { DolphinView::Sorting sorting = DolphinView::SortByName; switch (column) { case SizeColumn: sorting = DolphinView::SortBySize; break; case DateColumn: sorting = DolphinView::SortByDate; break; case NameColumn: default: break; } const TQt::SortOrder currSortOrder = sortOrder(); // temporary adjust the sorting and sort order to different values... const DolphinView::Sorting tempSorting = (sorting == DolphinView::SortByName) ? DolphinView::SortBySize : DolphinView::SortByName; m_dolphinView->setSorting(tempSorting); const TQt::SortOrder tempSortOrder = (currSortOrder == TQt::Ascending) ? TQt::Descending : TQt::Ascending; m_dolphinView->setSortOrder(tempSortOrder); // ... so that setting them again results in storing the new setting. m_dolphinView->setSorting(sorting); m_dolphinView->setSortOrder(currSortOrder); } } DolphinDetailsView::DolphinListViewItem::DolphinListViewItem(TQListView* parent, KFileItem* fileItem) : KFileListViewItem(parent, fileItem) { const int iconSize = DolphinSettings::instance().detailsView()->iconSize(); KFileItem* info = fileInfo(); setPixmap(DolphinDetailsView::NameColumn, info->pixmap(iconSize)); // The base class KFileListViewItem represents the column 'Size' only as byte values. // Adjust those values in a way that a mapping to GBytes, MBytes, KBytes and Bytes // is done. As the file size for directories is useless (only the size of the directory i-node // is given), it is removed completely. if (fileItem->isDir()) { setText(SizeColumn, " - "); } else { TQString sizeText(KIO::convertSize(fileItem->size())); sizeText.append(" "); setText(SizeColumn, sizeText); } // Dolphin allows to remove specific columns, but the base class KFileListViewItem // is not aware about this (or at least the class KFileDetailView does not react on // TQListView::remove()). Therefore the columns are rearranged here. const DolphinDetailsViewSettings* settings = DolphinSettings::instance().detailsView(); assert(settings != 0); int column_idx = DateColumn; // the columns for 'name' and 'size' cannot get removed for (int i = DolphinDetailsView::DateColumn; i <= DolphinDetailsView::GroupColumn; ++i) { if (column_idx < i) { setText(column_idx, text(i)); } if (settings->isColumnEnabled(i)) { ++column_idx; } } } DolphinDetailsView::DolphinListViewItem::~DolphinListViewItem() { } void DolphinDetailsView::DolphinListViewItem::paintCell(TQPainter* painter, const TQColorGroup& colorGroup, int column, int cellWidth, int alignment) { const TQListView* view = listView(); const bool isActive = TQT_BASE_OBJECT(view->parent()) == TQT_BASE_OBJECT(Dolphin::mainWin().activeView()); if (isSelected()) { // Per default the selection is drawn above the whole width of the item. As a consistent // behavior with the icon view is wanted, only the the column containing the file name // should be shown as selected. TQColorGroup defaultColorGroup(colorGroup); const TQColor highlightColor(isActive ? backgroundColor(column) : view->colorGroup().background()); defaultColorGroup.setColor(TQColorGroup::Highlight , highlightColor); defaultColorGroup.setColor(TQColorGroup::HighlightedText, colorGroup.color(TQColorGroup::Text)); KFileListViewItem::paintCell(painter, defaultColorGroup, column, cellWidth, alignment); if (column == 0) { // draw the selection only on the first column TQListView* parent = listView(); const int itemWidth = width(parent->fontMetrics(), parent, 0); if (isActive) { KFileListViewItem::paintCell(painter, colorGroup, column, itemWidth, alignment); } else { TQListViewItem::paintCell(painter, colorGroup, column, itemWidth, alignment); } } } else { if (isActive) { KFileListViewItem::paintCell(painter, colorGroup, column, cellWidth, alignment); } else { TQListViewItem::paintCell(painter, colorGroup, column, cellWidth, alignment); } } if (column < listView()->columns() - 1) { // draw a separator between columns painter->setPen(KGlobalSettings::buttonBackground()); painter->drawLine(cellWidth - 1, 0, cellWidth - 1, height() - 1); } } void DolphinDetailsView::DolphinListViewItem::paintFocus(TQPainter* painter, const TQColorGroup& colorGroup, const TQRect& rect) { // draw the focus consistently with the selection (see implementation notes // in DolphinListViewItem::paintCell) TQListView* parent = listView(); int visibleWidth = width(parent->fontMetrics(), parent, 0); const int colWidth = parent->columnWidth(0); if (visibleWidth > colWidth) { visibleWidth = colWidth; } TQRect focusRect(rect); focusRect.setWidth(visibleWidth); KFileListViewItem::paintFocus(painter, colorGroup, focusRect); } int DolphinDetailsView::filenameWidth(const TQListViewItem* item) const { assert(item != 0); int visibleWidth = item->width(fontMetrics(), this, 0); const int colWidth = columnWidth(0); if (visibleWidth > colWidth) { visibleWidth = colWidth; } return visibleWidth; } #include "dolphindetailsview.moc"