You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/libkonq/konq_iconviewwidget.cc

1928 lines
62 KiB

/* 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: */