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