mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-26 20:03:10 +00:00
437 lines
16 KiB
C++
437 lines
16 KiB
C++
/****************************************************************************************
|
|
* Copyright (c) 2003 Scott Wheeler <wheeler@kde.org> *
|
|
* Copyright (c) 2004 Max Howell <max.howell@methylblue.com> *
|
|
* Copyright (c) 2004-2008 Mark Kretschmann <kretschmann@kde.org> *
|
|
* Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> *
|
|
* Copyright (c) 2008 Sebastian Trueg <trueg@kde.org> *
|
|
* Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de> *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify it under *
|
|
* the terms of the GNU General Public License as published by the Free Software *
|
|
* Foundation; either version 2 of the License, or (at your option) any later *
|
|
* version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
|
|
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License along with *
|
|
* this program. If not, see <http://www.gnu.org/licenses/>. *
|
|
****************************************************************************************/
|
|
|
|
#include "CollectionSetup.h"
|
|
|
|
#include "amarokconfig.h"
|
|
#include "core/collections/Collection.h"
|
|
#include "core/support/Debug.h"
|
|
#include "core-impl/collections/support/CollectionManager.h"
|
|
#include "dialogs/DatabaseImporterDialog.h"
|
|
|
|
#include <KLocale>
|
|
#include <KGlobalSettings>
|
|
#include <KPushButton>
|
|
#include <KVBox>
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QCheckBox>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QLabel>
|
|
#include <QMenu>
|
|
|
|
CollectionSetup* CollectionSetup::s_instance;
|
|
|
|
|
|
CollectionSetup::CollectionSetup( QWidget *parent )
|
|
: QWidget( parent )
|
|
, m_rescanDirAction( new QAction( this ) )
|
|
{
|
|
m_ui.setupUi(this);
|
|
|
|
setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
|
|
|
|
setObjectName( "CollectionSetup" );
|
|
s_instance = this;
|
|
|
|
if( KGlobalSettings::graphicEffectsLevel() != KGlobalSettings::NoEffects )
|
|
m_ui.view->setAnimated( true );
|
|
connect( m_ui.view, SIGNAL(clicked(QModelIndex)),
|
|
this, SIGNAL(changed()) );
|
|
|
|
connect( m_ui.view, SIGNAL(pressed(QModelIndex)),
|
|
this, SLOT(slotPressed(QModelIndex)) );
|
|
connect( m_rescanDirAction, SIGNAL(triggered()),
|
|
this, SLOT(slotRescanDirTriggered()) );
|
|
|
|
KPushButton *rescan = new KPushButton( KIcon( "collection-rescan-amarok" ), i18n( "Full rescan" ), m_ui.buttonContainer );
|
|
rescan->setToolTip( i18n( "Rescan your entire collection. This will <i>not</i> delete any statistics." ) );
|
|
connect( rescan, SIGNAL(clicked()), CollectionManager::instance(), SLOT(startFullScan()) );
|
|
|
|
KPushButton *import = new KPushButton( KIcon( "tools-wizard" ), i18n( "Import batch file..." ), m_ui.buttonContainer );
|
|
import->setToolTip( i18n( "Import collection from file produced by amarokcollectionscanner." ) );
|
|
connect( import, SIGNAL(clicked()), this, SLOT(importCollection()) );
|
|
|
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
|
buttonLayout->addWidget( rescan );
|
|
buttonLayout->addWidget( import );
|
|
m_ui.buttonContainer->setLayout( buttonLayout );
|
|
|
|
m_recursive = new QCheckBox( i18n("&Scan folders recursively (requires full rescan if newly checked)"), m_ui.checkboxContainer );
|
|
m_monitor = new QCheckBox( i18n("&Watch folders for changes"), m_ui.checkboxContainer );
|
|
connect( m_recursive, SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
|
|
connect( m_monitor , SIGNAL(toggled(bool)), this, SIGNAL(changed()) );
|
|
|
|
QVBoxLayout *checkboxLayout = new QVBoxLayout();
|
|
checkboxLayout->addWidget( m_recursive );
|
|
checkboxLayout->addWidget( m_monitor );
|
|
m_ui.checkboxContainer->setLayout( checkboxLayout );
|
|
|
|
m_recursive->setToolTip( i18n( "If selected, Amarok will read all subfolders." ) );
|
|
m_monitor->setToolTip( i18n( "If selected, the collection folders will be watched "
|
|
"for changes.\nThe watcher will not notice changes behind symbolic links." ) );
|
|
|
|
m_recursive->setChecked( AmarokConfig::scanRecursively() );
|
|
m_monitor->setChecked( AmarokConfig::monitorChanges() );
|
|
|
|
// set the model _after_ constructing the checkboxes
|
|
m_model = new CollectionFolder::Model( this );
|
|
m_ui.view->setModel( m_model );
|
|
#ifndef Q_OS_WIN
|
|
m_ui.view->setRootIndex( m_model->setRootPath( QDir::rootPath() ) );
|
|
#else
|
|
m_ui.view->setRootIndex( m_model->setRootPath( m_model->myComputer().toString() ) );
|
|
#endif
|
|
|
|
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
|
|
QStringList dirs = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
|
|
m_model->setDirectories( dirs );
|
|
|
|
// make sure that the tree is expanded to show all selected items
|
|
foreach( const QString &dir, dirs )
|
|
{
|
|
QModelIndex index = m_model->index( dir );
|
|
m_ui.view->scrollTo( index, QAbstractItemView::EnsureVisible );
|
|
}
|
|
}
|
|
|
|
void
|
|
CollectionSetup::writeConfig()
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
AmarokConfig::setScanRecursively( recursive() );
|
|
AmarokConfig::setMonitorChanges( monitor() );
|
|
|
|
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
|
|
QStringList collectionFolders = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
|
|
|
|
if( m_model->directories() != collectionFolders )
|
|
{
|
|
debug() << "Selected collection folders: " << m_model->directories();
|
|
if( primaryCollection )
|
|
primaryCollection->setProperty( "collectionFolders", m_model->directories() );
|
|
|
|
debug() << "Old collection folders: " << collectionFolders;
|
|
CollectionManager::instance()->startFullScan();
|
|
}
|
|
}
|
|
|
|
bool
|
|
CollectionSetup::hasChanged() const
|
|
{
|
|
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
|
|
QStringList collectionFolders = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
|
|
|
|
return
|
|
m_model->directories() != collectionFolders ||
|
|
m_recursive->isChecked() != AmarokConfig::scanRecursively() ||
|
|
m_monitor->isChecked() != AmarokConfig::monitorChanges();
|
|
}
|
|
|
|
bool
|
|
CollectionSetup::recursive() const
|
|
{ return m_recursive && m_recursive->isChecked(); }
|
|
|
|
bool
|
|
CollectionSetup::monitor() const
|
|
{ return m_monitor && m_monitor->isChecked(); }
|
|
|
|
const QString
|
|
CollectionSetup::modelFilePath( const QModelIndex &index ) const
|
|
{
|
|
return m_model->filePath( index );
|
|
}
|
|
|
|
|
|
void
|
|
CollectionSetup::importCollection()
|
|
{
|
|
DatabaseImporterDialog *dlg = new DatabaseImporterDialog( this );
|
|
dlg->exec(); // be modal to avoid messing about by the user in the application
|
|
}
|
|
|
|
void
|
|
CollectionSetup::slotPressed( const QModelIndex &index )
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
// --- show context menu on right mouse button
|
|
if( ( QApplication::mouseButtons() & Qt::RightButton ) )
|
|
{
|
|
m_currDir = modelFilePath( index );
|
|
debug() << "Setting current dir to " << m_currDir;
|
|
|
|
// check if there is an sql collection covering the directory
|
|
// it's covered, so we can show the rescan option
|
|
if( isDirInCollection( m_currDir ) )
|
|
{
|
|
m_rescanDirAction->setText( i18n( "Rescan '%1'", m_currDir ) );
|
|
QMenu menu;
|
|
menu.addAction( m_rescanDirAction );
|
|
menu.exec( QCursor::pos() );
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CollectionSetup::slotRescanDirTriggered()
|
|
{
|
|
DEBUG_BLOCK
|
|
CollectionManager::instance()->startIncrementalScan( m_currDir );
|
|
}
|
|
|
|
|
|
bool
|
|
CollectionSetup::isDirInCollection( const QString& path ) const
|
|
{
|
|
DEBUG_BLOCK
|
|
|
|
Collections::Collection *primaryCollection = CollectionManager::instance()->primaryCollection();
|
|
QStringList collectionFolders = primaryCollection ? primaryCollection->property( "collectionFolders" ).toStringList() : QStringList();
|
|
|
|
KUrl url = KUrl( path );
|
|
KUrl parentUrl;
|
|
foreach( const QString &dir, collectionFolders )
|
|
{
|
|
debug() << "Collection Location: " << dir;
|
|
debug() << "path: " << path;
|
|
debug() << "scan Recursively: " << AmarokConfig::scanRecursively();
|
|
parentUrl.setPath( dir );
|
|
if ( !AmarokConfig::scanRecursively() )
|
|
{
|
|
if ( ( dir == path ) || ( QString( dir + '/' ) == path ) )
|
|
return true;
|
|
}
|
|
else //scan recursively
|
|
{
|
|
if ( parentUrl.isParentOf( path ) )
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// CLASS Model
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace CollectionFolder {
|
|
|
|
Model::Model( QObject *parent )
|
|
: QFileSystemModel( parent )
|
|
{
|
|
setFilter( QDir::AllDirs | QDir::NoDotAndDotDot );
|
|
}
|
|
|
|
Qt::ItemFlags
|
|
Model::flags( const QModelIndex &index ) const
|
|
{
|
|
Qt::ItemFlags flags = QFileSystemModel::flags( index );
|
|
const QString path = filePath( index );
|
|
if( isForbiddenPath( path ) )
|
|
flags ^= Qt::ItemIsEnabled; //disabled!
|
|
|
|
flags |= Qt::ItemIsUserCheckable;
|
|
|
|
return flags;
|
|
}
|
|
|
|
QVariant
|
|
Model::data( const QModelIndex& index, int role ) const
|
|
{
|
|
if( index.isValid() && index.column() == 0 && role == Qt::CheckStateRole )
|
|
{
|
|
const QString path = filePath( index );
|
|
if( recursive() && ancestorChecked( path ) )
|
|
return Qt::Checked; // always set children of recursively checked parents to checked
|
|
if( isForbiddenPath( path ) )
|
|
return Qt::Unchecked; // forbidden paths can never be checked
|
|
if( !m_checked.contains( path ) && descendantChecked( path ) )
|
|
return Qt::PartiallyChecked;
|
|
return m_checked.contains( path ) ? Qt::Checked : Qt::Unchecked;
|
|
}
|
|
return QFileSystemModel::data( index, role );
|
|
}
|
|
|
|
bool
|
|
Model::setData( const QModelIndex& index, const QVariant& value, int role )
|
|
{
|
|
if( index.isValid() && index.column() == 0 && role == Qt::CheckStateRole )
|
|
{
|
|
const QString path = filePath( index );
|
|
if( value.toInt() == Qt::Checked )
|
|
{
|
|
// New path selected
|
|
if( recursive() )
|
|
{
|
|
// Recursive, so clear any paths in m_checked that are made
|
|
// redundant by this new selection
|
|
QString _path = normalPath( path );
|
|
foreach( const QString &elem, m_checked )
|
|
{
|
|
if( normalPath( elem ).startsWith( _path ) )
|
|
m_checked.remove( elem );
|
|
}
|
|
}
|
|
m_checked << path;
|
|
}
|
|
else
|
|
{
|
|
// Path un-selected
|
|
m_checked.remove( path );
|
|
if( recursive() && ancestorChecked( path ) )
|
|
{
|
|
// Recursive, so we need to deal with the case of un-selecting
|
|
// an implicitly selected path
|
|
const QStringList ancestors = allCheckedAncestors( path );
|
|
QString topAncestor;
|
|
// Remove all selected ancestor of path, and find shallowest
|
|
// ancestor
|
|
foreach( QString elem, ancestors )
|
|
{
|
|
m_checked.remove( elem );
|
|
if( elem < topAncestor || topAncestor.isEmpty() )
|
|
topAncestor = elem;
|
|
}
|
|
// Check all paths reachable from topAncestor, except for
|
|
// those that are ancestors of path
|
|
checkRecursiveSubfolders( topAncestor, path );
|
|
}
|
|
}
|
|
// A check or un-check can possibly require the whole view to change,
|
|
// so we signal that the root's data is changed
|
|
emit dataChanged( QModelIndex(), QModelIndex() );
|
|
return true;
|
|
}
|
|
return QFileSystemModel::setData( index, value, role );
|
|
}
|
|
|
|
void
|
|
Model::setDirectories( QStringList &dirs )
|
|
{
|
|
m_checked.clear();
|
|
foreach( const QString &dir, dirs )
|
|
{
|
|
m_checked.insert( dir );
|
|
}
|
|
}
|
|
|
|
QStringList
|
|
Model::directories() const
|
|
{
|
|
QStringList dirs = m_checked.toList();
|
|
|
|
qSort( dirs.begin(), dirs.end() );
|
|
|
|
// we need to remove any children of selected items as
|
|
// they are redundant when recursive mode is chosen
|
|
if( recursive() )
|
|
{
|
|
foreach( const QString &dir, dirs )
|
|
{
|
|
if( ancestorChecked( dir ) )
|
|
dirs.removeAll( dir );
|
|
}
|
|
}
|
|
|
|
return dirs;
|
|
}
|
|
|
|
inline bool
|
|
Model::isForbiddenPath( const QString &path ) const
|
|
{
|
|
// we need the trailing slash otherwise we could forbid "/dev-music" for example
|
|
QString _path = normalPath( path );
|
|
return _path.startsWith( "/proc/" ) || _path.startsWith( "/dev/" ) || _path.startsWith( "/sys/" );
|
|
}
|
|
|
|
bool
|
|
Model::ancestorChecked( const QString &path ) const
|
|
{
|
|
// we need the trailing slash otherwise sibling folders with one as the prefix of the other are seen as parent/child
|
|
const QString _path = normalPath( path );
|
|
|
|
foreach( const QString &element, m_checked )
|
|
{
|
|
const QString _element = normalPath( element );
|
|
if( _path.startsWith( _element ) && _element != _path )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all checked paths that are an ancestor of
|
|
* the given path.
|
|
*/
|
|
QStringList
|
|
Model::allCheckedAncestors( const QString &path ) const
|
|
{
|
|
const QString _path = normalPath( path );
|
|
QStringList rtn;
|
|
foreach( const QString &element, m_checked )
|
|
{
|
|
const QString _element = normalPath( element );
|
|
if ( _path.startsWith( _element ) && _element != _path )
|
|
rtn << element;
|
|
}
|
|
return rtn;
|
|
}
|
|
|
|
bool
|
|
Model::descendantChecked( const QString &path ) const
|
|
{
|
|
// we need the trailing slash otherwise sibling folders with one as the prefix of the other are seen as parent/child
|
|
const QString _path = normalPath( path );
|
|
|
|
foreach( const QString& element, m_checked )
|
|
{
|
|
const QString _element = normalPath( element );
|
|
if( _element.startsWith( _path ) && _element != _path )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Model::checkRecursiveSubfolders( const QString &root, const QString &excludePath )
|
|
{
|
|
QString _root = normalPath( root );
|
|
QString _excludePath = normalPath( excludePath );
|
|
if( _root == _excludePath )
|
|
return;
|
|
QDirIterator it( _root );
|
|
while( it.hasNext() )
|
|
{
|
|
QString nextPath = it.next();
|
|
if( nextPath.endsWith( "/." ) || nextPath.endsWith( "/.." ) )
|
|
continue;
|
|
if( !_excludePath.startsWith( nextPath ) )
|
|
m_checked << nextPath;
|
|
else
|
|
checkRecursiveSubfolders( nextPath, excludePath );
|
|
}
|
|
}
|
|
|
|
} //namespace Collection
|