goldendict-ng/src/ui/favoritespanewidget.cc

1135 lines
30 KiB
C++

/* This file is (c) 2017 Abs62
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include <QApplication>
#include <QDockWidget>
#include <QKeyEvent>
#include <QClipboard>
#include <QDomDocument>
#include <QMessageBox>
#include <QtAlgorithms>
#include <QMap>
#include <QSaveFile>
#include <QStringBuilder>
#include <QDebug>
#include <algorithm>
#include <functional>
#include "favoritespanewidget.hh"
#include "gddebug.hh"
#include "globalbroadcaster.hh"
/************************************************** FavoritesPaneWidget *********************************************/
void FavoritesPaneWidget::setUp( Config::Class * cfg, QMenu * menu )
{
m_cfg = cfg;
m_favoritesTree = findChild< QTreeView * >( "favoritesTree" );
QDockWidget * favoritesPane = qobject_cast< QDockWidget * >( parentWidget() );
m_favoritesTree->setHeaderHidden( true );
// Delete selected items action
m_deleteSelectedAction = new QAction( this );
m_deleteSelectedAction->setText( tr( "&Delete Selected" ) );
m_deleteSelectedAction->setShortcut( QKeySequence( QKeySequence::Delete ) );
m_deleteSelectedAction->setShortcutContext( Qt::WidgetWithChildrenShortcut );
addAction( m_deleteSelectedAction );
connect( m_deleteSelectedAction, &QAction::triggered, this, &FavoritesPaneWidget::deleteSelectedItems );
// Copy selected items to clipboard
m_copySelectedToClipboard = new QAction( this );
m_copySelectedToClipboard->setText( tr( "Copy Selected" ) );
m_copySelectedToClipboard->setShortcut( QKeySequence( QKeySequence::Copy ) );
m_copySelectedToClipboard->setShortcutContext( Qt::WidgetWithChildrenShortcut );
addAction( m_copySelectedToClipboard );
connect( m_copySelectedToClipboard, &QAction::triggered, this, &FavoritesPaneWidget::copySelectedItems );
// Add folder to tree view
m_addFolder = new QAction( this );
m_addFolder->setText( tr( "Add folder" ) );
addAction( m_addFolder );
connect( m_addFolder, &QAction::triggered, this, &FavoritesPaneWidget::addFolder );
// Handle context menu, reusing some of the top-level window's History menu
m_favoritesMenu = new QMenu( this );
m_separator = m_favoritesMenu->addSeparator();
QListIterator< QAction * > actionsIter( menu->actions() );
while ( actionsIter.hasNext() )
m_favoritesMenu->addAction( actionsIter.next() );
// Make the favorites pane's titlebar
favoritesLabel.setText( tr( "Favorites:" ) );
favoritesLabel.setObjectName( "favoritesLabel" );
if ( layoutDirection() == Qt::LeftToRight ) {
favoritesLabel.setAlignment( Qt::AlignLeft );
}
else {
favoritesLabel.setAlignment( Qt::AlignRight );
}
favoritesPaneTitleBarLayout.addWidget( &favoritesLabel );
favoritesPaneTitleBarLayout.setContentsMargins( 5, 5, 5, 5 );
favoritesPaneTitleBar.setLayout( &favoritesPaneTitleBarLayout );
favoritesPaneTitleBar.setObjectName( "favoritesPaneTitleBar" );
favoritesPane->setTitleBarWidget( &favoritesPaneTitleBar );
// Favorites tree
m_favoritesModel = new FavoritesModel( Config::getFavoritiesFileName(), this );
listItemDelegate = new WordListItemDelegate( m_favoritesTree->itemDelegate() );
m_favoritesTree->setItemDelegate( listItemDelegate );
QAbstractItemModel * oldModel = m_favoritesTree->model();
m_favoritesTree->setModel( m_favoritesModel );
if ( oldModel )
oldModel->deleteLater();
connect( m_favoritesTree, &QTreeView::expanded, m_favoritesModel, &FavoritesModel::itemExpanded );
connect( m_favoritesTree, &QTreeView::collapsed, m_favoritesModel, &FavoritesModel::itemCollapsed );
connect( m_favoritesModel, &FavoritesModel::expandItem, m_favoritesTree, &QTreeView::expand );
m_favoritesModel->checkAllNodesForExpand();
m_favoritesTree->viewport()->setAcceptDrops( true );
m_favoritesTree->setDragEnabled( true );
// m_favoritesTree->setDragDropMode( QAbstractItemView::InternalMove );
m_favoritesTree->setDragDropMode( QAbstractItemView::DragDrop );
m_favoritesTree->setDefaultDropAction( Qt::MoveAction );
m_favoritesTree->setRootIsDecorated( true );
m_favoritesTree->setContextMenuPolicy( Qt::CustomContextMenu );
m_favoritesTree->setSelectionMode( QAbstractItemView::ExtendedSelection );
m_favoritesTree->setEditTriggers( QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed );
m_favoritesTree->installEventFilter( this );
m_favoritesTree->viewport()->installEventFilter( this );
// list selection and keyboard navigation
connect( m_favoritesTree, &QAbstractItemView::clicked, this, &FavoritesPaneWidget::onItemClicked );
connect( m_favoritesTree->selectionModel(),
&QItemSelectionModel::selectionChanged,
this,
&FavoritesPaneWidget::onSelectionChanged );
connect( m_favoritesTree, &QWidget::customContextMenuRequested, this, &FavoritesPaneWidget::showCustomMenu );
}
FavoritesPaneWidget::~FavoritesPaneWidget()
{
if ( listItemDelegate )
delete listItemDelegate;
}
bool FavoritesPaneWidget::eventFilter( QObject * obj, QEvent * ev )
{
// unused for now
return QWidget::eventFilter( obj, ev );
}
void FavoritesPaneWidget::copySelectedItems()
{
QModelIndexList selectedIdxs = m_favoritesTree->selectionModel()->selectedIndexes();
if ( selectedIdxs.isEmpty() ) {
// nothing to do
return;
}
QStringList selectedStrings = m_favoritesModel->getTextForIndexes( selectedIdxs );
QApplication::clipboard()->setText( selectedStrings.join( QString::fromLatin1( "\n" ) ) );
}
void FavoritesPaneWidget::deleteSelectedItems()
{
QModelIndexList selectedIdxs = m_favoritesTree->selectionModel()->selectedIndexes();
if ( selectedIdxs.isEmpty() ) {
// nothing to do
return;
}
if ( m_cfg->preferences.confirmFavoritesDeletion ) {
QMessageBox mb( QMessageBox::Warning,
"GoldenDict",
tr( "All selected items will be deleted. Continue?" ),
QMessageBox::Yes | QMessageBox::No );
mb.exec();
if ( mb.result() != QMessageBox::Yes )
return;
}
m_favoritesModel->removeItemsForIndexes( selectedIdxs );
}
void FavoritesPaneWidget::showCustomMenu( QPoint const & pos )
{
QModelIndexList selectedIdxs = m_favoritesTree->selectionModel()->selectedIndexes();
m_favoritesMenu->removeAction( m_copySelectedToClipboard );
m_favoritesMenu->removeAction( m_deleteSelectedAction );
m_favoritesMenu->removeAction( m_addFolder );
m_separator->setVisible( !selectedIdxs.isEmpty() );
if ( !selectedIdxs.isEmpty() ) {
m_favoritesMenu->insertAction( m_separator, m_copySelectedToClipboard );
m_favoritesMenu->insertAction( m_separator, m_deleteSelectedAction );
}
if ( selectedIdxs.size() <= 1 ) {
m_favoritesMenu->insertAction( m_separator, m_addFolder );
m_separator->setVisible( true );
}
m_favoritesMenu->exec( m_favoritesTree->mapToGlobal( pos ) );
}
void FavoritesPaneWidget::onSelectionChanged( const QItemSelection & selection, const QItemSelection & deselected )
{
Q_UNUSED( deselected )
if ( m_favoritesTree->selectionModel()->selectedIndexes().size() != 1 || selection.indexes().isEmpty() )
return;
itemSelectionChanged = true;
emitFavoritesItemRequested( selection.indexes().front() );
}
void FavoritesPaneWidget::onItemClicked( QModelIndex const & idx )
{
if ( !itemSelectionChanged && m_favoritesTree->selectionModel()->selectedIndexes().size() == 1 ) {
emitFavoritesItemRequested( idx );
}
itemSelectionChanged = false;
}
void FavoritesPaneWidget::emitFavoritesItemRequested( QModelIndex const & idx )
{
if ( m_favoritesModel->itemType( idx ) != TreeItem::Word ) {
// Item is not headword
return;
}
QString headword = m_favoritesModel->data( idx, Qt::DisplayRole ).toString();
QString path = m_favoritesModel->pathToItem( idx );
if ( !headword.isEmpty() )
emit favoritesItemRequested( headword, path );
}
void FavoritesPaneWidget::addFolder()
{
QModelIndexList selectedIdx = m_favoritesTree->selectionModel()->selectedIndexes();
if ( selectedIdx.size() > 1 )
return;
QModelIndex folderIdx;
if ( selectedIdx.size() )
folderIdx = m_favoritesModel->addNewFolder( selectedIdx.front() );
else
folderIdx = m_favoritesModel->addNewFolder( QModelIndex() );
if ( folderIdx.isValid() )
m_favoritesTree->edit( folderIdx );
}
void FavoritesPaneWidget::addHeadword( QString const & path, QString const & headword )
{
m_favoritesModel->addNewHeadword( path, headword );
GlobalBroadcaster::instance()->folderFavoritesMap[ path ].insert( headword );
}
bool FavoritesPaneWidget::removeHeadword( QString const & path, QString const & headword )
{
GlobalBroadcaster::instance()->folderFavoritesMap[ path ].remove( headword );
return m_favoritesModel->removeHeadword( path, headword );
}
bool FavoritesPaneWidget::isHeadwordPresent( const QString & path, const QString & headword )
{
return m_favoritesModel->isHeadwordPresent( path, headword );
}
void FavoritesPaneWidget::getDataInXml( QByteArray & dataStr )
{
m_favoritesModel->getDataInXml( dataStr );
}
void FavoritesPaneWidget::getDataInPlainText( QString & dataStr )
{
m_favoritesModel->getDataInPlainText( dataStr );
}
bool FavoritesPaneWidget::setDataFromXml( QString const & dataStr )
{
return m_favoritesModel->setDataFromXml( dataStr );
}
bool FavoritesPaneWidget::setDataFromTxt( QString const & dataStr )
{
return m_favoritesModel->setDataFromTxt( dataStr );
}
void FavoritesPaneWidget::setSaveInterval( unsigned interval )
{
if ( timerId ) {
killTimer( timerId );
timerId = 0;
}
if ( interval ) {
m_favoritesModel->saveData();
timerId = startTimer( interval * 60000 );
}
}
void FavoritesPaneWidget::timerEvent( QTimerEvent * ev )
{
Q_UNUSED( ev )
m_favoritesModel->saveData();
}
void FavoritesPaneWidget::saveData()
{
m_favoritesModel->saveData();
}
/************************************************** TreeItem *********************************************/
TreeItem::TreeItem( const QVariant & data, TreeItem * parent, Type type ):
itemData( data ),
parentItem( parent ),
m_type( type ),
m_expanded( false )
{
}
TreeItem::~TreeItem()
{
qDeleteAll( childItems );
}
void TreeItem::appendChild( TreeItem * item )
{
childItems.append( item );
}
void TreeItem::insertChild( int row, TreeItem * item )
{
if ( row > childItems.count() )
row = childItems.count();
childItems.insert( row, item );
}
TreeItem * TreeItem::child( int row ) const
{
return childItems.value( row );
}
void TreeItem::deleteChild( int row )
{
if ( row < 0 || row >= childItems.count() )
return;
TreeItem * it = childItems.at( row );
childItems.removeAt( row );
delete it;
}
int TreeItem::childCount() const
{
return childItems.count();
}
QVariant TreeItem::data() const
{
return itemData;
}
void TreeItem::setData( const QVariant & newData )
{
itemData = newData;
}
int TreeItem::row() const
{
if ( parentItem )
return parentItem->childItems.indexOf( const_cast< TreeItem * >( this ) );
return 0;
}
TreeItem * TreeItem::parent()
{
return parentItem;
}
Qt::ItemFlags TreeItem::flags() const
{
Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
if ( m_type == Folder )
f |= Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
else if ( m_type == Root )
f |= Qt::ItemIsDropEnabled;
return f;
}
QString TreeItem::fullPath() const
{
// Get full path from root item
QString path;
TreeItem * par = parentItem;
for ( ;; ) {
if ( !par )
break;
path = par->data().toString() + "/" + path;
par = par->parentItem;
}
return path;
}
TreeItem * TreeItem::duplicateItem( TreeItem * newParent ) const
{
TreeItem * newItem = new TreeItem( itemData, newParent, m_type );
if ( m_type == Folder ) {
QList< TreeItem * >::const_iterator it = childItems.begin();
for ( ; it != childItems.end(); ++it )
newItem->appendChild( ( *it )->duplicateItem( newItem ) );
}
return newItem;
}
bool TreeItem::haveAncestor( TreeItem * item )
{
TreeItem * par = parentItem;
for ( ;; ) {
if ( !par )
break;
if ( par == item )
return true;
par = par->parent();
}
return false;
}
bool TreeItem::haveSameItem( TreeItem * item, bool allowSelf )
{
QList< TreeItem * >::const_iterator it = childItems.begin();
QString name = item->data().toString();
for ( ; it != childItems.end(); ++it ) {
if ( *it == item && !allowSelf )
return true;
if ( ( *it )->data().toString() == name && ( *it )->type() == item->type() && ( *it ) != item )
return true;
}
return false;
}
QStringList TreeItem::getTextFromAllChilds() const
{
QStringList list;
QList< TreeItem * >::const_iterator it = childItems.begin();
for ( ; it != childItems.end(); ++it ) {
if ( ( *it )->type() == Word ) {
QString txt = ( *it )->data().toString();
list.append( txt );
}
else // Folder
{
QStringList childList = ( *it )->getTextFromAllChilds();
list.append( childList );
}
}
return list;
}
/************************************************** FavoritesModel *********************************************/
FavoritesModel::FavoritesModel( QString favoritesFilename, QObject * parent ):
QAbstractItemModel( parent ),
m_favoritesFilename( favoritesFilename ),
rootItem( 0 ),
dirty( false )
{
readData();
dirty = false;
}
FavoritesModel::~FavoritesModel()
{
if ( rootItem )
delete rootItem;
}
Qt::ItemFlags FavoritesModel::flags( const QModelIndex & idx ) const
{
TreeItem * item = getItem( idx );
return item->flags();
}
QVariant FavoritesModel::headerData( int, Qt::Orientation, int ) const
{
return QVariant();
}
QModelIndex FavoritesModel::index( int row, int column, const QModelIndex & parentIdx ) const
{
// if(!hasIndex(row, column, parent))
// return QModelIndex();
TreeItem * parentItem = getItem( parentIdx );
TreeItem * childItem = parentItem->child( row );
if ( childItem )
return createIndex( row, column, childItem );
return QModelIndex();
}
QModelIndex FavoritesModel::parent( const QModelIndex & index ) const
{
if ( !index.isValid() )
return QModelIndex();
TreeItem * childItem = getItem( index );
if ( childItem == rootItem )
return QModelIndex();
TreeItem * parentItem = childItem->parent();
if ( parentItem == rootItem )
return QModelIndex();
return createIndex( parentItem->row(), 0, parentItem );
}
int FavoritesModel::rowCount( const QModelIndex & parent ) const
{
if ( parent.column() > 0 )
return 0;
TreeItem * parentItem = getItem( parent );
return parentItem->childCount();
}
int FavoritesModel::columnCount( const QModelIndex & ) const
{
return 1;
}
bool FavoritesModel::removeRows( int row, int count, const QModelIndex & parent )
{
TreeItem * parentItem = getItem( parent );
beginRemoveRows( parent, row, row + count - 1 );
for ( int i = 0; i < count; i++ )
parentItem->deleteChild( row );
endRemoveRows();
dirty = true;
return true;
}
bool FavoritesModel::setData( const QModelIndex & index, const QVariant & value, int role )
{
if ( role != Qt::EditRole || !index.isValid() || value.toString().isEmpty() )
return false;
QModelIndex parentIdx = parent( index );
if ( findItemInFolder( value.toString(), TreeItem::Folder, parentIdx ).isValid() ) {
// Such folder is already presented in parent folder
return false;
}
TreeItem * item = getItem( index );
item->setData( value );
dirty = true;
return true;
}
QVariant FavoritesModel::data( QModelIndex const & index, int role ) const
{
if ( !index.isValid() )
return QVariant();
TreeItem * item = getItem( index );
if ( item == rootItem )
return QVariant();
if ( role == Qt::DisplayRole || role == Qt::ToolTipRole ) {
return item->data();
}
else if ( role == Qt::DecorationRole ) {
if ( item->type() == TreeItem::Folder || item->type() == TreeItem::Root )
return QIcon( ":/icons/folder.svg" );
return QVariant();
}
if ( role == Qt::EditRole ) {
if ( item->type() == TreeItem::Folder )
return item->data();
return QVariant();
}
return QVariant();
}
Qt::DropActions FavoritesModel::supportedDropActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
void FavoritesModel::readData()
{
// Read data from "favorities" file
beginResetModel();
if ( rootItem )
delete rootItem;
rootItem = new TreeItem( QVariant(), 0, TreeItem::Root );
QFile favoritesFile( m_favoritesFilename );
if ( !favoritesFile.open( QFile::ReadOnly ) ) {
gdDebug( "No favorites file found" );
return;
}
QString errorStr;
int errorLine, errorColumn;
dom.clear();
if ( !dom.setContent( &favoritesFile, false, &errorStr, &errorLine, &errorColumn ) ) {
// Mailformed file
gdWarning( "Favorites file parsing error: %s at %d,%d\n", errorStr.toUtf8().data(), errorLine, errorColumn );
QMessageBox mb( QMessageBox::Warning, "GoldenDict", tr( "Error in favorities file" ), QMessageBox::Ok );
mb.exec();
dom.clear();
favoritesFile.close();
QFile::rename( m_favoritesFilename,
m_favoritesFilename % QStringLiteral( "." )
% QDateTime::currentDateTime().toString( QStringLiteral( "yyyyMMdd_HHmmss" ) )
% QStringLiteral( ".bad" ) );
}
else
favoritesFile.close();
QDomNode rootNode = dom.documentElement();
addFolder( rootItem, rootNode );
dom.clear();
endResetModel();
dirty = false;
}
void FavoritesModel::saveData()
{
if ( !dirty )
return;
QSaveFile tmpFile( m_favoritesFilename );
if ( !tmpFile.open( QFile::WriteOnly ) ) {
gdWarning( "Can't write favorites file, error: %s", tmpFile.errorString().toUtf8().data() );
return;
}
dom.clear();
QDomElement el = dom.createElement( "root" );
dom.appendChild( el );
storeFolder( rootItem, el );
QByteArray result( dom.toByteArray() );
if ( tmpFile.write( result ) != result.size() ) {
gdWarning( "Can't write favorites file, error: %s", tmpFile.errorString().toUtf8().data() );
return;
}
if ( tmpFile.commit() ) {
dirty = false;
}
else {
qDebug() << "Failed to save favorite file";
}
dom.clear();
}
void FavoritesModel::addFolder( TreeItem * parent, QDomNode & node )
{
QDomNodeList nodes = node.childNodes();
for ( int i = 0; i < nodes.count(); i++ ) {
QDomElement el = nodes.at( i ).toElement();
if ( el.nodeName() == "folder" ) {
// New subfolder
QString name = el.attribute( "name", "" );
TreeItem * item = new TreeItem( name, parent, TreeItem::Folder );
item->setExpanded( el.attribute( "expanded", "0" ) == "1" );
parent->appendChild( item );
addFolder( item, el );
}
else {
QString word = el.text();
parent->appendChild( new TreeItem( word, parent, TreeItem::Word ) );
GlobalBroadcaster::instance()->folderFavoritesMap[ parent->data().toString() ].insert( word );
}
}
dirty = true;
}
void FavoritesModel::storeFolder( TreeItem * folder, QDomNode & node )
{
int n = folder->childCount();
for ( int i = 0; i < n; i++ ) {
TreeItem * child = folder->child( i );
QString name = child->data().toString();
if ( child->type() == TreeItem::Folder ) {
QDomElement el = dom.createElement( "folder" );
el.setAttribute( "name", name );
el.setAttribute( "expanded", child->isExpanded() ? "1" : "0" );
node.appendChild( el );
storeFolder( child, el );
}
else {
QDomElement el = dom.createElement( "headword" );
el.appendChild( dom.createTextNode( name ) );
node.appendChild( el );
}
}
}
void FavoritesModel::itemExpanded( const QModelIndex & index )
{
if ( index.isValid() ) {
TreeItem * item = getItem( index );
item->setExpanded( true );
}
}
void FavoritesModel::itemCollapsed( const QModelIndex & index )
{
if ( index.isValid() ) {
TreeItem * item = getItem( index );
item->setExpanded( false );
}
}
void FavoritesModel::checkAllNodesForExpand()
{
checkNodeForExpand( rootItem, QModelIndex() );
}
void FavoritesModel::checkNodeForExpand( const TreeItem * item, const QModelIndex & parent )
{
for ( int i = 0; i < item->childCount(); i++ ) {
TreeItem * ch = item->child( i );
if ( ch->type() == TreeItem::Folder && ch->isExpanded() ) {
// We need to expand this node...
QModelIndex idx = index( i, 0, parent );
emit expandItem( idx );
// ...and check it for children nodes
checkNodeForExpand( item->child( i ), idx );
}
}
}
QStringList FavoritesModel::mimeTypes() const
{
return QStringList( QString::fromLatin1( FAVORITES_MIME_TYPE ) );
}
QMimeData * FavoritesModel::mimeData( const QModelIndexList & indexes ) const
{
FavoritesMimeData * data = new FavoritesMimeData();
data->setIndexesList( indexes );
return data;
}
bool FavoritesModel::dropMimeData(
const QMimeData * data, Qt::DropAction action, int row, int, const QModelIndex & par )
{
if ( action == Qt::MoveAction || action == Qt::CopyAction ) {
if ( data->hasFormat( FAVORITES_MIME_TYPE ) ) {
FavoritesMimeData const * mimeData = qobject_cast< FavoritesMimeData const * >( data );
if ( mimeData ) {
QModelIndexList const & list = mimeData->getIndexesList();
if ( list.isEmpty() )
return false;
TreeItem * parentItem = getItem( par );
QModelIndex parentIdx = par;
if ( row < 0 )
row = 0;
QList< QModelIndex >::const_iterator it = list.begin();
QList< TreeItem * > movedItems;
for ( ; it != list.end(); ++it ) {
TreeItem * item = getItem( *it );
// Check if we can copy/move this item
if ( parentItem->haveAncestor( item ) || parentItem->haveSameItem( item, action == Qt::MoveAction ) )
return false;
movedItems.append( item );
}
// Insert items to new place
beginInsertRows( parentIdx, row, row + movedItems.count() - 1 );
for ( int i = 0; i < movedItems.count(); i++ ) {
TreeItem * item = movedItems.at( i );
TreeItem * newItem = item->duplicateItem( parentItem );
parentItem->insertChild( row + i, newItem );
}
endInsertRows();
dirty = true;
return true;
}
}
}
return false;
}
QModelIndex FavoritesModel::findItemInFolder( const QString & itemName, int itemType, const QModelIndex & parentIdx )
{
TreeItem * parentItem = getItem( parentIdx );
for ( int i = 0; i < parentItem->childCount(); i++ ) {
TreeItem * item = parentItem->child( i );
if ( item->data().toString() == itemName && item->type() == itemType )
return createIndex( i, 0, item );
}
return QModelIndex();
}
TreeItem * FavoritesModel::getItem( const QModelIndex & index ) const
{
if ( index.isValid() ) {
TreeItem * item = static_cast< TreeItem * >( index.internalPointer() );
if ( item )
return item;
}
return rootItem;
}
QStringList FavoritesModel::getTextForIndexes( const QModelIndexList & idxList ) const
{
QStringList list;
QModelIndexList::const_iterator it = idxList.begin();
for ( ; it != idxList.end(); ++it ) {
TreeItem * item = getItem( *it );
if ( item->type() == TreeItem::Word )
list.append( item->data().toString() );
else
list.append( item->getTextFromAllChilds() );
}
return list;
}
void FavoritesModel::removeItemsForIndexes( const QModelIndexList & idxList )
{
// We should delete items from lowest tree level and in decreasing order
// so that first deletions won't affect the indexes for subsequent deletions.
QMap< int, QModelIndexList > itemsToDelete;
int lowestLevel = 0;
QModelIndexList::const_iterator it = idxList.begin();
for ( ; it != idxList.end(); ++it ) {
int n = level( *it );
if ( n > lowestLevel )
lowestLevel = n;
itemsToDelete[ n ].append( *it );
}
for ( int i = lowestLevel; i >= 0; i-- ) {
QModelIndexList idxSublist = itemsToDelete[ i ];
// std::greater does not work as operator < not implemented
#if __cplusplus >= 201703L
std::sort( idxSublist.begin(), idxSublist.end(), std::not_fn( std::less< QModelIndex >() ) );
#else
std::sort( idxSublist.begin(), idxSublist.end(), std::not2( std::less< QModelIndex >() ) );
#endif
it = idxSublist.begin();
for ( ; it != idxSublist.end(); ++it ) {
QModelIndex parentIdx = parent( *it );
removeRows( ( *it ).row(), 1, parentIdx );
}
}
}
QModelIndex FavoritesModel::addNewFolder( const QModelIndex & idx )
{
QModelIndex parentIdx;
if ( idx.isValid() )
parentIdx = parent( idx );
else
parentIdx = idx;
QString baseName = QString::fromLatin1( "New folder" );
// Create unique name
QString name = baseName;
if ( findItemInFolder( name, TreeItem::Folder, parentIdx ).isValid() ) {
int i;
for ( i = 1; i < 1000; i++ ) {
name = baseName + QString::number( i );
if ( !findItemInFolder( name, TreeItem::Folder, parentIdx ).isValid() )
break;
}
if ( i >= 1000 )
return QModelIndex();
}
// Create folder with unique name
TreeItem * parentItem = getItem( parentIdx );
int row;
if ( idx.isValid() ) {
// Insert after selected element
row = idx.row() + 1;
}
else {
// No selected element - add to end of root folder
row = parentItem->childCount();
}
beginInsertRows( parentIdx, row, row );
TreeItem * newFolder = new TreeItem( name, parentItem, TreeItem::Folder );
parentItem->insertChild( row, newFolder );
endInsertRows();
dirty = true;
return createIndex( row, 0, newFolder );
}
bool FavoritesModel::addNewHeadword( const QString & path, const QString & headword )
{
QModelIndex parentIdx;
// Find or create target folder
QStringList folders = path.split( "/", Qt::SkipEmptyParts );
QStringList::const_iterator it = folders.begin();
for ( ; it != folders.end(); ++it )
parentIdx = forceFolder( *it, parentIdx );
// Add headword
return addHeadword( headword, parentIdx );
}
bool FavoritesModel::removeHeadword( const QString & path, const QString & headword )
{
QModelIndex idx;
// Find target folder
QStringList folders = path.split( "/", Qt::SkipEmptyParts );
QStringList::const_iterator it = folders.begin();
for ( ; it != folders.end(); ++it ) {
idx = findItemInFolder( *it, TreeItem::Folder, idx );
if ( !idx.isValid() )
break;
}
if ( path.isEmpty() || idx.isValid() ) {
idx = findItemInFolder( headword, TreeItem::Word, idx );
if ( idx.isValid() ) {
QModelIndexList list;
list.append( idx );
removeItemsForIndexes( list );
return true;
}
}
return false;
}
bool FavoritesModel::isHeadwordPresent( const QString & path, const QString & headword )
{
QModelIndex idx;
// Find target folder
QStringList folders = path.split( "/", Qt::SkipEmptyParts );
QStringList::const_iterator it = folders.begin();
for ( ; it != folders.end(); ++it ) {
idx = findItemInFolder( *it, TreeItem::Folder, idx );
if ( !idx.isValid() )
break;
}
if ( path.isEmpty() || idx.isValid() ) {
idx = findItemInFolder( headword, TreeItem::Word, idx );
return idx.isValid();
}
return false;
}
QModelIndex FavoritesModel::forceFolder( QString const & name, const QModelIndex & parentIdx )
{
QModelIndex idx = findItemInFolder( name, TreeItem::Folder, parentIdx );
if ( idx.isValid() )
return idx;
// Folder not found, create it
TreeItem * parentItem = getItem( parentIdx );
TreeItem * newItem = new TreeItem( name, parentItem, TreeItem::Folder );
int row = parentItem->childCount();
beginInsertRows( parentIdx, row, row );
parentItem->appendChild( newItem );
endInsertRows();
dirty = true;
return createIndex( row, 0, newItem );
}
bool FavoritesModel::addHeadword( const QString & word, const QModelIndex & parentIdx )
{
QModelIndex idx = findItemInFolder( word, TreeItem::Word, parentIdx );
if ( idx.isValid() )
return false;
// Headword not found, append it
TreeItem * parentItem = getItem( parentIdx );
TreeItem * newItem = new TreeItem( word, parentItem, TreeItem::Word );
int row = parentItem->childCount();
beginInsertRows( parentIdx, row, row );
parentItem->appendChild( newItem );
endInsertRows();
dirty = true;
return true;
}
int FavoritesModel::level( QModelIndex const & idx )
{
int n = 0;
QModelIndex parentIdx = parent( idx );
while ( parentIdx.isValid() ) {
n++;
parentIdx = parent( parentIdx );
}
return n;
}
QString FavoritesModel::pathToItem( QModelIndex const & idx )
{
QString path;
QModelIndex parentIdx = parent( idx );
while ( parentIdx.isValid() ) {
if ( !path.isEmpty() )
path = "/" + path;
path = data( parentIdx, Qt::DisplayRole ).toString() + path;
parentIdx = parent( parentIdx );
}
return path;
}
void FavoritesModel::getDataInXml( QByteArray & dataStr )
{
dom.clear();
QDomElement el = dom.createElement( "root" );
dom.appendChild( el );
storeFolder( rootItem, el );
dataStr = dom.toByteArray();
dom.clear();
}
void FavoritesModel::getDataInPlainText( QString & dataStr )
{
QModelIndexList list;
list.append( QModelIndex() );
dataStr = getTextForIndexes( list ).join( QString::fromLatin1( "\n" ) );
}
bool FavoritesModel::setDataFromXml( QString const & dataStr )
{
QString errorStr;
int errorLine, errorColumn;
dom.clear();
if ( !dom.setContent( dataStr, false, &errorStr, &errorLine, &errorColumn ) ) {
// Mailformed data
gdWarning( "XML parsing error: %s at %d,%d\n", errorStr.toUtf8().data(), errorLine, errorColumn );
dom.clear();
return false;
}
beginResetModel();
if ( rootItem )
delete rootItem;
rootItem = new TreeItem( QVariant(), 0, TreeItem::Root );
QDomNode rootNode = dom.documentElement();
addFolder( rootItem, rootNode );
endResetModel();
dom.clear();
dirty = true;
return true;
}
bool FavoritesModel::setDataFromTxt( QString const & dataStr )
{
auto words = dataStr.split( '\n', Qt::SkipEmptyParts );
beginResetModel();
if ( rootItem )
delete rootItem;
rootItem = new TreeItem( QVariant(), 0, TreeItem::Root );
for ( auto const & word : words ) {
rootItem->appendChild( new TreeItem( word, rootItem, TreeItem::Word ) );
}
endResetModel();
dirty = true;
return true;
}