diff options
Diffstat (limited to 'libkonq/konq_iconviewwidget.cc')
-rw-r--r-- | libkonq/konq_iconviewwidget.cc | 1927 |
1 files changed, 1927 insertions, 0 deletions
diff --git a/libkonq/konq_iconviewwidget.cc b/libkonq/konq_iconviewwidget.cc new file mode 100644 index 000000000..99d92ebde --- /dev/null +++ b/libkonq/konq_iconviewwidget.cc @@ -0,0 +1,1927 @@ +/* This file is part of the KDE projects + Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> + Copyright (C) 2000 - 2005 David Faure <faure@kde.org> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "konq_iconviewwidget.h" +#include "konq_operations.h" +#include "konq_undo.h" +#include "konq_sound.h" +#include "konq_filetip.h" + +#include <qclipboard.h> +#include <qlayout.h> +#include <qtimer.h> +#include <qpainter.h> +#include <qtooltip.h> +#include <qlabel.h> +#include <qmovie.h> +#include <qregexp.h> +#include <qcursor.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kio/previewjob.h> +#include <kfileivi.h> +#include <konq_settings.h> +#include <konq_drag.h> +#include <kglobalsettings.h> +#include <kpropertiesdialog.h> +#include <kipc.h> +#include <kicontheme.h> +#include <kiconeffect.h> +#include <kurldrag.h> +#include <kstandarddirs.h> +#include <kprotocolinfo.h> +#include <ktrader.h> + +#include <assert.h> +#include <unistd.h> +#include <klocale.h> + + +struct KonqIconViewWidgetPrivate +{ + KonqIconViewWidgetPrivate() { + pActiveItem = 0; + bSoundPreviews = false; + pSoundItem = 0; + bSoundItemClicked = false; + pSoundPlayer = 0; + pSoundTimer = 0; + pPreviewJob = 0; + bAllowSetWallpaper = false; + + doAnimations = true; + m_movie = 0L; + m_movieBlocked = 0; + pFileTip = 0; + pActivateDoubleClick = 0L; + bCaseInsensitive = true; + pPreviewMimeTypes = 0L; + bProgramsURLdrag = false; + } + ~KonqIconViewWidgetPrivate() { + delete pSoundPlayer; + delete pSoundTimer; + delete m_movie; + delete pFileTip; + delete pActivateDoubleClick; + delete pPreviewMimeTypes; + //delete pPreviewJob; done by stopImagePreview + } + KFileIVI *pActiveItem; + // Sound preview + KFileIVI *pSoundItem; + KonqSoundPlayer *pSoundPlayer; + QTimer *pSoundTimer; + bool bSoundPreviews; + bool bSoundItemClicked; + bool bAllowSetWallpaper; + bool bCaseInsensitive; + bool bBoostPreview; + + // Animated icons support + bool doAnimations; + QMovie* m_movie; + int m_movieBlocked; + QString movieFileName; + + KIO::PreviewJob *pPreviewJob; + KonqFileTip* pFileTip; + QStringList previewSettings; + bool renameItem; + bool firstClick; + bool releaseMouseEvent; + QPoint mousePos; + int mouseState; + QTimer *pActivateDoubleClick; + QStringList* pPreviewMimeTypes; + bool bProgramsURLdrag; +}; + +KonqIconViewWidget::KonqIconViewWidget( QWidget * parent, const char * name, WFlags f, bool kdesktop ) + : KIconView( parent, name, f ), + m_rootItem( 0L ), m_size( 0 ) /* default is DesktopIcon size */, + m_bDesktop( kdesktop ), + m_bSetGridX( !kdesktop ) /* No line breaking on the desktop */ +{ + d = new KonqIconViewWidgetPrivate; + connect( this, SIGNAL( dropped( QDropEvent *, const QValueList<QIconDragItem> & ) ), + this, SLOT( slotDropped( QDropEvent*, const QValueList<QIconDragItem> & ) ) ); + + connect( this, SIGNAL( selectionChanged() ), + this, SLOT( slotSelectionChanged() ) ); + + kapp->addKipcEventMask( KIPC::IconChanged ); + connect( kapp, SIGNAL(iconChanged(int)), SLOT(slotIconChanged(int)) ); + connect( this, SIGNAL(onItem(QIconViewItem *)), SLOT(slotOnItem(QIconViewItem *)) ); + connect( this, SIGNAL(onViewport()), SLOT(slotOnViewport()) ); + connect( this, SIGNAL(itemRenamed(QIconViewItem *, const QString &)), SLOT(slotItemRenamed(QIconViewItem *, const QString &)) ); + + m_pSettings = KonqFMSettings::settings(); // already needed in setItemTextPos(), calculateGridX() + d->bBoostPreview = boostPreview(); + + // hardcoded settings + setSelectionMode( QIconView::Extended ); + setItemTextPos( QIconView::Bottom ); + d->releaseMouseEvent = false; + d->pFileTip = new KonqFileTip(this); + d->firstClick = false; + calculateGridX(); + setAutoArrange( true ); + setSorting( true, sortDirection() ); + readAnimatedIconsConfig(); + m_bSortDirsFirst = true; + m_bMousePressed = false; + m_LineupMode = LineupBoth; + // emit our signals + slotSelectionChanged(); + m_iconPositionGroupPrefix = QString::fromLatin1( "IconPosition::" ); + KonqUndoManager::incRef(); +} + +KonqIconViewWidget::~KonqIconViewWidget() +{ + stopImagePreview(); + KonqUndoManager::decRef(); + delete d; +} + +bool KonqIconViewWidget::maySetWallpaper() +{ + return d->bAllowSetWallpaper; +} + +void KonqIconViewWidget::setMaySetWallpaper(bool b) +{ + d->bAllowSetWallpaper = b; +} + +void KonqIconViewWidget::focusOutEvent( QFocusEvent * ev ) +{ + // We can't possibly have the mouse pressed and still lose focus. + // Well, we can, but when we regain focus we should assume the mouse is + // not down anymore or the slotOnItem code will break with highlighting! + m_bMousePressed = false; + + // This will ensure that tooltips don't pop up and the mouseover icon + // effect will go away if the mouse goes out of the view without + // first moving into an empty portion of the view + // Fixes part of #86968, and #85204 + // Matt Newell 2004-09-24 + slotOnViewport(); + + KIconView::focusOutEvent( ev ); +} + +void KonqIconViewWidget::slotItemRenamed(QIconViewItem *item, const QString &name) +{ + kdDebug(1203) << "KonqIconViewWidget::slotItemRenamed" << endl; + KFileIVI *viewItem = static_cast<KFileIVI *>(item); + KFileItem *fileItem = viewItem->item(); + + // The correct behavior is to show the old name until the rename has successfully + // completed. Unfortunately, KIconView forces us to allow the text to be changed + // before we try the rename, so set it back to the pre-rename state. + viewItem->setText( fileItem->text() ); + kdDebug(1203)<<" fileItem->text() ;"<<fileItem->text()<<endl; + // Don't do anything if the user renamed to a blank name. + if( !name.isEmpty() ) + { + // Actually attempt the rename. If it succeeds, KDirLister will update the name. + KURL oldurl( fileItem->url() ); + KURL newurl( oldurl ); + newurl.setPath( newurl.directory(false) + KIO::encodeFileName( name ) ); + kdDebug(1203)<<" newurl :"<<newurl<<endl; + // We use url()+name so that it also works if the name is a relative path (#51176) + KonqOperations::rename( this, oldurl, newurl ); + } +} + +void KonqIconViewWidget::slotIconChanged( int group ) +{ + if (group != KIcon::Desktop) + return; + + int size = m_size; + if ( m_size == 0 ) + m_size = -1; // little trick to force grid change in setIcons + setIcons( size ); // force re-determining all icons + readAnimatedIconsConfig(); +} + +void KonqIconViewWidget::readAnimatedIconsConfig() +{ + KConfigGroup cfgGroup( KGlobal::config(), "DesktopIcons" ); + d->doAnimations = cfgGroup.readBoolEntry( "Animated", true /*default*/ ); +} + +void KonqIconViewWidget::slotOnItem( QIconViewItem *_item ) +{ + KFileIVI* item = static_cast<KFileIVI *>( _item ); + // Reset icon of previous item + if( d->pActiveItem != 0L && d->pActiveItem != item ) + { + if ( d->m_movie && d->pActiveItem->isAnimated() ) + { + d->m_movie->pause(); // we'll see below what we do with it + d->pActiveItem->setAnimated( false ); + d->pActiveItem->refreshIcon( true ); + } + else { + d->pActiveItem->setActive( false ); + } + d->pActiveItem = 0L; + d->pFileTip->setItem( 0L ); + } + + // Stop sound + if (d->pSoundPlayer != 0 && item != d->pSoundItem) + { + d->pSoundPlayer->stop(); + + d->pSoundItem = 0; + if (d->pSoundTimer && d->pSoundTimer->isActive()) + d->pSoundTimer->stop(); + } + + if ( !m_bMousePressed ) + { + if( item != d->pActiveItem ) + { + d->pActiveItem = item; + d->pFileTip->setItem( d->pActiveItem->item(), + item->rect(), + item->pixmap() ); + + if ( d->doAnimations && d->pActiveItem && d->pActiveItem->hasAnimation() ) + { + //kdDebug(1203) << "Playing animation for: " << d->pActiveItem->mouseOverAnimation() << endl; + // Check if cached movie can be used +#if 0 // Qt-mng bug, reusing the movie doesn't work currently. + if ( d->m_movie && d->movieFileName == d->pActiveItem->mouseOverAnimation() ) + { + d->pActiveItem->setAnimated( true ); + if (d->m_movieBlocked) { + kdDebug(1203) << "onitem, but blocked" << endl; + d->m_movie->pause(); + } + else { + kdDebug(1203) << "we go ahead.." << endl; + d->m_movieBlocked++; + QTimer::singleShot(300, this, SLOT(slotReenableAnimation())); + d->m_movie->restart(); + d->m_movie->unpause(); + } + } + else +#endif + { + QMovie movie = KGlobal::iconLoader()->loadMovie( d->pActiveItem->mouseOverAnimation(), KIcon::Desktop, d->pActiveItem->iconSize() ); + if ( !movie.isNull() ) + { + delete d->m_movie; + d->m_movie = new QMovie( movie ); // shallow copy, don't worry + // Fix alpha-channel - currently only if no background pixmap, + // the bg pixmap case requires to uncomment the code at qmovie.cpp:404 + const QPixmap* pm = backgroundPixmap(); + bool hasPixmap = pm && !pm->isNull(); + if ( !hasPixmap ) { + pm = viewport()->backgroundPixmap(); + hasPixmap = pm && !pm->isNull(); + } + if (!hasPixmap && backgroundMode() != NoBackground) + d->m_movie->setBackgroundColor( viewport()->backgroundColor() ); + d->m_movie->connectUpdate( this, SLOT( slotMovieUpdate(const QRect &) ) ); + d->m_movie->connectStatus( this, SLOT( slotMovieStatus(int) ) ); + d->movieFileName = d->pActiveItem->mouseOverAnimation(); + d->pActiveItem->setAnimated( true ); + } + else + { + d->pActiveItem->setAnimated( false ); + if (d->m_movie) + d->m_movie->pause(); + // No movie available, remember it + d->pActiveItem->setMouseOverAnimation( QString::null ); + } + } + } // animations + // Only do the normal "mouseover" effect if no animation is in use + if (d->pActiveItem && !d->pActiveItem->isAnimated()) + { + d->pActiveItem->setActive( true ); + } + } + else // No change in current item + { + // No effect. If we want to underline on hover, we should + // force the IVI to repaint here, though! + d->pActiveItem = 0L; + d->pFileTip->setItem( 0L ); + } + } // bMousePressed + else + { + // All features disabled during mouse clicking, e.g. rectangular + // selection + d->pActiveItem = 0L; + d->pFileTip->setItem( 0L ); + } + + // ## shouldn't this be disabled during rectangular selection too ? + if (d->bSoundPreviews && d->pSoundPlayer && + d->pSoundPlayer->mimeTypes().contains( + item->item()->mimetype()) + && KGlobalSettings::showFilePreview(item->item()->url()) + && topLevelWidget() == kapp->activeWindow()) + { + d->pSoundItem = item; + d->bSoundItemClicked = false; + if (!d->pSoundTimer) + { + d->pSoundTimer = new QTimer(this); + connect(d->pSoundTimer, SIGNAL(timeout()), SLOT(slotStartSoundPreview())); + } + if (d->pSoundTimer->isActive()) + d->pSoundTimer->stop(); + d->pSoundTimer->start(500, true); + } + else + { + if (d->pSoundPlayer) + d->pSoundPlayer->stop(); + d->pSoundItem = 0; + if (d->pSoundTimer && d->pSoundTimer->isActive()) + d->pSoundTimer->stop(); + } +} + +void KonqIconViewWidget::slotOnViewport() +{ + d->pFileTip->setItem( 0L ); + + if (d->pSoundPlayer) + d->pSoundPlayer->stop(); + d->pSoundItem = 0; + if (d->pSoundTimer && d->pSoundTimer->isActive()) + d->pSoundTimer->stop(); + + if (d->pActiveItem == 0L) + return; + + if ( d->doAnimations && d->m_movie && d->pActiveItem->isAnimated() ) + { + d->pActiveItem->setAnimated( false ); +#if 0 + // Aborting before the end of the animation ? + if (d->m_movie->running()) { + d->m_movie->pause(); + d->m_movieBlocked++; + kdDebug(1203) << "on viewport, blocking" << endl; + QTimer::singleShot(300, this, SLOT(slotReenableAnimation())); + } +#endif + d->pActiveItem->refreshIcon( true ); + Q_ASSERT( d->pActiveItem->state() == KIcon::DefaultState ); + //delete d->m_movie; + //d->m_movie = 0L; + // TODO a timer to delete the movie after some time if unused? + } + else + { + d->pActiveItem->setActive( false ); + } + d->pActiveItem = 0L; +} + +void KonqIconViewWidget::slotStartSoundPreview() +{ + if (!d->pSoundItem || d->bSoundItemClicked) + return; + + d->pSoundPlayer->play(d->pSoundItem->item()->url().url()); +} + + +void KonqIconViewWidget::slotPreview(const KFileItem *item, const QPixmap &pix) +{ + // ### slow. Idea: move KonqKfmIconView's m_itemDict into this class + for (QIconViewItem *it = firstItem(); it; it = it->nextItem()) + { + KFileIVI* current = static_cast<KFileIVI *>(it); + if (current->item() == item) + { + if (item->overlays() & KIcon::HiddenOverlay) { + QPixmap p(pix); + + KIconEffect::semiTransparent(p); + current->setThumbnailPixmap(p); + } else { + current->setThumbnailPixmap(pix); + } + break; + } + } +} + +void KonqIconViewWidget::slotPreviewResult() +{ + d->pPreviewJob = 0; + emit imagePreviewFinished(); +} + +void KonqIconViewWidget::slotToolTipPreview(const KFileItem* , const QPixmap &) +{ +// unused - remove for KDE4 +} + +void KonqIconViewWidget::slotToolTipPreviewResult() +{ +// unused - remove for KDE4 +} + +void KonqIconViewWidget::slotMovieUpdate( const QRect& rect ) +{ + //kdDebug(1203) << "KonqIconViewWidget::slotMovieUpdate " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; + Q_ASSERT( d ); + Q_ASSERT( d->m_movie ); + // seems stopAnimation triggers one last update + if ( d->pActiveItem && d->m_movie && d->pActiveItem->isAnimated() ) { + const QPixmap &frame = d->m_movie->framePixmap(); + // This can happen if the icon was scaled to the desired size, so KIconLoader + // will happily return a movie with different dimensions than the icon + int iconSize=d->pActiveItem->iconSize(); + if (iconSize==0) iconSize = KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + if ( frame.width() != iconSize || frame.height() != iconSize ) { + d->pActiveItem->setAnimated( false ); + d->m_movie->pause(); + // No movie available, remember it + d->pActiveItem->setMouseOverAnimation( QString::null ); + d->pActiveItem->setActive( true ); + return; + } + d->pActiveItem->setPixmapDirect( frame, false, false /*no redraw*/ ); + QRect pixRect = d->pActiveItem->pixmapRect(false); + repaintContents( pixRect.x() + rect.x(), pixRect.y() + rect.y(), rect.width(), rect.height(), false ); + } +} + +void KonqIconViewWidget::slotMovieStatus( int status ) +{ + if ( status < 0 ) { + // Error playing the MNG -> forget about it and do normal iconeffect + if ( d->pActiveItem && d->pActiveItem->isAnimated() ) { + d->pActiveItem->setAnimated( false ); + d->pActiveItem->setMouseOverAnimation( QString::null ); + d->pActiveItem->setActive( true ); + } + } +} + +void KonqIconViewWidget::slotReenableAnimation() +{ + if (!--d->m_movieBlocked) { + if ( d->pActiveItem && d->m_movie && d->m_movie->paused()) { + kdDebug(1203) << "reenabled animation" << endl; + d->m_movie->restart(); + d->m_movie->unpause(); + } + } +} + +void KonqIconViewWidget::clear() +{ + d->pFileTip->setItem( 0L ); + stopImagePreview(); // Just in case + KIconView::clear(); + d->pActiveItem = 0L; +} + +void KonqIconViewWidget::takeItem( QIconViewItem *item ) +{ + if ( d->pActiveItem == static_cast<KFileIVI *>(item) ) + { + d->pFileTip->setItem( 0L ); + d->pActiveItem = 0L; + } + + if ( d->pPreviewJob ) + d->pPreviewJob->removeItem( static_cast<KFileIVI *>(item)->item() ); + + KIconView::takeItem( item ); +} + +// Currently unused - remove in KDE 4.0 +void KonqIconViewWidget::setThumbnailPixmap( KFileIVI * item, const QPixmap & pixmap ) +{ + if ( item ) + { + if ( d->pActiveItem == item ) + { + d->pFileTip->setItem( 0L ); + d->pActiveItem = 0L; + } + item->setThumbnailPixmap( pixmap ); + if ( m_bSetGridX && item->width() > gridX() ) + { + setGridX( item->width() ); + if (autoArrange()) + arrangeItemsInGrid(); + } + } +} + +bool KonqIconViewWidget::initConfig( bool bInit ) +{ + bool fontChanged = false; + + // Color settings + QColor normalTextColor = m_pSettings->normalTextColor(); + setItemColor( normalTextColor ); + + if (m_bDesktop) + { + QColor itemTextBg = m_pSettings->itemTextBackground(); + if ( itemTextBg.isValid() ) + setItemTextBackground( itemTextBg ); + else + setItemTextBackground( NoBrush ); + } + + bool on = m_pSettings->showFileTips() && QToolTip::isGloballyEnabled(); + d->pFileTip->setOptions(on, + m_pSettings->showPreviewsInFileTips(), + m_pSettings->numFileTips()); + + // if the user wants our own tooltip, don't show the one from Qts ListView + setShowToolTips(!on); + + // Font settings + QFont font( m_pSettings->standardFont() ); + if (!m_bDesktop) + font.setUnderline( m_pSettings->underlineLink() ); + + if ( font != KonqIconViewWidget::font() ) + { + setFont( font ); + if (!bInit) + { + // QIconView doesn't do it by default... but if the font is made much + // bigger, we really need to give more space between the icons + fontChanged = true; + } + } + + setIconTextHeight( m_pSettings->iconTextHeight() ); + + if ( (itemTextPos() == QIconView::Right) && (maxItemWidth() != gridXValue()) ) + { + int size = m_size; + m_size = -1; // little trick to force grid change in setIcons + setIcons( size ); // force re-determining all icons + } + else if ( d->bBoostPreview != boostPreview() ) // Update icons if settings for preview icon size have changed + setIcons(m_size); + else if (!bInit) + updateContents(); + return fontChanged; +} + +bool KonqIconViewWidget::boostPreview() const +{ + if ( m_bDesktop ) return false; + + KConfigGroup group( KGlobal::config(), "PreviewSettings" ); + return group.readBoolEntry( "BoostSize", false ); +} + +void KonqIconViewWidget::disableSoundPreviews() +{ + d->bSoundPreviews = false; + + if (d->pSoundPlayer) + d->pSoundPlayer->stop(); + d->pSoundItem = 0; + if (d->pSoundTimer && d->pSoundTimer->isActive()) + d->pSoundTimer->stop(); +} + +void KonqIconViewWidget::setIcons( int size, const QStringList& stopImagePreviewFor ) +{ + // size has changed? + bool sizeChanged = (m_size != size); + int oldGridX = gridX(); + m_size = size; + + // boost preview option has changed? + bool boost = boostPreview(); + bool previewSizeChanged = ( d->bBoostPreview != boost ); + d->bBoostPreview = boost; + + if ( sizeChanged || previewSizeChanged ) + { + int realSize = size ? size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + // choose spacing depending on font, but min 5 (due to KFileIVI move limit) + setSpacing( ( m_bDesktop || ( realSize > KIcon::SizeSmall ) ) ? + QMAX( 5, QFontMetrics(font()).width('n') ) : 0 ); + } + + if ( sizeChanged || previewSizeChanged || !stopImagePreviewFor.isEmpty() ) + { + calculateGridX(); + } + bool stopAll = !stopImagePreviewFor.isEmpty() && stopImagePreviewFor.first() == "*"; + + // Disable repaints that can be triggered by ivi->setIcon(). Since icons are + // resized in-place, if the icon size is increasing it can happens that the right + // or bottom icons exceed the size of the viewport.. here we prevent the repaint + // event that will be triggered in that case. + bool prevUpdatesState = viewport()->isUpdatesEnabled(); + viewport()->setUpdatesEnabled( false ); + + // Do this even if size didn't change, since this is used by refreshMimeTypes... + for ( QIconViewItem *it = firstItem(); it; it = it->nextItem() ) { + KFileIVI * ivi = static_cast<KFileIVI *>( it ); + // Set a normal icon for files that are not thumbnails, and for files + // that are thumbnails but for which it should be stopped + if ( !ivi->isThumbnail() || + sizeChanged || + previewSizeChanged || + stopAll || + mimeTypeMatch( ivi->item()->mimetype(), stopImagePreviewFor ) ) + { + ivi->setIcon( size, ivi->state(), true, false ); + } + else + ivi->invalidateThumb( ivi->state(), true ); + } + + // Restore viewport update to previous state + viewport()->setUpdatesEnabled( prevUpdatesState ); + + if ( ( sizeChanged || previewSizeChanged || oldGridX != gridX() || + !stopImagePreviewFor.isEmpty() ) && autoArrange() ) + arrangeItemsInGrid( true ); // take new grid into account and repaint + else + viewport()->update(); //Repaint later.. +} + +bool KonqIconViewWidget::mimeTypeMatch( const QString& mimeType, const QStringList& mimeList ) const +{ + // Code duplication from KIO::PreviewJob + KMimeType::Ptr mime = KMimeType::mimeType( mimeType ); + for (QStringList::ConstIterator mt = mimeList.begin(); mt != mimeList.end(); ++mt) + { + if ( mime->is( *mt ) ) + return true; + // Support for *mt == "image/*" + QString tmp( mimeType ); + if ( (*mt).endsWith("*") && tmp.replace(QRegExp("/.*"), "/*") == (*mt) ) + return true; + if ( (*mt) == "text/plain" ) + { + QVariant textProperty = mime->property( "X-KDE-text" ); + if ( textProperty.type() == QVariant::Bool && textProperty.toBool() ) + return true; + } + } + return false; +} + +void KonqIconViewWidget::setItemTextPos( ItemTextPos pos ) +{ + // can't call gridXValue() because this already would need the new itemTextPos() + int sz = m_size ? m_size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + + if ( m_bSetGridX ) + if ( pos == QIconView::Bottom ) + setGridX( QMAX( sz + 50, previewIconSize( sz ) + 13 ) ); + else + { + setMaxItemWidth( QMAX( sz, previewIconSize( sz ) ) + m_pSettings->iconTextWidth() ); + setGridX( -1 ); + } + + KIconView::setItemTextPos( pos ); +} + +void KonqIconViewWidget::gridValues( int* x, int* y, int* dx, int* dy, + int* nx, int* ny ) +{ + int previewSize = previewIconSize( m_size ); + int iconSize = m_size ? m_size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + + // Grid size + // as KFileIVI limits to move an icon to x >= 5, y >= 5, we define a grid cell as: + // spacing() must be >= 5 (currently set to 5 in setIcons()) + // horizontal: left spacing() + <width> + // vertical : top spacing(), <height>, bottom spacing() + // The doubled space in y-direction gives a better visual separation and makes it clearer + // to which item the text belongs + *dx = spacing() + QMAX( QMAX( iconSize, previewSize ), m_pSettings->iconTextWidth() ); + int textHeight = iconTextHeight() * fontMetrics().height(); + *dy = spacing() + QMAX( iconSize, previewSize ) + 2 + textHeight + spacing(); + + // Icon Area + int w, h; + if ( m_IconRect.isValid() ) { // w and h must be != 0, otherwise we would get a div by zero + *x = m_IconRect.left(); w = m_IconRect.width(); + *y = m_IconRect.top(); h = m_IconRect.height(); + } + else { + *x = 0; w = viewport()->width(); + *y = 0; h = viewport()->height(); + } + + // bug:110775 avoid div by zero (happens e.g. when iconTextHeight or iconTextWidth are very large) + if ( *dx > w ) + *dx = w; + + if ( *dy > h ) + *dy = h; + + *nx = w / *dx; + *ny = h / *dy; + // TODO: Check that items->count() <= nx * ny + + // Let have exactly nx columns and ny rows + if(*nx && *ny) { + *dx = w / *nx; + *dy = h / *ny; + } + kdDebug(1203) << "x=" << *x << " y=" << *y << " spacing=" << spacing() << " iconSize=" << iconSize + << " w=" << w << " h=" << h + << " nx=" << *nx << " ny=" << *ny + << " dx=" << *dx << " dy=" << *dy << endl; +} + +void KonqIconViewWidget::calculateGridX() +{ + if ( m_bSetGridX ) + if ( itemTextPos() == QIconView::Bottom ) + setGridX( gridXValue() ); + else + { + setMaxItemWidth( gridXValue() ); + setGridX( -1 ); + } +} + +int KonqIconViewWidget::gridXValue() const +{ + // this method is only used in konqi as filemanager (not desktop) + int sz = m_size ? m_size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + int newGridX; + + if ( itemTextPos() == QIconView::Bottom ) + newGridX = QMAX( sz + 50, previewIconSize( sz ) + 13 ); + else + newGridX = QMAX( sz, previewIconSize( sz ) ) + m_pSettings->iconTextWidth(); + + //kdDebug(1203) << "gridXValue: " << newGridX << " sz=" << sz << endl; + return newGridX; +} + +void KonqIconViewWidget::refreshMimeTypes() +{ + updatePreviewMimeTypes(); + for ( QIconViewItem *it = firstItem(); it; it = it->nextItem() ) + (static_cast<KFileIVI *>( it ))->item()->refreshMimeType(); + setIcons( m_size ); +} + +void KonqIconViewWidget::setURL( const KURL &kurl ) +{ + stopImagePreview(); + m_url = kurl; + + d->pFileTip->setPreview( KGlobalSettings::showFilePreview(m_url) ); + + if ( m_url.isLocalFile() ) + m_dotDirectoryPath = m_url.path(1).append( ".directory" ); + else + m_dotDirectoryPath = QString::null; +} + +void KonqIconViewWidget::startImagePreview( const QStringList &, bool force ) +{ + stopImagePreview(); // just in case + + // Check config + if ( !KGlobalSettings::showFilePreview( url() ) ) { + kdDebug(1203) << "Previews disabled for protocol " << url().protocol() << endl; + emit imagePreviewFinished(); + return; + } + + if ((d->bSoundPreviews = d->previewSettings.contains( "audio/" )) && + !d->pSoundPlayer) + { + KLibFactory *factory = KLibLoader::self()->factory("konq_sound"); + if (factory) + d->pSoundPlayer = static_cast<KonqSoundPlayer *>( + factory->create(this, 0, "KonqSoundPlayer")); + d->bSoundPreviews = (d->pSoundPlayer != 0L); + } + + KFileItemList items; + for ( QIconViewItem *it = firstItem(); it; it = it->nextItem() ) + if ( force || !static_cast<KFileIVI *>( it )->hasValidThumbnail() ) + items.append( static_cast<KFileIVI *>( it )->item() ); + + bool onlyAudio = true; + for ( QStringList::ConstIterator it = d->previewSettings.begin(); it != d->previewSettings.end(); ++it ) { + if ( (*it).startsWith( "audio/" ) ) + d->bSoundPreviews = true; + else + onlyAudio = false; + } + + if ( items.isEmpty() || onlyAudio ) { + emit imagePreviewFinished(); + return; // don't start the preview job if not really necessary + } + + int iconSize = m_size ? m_size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + int size; + + d->bBoostPreview = boostPreview(); + size = previewIconSize( iconSize ); + + if ( !d->bBoostPreview ) + iconSize /= 2; + + d->pPreviewJob = KIO::filePreview( items, size, size, iconSize, + m_pSettings->textPreviewIconTransparency(), true /* scale */, + true /* save */, &(d->previewSettings) ); + connect( d->pPreviewJob, SIGNAL( gotPreview( const KFileItem *, const QPixmap & ) ), + this, SLOT( slotPreview( const KFileItem *, const QPixmap & ) ) ); + connect( d->pPreviewJob, SIGNAL( result( KIO::Job * ) ), + this, SLOT( slotPreviewResult() ) ); +} + +void KonqIconViewWidget::stopImagePreview() +{ + if (d->pPreviewJob) + { + d->pPreviewJob->kill(); + d->pPreviewJob = 0; + // Now that previews are updated in-place, calling + // arrangeItemsInGrid() here is not needed anymore + } +} + +bool KonqIconViewWidget::isPreviewRunning() const +{ + return d->pPreviewJob; +} + +KFileItemList KonqIconViewWidget::selectedFileItems() +{ + KFileItemList lstItems; + + QIconViewItem *it = firstItem(); + for (; it; it = it->nextItem() ) + if ( it->isSelected() ) { + KFileItem *fItem = (static_cast<KFileIVI *>(it))->item(); + lstItems.append( fItem ); + } + return lstItems; +} + +void KonqIconViewWidget::slotDropped( QDropEvent *ev, const QValueList<QIconDragItem> & ) +{ + // Drop on background + KURL dirURL = url(); + if ( m_rootItem ) { + bool dummy; + dirURL = m_rootItem->mostLocalURL(dummy); + } + KonqOperations::doDrop( m_rootItem /* may be 0L */, dirURL, ev, this ); +} + +void KonqIconViewWidget::slotAboutToCreate(const QPoint &, const QValueList<KIO::CopyInfo> &) +{ + // Do nothing :-) +} + +void KonqIconViewWidget::drawBackground( QPainter *p, const QRect &r ) +{ + drawBackground(p, r, r.topLeft()); +} + +void KonqIconViewWidget::drawBackground( QPainter *p, const QRect &r , const QPoint &pt) +{ + const QPixmap *pm = backgroundPixmap(); + bool hasPixmap = pm && !pm->isNull(); + if ( !hasPixmap ) { + pm = viewport()->backgroundPixmap(); + hasPixmap = pm && !pm->isNull(); + } + + QRect rtgt(r); + rtgt.moveTopLeft(pt); + if (!hasPixmap && backgroundMode() != NoBackground) { + p->fillRect(rtgt, viewport()->backgroundColor()); + return; + } + + if (hasPixmap) { + int ax = (r.x() + contentsX() + leftMargin()) % pm->width(); + int ay = (r.y() + contentsY() + topMargin()) % pm->height(); + p->drawTiledPixmap(rtgt, *pm, QPoint(ax, ay)); + } +} + +QDragObject * KonqIconViewWidget::dragObject() +{ + if ( !currentItem() ) + return 0; + + return konqDragObject( viewport() ); +} + +KonqIconDrag * KonqIconViewWidget::konqDragObject( QWidget * dragSource ) +{ + //kdDebug(1203) << "KonqIconViewWidget::konqDragObject" << endl; + + KonqIconDrag2 * drag = new KonqIconDrag2( dragSource ); + QIconViewItem *primaryItem = currentItem(); + // Append all items to the drag object + for ( QIconViewItem *it = firstItem(); it; it = it->nextItem() ) { + if ( it->isSelected() ) { + if (!primaryItem) + primaryItem = it; + KFileItem* fileItem = (static_cast<KFileIVI *>(it))->item(); + KURL url = fileItem->url(); + bool dummy; + KURL mostLocalURL = fileItem->mostLocalURL(dummy); + QString itemURL = KURLDrag::urlToString(url); + kdDebug(1203) << "itemURL=" << itemURL << endl; + QIconDragItem id; + id.setData( QCString(itemURL.latin1()) ); + drag->append( id, + QRect( it->pixmapRect(false).topLeft() - m_mousePos, + it->pixmapRect().size() ), + QRect( it->textRect(false).topLeft() - m_mousePos, + it->textRect().size() ), + itemURL, mostLocalURL ); + } + } + + if (primaryItem) + drag->setPixmap( *primaryItem->pixmap(), m_mousePos - primaryItem->pixmapRect(false).topLeft() ); + + return drag; +} + +void KonqIconViewWidget::contentsDragEnterEvent( QDragEnterEvent *e ) +{ + if ( e->provides( "text/uri-list" ) ) + { + QByteArray payload = e->encodedData( "text/uri-list" ); + if ( !payload.size() ) + kdError() << "Empty data !" << endl; + // Cache the URLs, since we need them every time we move over a file + // (see KFileIVI) + bool ok = KURLDrag::decode( e, m_lstDragURLs ); + if( !ok ) + kdError() << "Couldn't decode urls dragged !" << endl; + } + + KURL::List uriList; + if ( KURLDrag::decode(e, uriList) ) + { + if ( uriList.first().protocol() == "programs" ) + { + e->ignore(); + emit dragEntered( false ); + d->bProgramsURLdrag = true; + return; + } + } + + KIconView::contentsDragEnterEvent( e ); + emit dragEntered( true /*accepted*/ ); +} + +void KonqIconViewWidget::contentsDragMoveEvent( QDragMoveEvent *e ) +{ + if ( d->bProgramsURLdrag ) { + emit dragMove( false ); + e->ignore(); + cancelPendingHeldSignal(); + return; + } + + QIconViewItem *item = findItem( e->pos() ); + if ( e->source() != viewport() && + !item && m_rootItem && !m_rootItem->isWritable() ) { + emit dragMove( false ); + e->ignore(); + cancelPendingHeldSignal(); + return; + } + emit dragMove( true ); + KIconView::contentsDragMoveEvent( e ); +} + +void KonqIconViewWidget::contentsDragLeaveEvent( QDragLeaveEvent *e ) +{ + d->bProgramsURLdrag = false; + KIconView::contentsDragLeaveEvent(e); + emit dragLeft(); +} + + +void KonqIconViewWidget::setItemColor( const QColor &c ) +{ + iColor = c; +} + +QColor KonqIconViewWidget::itemColor() const +{ + return iColor; +} + +void KonqIconViewWidget::disableIcons( const KURL::List & lst ) +{ + for ( QIconViewItem *kit = firstItem(); kit; kit = kit->nextItem() ) + { + bool bFound = false; + // Wow. This is ugly. Matching two lists together.... + // Some sorting to optimise this would be a good idea ? + for (KURL::List::ConstIterator it = lst.begin(); !bFound && it != lst.end(); ++it) + { + if ( static_cast<KFileIVI *>( kit )->item()->url() == *it ) + { + bFound = true; + // maybe remove "it" from lst here ? + } + } + static_cast<KFileIVI *>( kit )->setDisabled( bFound ); + } +} + +void KonqIconViewWidget::slotSelectionChanged() +{ + // This code is very related to ListViewBrowserExtension::updateActions + int canCopy = 0; + int canDel = 0; + int canTrash = 0; + bool bInTrash = false; + int iCount = 0; + + for ( QIconViewItem *it = firstItem(); it; it = it->nextItem() ) + { + if ( it->isSelected() ) + { + iCount++; + canCopy++; + + KFileItem *item = ( static_cast<KFileIVI *>( it ) )->item(); + KURL url = item->url(); + QString local_path = item->localPath(); + + if ( url.directory(false) == KGlobalSettings::trashPath() ) + bInTrash = true; + if ( KProtocolInfo::supportsDeleting( url ) ) + canDel++; + if ( !local_path.isEmpty() ) + canTrash++; + } + } + + emit enableAction( "cut", canDel > 0 ); + emit enableAction( "copy", canCopy > 0 ); + emit enableAction( "trash", canDel > 0 && !bInTrash && canTrash==canDel ); + emit enableAction( "del", canDel > 0 ); + emit enableAction( "properties", iCount > 0 && KPropertiesDialog::canDisplay( selectedFileItems() ) ); + emit enableAction( "editMimeType", ( iCount == 1 ) ); + emit enableAction( "rename", ( iCount == 1) && !bInTrash ); +} + +void KonqIconViewWidget::renameCurrentItem() +{ + if ( currentItem() ) + currentItem()->rename(); +} + +void KonqIconViewWidget::renameSelectedItem() +{ + kdDebug(1203) << " -- KonqIconViewWidget::renameSelectedItem() -- " << endl; + QIconViewItem * item = 0L; + QIconViewItem *it = firstItem(); + for (; it; it = it->nextItem() ) + if ( it->isSelected() && !item ) + { + item = it; + break; + } + if (!item) + { + Q_ASSERT(item); + return; + } + item->rename(); +} + +void KonqIconViewWidget::cutSelection() +{ + kdDebug(1203) << " -- KonqIconViewWidget::cutSelection() -- " << endl; + KonqIconDrag * obj = konqDragObject( /* no parent ! */ ); + obj->setMoveSelection( true ); + QApplication::clipboard()->setData( obj ); +} + +void KonqIconViewWidget::copySelection() +{ + kdDebug(1203) << " -- KonqIconViewWidget::copySelection() -- " << endl; + KonqIconDrag * obj = konqDragObject( /* no parent ! */ ); + QApplication::clipboard()->setData( obj ); +} + +void KonqIconViewWidget::pasteSelection() +{ + paste( url() ); +} + +void KonqIconViewWidget::paste( const KURL &url ) +{ + KonqOperations::doPaste( this, url ); +} + +KURL::List KonqIconViewWidget::selectedUrls() +{ + return selectedUrls( UserVisibleUrls ); +} + +KURL::List KonqIconViewWidget::selectedUrls( UrlFlags flags ) const +{ + KURL::List lstURLs; + bool dummy; + for ( QIconViewItem *it = firstItem(); it; it = it->nextItem() ) + if ( it->isSelected() ) { + KFileItem* item = (static_cast<KFileIVI *>( it ))->item(); + lstURLs.append( flags == MostLocalUrls ? item->mostLocalURL( dummy ) : item->url() ); + } + return lstURLs; +} + +QRect KonqIconViewWidget::iconArea() const +{ + return m_IconRect; +} + +void KonqIconViewWidget::setIconArea(const QRect &rect) +{ + m_IconRect = rect; +} + +int KonqIconViewWidget::lineupMode() const +{ + return m_LineupMode; +} + +void KonqIconViewWidget::setLineupMode(int mode) +{ + m_LineupMode = mode; +} + +bool KonqIconViewWidget::sortDirectoriesFirst() const +{ + return m_bSortDirsFirst; +} + +void KonqIconViewWidget::setSortDirectoriesFirst( bool b ) +{ + m_bSortDirsFirst = b; +} + +void KonqIconViewWidget::contentsMouseMoveEvent( QMouseEvent *e ) +{ + if ( (d->pSoundPlayer && d->pSoundPlayer->isPlaying()) || (d->pSoundTimer && d->pSoundTimer->isActive())) + { + // The following call is SO expensive (the ::widgetAt call eats up to 80% + // of the mouse move cpucycles!), so it's mandatory to place that function + // under strict checks, such as d->pSoundPlayer->isPlaying() + if ( QApplication::widgetAt( QCursor::pos() ) != topLevelWidget() ) + { + if (d->pSoundPlayer) + d->pSoundPlayer->stop(); + d->pSoundItem = 0; + if (d->pSoundTimer && d->pSoundTimer->isActive()) + d->pSoundTimer->stop(); + } + } + d->renameItem= false; + KIconView::contentsMouseMoveEvent( e ); +} + +void KonqIconViewWidget::contentsDropEvent( QDropEvent * ev ) +{ + QIconViewItem *i = findItem( ev->pos() ); + + if ( ev->source() != viewport() && + !i && m_rootItem && !m_rootItem->isWritable() ) { + ev->accept( false ); + return; + } + + // Short-circuit QIconView if Ctrl is pressed, so that it's possible + // to drop a file into its own parent widget to copy it. + if ( !i && (ev->action() == QDropEvent::Copy || ev->action() == QDropEvent::Link) + && ev->source() && ev->source() == viewport()) + { + // First we need to call QIconView though, to clear the drag shape + bool bMovable = itemsMovable(); + setItemsMovable(false); // hack ? call it what you want :-) + KIconView::contentsDropEvent( ev ); + setItemsMovable(bMovable); + + QValueList<QIconDragItem> lst; + slotDropped(ev, lst); + } + else + { + KIconView::contentsDropEvent( ev ); + emit dropped(); // What is this for ? (David) KDE4: remove + } + // Don't do this here, it's too early ! + // slotSaveIconPositions(); + // If we want to save after the new file gets listed, though, + // we could reimplement contentsDropEvent in KDIconView and set m_bNeedSave. Bah. + + // This signal is sent last because we need to ensure it is + // taken in account when all the slots triggered by the dropped() signal + // are executed. This way we know that the Drag and Drop is truely finished + emit dragFinished(); +} + +void KonqIconViewWidget::doubleClickTimeout() +{ + d->renameItem= true; + mousePressChangeValue(); + if ( d->releaseMouseEvent ) + { + QMouseEvent e( QEvent::MouseButtonPress,d->mousePos , 1, d->mouseState); + QIconViewItem* item = findItem( e.pos() ); + KURL url; + if ( item ) + { + url= ( static_cast<KFileIVI *>( item ) )->item()->url(); + bool brenameTrash =false; + if ( url.isLocalFile() && (url.directory(false) == KGlobalSettings::trashPath() || url.path(1).startsWith(KGlobalSettings::trashPath()))) + brenameTrash = true; + + if ( url.isLocalFile() && !brenameTrash && d->renameItem && m_pSettings->renameIconDirectly() && e.button() == LeftButton && item->textRect( false ).contains(e.pos())) + { + if( d->pActivateDoubleClick->isActive () ) + d->pActivateDoubleClick->stop(); + item->rename(); + m_bMousePressed = false; + } + } + } + else + { + QMouseEvent e( QEvent::MouseMove,d->mousePos , 1, d->mouseState); + KIconView::contentsMousePressEvent( &e ); + } + if( d->pActivateDoubleClick->isActive() ) + d->pActivateDoubleClick->stop(); + + d->releaseMouseEvent = false; + d->renameItem= false; +} + +void KonqIconViewWidget::wheelEvent(QWheelEvent* e) +{ + // when scrolling with mousewheel, stop possible pending filetip + d->pFileTip->setItem( 0 ); + + if (e->state() == ControlButton) + { + if (e->delta() >= 0) + { + emit incIconSize(); + } + else + { + emit decIconSize(); + } + e->accept(); + return; + } + + KIconView::wheelEvent(e); +} + +void KonqIconViewWidget::leaveEvent( QEvent *e ) +{ + // when leaving the widget, stop possible pending filetip and icon effect + slotOnViewport(); + + KIconView::leaveEvent(e); +} + +void KonqIconViewWidget::mousePressChangeValue() +{ + //kdDebug(1203) << "KonqIconViewWidget::contentsMousePressEvent" << endl; + m_bMousePressed = true; + if (d->pSoundPlayer) + d->pSoundPlayer->stop(); + d->bSoundItemClicked = true; + d->firstClick = false; + + // Once we click on the item, we don't want a tooltip + // Fixes part of #86968 + d->pFileTip->setItem( 0 ); +} + +void KonqIconViewWidget::contentsMousePressEvent( QMouseEvent *e ) +{ + if(d->pActivateDoubleClick && d->pActivateDoubleClick->isActive ()) + d->pActivateDoubleClick->stop(); + QIconViewItem* item = findItem( e->pos() ); + m_mousePos = e->pos(); + KURL url; + if ( item ) + { + url = ( static_cast<KFileIVI *>( item ) )->item()->url(); + bool brenameTrash =false; + if ( url.isLocalFile() && (url.directory(false) == KGlobalSettings::trashPath() || url.path(1).startsWith(KGlobalSettings::trashPath()))) + brenameTrash = true; + if ( !brenameTrash && !KGlobalSettings::singleClick() && m_pSettings->renameIconDirectly() && e->button() == LeftButton && item->textRect( false ).contains(e->pos())&& !d->firstClick && url.isLocalFile() && (!url.protocol().find("device", 0, false)==0)) + { + d->firstClick = true; + d->mousePos = e->pos(); + d->mouseState = e->state(); + if (!d->pActivateDoubleClick) + { + d->pActivateDoubleClick = new QTimer(this); + connect(d->pActivateDoubleClick, SIGNAL(timeout()), this, SLOT(doubleClickTimeout())); + } + if( d->pActivateDoubleClick->isActive () ) + d->pActivateDoubleClick->stop(); + else + d->pActivateDoubleClick->start(QApplication::doubleClickInterval()); + d->releaseMouseEvent = false; + return; + } + else + d->renameItem= false; + } + else + d->renameItem= false; + mousePressChangeValue(); + if(d->pActivateDoubleClick && d->pActivateDoubleClick->isActive()) + d->pActivateDoubleClick->stop(); + KIconView::contentsMousePressEvent( e ); + +} + +void KonqIconViewWidget::contentsMouseReleaseEvent( QMouseEvent *e ) +{ + KIconView::contentsMouseReleaseEvent( e ); + if(d->releaseMouseEvent && d->pActivateDoubleClick && d->pActivateDoubleClick->isActive ()) + d->pActivateDoubleClick->stop(); + slotSelectionChanged(); + d->releaseMouseEvent = true; + m_bMousePressed = false; +} + +void KonqIconViewWidget::slotSaveIconPositions() +{ + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + // This code is currently not used but left in for compatibility reasons. + // It can be removed in KDE 4.0 + // Saving of desktop icon positions is now done in KDIconView::saveIconPositions() + // in kdebase/kdesktop/kdiconview.cc + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + + if ( m_dotDirectoryPath.isEmpty() ) + return; + if ( !m_bDesktop ) + return; // Currently not available in Konqueror + kdDebug(1214) << "KonqIconViewWidget::slotSaveIconPositions" << endl; + KSimpleConfig dotDirectory( m_dotDirectoryPath ); + QIconViewItem *it = firstItem(); + if ( !it ) + return; // No more icons. Maybe we're closing and they've been removed already + while ( it ) + { + KFileIVI *ivi = static_cast<KFileIVI *>( it ); + KFileItem *item = ivi->item(); + + dotDirectory.setGroup( QString( m_iconPositionGroupPrefix ).append( item->url().fileName() ) ); + kdDebug(1214) << "KonqIconViewWidget::slotSaveIconPositions " << item->url().fileName() << " " << it->x() << " " << it->y() << endl; + dotDirectory.writeEntry( QString( "X %1" ).arg( width() ), it->x() ); + dotDirectory.writeEntry( QString( "Y %1" ).arg( height() ), it->y() ); + dotDirectory.writeEntry( "Exists", true ); + + it = it->nextItem(); + } + + QStringList groups = dotDirectory.groupList(); + QStringList::ConstIterator gIt = groups.begin(); + QStringList::ConstIterator gEnd = groups.end(); + for (; gIt != gEnd; ++gIt ) + if ( (*gIt).left( m_iconPositionGroupPrefix.length() ) == m_iconPositionGroupPrefix ) + { + dotDirectory.setGroup( *gIt ); + if ( dotDirectory.hasKey( "Exists" ) ) + dotDirectory.deleteEntry( "Exists", false ); + else + { + kdDebug(1214) << "KonqIconViewWidget::slotSaveIconPositions deleting group " << *gIt << endl; + dotDirectory.deleteGroup( *gIt ); + } + } + + dotDirectory.sync(); + + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + // This code is currently not used but left in for compatibility reasons. + // It can be removed in KDE 4.0 + // Saving of desktop icon positions is now done in KDIconView::saveIconPositions() + // in kdebase/kdesktop/kdiconview.cc + // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +} + +// Adapted version of QIconView::insertInGrid, that works relative to +// m_IconRect, instead of the entire viewport. + +void KonqIconViewWidget::insertInGrid(QIconViewItem *item) +{ + if (0L == item) + return; + + if (!m_IconRect.isValid()) + { + KIconView::insertInGrid(item); + return; + } + + QRegion r(m_IconRect); + QIconViewItem *i = firstItem(); + int y = -1; + for (; i; i = i->nextItem() ) + { + r = r.subtract(i->rect()); + y = QMAX(y, i->y() + i->height()); + } + + QMemArray<QRect> rects = r.rects(); + QMemArray<QRect>::Iterator it = rects.begin(); + bool foundPlace = FALSE; + for (; it != rects.end(); ++it) + { + QRect rect = *it; + if (rect.width() >= item->width() && rect.height() >= item->height()) + { + int sx = 0, sy = 0; + if (rect.width() >= item->width() + spacing()) + sx = spacing(); + if (rect.height() >= item->height() + spacing()) + sy = spacing(); + item->move(rect.x() + sx, rect.y() + sy); + foundPlace = true; + break; + } + } + + if (!foundPlace) + item->move(m_IconRect.topLeft()); + + //item->dirty = false; + return; +} + + +/* + * The algorithm used for lineing up the icons could be called + * "beating flat the icon field". Imagine the icon field to be some height + * field on a regular grid, with the height being the number of icons in + * each grid element. Now imagine slamming on the field with a shovel or + * some other flat surface. The high peaks will be flattened and spread out + * over their adjacent areas. This is basically what the algorithm tries to + * simulate. + * + * First, the icons are binned to a grid of the desired size. If all bins + * are containing at most one icon, we're done, of course. We just have to + * move all icons to the center of each grid element. + * For each bin which has more than one icon in it, we calculate 4 + * "friction coefficients", one for each cardinal direction. The friction + * coefficient of a direction is the number of icons adjacent in that + * direction. The idea is that this number is somewhat a measure in which + * direction the icons should flow: icons flow in the direction of lowest + * friction coefficient. We move a maximum of one icon per bin and loop over + * all bins. This procedure is repeated some maximum number of times or until + * no icons are moved anymore. + * + * I don't know if this algorithm is good or bad, I don't even know if it will + * work all the time. It seems a correct thing to do, however, and it seems to + * work particularly well. In any case, the number of runs is limited so there + * can be no races. + */ + +void KonqIconViewWidget::lineupIcons() +{ + // even if there are no items yet, calculate the maxItemWidth to have the correct + // item rect when we insert new items + + // Create a grid of (ny x nx) bins. + int x0, y0, dx, dy, nx, ny; + gridValues( &x0, &y0, &dx, &dy, &nx, &ny ); + + int itemWidth = dx - spacing(); + bool newItemWidth = false; + if ( maxItemWidth() != itemWidth ) { + newItemWidth = true; + setMaxItemWidth( itemWidth ); + setFont( font() ); // Force calcRect() + } + + if ( !firstItem() ) { + kdDebug(1203) << "No icons at all ?\n"; + return; + } + + int iconSize = m_size ? m_size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + + typedef QValueList<QIconViewItem*> Bin; + Bin*** bins = new Bin**[nx]; + int i; + int j; + for ( i = 0; i < nx ; i++ ) { + bins[i] = new Bin*[ny]; + for ( j = 0; j < ny; j++ ) + bins[i][j] = 0L; + } + + // Insert items into grid + int textHeight = iconTextHeight() * fontMetrics().height(); + + for ( QIconViewItem* item = firstItem(); item; item = item->nextItem() ) { + int x = item->x() + item->width() / 2 - x0; + int y = item->pixmapRect( false ).bottom() - iconSize / 2 + - ( dy - ( iconSize + textHeight ) ) / 2 - y0; + int posX = QMIN( nx-1, QMAX( 0, x / dx ) ); + int posY = QMIN( ny-1, QMAX( 0, y / dy ) ); + + if ( !bins[posX][posY] ) + bins[posX][posY] = new Bin; + bins[posX][posY]->prepend( item ); + } + + // The shuffle code + int n, k; + const int infinity = 10000; + int nmoves = 1; + for ( n = 0; n < 30 && nmoves > 0; n++ ) { + nmoves = 0; + for ( i = 0; i < nx; i++ ) { + for ( j = 0; j < ny; j++ ) { + if ( !bins[i][j] || ( bins[i][j]->count() <= 1 ) ) + continue; + + // Calculate the 4 "friction coefficients". + int tf = 0, bf = 0, lf = 0, rf = 0; + for ( k = j-1; k >= 0 && bins[i][k] && bins[i][k]->count(); k-- ) + tf += bins[i][k]->count(); + if ( k == -1 ) + tf += infinity; + + for ( k = j+1; k < ny && bins[i][k] && bins[i][k]->count(); k++ ) + bf += bins[i][k]->count(); + if ( k == ny ) + bf += infinity; + + for ( k = i-1; k >= 0 && bins[k][j] && bins[k][j]->count(); k-- ) + lf += bins[k][j]->count(); + if ( k == -1 ) + lf += infinity; + + for ( k = i+1; k < nx && bins[k][j] && bins[k][j]->count(); k++ ) + rf += bins[k][j]->count(); + if ( k == nx ) + rf += infinity; + + // If we are stuck between walls, continue + if ( tf >= infinity && bf >= infinity && + lf >= infinity && rf >= infinity ) + continue; + + // Is there a preferred lineup direction? + if ( m_LineupMode == LineupHorizontal ) { + tf += infinity; + bf += infinity; + } + else if ( m_LineupMode == LineupVertical ) { + lf += infinity; + rf += infinity; + } + + // Move one item in the direction of the least friction + QIconViewItem* movedItem; + Bin* items = bins[i][j]; + + int mini = QMIN( QMIN( tf, bf ), QMIN( lf, rf ) ); + if ( tf == mini ) { + // move top item in (i,j) to (i,j-1) + Bin::iterator it = items->begin(); + movedItem = *it; + for ( ++it; it != items->end(); ++it ) { + if ( (*it)->y() < movedItem->y() ) + movedItem = *it; + } + items->remove( movedItem ); + if ( !bins[i][j-1] ) + bins[i][j-1] = new Bin; + bins[i][j-1]->prepend( movedItem ); + } + else if ( bf ==mini ) { + // move bottom item in (i,j) to (i,j+1) + Bin::iterator it = items->begin(); + movedItem = *it; + for ( ++it; it != items->end(); ++it ) { + if ( (*it)->y() > movedItem->y() ) + movedItem = *it; + } + items->remove( movedItem ); + if ( !bins[i][j+1] ) + bins[i][j+1] = new Bin; + bins[i][j+1]->prepend( movedItem ); + } + else if ( lf == mini ) + { + // move left item in (i,j) to (i-1,j) + Bin::iterator it = items->begin(); + movedItem = *it; + for ( ++it; it != items->end(); ++it ) { + if ( (*it)->x() < movedItem->x() ) + movedItem = *it; + } + items->remove( movedItem ); + if ( !bins[i-1][j] ) + bins[i-1][j] = new Bin; + bins[i-1][j]->prepend( movedItem ); + } + else { + // move right item in (i,j) to (i+1,j) + Bin::iterator it = items->begin(); + movedItem = *it; + for ( ++it; it != items->end(); ++it ) { + if ( (*it)->x() > movedItem->x() ) + movedItem = *it; + } + items->remove( movedItem ); + if ( !bins[i+1][j] ) + bins[i+1][j] = new Bin; + bins[i+1][j]->prepend( movedItem ); + } + nmoves++; + } + } + } + + // Perform the actual moving + QRegion repaintRegion; + QValueList<QIconViewItem*> movedItems; + + for ( i = 0; i < nx; i++ ) { + for ( j = 0; j < ny; j++ ) { + Bin* bin = bins[i][j]; + if ( !bin ) + continue; + if ( !bin->isEmpty() ) { + QIconViewItem* item = bin->first(); + int newX = x0 + i*dx + spacing() + + QMAX(0, ( (dx-spacing()) - item->width() ) / 2); // pixmap can be larger as iconsize + // align all icons vertically to their text + int newY = y0 + j*dy + dy - spacing() - ( item->pixmapRect().bottom() + 2 + textHeight ); + if ( item->x() != newX || item->y() != newY ) { + QRect oldRect = item->rect(); + movedItems.prepend( item ); + item->move( newX, newY ); + if ( item->rect() != oldRect ) + repaintRegion = repaintRegion.unite( oldRect ); + } + } + delete bin; + bins[i][j] = 0L; + } + } + + // repaint + if ( newItemWidth ) + updateContents(); + else { + // Repaint only repaintRegion... + QMemArray<QRect> rects = repaintRegion.rects(); + for ( uint l = 0; l < rects.count(); l++ ) { + kdDebug( 1203 ) << "Repainting (" << rects[l].x() << "," + << rects[l].y() << ")\n"; + repaintContents( rects[l], false ); + } + // Repaint icons that were moved + while ( !movedItems.isEmpty() ) { + repaintItem( movedItems.first() ); + movedItems.remove( movedItems.first() ); + } + } + + for ( i = 0; i < nx ; i++ ) { + delete [] bins[i]; + } + delete [] bins; +} + +void KonqIconViewWidget::lineupIcons( QIconView::Arrangement arrangement ) +{ + int x0, y0, dx, dy, nxmax, nymax; + gridValues( &x0, &y0, &dx, &dy, &nxmax, &nymax ); + int textHeight = iconTextHeight() * fontMetrics().height(); + + QRegion repaintRegion; + QValueList<QIconViewItem*> movedItems; + int nx = 0, ny = 0; + + QIconViewItem* item; + for ( item = firstItem(); item; item = item->nextItem() ) { + int newX = x0 + nx*dx + spacing() + + QMAX(0, ( (dx-spacing()) - item->width() ) / 2); // icon can be larger as defined + // align all icons vertically to their text + int newY = y0 + ny*dy + dy - spacing() - ( item->pixmapRect().bottom() + 2 + textHeight ); + if ( item->x() != newX || item->y() != newY ) { + QRect oldRect = item->rect(); + movedItems.prepend( item ); + item->move( newX, newY ); + if ( item->rect() != oldRect ) + repaintRegion = repaintRegion.unite( oldRect ); + } + if ( arrangement == QIconView::LeftToRight ) { + nx++; + if ( nx >= nxmax ) { + ny++; + nx = 0; + } + } + else { + ny++; + if ( ny >= nymax ) { + nx++; + ny = 0; + } + } + } + + // Repaint only repaintRegion... + QMemArray<QRect> rects = repaintRegion.rects(); + for ( uint l = 0; l < rects.count(); l++ ) { + kdDebug( 1203 ) << "Repainting (" << rects[l].x() << "," + << rects[l].y() << ")\n"; + repaintContents( rects[l], false ); + } + // Repaint icons that were moved + while ( !movedItems.isEmpty() ) { + repaintItem( movedItems.first() ); + movedItems.remove( movedItems.first() ); + } +} + +int KonqIconViewWidget::largestPreviewIconSize( int size ) const +{ + int iconSize = size ? size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + + if (iconSize < 28) + return 48; + if (iconSize < 40) + return 64; + if (iconSize < 60) + return 96; + if (iconSize < 120) + return 128; + + return 192; +} + +int KonqIconViewWidget::previewIconSize( int size ) const +{ + int iconSize = size ? size : KGlobal::iconLoader()->currentSize( KIcon::Desktop ); + + if (!d->bBoostPreview) + return iconSize; + + return largestPreviewIconSize( iconSize ); +} + +void KonqIconViewWidget::visualActivate(QIconViewItem * item) +{ + // Rect of the QIconViewItem. + QRect irect = item->rect(); + + // Rect of the QIconViewItem's pixmap area. + QRect rect = item->pixmapRect(); + + // Adjust to correct position. If this isn't done, the fact that the + // text may be wider than the pixmap puts us off-centre. + rect.moveBy(irect.x(), irect.y()); + + // Adjust for scrolling (David) + rect.moveBy( -contentsX(), -contentsY() ); + + KIconEffect::visualActivate(viewport(), rect); +} + +void KonqIconViewWidget::backgroundPixmapChange( const QPixmap & ) +{ + viewport()->update(); +} + +void KonqIconViewWidget::setPreviewSettings( const QStringList& settings ) +{ + d->previewSettings = settings; + updatePreviewMimeTypes(); + + int size = m_size; + m_size = -1; // little trick to force grid change in setIcons + setIcons( size ); // force re-determining all icons +} + +const QStringList& KonqIconViewWidget::previewSettings() +{ + return d->previewSettings; +} + +void KonqIconViewWidget::setNewURL( const QString& url ) +{ + KURL u; + if ( url.startsWith( "/" ) ) + u.setPath( url ); + else + u = url; + setURL( u ); +} + +void KonqIconViewWidget::setCaseInsensitiveSort( bool b ) +{ + d->bCaseInsensitive = b; +} + +bool KonqIconViewWidget::caseInsensitiveSort() const +{ + return d->bCaseInsensitive; +} + +bool KonqIconViewWidget::canPreview( KFileItem* item ) +{ + if ( !KGlobalSettings::showFilePreview( url() ) ) + return false; + + if ( d->pPreviewMimeTypes == 0L ) + updatePreviewMimeTypes(); + + return mimeTypeMatch( item->mimetype(), *( d->pPreviewMimeTypes ) ); +} + +void KonqIconViewWidget::updatePreviewMimeTypes() +{ + if ( d->pPreviewMimeTypes == 0L ) + d->pPreviewMimeTypes = new QStringList; + else + d->pPreviewMimeTypes->clear(); + + // Load the list of plugins to determine which mimetypes are supported + KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator"); + KTrader::OfferList::ConstIterator it; + + for ( it = plugins.begin(); it != plugins.end(); ++it ) { + if ( d->previewSettings.contains((*it)->desktopEntryName()) ) { + QStringList mimeTypes = (*it)->property("MimeTypes").toStringList(); + for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt) + d->pPreviewMimeTypes->append(*mt); + } + } +} + +#include "konq_iconviewwidget.moc" + +/* vim: set et sw=4 ts=8 softtabstop=4: */ |