File indexing completed on 2024-05-05 16:39:00

0001 /* This file is part of the KDE project
0002    Copyright (C) 1998-2003 Carsten Pfeiffer <pfeiffer@kde.org>
0003 
0004    This program is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU General Public
0006    License as published by the Free Software Foundation, version 2.
0007 
0008    This program is distributed in the hope that it will be useful,
0009    but WITHOUT ANY WARRANTY; without even the implied warranty of
0010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0011     General Public License for more details.
0012 
0013    You should have received a copy of the GNU General Public License
0014    along with this program; see the file COPYING.  If not, write to
0015    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0016    Boston, MA 02110-1301, USA.
0017 */
0018 
0019 #include "filewidget.h"
0020 
0021 #include <KActionCollection>
0022 #include <KActionMenu>
0023 #include <KCompletion>
0024 #include <KConfigGroup>
0025 #include <KFileItemActions>
0026 #include <KFileItemListProperties>
0027 #include <KLocalizedString>
0028 
0029 #include <QAbstractItemModel>
0030 #include <QAbstractItemView>
0031 #include <QEvent>
0032 #include <QKeyEvent>
0033 #include <QMenu>
0034 #include <QMimeDatabase>
0035 #include <QModelIndex>
0036 #include <QResizeEvent>
0037 #include <QTimer>
0038 
0039 #include "filefinder.h"
0040 #include "kuickdata.h"
0041 #include "kuickshow_debug.h"
0042 #include "kuickshow.h"
0043 
0044 
0045 FileWidget::FileWidget( const QUrl& url, QWidget *parent )
0046     : KDirOperator( url, parent ),
0047       m_validCompletion( false ),
0048       m_fileFinder( 0L ),
0049       m_fileItemActions( 0L )
0050 {
0051     setEnableDirHighlighting( true );
0052 
0053     KConfigGroup group(KSharedConfig::openConfig(), "Filebrowser");
0054     setViewConfig( group );
0055     readConfig( group );
0056     setView( KFile::Default );
0057 
0058     // setOnlyDoubleClickSelectsFiles( true );
0059     reloadConfiguration();
0060 
0061     completionObject()->setCompletionMode( KCompletion::CompletionAuto );
0062     dirCompletionObject()->setCompletionMode( KCompletion::CompletionAuto);
0063 
0064     slotViewChanged();
0065     connect( this, SIGNAL( viewChanged( QAbstractItemView * )),
0066          SLOT( slotViewChanged() ));
0067 
0068     connect( dirLister(), SIGNAL( clear() ), SLOT( slotItemsCleared() ));
0069     connect( dirLister(), SIGNAL( itemsDeleted( const KFileItemList&  ) ),
0070          SLOT( slotItemsDeleted( const KFileItemList& ) ));
0071 
0072     connect( this, SIGNAL( fileHighlighted( const KFileItem& )),
0073          SLOT( slotHighlighted( const KFileItem& )));
0074 
0075     connect( this, SIGNAL(urlEntered(const QUrl&)),
0076              SLOT( slotURLEntered( const QUrl& )));
0077 
0078     // should actually be KDirOperator's job!
0079     connect( this, SIGNAL( finishedLoading() ), SLOT( slotFinishedLoading() ));
0080 
0081     connect( this, SIGNAL( contextMenuAboutToShow( const KFileItem&, QMenu *) ),
0082              SLOT( slotContextMenu( const KFileItem&, QMenu *)));
0083 }
0084 
0085 FileWidget::~FileWidget()
0086 {
0087     delete m_fileFinder;
0088 }
0089 
0090 void FileWidget::initActions()
0091 {
0092     KActionCollection *coll = actionCollection();
0093     KActionMenu *menu = static_cast<KActionMenu*>( coll->action("popupMenu") );
0094 
0095     menu->addAction(coll->action("kuick_showInOtherWindow"));
0096     menu->addAction(coll->action("kuick_showInSameWindow"));
0097     menu->addAction(coll->action("kuick_showFullscreen"));
0098     menu->addSeparator();
0099 
0100     // properties dialog is now in kfile, but not at the right position,
0101     // so we move it to the real bottom
0102     menu->menu()->removeAction( coll->action( "properties" ) );
0103 /*
0104     KMenu *pMenu = menu->menu();
0105     int lastItemId = pMenu->idAt( pMenu->count() - 1 );
0106     QMenuItem *mItem = pMenu->findItem( lastItemId );
0107     if ( mItem && !mItem->isSeparator() )
0108         menu->addSeparator();
0109 */
0110     // those at the bottom
0111     menu->addAction(coll->action("kuick_print") );
0112     menu->addSeparator();
0113     menu->addAction(coll->action("properties") );
0114 }
0115 
0116 void FileWidget::reloadConfiguration()
0117 {
0118     if ( kdata->fileFilter != nameFilter() ) {
0119     // At first, our list must have folders
0120     QStringList mimes;
0121     mimes.append("inode/directory");
0122 
0123     // Then, all the images!
0124     QMimeDatabase mimedb;
0125     QList<QMimeType> l = mimedb.allMimeTypes();
0126     for (QList<QMimeType>::const_iterator it = l.begin(); it != l.end(); ++it)
0127         if (it->name().startsWith( "image/" ))
0128         mimes.append( it->name() );
0129 
0130     // Ok, show what we've done
0131     setMimeFilter (mimes);
0132     updateDir();
0133     }
0134 }
0135 
0136 bool FileWidget::hasFiles() const
0137 {
0138     return (numFiles() > 0);
0139 }
0140 
0141 void FileWidget::slotContextMenu( const KFileItem& item, QMenu *popupMenu )
0142 {
0143     bool image = isImage( item );
0144     actionCollection()->action("kuick_showInSameWindow")->setEnabled( image );
0145     actionCollection()->action("kuick_showInOtherWindow")->setEnabled( image );
0146     actionCollection()->action("kuick_showFullscreen")->setEnabled( image );
0147     actionCollection()->action("kuick_print")->setEnabled( image );
0148 
0149     KActionCollection *coll = actionCollection();
0150     KActionMenu *menu = static_cast<KActionMenu*>( coll->action("popupMenu") );
0151 
0152     menu->addAction(coll->action("kuick_showInOtherWindow"));
0153     menu->addAction(coll->action("kuick_showInSameWindow"));
0154     menu->addAction(coll->action("kuick_showFullscreen"));
0155     menu->addSeparator();
0156 
0157     if (!item.isNull()) {
0158     KFileItemList items;
0159     items.append(item);
0160     KFileItemListProperties properties( items );
0161     if ( !m_fileItemActions ) {
0162         m_fileItemActions = new KFileItemActions( this );
0163         m_fileItemActions->setParentWidget( this );
0164     }
0165     m_fileItemActions->setItemListProperties( properties );
0166     m_fileItemActions->addOpenWithActionsTo( menu->menu(), QString() );
0167     }
0168 
0169    // properties dialog is now in kfile, but not at the right position,
0170     // so we move it to the real bottom
0171     menu->menu()->removeAction( coll->action( "properties" ) );
0172 
0173     // those at the bottom
0174     menu->addAction(coll->action("kuick_print") );
0175     menu->addSeparator();
0176     menu->addAction(coll->action("properties") );
0177 }
0178 
0179 void FileWidget::findCompletion( const QString& text )
0180 {
0181     if ( text.at(0) == '/' || text.at(0) == '~' ||
0182      text.indexOf('/') != -1 ) {
0183     QString t = m_fileFinder->completion()->makeCompletion( text );
0184 
0185     if (m_fileFinder->completionMode() == KCompletion::CompletionPopup ||
0186             m_fileFinder->completionMode() == KCompletion::CompletionPopupAuto)
0187         m_fileFinder->setCompletedItems(
0188                   m_fileFinder->completion()->allMatches() );
0189     else
0190             if ( !t.isNull() )
0191                 m_fileFinder->setCompletedText( t );
0192 
0193     return;
0194     }
0195 
0196     QString file = makeDirCompletion( text );
0197     if ( file.isNull() )
0198     file = makeCompletion( text );
0199     m_validCompletion = !file.isNull();
0200 
0201     if ( m_validCompletion ) {
0202         QUrl completeUrl = url();
0203         completeUrl.setPath(completeUrl.adjusted(QUrl::RemoveFilename).path() + file);
0204         KDirOperator::setCurrentItem( completeUrl );
0205     }
0206 }
0207 
0208 bool FileWidget::eventFilter( QObject *o, QEvent *e )
0209 {
0210     if ( e->type() == QEvent::KeyPress ) {
0211     QKeyEvent *k = static_cast<QKeyEvent*>( e );
0212 
0213     if ( (k->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) == 0 ) {
0214         int key = k->key();
0215         if ( actionCollection()->action("delete")->shortcuts().contains( key ) )
0216             {
0217                 k->accept();
0218         KFileItem item = getCurrentItem( false );
0219         if ( !item.isNull() ) {
0220                     KFileItemList list;
0221                     list.append( item );
0222             del( list, this, (k->modifiers() & Qt::ShiftModifier) == 0 );
0223                 }
0224         return true;
0225         }
0226 
0227         const QString& text = k->text();
0228         if ( !text.isEmpty() && text.unicode()->isPrint() ) {
0229                 k->accept();
0230 
0231                 if ( !m_fileFinder ) {
0232             m_fileFinder = new FileFinder( this );
0233             m_fileFinder->setObjectName( "file finder" );
0234             connect( m_fileFinder, SIGNAL( completion(const QString&)),
0235                  SLOT( findCompletion( const QString& )));
0236             connect( m_fileFinder,
0237                  SIGNAL( enterDir( const QString& ) ),
0238                  SLOT( slotReturnPressed( const QString& )));
0239             m_fileFinder->move( width()  - m_fileFinder->width(),
0240                     height() - m_fileFinder->height() );
0241         }
0242 
0243         bool first = m_fileFinder->isHidden();
0244 
0245         m_fileFinder->setText( text );
0246         m_fileFinder->raise();
0247         m_fileFinder->show();
0248         m_fileFinder->setFocus();
0249         if ( first )
0250             findCompletion( text );
0251 
0252         return true;
0253         }
0254         }
0255 
0256         k->ignore();
0257     }
0258     return KDirOperator::eventFilter( o, e );
0259 }
0260 
0261 
0262 bool FileWidget::isImage( const KFileItem& item )
0263 {
0264 //     return item && !item.isDir();
0265     if ( !item.isNull() )
0266     {
0267         return item.isReadable() && item.mimetype().startsWith( "image/");
0268     }
0269     return false;
0270 }
0271 
0272 KFileItem FileWidget::gotoFirstImage()
0273 {
0274     QModelIndex modelIndex = view()->model()->index( 0, 0 );
0275     while ( modelIndex.isValid() ) {
0276         KFileItem item = fileItemFor(modelIndex);
0277     if ( isImage( item ) ) {
0278             setCurrentItem( item );
0279         return item;
0280     }
0281 
0282     modelIndex = modelIndex.sibling( modelIndex.row() + 1, modelIndex.column() );
0283     }
0284     return KFileItem();
0285 }
0286 
0287 KFileItem FileWidget::gotoLastImage()
0288 {
0289     QAbstractItemModel *model = view()->model();
0290     int numRows = model->rowCount();
0291     QModelIndex index = model->index(numRows - 1, 0); // last item
0292     while ( index.isValid() ) {
0293     KFileItem fileItem = fileItemFor( index );
0294     if (isImage( fileItem )) {
0295         setCurrentItem( fileItem );
0296             return fileItem;
0297     }
0298 
0299         index = index.parent();
0300     }
0301 
0302     return KFileItem();
0303 }
0304 
0305 KFileItem FileWidget::getNext( bool go )
0306 {
0307     KFileItem item = getItem( Next, true );
0308     if ( !item.isNull() ) {
0309     if ( go )
0310             // This needs to be done on a single shot timer because,
0311             // if the setCurrentItem() is done immediately, the current
0312             // index of the view() seems to be cleared soon afterwards by
0313             // a KCoreDirLister::completed() signal.  It is not clear what
0314             // causes the sending of this signal, but the result is that
0315             // the current index is left cleared and the next call of
0316             // getItem() will fail.
0317             QTimer::singleShot(0, this, [item, this]() { setCurrentItem(item); });
0318     return item;
0319     }
0320 
0321     return KFileItem();
0322 }
0323 
0324 KFileItem FileWidget::getPrevious( bool go )
0325 {
0326     KFileItem item = getItem( Previous, true );
0327     if ( !item.isNull() ) {
0328     if ( go )
0329             QTimer::singleShot(0, this, [item, this]() { setCurrentItem(item); });
0330     return item;
0331     }
0332 
0333     return KFileItem();
0334 }
0335 
0336 // returns a null item when there is no previous/next item/image
0337 // this sucks! Use KFileView::currentFileItem() when implemented
0338 KFileItem FileWidget::getItem( WhichItem which, bool onlyImage ) const
0339 {
0340     QModelIndex currentIndex = view()->currentIndex();
0341     if ( !currentIndex.isValid() ) {
0342         qDebug("no current index");
0343         return KFileItem();
0344     }
0345 
0346     QModelIndex index = currentIndex;
0347     KFileItem item;
0348     const int column = index.column();
0349     item = fileItemFor( currentIndex );
0350     if ( item.isNull() )
0351         qDebug("### current item is null: %s, %s", index.data().typeName(), qUtf8Printable(currentIndex.data().value<QString>()));
0352 
0353     switch( which ) {
0354     case Previous: {
0355         index = index.sibling( index.row() - 1, column );
0356         while ( index.isValid() ) {
0357             item = fileItemFor(index);
0358         if ( !item.isNull() && (!onlyImage || isImage( item )) ) {
0359                 return item;
0360         }
0361             index = index.sibling( index.row() - 1, column );
0362     }
0363 
0364         return KFileItem(); // no previous item / image
0365     }
0366 
0367     case Next: {
0368         index = index.sibling( index.row() + 1, column );
0369         while ( index.isValid() ) {
0370             item = fileItemFor(index);
0371         if ( !item.isNull() && (!onlyImage || isImage( item )) ) {
0372                 return item;
0373         }
0374             index = index.sibling( index.row() + 1, column );
0375     }
0376 
0377         return KFileItem(); // no further item / image
0378      }
0379 
0380      case Current:
0381     default:
0382         return fileItemFor(currentIndex);
0383     }
0384     return KFileItem();
0385 }
0386 
0387 void FileWidget::slotViewChanged()
0388 {
0389     view()->installEventFilter( this );
0390 }
0391 
0392 void FileWidget::slotItemsCleared()
0393 {
0394     m_currentURL.clear();
0395 }
0396 
0397 void FileWidget::slotItemsDeleted( const KFileItemList& items )
0398 {
0399     KFileItem current = getCurrentItem( false );
0400     if ( !items.contains(current) ) {
0401     return; // all ok, we already have a new current item
0402     }
0403 
0404     KFileItem next = getNext();
0405     if ( next.isNull() )
0406     next = getPrevious();
0407 
0408     if ( !next.isNull() )
0409     m_currentURL = next.url().url();
0410 }
0411 
0412 void FileWidget::slotHighlighted( const KFileItem& item )
0413 {
0414     if ( !item.isNull() ) {
0415         m_currentURL = item.url().url();
0416     }
0417     else {
0418         m_currentURL = QString();
0419     }
0420 }
0421 
0422 void FileWidget::slotReturnPressed( const QString& t )
0423 {
0424     // we need a / at the end, otherwise replacedPath() will cut off the dir,
0425     // assuming it is a filename
0426     QString text = t;
0427     if ( text.at( text.length()-1 ) != '/' )
0428     text += '/';
0429 
0430     if ( text.at(0) == '/' || text.at(0) == '~' ) {
0431     QString dir = m_fileFinder->completion()->replacedPath( text );
0432         setUrl( QUrl::fromLocalFile(dir), true );
0433     }
0434 
0435     else if ( text.indexOf('/') != (int) text.length() -1 ) { // relative path
0436     QString dir = m_fileFinder->completion()->replacedPath( text );
0437         QUrl u = url().resolved(QUrl(dir));
0438     setUrl( u, true );
0439     }
0440 
0441     else if ( m_validCompletion ) {
0442     KFileItem item = getCurrentItem( true );
0443 
0444     if ( !item.isNull() ) {
0445         if ( item.isDir() )
0446         setUrl( item.url(), true );
0447         else
0448         emit fileSelected( item );
0449     }
0450     }
0451 }
0452 
0453 void FileWidget::setInitialItem( const QUrl& url )
0454 {
0455     m_initialName = url;
0456 }
0457 
0458 void FileWidget::slotURLEntered( const QUrl& url )
0459 {
0460     if ( m_fileFinder )
0461         m_fileFinder->completion()->setDir( url );
0462 }
0463 
0464 void FileWidget::slotFinishedLoading()
0465 {
0466     const KFileItem& current = getCurrentItem( false );
0467     if ( !m_initialName.isEmpty() )
0468         setCurrentItem( m_initialName );
0469     else if ( current.isNull() ) {
0470         QModelIndex first = view()->model()->index(0, 0);
0471         if (first.isValid()) {
0472             KFileItem item = first.data(KDirModel::FileItemRole).value<KFileItem>();
0473             if (!item.isNull()) {
0474                 setCurrentItem( item );
0475             }
0476         }
0477     }
0478 
0479     m_initialName = QUrl();
0480     emit finished();
0481 }
0482 
0483 QSize FileWidget::sizeHint() const
0484 {
0485   return QSize( 300, 300 );
0486 }
0487 
0488 void FileWidget::resizeEvent( QResizeEvent *e )
0489 {
0490     KDirOperator::resizeEvent( e );
0491     if ( m_fileFinder )
0492     m_fileFinder->move( width()  - m_fileFinder->width(),
0493                 height() - m_fileFinder->height() );
0494 }