goldendict-ng/favoritespanewidget.cc

1191 lines
30 KiB
C++

/* This file is (c) 2017 Abs62
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include <QDebug>
#include <QApplication>
#include <QDockWidget>
#include <QKeyEvent>
#include <QClipboard>
#include <QDomDocument>
#include <QMessageBox>
#include <QtAlgorithms>
#include <QMap>
#include <algorithm>
#include <functional>
#include "favoritespanewidget.hh"
#include "gddebug.hh"
#include "atomic_rename.hh"
/************************************************** FavoritesPaneWidget *********************************************/
void FavoritesPaneWidget::setUp( Config::Class * cfg, QMenu * menu )
{
m_cfg = cfg;
m_favoritesTree = findChild< TreeView * >( "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, SIGNAL( triggered() ),
this, SLOT( 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, SIGNAL( triggered() ),
this, SLOT( copySelectedItems() ) );
// Add folder to tree view
m_addFolder = new QAction( this );
m_addFolder->setText( tr( "Add folder" ) );
addAction( m_addFolder );
connect( m_addFolder, SIGNAL( triggered() ), this, SLOT( 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, SIGNAL( expanded( QModelIndex ) ),
m_favoritesModel, SLOT( itemExpanded( QModelIndex ) ) );
connect( m_favoritesTree, SIGNAL( collapsed( QModelIndex ) ),
m_favoritesModel, SLOT( itemCollapsed( QModelIndex ) ) );
connect( m_favoritesModel, SIGNAL( expandItem( QModelIndex) ),
m_favoritesTree, SLOT( expand( QModelIndex ) ) );
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, SIGNAL( clicked( QModelIndex const & ) ),
this, SLOT( onItemClicked( QModelIndex const & ) ) );
connect ( m_favoritesTree->selectionModel(), SIGNAL( selectionChanged ( QItemSelection const & , QItemSelection const & ) ),
this, SLOT( onSelectionChanged( QItemSelection const & ) ) );
connect( m_favoritesTree, SIGNAL( customContextMenuRequested( QPoint const & ) ),
this, SLOT( showCustomMenu( QPoint const & ) ) );
}
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( QItemSelection const & selection )
{
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 );
}
bool FavoritesPaneWidget::removeHeadword( QString const & path, QString const & 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();
renameAtomically( m_favoritesFilename, m_favoritesFilename + ".bak" );
}
else
favoritesFile.close();
QDomNode rootNode = dom.documentElement();
addFolder( rootItem, rootNode );
dom.clear();
endResetModel();
dirty = false;
}
void FavoritesModel::saveData()
{
if( !dirty )
return;
QFile tmpFile( m_favoritesFilename + ".tmp" );
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;
}
tmpFile.close();
if( renameAtomically( tmpFile.fileName(), m_favoritesFilename ) )
dirty = false;
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 ) );
}
}
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;
}