1 /* This file is (c) 2017 Abs62
2  * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
3 
4 #include <QDebug>
5 #include <QApplication>
6 #include <QDockWidget>
7 #include <QKeyEvent>
8 #include <QClipboard>
9 #include <QDomDocument>
10 #include <QMessageBox>
11 #include <QtAlgorithms>
12 #include <QMap>
13 
14 #include "favoritespanewidget.hh"
15 #include "gddebug.hh"
16 #include "atomic_rename.hh"
17 
18 /************************************************** FavoritesPaneWidget *********************************************/
19 
setUp(Config::Class * cfg,QMenu * menu)20 void FavoritesPaneWidget::setUp( Config::Class * cfg, QMenu * menu )
21 {
22   m_cfg = cfg;
23   m_favoritesTree = findChild< TreeView * >( "favoritesTree" );
24   QDockWidget * favoritesPane = qobject_cast< QDockWidget * >( parentWidget() );
25   m_favoritesTree->setHeaderHidden( true );
26 
27   // Delete selected items action
28   m_deleteSelectedAction = new QAction( this );
29   m_deleteSelectedAction->setText( tr( "&Delete Selected" ) );
30   m_deleteSelectedAction->setShortcut( QKeySequence( QKeySequence::Delete ) );
31   m_deleteSelectedAction->setShortcutContext( Qt::WidgetWithChildrenShortcut );
32   addAction( m_deleteSelectedAction );
33   connect( m_deleteSelectedAction, SIGNAL( triggered() ),
34            this, SLOT( deleteSelectedItems() ) );
35 
36   // Copy selected items to clipboard
37   m_copySelectedToClipboard = new QAction( this );
38   m_copySelectedToClipboard->setText( tr( "Copy Selected" ) );
39   m_copySelectedToClipboard->setShortcut( QKeySequence( QKeySequence::Copy ) );
40   m_copySelectedToClipboard->setShortcutContext( Qt::WidgetWithChildrenShortcut );
41   addAction( m_copySelectedToClipboard );
42   connect( m_copySelectedToClipboard, SIGNAL( triggered() ),
43            this, SLOT( copySelectedItems() ) );
44 
45   // Add folder to tree view
46   m_addFolder = new QAction( this );
47   m_addFolder->setText( tr( "Add folder" ) );
48   addAction( m_addFolder );
49   connect( m_addFolder, SIGNAL( triggered() ), this, SLOT( addFolder() ) );
50 
51 
52   // Handle context menu, reusing some of the top-level window's History menu
53   m_favoritesMenu = new QMenu( this );
54   m_separator = m_favoritesMenu->addSeparator();
55   QListIterator< QAction * > actionsIter( menu->actions() );
56   while ( actionsIter.hasNext() )
57     m_favoritesMenu->addAction( actionsIter.next() );
58 
59   // Make the favorites pane's titlebar
60 
61   favoritesLabel.setText( tr( "Favorites:" ) );
62   favoritesLabel.setObjectName( "favoritesLabel" );
63   if ( layoutDirection() == Qt::LeftToRight )
64   {
65     favoritesLabel.setAlignment( Qt::AlignLeft );
66   }
67   else
68   {
69     favoritesLabel.setAlignment( Qt::AlignRight );
70   }
71 
72   favoritesPaneTitleBarLayout.addWidget( &favoritesLabel );
73   favoritesPaneTitleBarLayout.setContentsMargins(5, 5, 5, 5);
74   favoritesPaneTitleBar.setLayout( &favoritesPaneTitleBarLayout );
75   favoritesPaneTitleBar.setObjectName("favoritesPaneTitleBar");
76   favoritesPane->setTitleBarWidget( &favoritesPaneTitleBar );
77 
78   // Favorites tree
79   m_favoritesModel = new FavoritesModel( Config::getFavoritiesFileName(), this );
80 
81   listItemDelegate = new WordListItemDelegate( m_favoritesTree->itemDelegate() );
82   m_favoritesTree->setItemDelegate( listItemDelegate );
83 
84   QAbstractItemModel * oldModel = m_favoritesTree->model();
85   m_favoritesTree->setModel( m_favoritesModel );
86   if( oldModel )
87     oldModel->deleteLater();
88 
89   connect( m_favoritesTree, SIGNAL( expanded( QModelIndex ) ),
90            m_favoritesModel, SLOT( itemExpanded( QModelIndex ) ) );
91 
92   connect( m_favoritesTree, SIGNAL( collapsed( QModelIndex ) ),
93            m_favoritesModel, SLOT( itemCollapsed( QModelIndex ) ) );
94 
95   connect( m_favoritesModel, SIGNAL( expandItem( QModelIndex) ),
96            m_favoritesTree, SLOT( expand( QModelIndex ) ) );
97 
98   m_favoritesModel->checkAllNodesForExpand();
99   m_favoritesTree->viewport()->setAcceptDrops( true );
100   m_favoritesTree->setDragEnabled( true );
101 //  m_favoritesTree->setDragDropMode( QAbstractItemView::InternalMove );
102   m_favoritesTree->setDragDropMode( QAbstractItemView::DragDrop );
103   m_favoritesTree->setDefaultDropAction( Qt::MoveAction );
104 
105   m_favoritesTree->setRootIsDecorated( true );
106 
107   m_favoritesTree->setContextMenuPolicy( Qt::CustomContextMenu );
108   m_favoritesTree->setSelectionMode( QAbstractItemView::ExtendedSelection );
109 
110   m_favoritesTree->setEditTriggers( QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed );
111 
112   m_favoritesTree->installEventFilter( this );
113   m_favoritesTree->viewport()->installEventFilter( this );
114 
115   // list selection and keyboard navigation
116   connect( m_favoritesTree, SIGNAL( clicked( QModelIndex const & ) ),
117            this, SLOT( onItemClicked( QModelIndex const & ) ) );
118 
119   connect ( m_favoritesTree->selectionModel(), SIGNAL( selectionChanged ( QItemSelection const & , QItemSelection const & ) ),
120       this, SLOT( onSelectionChanged( QItemSelection const & ) ) );
121 
122   connect( m_favoritesTree, SIGNAL( customContextMenuRequested( QPoint const & ) ),
123            this, SLOT( showCustomMenu( QPoint const & ) ) );
124 }
125 
~FavoritesPaneWidget()126 FavoritesPaneWidget::~FavoritesPaneWidget()
127 {
128   if( listItemDelegate )
129     delete listItemDelegate;
130 }
131 
eventFilter(QObject * obj,QEvent * ev)132 bool FavoritesPaneWidget::eventFilter( QObject * obj, QEvent * ev )
133 {
134   // unused for now
135 
136   return QWidget::eventFilter( obj, ev );
137 }
138 
copySelectedItems()139 void FavoritesPaneWidget::copySelectedItems()
140 {
141   QModelIndexList selectedIdxs = m_favoritesTree->selectionModel()->selectedIndexes();
142 
143   if ( selectedIdxs.isEmpty() )
144   {
145     // nothing to do
146     return;
147   }
148 
149   QStringList selectedStrings = m_favoritesModel->getTextForIndexes( selectedIdxs );
150 
151   QApplication::clipboard()->setText( selectedStrings.join( QString::fromLatin1( "\n" ) ) );
152 }
153 
deleteSelectedItems()154 void FavoritesPaneWidget::deleteSelectedItems()
155 {
156   QModelIndexList selectedIdxs = m_favoritesTree->selectionModel()->selectedIndexes();
157 
158   if ( selectedIdxs.isEmpty() )
159   {
160     // nothing to do
161     return;
162   }
163 
164   if( m_cfg->preferences.confirmFavoritesDeletion )
165   {
166     QMessageBox mb( QMessageBox::Warning, "GoldenDict",
167                     tr( "All selected items will be deleted. Continue?" ),
168                     QMessageBox::Yes | QMessageBox::No );
169     mb.exec();
170     if( mb.result() != QMessageBox::Yes )
171       return;
172   }
173 
174   m_favoritesModel->removeItemsForIndexes( selectedIdxs );
175 }
176 
showCustomMenu(QPoint const & pos)177 void FavoritesPaneWidget::showCustomMenu(QPoint const & pos)
178 {
179   QModelIndexList selectedIdxs = m_favoritesTree->selectionModel()->selectedIndexes();
180 
181   m_favoritesMenu->removeAction( m_copySelectedToClipboard );
182   m_favoritesMenu->removeAction( m_deleteSelectedAction );
183   m_favoritesMenu->removeAction( m_addFolder );
184 
185   m_separator->setVisible( !selectedIdxs.isEmpty() );
186 
187   if ( !selectedIdxs.isEmpty() )
188   {
189     m_favoritesMenu->insertAction( m_separator, m_copySelectedToClipboard );
190     m_favoritesMenu->insertAction( m_separator, m_deleteSelectedAction );
191   }
192 
193   if( selectedIdxs.size() <= 1 )
194   {
195     m_favoritesMenu->insertAction( m_separator, m_addFolder );
196     m_separator->setVisible( true );
197   }
198 
199   m_favoritesMenu->exec( m_favoritesTree->mapToGlobal( pos ) );
200 }
201 
onSelectionChanged(QItemSelection const & selection)202 void FavoritesPaneWidget::onSelectionChanged( QItemSelection const & selection )
203 {
204   if ( m_favoritesTree->selectionModel()->selectedIndexes().size() != 1
205        || selection.indexes().isEmpty() )
206     return;
207 
208   itemSelectionChanged = true;
209   emitFavoritesItemRequested( selection.indexes().front() );
210 }
211 
onItemClicked(QModelIndex const & idx)212 void FavoritesPaneWidget::onItemClicked( QModelIndex const & idx )
213 {
214   if ( !itemSelectionChanged && m_favoritesTree->selectionModel()->selectedIndexes().size() == 1 )
215   {
216     emitFavoritesItemRequested( idx );
217   }
218   itemSelectionChanged = false;
219 }
220 
emitFavoritesItemRequested(QModelIndex const & idx)221 void FavoritesPaneWidget::emitFavoritesItemRequested( QModelIndex const & idx )
222 {
223   if( m_favoritesModel->itemType( idx ) != TreeItem::Word )
224   {
225     // Item is not headword
226     return;
227   }
228 
229   QString headword = m_favoritesModel->data( idx, Qt::DisplayRole ).toString();
230   QString path = m_favoritesModel->pathToItem( idx );
231 
232   if( !headword.isEmpty() )
233     emit favoritesItemRequested( headword, path );
234 }
235 
addFolder()236 void FavoritesPaneWidget::addFolder()
237 {
238   QModelIndexList selectedIdx = m_favoritesTree->selectionModel()->selectedIndexes();
239   if( selectedIdx.size() > 1 )
240     return;
241 
242   QModelIndex folderIdx;
243   if( selectedIdx.size() )
244     folderIdx = m_favoritesModel->addNewFolder( selectedIdx.front() );
245   else
246     folderIdx = m_favoritesModel->addNewFolder( QModelIndex() );
247 
248   if( folderIdx.isValid() )
249     m_favoritesTree->edit( folderIdx );
250 }
251 
addHeadword(QString const & path,QString const & headword)252 void FavoritesPaneWidget::addHeadword( QString const & path, QString const & headword )
253 {
254   m_favoritesModel->addNewHeadword( path, headword );
255 }
256 
removeHeadword(QString const & path,QString const & headword)257 bool FavoritesPaneWidget::removeHeadword( QString const & path, QString const & headword )
258 {
259   return m_favoritesModel->removeHeadword( path, headword );
260 }
261 
isHeadwordPresent(const QString & path,const QString & headword)262 bool FavoritesPaneWidget::isHeadwordPresent( const QString & path, const QString & headword )
263 {
264   return m_favoritesModel->isHeadwordPresent( path, headword );
265 }
266 
getDataInXml(QByteArray & dataStr)267 void FavoritesPaneWidget::getDataInXml( QByteArray & dataStr )
268 {
269   m_favoritesModel->getDataInXml( dataStr );
270 }
271 
getDataInPlainText(QString & dataStr)272 void FavoritesPaneWidget::getDataInPlainText( QString & dataStr )
273 {
274   m_favoritesModel->getDataInPlainText( dataStr );
275 }
276 
setDataFromXml(QString const & dataStr)277 bool FavoritesPaneWidget::setDataFromXml( QString const & dataStr )
278 {
279   return m_favoritesModel->setDataFromXml( dataStr );
280 }
281 
setSaveInterval(unsigned interval)282 void FavoritesPaneWidget::setSaveInterval( unsigned interval )
283 {
284   if( timerId )
285   {
286     killTimer( timerId );
287     timerId = 0;
288   }
289   if( interval )
290   {
291     m_favoritesModel->saveData();
292     timerId = startTimer( interval * 60000 );
293   }
294 }
295 
timerEvent(QTimerEvent * ev)296 void FavoritesPaneWidget::timerEvent( QTimerEvent * ev )
297 {
298   Q_UNUSED( ev )
299   m_favoritesModel->saveData();
300 }
301 
saveData()302 void FavoritesPaneWidget::saveData()
303 {
304   m_favoritesModel->saveData();
305 }
306 
307 /************************************************** TreeItem *********************************************/
308 
TreeItem(const QVariant & data,TreeItem * parent,Type type)309 TreeItem::TreeItem( const QVariant &data, TreeItem *parent, Type type ) :
310   itemData( data ),
311   parentItem( parent ),
312   m_type( type ),
313   m_expanded( false )
314 {
315 }
316 
~TreeItem()317 TreeItem::~TreeItem()
318 {
319   qDeleteAll( childItems );
320 }
321 
appendChild(TreeItem * item)322 void TreeItem::appendChild( TreeItem *item )
323 {
324   childItems.append( item );
325 }
326 
insertChild(int row,TreeItem * item)327 void TreeItem::insertChild( int row, TreeItem * item )
328 {
329   if( row > childItems.count() )
330     row = childItems.count();
331   childItems.insert( row, item );
332 }
333 
child(int row) const334 TreeItem *TreeItem::child( int row ) const
335 {
336   return childItems.value( row );
337 }
338 
deleteChild(int row)339 void TreeItem::deleteChild( int row )
340 {
341   if( row < 0 || row >= childItems.count() )
342     return;
343 
344   TreeItem *it = childItems.at( row );
345   childItems.removeAt( row );
346   delete it;
347 }
348 
childCount() const349 int TreeItem::childCount() const
350 {
351   return childItems.count();
352 }
353 
data() const354 QVariant TreeItem::data() const
355 {
356   return itemData;
357 }
358 
setData(const QVariant & newData)359 void TreeItem::setData( const QVariant & newData )
360 {
361   itemData = newData;
362 }
363 
row() const364 int TreeItem::row() const
365 {
366   if( parentItem )
367       return parentItem->childItems.indexOf( const_cast< TreeItem * >( this ) );
368 
369   return 0;
370 }
371 
parent()372 TreeItem *TreeItem::parent()
373 {
374   return parentItem;
375 }
376 
flags() const377 Qt::ItemFlags TreeItem::flags() const
378 {
379   Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable |
380                     Qt::ItemIsDragEnabled;
381   if( m_type == Folder )
382     f |= Qt::ItemIsEditable | Qt::ItemIsDropEnabled;
383   else
384   if( m_type == Root )
385     f |= Qt::ItemIsDropEnabled;
386 
387   return f;
388 }
389 
fullPath() const390 QString TreeItem::fullPath() const
391 {
392   // Get full path from root item
393   QString path;
394   TreeItem * par = parentItem;
395   for( ; ; )
396   {
397     if( !par )
398       break;
399     path = par->data().toString() + "/" + path;
400     par = par->parentItem;
401   }
402   return path;
403 }
404 
duplicateItem(TreeItem * newParent) const405 TreeItem * TreeItem::duplicateItem( TreeItem * newParent ) const
406 {
407   TreeItem * newItem = new TreeItem( itemData, newParent, m_type );
408   if( m_type == Folder )
409   {
410     QList< TreeItem * >::const_iterator it = childItems.begin();
411     for( ; it != childItems.end(); ++it )
412       newItem->appendChild( (*it)->duplicateItem( newItem ) );
413   }
414   return newItem;
415 }
416 
haveAncestor(TreeItem * item)417 bool TreeItem::haveAncestor( TreeItem * item )
418 {
419   TreeItem *par = parentItem;
420   for( ; ; )
421   {
422     if( !par )
423       break;
424     if( par == item )
425       return true;
426     par = par->parent();
427   }
428   return false;
429 }
430 
haveSameItem(TreeItem * item,bool allowSelf)431 bool TreeItem::haveSameItem( TreeItem * item, bool allowSelf )
432 {
433   QList< TreeItem * >::const_iterator it = childItems.begin();
434   QString name = item->data().toString();
435   for( ; it != childItems.end(); ++it )
436   {
437     if( *it == item && !allowSelf )
438       return true;
439     if( (*it)->data().toString() == name && (*it)->type() == item->type() && (*it) != item )
440       return true;
441   }
442 
443   return false;
444 }
445 
getTextFromAllChilds() const446 QStringList TreeItem::getTextFromAllChilds() const
447 {
448   QStringList list;
449   QList< TreeItem * >::const_iterator it = childItems.begin();
450   for( ; it != childItems.end(); ++it )
451   {
452     if( (*it)->type() == Word )
453     {
454       QString txt = (*it)->data().toString();
455       list.append( txt );
456     }
457     else // Folder
458     {
459       QStringList childList = (*it)->getTextFromAllChilds();
460       list.append( childList );
461     }
462   }
463   return list;
464 }
465 
466 /************************************************** FavoritesModel *********************************************/
467 
FavoritesModel(QString favoritesFilename,QObject * parent)468 FavoritesModel::FavoritesModel( QString favoritesFilename, QObject * parent ) :
469   QAbstractItemModel( parent ),
470   m_favoritesFilename( favoritesFilename ),
471   rootItem( 0 ),
472   dirty( false )
473 {
474   readData();
475   dirty = false;
476 }
477 
~FavoritesModel()478 FavoritesModel::~FavoritesModel()
479 {
480   if( rootItem )
481     delete rootItem;
482 }
483 
flags(const QModelIndex & idx) const484 Qt::ItemFlags FavoritesModel::flags( const QModelIndex &idx ) const
485 {
486   TreeItem * item = getItem( idx );
487   return item->flags();
488 }
489 
headerData(int,Qt::Orientation,int) const490 QVariant FavoritesModel::headerData( int , Qt::Orientation,
491                                      int ) const
492 {
493   return QVariant();
494 }
495 
index(int row,int column,const QModelIndex & parentIdx) const496 QModelIndex FavoritesModel::index( int row, int column, const QModelIndex &parentIdx ) const
497 {
498 //  if(!hasIndex(row, column, parent))
499 //    return QModelIndex();
500 
501   TreeItem *parentItem = getItem( parentIdx );
502 
503   TreeItem *childItem = parentItem->child(row);
504   if( childItem )
505     return createIndex( row, column, childItem );
506 
507   return QModelIndex();
508 }
509 
parent(const QModelIndex & index) const510 QModelIndex FavoritesModel::parent( const QModelIndex &index ) const
511 {
512   if ( !index.isValid() )
513     return QModelIndex();
514 
515   TreeItem *childItem = getItem( index );
516   if( childItem == rootItem )
517     return QModelIndex();
518 
519   TreeItem *parentItem = childItem->parent();
520 
521   if( parentItem == rootItem )
522     return QModelIndex();
523 
524   return createIndex( parentItem->row(), 0, parentItem );
525 }
526 
rowCount(const QModelIndex & parent) const527 int FavoritesModel::rowCount(const QModelIndex &parent) const
528 {
529   if ( parent.column() > 0 )
530     return 0;
531 
532   TreeItem *parentItem = getItem( parent );
533 
534   return parentItem->childCount();
535 }
536 
columnCount(const QModelIndex &) const537 int FavoritesModel::columnCount(const QModelIndex & ) const
538 {
539   return 1;
540 }
541 
removeRows(int row,int count,const QModelIndex & parent)542 bool FavoritesModel::removeRows( int row, int count, const QModelIndex &parent )
543 {
544   TreeItem * parentItem = getItem( parent );
545 
546   beginRemoveRows( parent, row, row + count - 1 );
547 
548   for( int i = 0; i < count; i++ )
549     parentItem->deleteChild( row );
550 
551   endRemoveRows();
552 
553   dirty = true;
554 
555   return true;
556 }
557 
setData(const QModelIndex & index,const QVariant & value,int role)558 bool FavoritesModel::setData( const QModelIndex & index, const QVariant & value, int role )
559 {
560   if( role != Qt::EditRole || !index.isValid() || value.toString().isEmpty() )
561     return false;
562 
563   QModelIndex parentIdx = parent( index );
564   if( findItemInFolder( value.toString(), TreeItem::Folder, parentIdx ).isValid() )
565   {
566     // Such folder is already presented in parent folder
567     return false;
568   }
569 
570   TreeItem * item = getItem( index );
571   item->setData( value );
572 
573   dirty = true;
574 
575   return true;
576 }
577 
data(QModelIndex const & index,int role) const578 QVariant FavoritesModel::data( QModelIndex const & index, int role ) const
579 {
580   if( !index.isValid() )
581     return QVariant();
582 
583   TreeItem *item = getItem( index );
584   if( item == rootItem )
585     return QVariant();
586 
587   if ( role == Qt::DisplayRole || role == Qt::ToolTipRole )
588   {
589     return item->data();
590   }
591   else
592   if( role == Qt::DecorationRole )
593   {
594     if( item->type() == TreeItem::Folder || item->type() == TreeItem::Root )
595       return QIcon( ":/icons/folder.png" );
596 
597     return QVariant();
598   }
599   if( role == Qt::EditRole )
600   {
601     if( item->type() == TreeItem::Folder )
602       return item->data();
603 
604     return QVariant();
605   }
606 
607   return QVariant();
608 }
609 
supportedDropActions() const610 Qt::DropActions FavoritesModel::supportedDropActions() const
611 {
612   return Qt::MoveAction | Qt::CopyAction;
613 }
614 
readData()615 void FavoritesModel::readData()
616 {
617   // Read data from "favorities" file
618 
619   beginResetModel();
620 
621   if( rootItem )
622     delete rootItem;
623 
624   rootItem = new TreeItem( QVariant(), 0, TreeItem::Root );
625 
626   QFile favoritesFile( m_favoritesFilename );
627   if( !favoritesFile.open( QFile::ReadOnly ) )
628   {
629     gdWarning( "No favorities file found" );
630     return;
631   }
632 
633   QString errorStr;
634   int errorLine, errorColumn;
635   dom.clear();
636 
637   if ( !dom.setContent( &favoritesFile, false, &errorStr, &errorLine, &errorColumn  ) )
638   {
639     // Mailformed file
640     gdWarning( "Favorites file parsing error: %s at %d,%d\n", errorStr.toUtf8().data(),  errorLine,  errorColumn );
641 
642     QMessageBox mb( QMessageBox::Warning, "GoldenDict",
643                       tr( "Error in favorities file" ),
644                       QMessageBox::Ok );
645     mb.exec();
646 
647     dom.clear();
648     favoritesFile.close();
649     renameAtomically( m_favoritesFilename, m_favoritesFilename + ".bak" );
650   }
651   else
652     favoritesFile.close();
653 
654   QDomNode rootNode = dom.documentElement();
655   addFolder( rootItem, rootNode );
656   dom.clear();
657 
658   endResetModel();
659   dirty = false;
660 }
661 
saveData()662 void FavoritesModel::saveData()
663 {
664   if( !dirty )
665     return;
666 
667   QFile tmpFile( m_favoritesFilename + ".tmp" );
668   if( !tmpFile.open( QFile::WriteOnly ) )
669   {
670     gdWarning( "Can't write favorites file, error: %s", tmpFile.errorString().toUtf8().data() );
671     return;
672   }
673 
674   dom.clear();
675 
676   QDomElement el = dom.createElement( "root" );
677   dom.appendChild( el );
678   storeFolder( rootItem, el );
679 
680   QByteArray result( dom.toByteArray() );
681 
682   if ( tmpFile.write( result ) != result.size() )
683   {
684     gdWarning( "Can't write favorites file, error: %s", tmpFile.errorString().toUtf8().data() );
685     return;
686   }
687 
688   tmpFile.close();
689 
690   if( renameAtomically( tmpFile.fileName(), m_favoritesFilename ) )
691     dirty = false;
692 
693   dom.clear();
694 }
695 
addFolder(TreeItem * parent,QDomNode & node)696 void FavoritesModel::addFolder( TreeItem *parent, QDomNode &node )
697 {
698   QDomNodeList nodes = node.childNodes();
699   for( int i = 0; i < nodes.count(); i++ )
700   {
701     QDomElement el = nodes.at( i ).toElement();
702     if( el.nodeName() == "folder" )
703     {
704       // New subfolder
705       QString name = el.attribute( "name", "" );
706       TreeItem *item = new TreeItem( name, parent, TreeItem::Folder );
707       item->setExpanded( el.attribute( "expanded", "0" ) == "1" );
708       parent->appendChild( item );
709       addFolder( item, el );
710     }
711     else
712     {
713       QString word = el.text();
714       parent->appendChild( new TreeItem( word, parent, TreeItem::Word ) );
715     }
716   }
717   dirty = true;
718 }
719 
storeFolder(TreeItem * folder,QDomNode & node)720 void FavoritesModel::storeFolder( TreeItem * folder, QDomNode & node )
721 {
722   int n = folder->childCount();
723   for( int i = 0; i < n; i++ )
724   {
725     TreeItem * child = folder->child( i );
726     QString name = child->data().toString();
727     if( child->type() == TreeItem::Folder )
728     {
729       QDomElement el = dom.createElement( "folder" );
730       el.setAttribute( "name", name );
731       el.setAttribute( "expanded", child->isExpanded() ? "1" : "0" );
732       node.appendChild( el );
733       storeFolder( child, el );
734     }
735     else
736     {
737       QDomElement el = dom.createElement( "headword" );
738       el.appendChild( dom.createTextNode( name ) );
739       node.appendChild( el );
740     }
741   }
742 }
743 
itemExpanded(const QModelIndex & index)744 void FavoritesModel::itemExpanded( const QModelIndex & index )
745 {
746   if( index.isValid() )
747   {
748     TreeItem *item = getItem( index );
749     item->setExpanded( true );
750   }
751 }
752 
itemCollapsed(const QModelIndex & index)753 void FavoritesModel::itemCollapsed( const QModelIndex & index )
754 {
755   if( index.isValid() )
756   {
757     TreeItem *item = getItem( index );
758     item->setExpanded( false );
759   }
760 }
761 
checkAllNodesForExpand()762 void FavoritesModel::checkAllNodesForExpand()
763 {
764   checkNodeForExpand( rootItem, QModelIndex() );
765 }
766 
checkNodeForExpand(const TreeItem * item,const QModelIndex & parent)767 void FavoritesModel::checkNodeForExpand( const TreeItem * item, const QModelIndex & parent )
768 {
769   for( int i = 0; i < item->childCount(); i++ )
770   {
771     TreeItem * ch = item->child( i );
772     if( ch->type() == TreeItem::Folder && ch->isExpanded() )
773     {
774       // We need to expand this node...
775       QModelIndex idx = index( i, 0, parent );
776       emit expandItem( idx );
777 
778       // ...and check it for children nodes
779       checkNodeForExpand( item->child( i ), idx );
780     }
781   }
782 }
783 
mimeTypes() const784 QStringList FavoritesModel::mimeTypes() const
785 {
786   return QStringList( QString::fromLatin1( FAVORITES_MIME_TYPE ) );
787 }
788 
mimeData(const QModelIndexList & indexes) const789 QMimeData *FavoritesModel::mimeData( const QModelIndexList & indexes ) const
790 {
791   FavoritesMimeData *data = new FavoritesMimeData();
792   data->setIndexesList( indexes );
793   return data;
794 }
795 
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int,const QModelIndex & par)796 bool FavoritesModel::dropMimeData( const QMimeData *data, Qt::DropAction action,
797                                    int row, int, const QModelIndex &par )
798 {
799   if( action == Qt::MoveAction || action == Qt::CopyAction )
800   {
801     if( data->hasFormat( FAVORITES_MIME_TYPE ) )
802     {
803       FavoritesMimeData const * mimeData = qobject_cast< FavoritesMimeData const * >( data );
804       if( mimeData )
805       {
806         QModelIndexList const & list = mimeData->getIndexesList();
807 
808         if( list.isEmpty() )
809           return false;
810 
811         TreeItem * parentItem = getItem( par );
812         QModelIndex parentIdx = par;
813 
814         if( row < 0 )
815           row = 0;
816 
817         QList< QModelIndex >::const_iterator it = list.begin();
818         QList< TreeItem * > movedItems;
819         for( ; it != list.end(); ++it )
820         {
821           TreeItem * item = getItem( *it );
822 
823           // Check if we can copy/move this item
824           if( parentItem->haveAncestor( item ) || parentItem->haveSameItem( item, action == Qt::MoveAction ) )
825             return false;
826 
827           movedItems.append( item );
828         }
829 
830         // Insert items to new place
831 
832         beginInsertRows( parentIdx, row, row + movedItems.count() - 1 );
833         for( int i = 0; i < movedItems.count(); i++ )
834         {
835           TreeItem * item = movedItems.at( i );
836           TreeItem *newItem = item->duplicateItem( parentItem );
837           parentItem->insertChild( row + i, newItem );
838         }
839         endInsertRows();
840 
841         dirty = true;
842 
843         return true;
844       }
845     }
846   }
847   return false;
848 }
849 
findItemInFolder(const QString & itemName,int itemType,const QModelIndex & parentIdx)850 QModelIndex FavoritesModel::findItemInFolder( const QString & itemName, int itemType,
851                                               const QModelIndex & parentIdx )
852 {
853   TreeItem * parentItem = getItem( parentIdx );
854   for( int i = 0; i < parentItem->childCount(); i++ )
855   {
856     TreeItem * item = parentItem->child( i );
857     if( item->data().toString() == itemName && item->type() == itemType )
858       return createIndex( i, 0, item );
859   }
860   return QModelIndex();
861 }
862 
getItem(const QModelIndex & index) const863 TreeItem *FavoritesModel::getItem( const QModelIndex &index ) const
864 {
865   if( index.isValid() )
866   {
867     TreeItem *item = static_cast< TreeItem * >( index.internalPointer() );
868     if (item)
869       return item;
870   }
871   return rootItem;
872 }
873 
getTextForIndexes(const QModelIndexList & idxList) const874 QStringList FavoritesModel::getTextForIndexes( const QModelIndexList & idxList ) const
875 {
876   QStringList list;
877   QModelIndexList::const_iterator it = idxList.begin();
878   for( ; it != idxList.end(); ++it )
879   {
880     TreeItem *item = getItem( *it );
881     if( item->type() == TreeItem::Word )
882       list.append( item->data().toString() );
883     else
884       list.append( item->getTextFromAllChilds() );
885   }
886   return list;
887 }
888 
removeItemsForIndexes(const QModelIndexList & idxList)889 void FavoritesModel::removeItemsForIndexes( const QModelIndexList & idxList )
890 {
891   // We should delete items from lowest tree level and in decreasing order
892   // so that first deletions won't affect the indexes for subsequent deletions.
893 
894   QMap< int, QModelIndexList > itemsToDelete;
895   int lowestLevel = 0;
896 
897   QModelIndexList::const_iterator it = idxList.begin();
898   for( ; it != idxList.end(); ++it )
899   {
900     int n = level( *it );
901     if( n > lowestLevel )
902       lowestLevel = n;
903     itemsToDelete[ n ].append( *it );
904   }
905 
906   for( int i = lowestLevel; i >= 0; i-- )
907   {
908     QModelIndexList idxSublist = itemsToDelete[ i ];
909     qSort( idxSublist.begin(), idxSublist.end(), qGreater< QModelIndex >() );
910 
911     it = idxSublist.begin();
912     for( ; it != idxSublist.end(); ++it )
913     {
914       QModelIndex parentIdx = parent( *it );
915       removeRows( (*it).row(), 1, parentIdx );
916     }
917   }
918 }
919 
addNewFolder(const QModelIndex & idx)920 QModelIndex FavoritesModel::addNewFolder( const QModelIndex & idx )
921 {
922   QModelIndex parentIdx;
923   if( idx.isValid() )
924     parentIdx = parent( idx );
925   else
926     parentIdx = idx;
927 
928   QString baseName = QString::fromLatin1( "New folder" );
929 
930   // Create unique name
931 
932   QString name = baseName;
933   if( findItemInFolder( name, TreeItem::Folder, parentIdx ).isValid() )
934   {
935     int i;
936     for( i = 1; i < 1000; i++ )
937     {
938       name = baseName + QString::number( i );
939       if( !findItemInFolder( name, TreeItem::Folder, parentIdx ).isValid() )
940         break;
941     }
942     if( i >= 1000 )
943       return QModelIndex();
944   }
945 
946   // Create folder with unique name
947 
948   TreeItem *parentItem = getItem( parentIdx );
949   int row;
950 
951   if( idx.isValid() )
952   {
953     // Insert after selected element
954     row = idx.row() + 1;
955   }
956   else
957   {
958     // No selected element - add to end of root folder
959     row = parentItem->childCount();
960   }
961 
962   beginInsertRows( parentIdx, row, row );
963   TreeItem * newFolder = new TreeItem( name, parentItem, TreeItem::Folder );
964   parentItem->insertChild( row, newFolder );
965   endInsertRows();
966 
967   dirty = true;
968 
969   return createIndex( row, 0, newFolder );
970 }
971 
addNewHeadword(const QString & path,const QString & headword)972 bool FavoritesModel::addNewHeadword( const QString & path, const QString & headword )
973 {
974   QModelIndex parentIdx;
975 
976   // Find or create target folder
977 
978   QStringList folders = path.split( "/", QString::SkipEmptyParts );
979   QStringList::const_iterator it = folders.begin();
980   for( ; it != folders.end(); ++it )
981     parentIdx = forceFolder( *it, parentIdx );
982 
983   // Add headword
984 
985   return addHeadword( headword, parentIdx );
986 }
987 
removeHeadword(const QString & path,const QString & headword)988 bool FavoritesModel::removeHeadword( const QString & path, const QString & headword )
989 {
990   QModelIndex idx;
991 
992   // Find target folder
993 
994   QStringList folders = path.split( "/", QString::SkipEmptyParts );
995   QStringList::const_iterator it = folders.begin();
996   for( ; it != folders.end(); ++it )
997   {
998     idx = findItemInFolder( *it, TreeItem::Folder, idx );
999     if( !idx.isValid() )
1000       break;
1001   }
1002 
1003   if( path.isEmpty() || idx.isValid() )
1004   {
1005     idx = findItemInFolder( headword, TreeItem::Word, idx );
1006     if( idx.isValid() )
1007     {
1008       QModelIndexList list;
1009       list.append( idx );
1010       removeItemsForIndexes( list );
1011       return true;
1012     }
1013   }
1014 
1015   return false;
1016 }
1017 
isHeadwordPresent(const QString & path,const QString & headword)1018 bool FavoritesModel::isHeadwordPresent( const QString & path, const QString & headword )
1019 {
1020   QModelIndex idx;
1021 
1022   // Find target folder
1023 
1024   QStringList folders = path.split( "/", QString::SkipEmptyParts );
1025   QStringList::const_iterator it = folders.begin();
1026   for( ; it != folders.end(); ++it )
1027   {
1028     idx = findItemInFolder( *it, TreeItem::Folder, idx );
1029     if( !idx.isValid() )
1030       break;
1031   }
1032 
1033   if( path.isEmpty() || idx.isValid() )
1034   {
1035     idx = findItemInFolder( headword, TreeItem::Word, idx );
1036     return idx.isValid();
1037   }
1038 
1039   return false;
1040 }
1041 
forceFolder(QString const & name,const QModelIndex & parentIdx)1042 QModelIndex FavoritesModel::forceFolder( QString const & name, const QModelIndex & parentIdx )
1043 {
1044   QModelIndex idx = findItemInFolder( name, TreeItem::Folder, parentIdx );
1045   if( idx.isValid() )
1046     return idx;
1047 
1048   // Folder not found, create it
1049   TreeItem * parentItem = getItem( parentIdx );
1050   TreeItem * newItem = new TreeItem( name, parentItem, TreeItem::Folder );
1051   int row = parentItem->childCount();
1052 
1053   beginInsertRows( parentIdx, row, row );
1054   parentItem->appendChild( newItem );
1055   endInsertRows();
1056 
1057   dirty = true;
1058 
1059   return createIndex( row, 0, newItem );
1060 }
1061 
addHeadword(const QString & word,const QModelIndex & parentIdx)1062 bool FavoritesModel::addHeadword( const QString & word, const QModelIndex & parentIdx )
1063 {
1064   QModelIndex idx = findItemInFolder( word, TreeItem::Word, parentIdx );
1065   if( idx.isValid() )
1066     return false;
1067 
1068   // Headword not found, append it
1069   TreeItem * parentItem = getItem( parentIdx );
1070   TreeItem * newItem = new TreeItem( word, parentItem, TreeItem::Word );
1071   int row = parentItem->childCount();
1072 
1073   beginInsertRows( parentIdx, row, row );
1074   parentItem->appendChild( newItem );
1075   endInsertRows();
1076 
1077   dirty = true;
1078 
1079   return true;
1080 }
1081 
level(QModelIndex const & idx)1082 int FavoritesModel::level( QModelIndex const & idx )
1083 {
1084   int n = 0;
1085   QModelIndex parentIdx = parent( idx );
1086   while( parentIdx.isValid() )
1087   {
1088     n++;
1089     parentIdx = parent( parentIdx );
1090   }
1091   return n;
1092 }
1093 
pathToItem(QModelIndex const & idx)1094 QString FavoritesModel::pathToItem( QModelIndex const & idx )
1095 {
1096   QString path;
1097   QModelIndex parentIdx = parent( idx );
1098   while( parentIdx.isValid() )
1099   {
1100     if( !path.isEmpty() )
1101       path = "/" + path;
1102 
1103     path = data( parentIdx, Qt::DisplayRole ).toString() + path;
1104 
1105     parentIdx = parent( parentIdx );
1106   }
1107   return path;
1108 }
1109 
getDataInXml(QByteArray & dataStr)1110 void FavoritesModel::getDataInXml( QByteArray & dataStr )
1111 {
1112   dom.clear();
1113 
1114   QDomElement el = dom.createElement( "root" );
1115   dom.appendChild( el );
1116   storeFolder( rootItem, el );
1117 
1118   dataStr = dom.toByteArray();
1119   dom.clear();
1120 }
1121 
getDataInPlainText(QString & dataStr)1122 void FavoritesModel::getDataInPlainText( QString & dataStr )
1123 {
1124   QModelIndexList list;
1125   list.append( QModelIndex() );
1126   dataStr = getTextForIndexes( list ).join( QString::fromLatin1( "\n" ) );
1127 }
1128 
setDataFromXml(QString const & dataStr)1129 bool FavoritesModel::setDataFromXml( QString const & dataStr )
1130 {
1131   QString errorStr;
1132   int errorLine, errorColumn;
1133   dom.clear();
1134 
1135   if ( !dom.setContent( dataStr, false, &errorStr, &errorLine, &errorColumn  ) )
1136   {
1137     // Mailformed data
1138     gdWarning( "XML parsing error: %s at %d,%d\n", errorStr.toUtf8().data(),  errorLine,  errorColumn );
1139     dom.clear();
1140     return false;
1141   }
1142 
1143   beginResetModel();
1144 
1145   if( rootItem )
1146     delete rootItem;
1147 
1148   rootItem = new TreeItem( QVariant(), 0, TreeItem::Root );
1149 
1150   QDomNode rootNode = dom.documentElement();
1151   addFolder( rootItem, rootNode );
1152 
1153   endResetModel();
1154 
1155   dom.clear();
1156   dirty = true;
1157   return true;
1158 }
1159