/* This file is (c) 2017 Abs62 * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include #include #include #include #include #include #include #include #include #include #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< 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, 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 ); QItemSelectionModel * oldModel = m_favoritesTree->selectionModel(); m_favoritesTree->setModel( m_favoritesModel ); 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 ); } 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 ); } 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(); } /************************************************** 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 ) { saveData(); 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.png" ); 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 ) ) { gdWarning( "No favorities file found" ); return; } QString errorStr; int errorLine, errorColumn; dom.clear(); if ( !dom.setContent( &favoritesFile, false, &errorStr, &errorLine, &errorColumn ) ) { // Mailformed file QString errStr; errStr.sprintf( "Error: %s at %d,%d\n", errorStr.toLocal8Bit().constData(), errorLine, errorColumn ); gdWarning( errStr.toUtf8().data() ); 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( "text/plain" ) ); } QMimeData *FavoritesModel::mimeData( const QModelIndexList &indexes ) const { QString headwords; QList< QModelIndex >::const_iterator it = indexes.begin(); for ( ; it != indexes.end(); ++it ) { TreeItem *item = getItem( *it ); headwords += item->fullPath() + "\n\r"; headwords += item->data().toString() + "\n\r"; headwords += QString::number( item->type() ) + "\n\r"; } QMimeData *data = new QMimeData(); data->setText( headwords ); return data; } bool FavoritesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int, const QModelIndex &par) { if( action == Qt::MoveAction || action == Qt::CopyAction ) { QList< QModelIndex > list; list = itemsListFromText( data->text() ); 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(); } QModelIndex FavoritesModel::findItem( const QString & path, const QString & headword, int type ) { TreeItem *par = rootItem; if( !path.isEmpty() ) { // Find target folder QStringList folders = path.split( "/", QString::SkipEmptyParts ); QStringList::const_iterator it = folders.begin(); for( ; it != folders.end(); ++it ) { int i; for( i = 0; i < par->childCount(); i++ ) { TreeItem * item = par->child( i ); QString name = item->data().toString(); if( *it == name && item->type() == TreeItem::Folder ) break; } if( i >= par->childCount() ) // Folder not found return QModelIndex(); par = par->child( i ); } } // Find headword/folder in target folder for( int i = 0; i < par->childCount(); i++ ) { TreeItem * item = par->child( i ); QString name = item->data().toString(); if( name == headword && item->type() == type ) { // Item found, create index return createIndex( i, 0, item ); } } return QModelIndex(); } QList< QModelIndex > FavoritesModel::itemsListFromText( const QString & text ) { int pos = 0; QString delim = QString::fromLatin1( "\n\r" ); QList< QModelIndex > list; for( ; ; ) { int delimPos = text.indexOf( delim, pos ); if( delimPos < 0 ) break; QString path = text.mid( pos, delimPos - pos ); pos = delimPos + 2; delimPos = text.indexOf( delim, pos ); if( delimPos < 0 ) break; QString headword = text.mid( pos, delimPos - pos ); pos = delimPos + 2; delimPos = text.indexOf( delim, pos ); if( delimPos < 0 ) break; int type = text.mid( pos, delimPos - pos ).toInt(); QModelIndex idx = findItem( path, headword, type ); if( idx.isValid() ) list.append( idx ); pos = delimPos + 2; } return list; } 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 ]; qSort( idxSublist.begin(), idxSublist.end(), qGreater< QModelIndex >() ); 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( "/", QString::SkipEmptyParts ); QStringList::const_iterator it = folders.begin(); for( ; it != folders.end(); ++it ) parentIdx = forceFolder( *it, parentIdx ); // Add headword return addHeadword( headword, parentIdx ); } 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 QString errStr; errStr.sprintf( "Error: %s at %d,%d\n", errorStr.toLocal8Bit().constData(), errorLine, errorColumn ); gdWarning( errStr.toUtf8().data() ); 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; }