diff --git a/config.cc b/config.cc
index a44d732d..24bfd856 100644
--- a/config.cc
+++ b/config.cc
@@ -258,6 +258,7 @@ Group loadGroup( QDomElement grp, unsigned * nextId = 0 )
g.name = grp.attribute( "name" );
g.icon = grp.attribute( "icon" );
+ g.favoritesFolder = grp.attribute( "favoritesFolder" );
if ( !grp.attribute( "iconData" ).isEmpty() )
g.iconData = QByteArray::fromBase64( grp.attribute( "iconData" ).toLatin1() );
@@ -1029,6 +1030,13 @@ void saveGroup( Group const & data, QDomElement & group )
group.setAttributeNode( name );
+ if( data.favoritesFolder.size() )
+ {
+ QDomAttr folder = dd.createAttribute( "favoritesFolder" );
+ folder.setValue( data.favoritesFolder );
+ group.setAttributeNode( folder );
+ }
+
if ( data.icon.size() )
{
QDomAttr icon = dd.createAttribute( "icon" );
@@ -1996,6 +2004,11 @@ QString getHistoryFileName() throw( exError )
return getHomeDir().filePath( "history" );
}
+QString getFavoritiesFileName() throw( exError )
+{
+ return getHomeDir().filePath( "favorites" );
+}
+
QString getUserCssFileName() throw( exError )
{
return getHomeDir().filePath( "article-style.css" );
diff --git a/config.hh b/config.hh
index f51c844f..fa19ae3e 100644
--- a/config.hh
+++ b/config.hh
@@ -87,6 +87,7 @@ struct Group
QString name, icon;
QByteArray iconData;
QKeySequence shortcut;
+ QString favoritesFolder;
QVector< DictionaryRef > dictionaries;
Config::MutedDictionaries mutedDictionaries; // Disabled via dictionary bar
Config::MutedDictionaries popupMutedDictionaries; // Disabled via dictionary bar in popup
@@ -95,6 +96,7 @@ struct Group
bool operator == ( Group const & other ) const
{ return id == other.id && name == other.name && icon == other.icon &&
+ favoritesFolder == other.favoritesFolder &&
dictionaries == other.dictionaries && shortcut == other.shortcut &&
mutedDictionaries == other.mutedDictionaries &&
popupMutedDictionaries == other.popupMutedDictionaries &&
@@ -662,6 +664,9 @@ QString getPidFileName() throw( exError );
/// Returns the filename of a history file which stores search history.
QString getHistoryFileName() throw( exError );
+/// Returns the filename of a favorities file.
+QString getFavoritiesFileName() throw( exError );
+
/// Returns the user .css file name.
QString getUserCssFileName() throw( exError );
diff --git a/dictgroupwidget.ui b/dictgroupwidget.ui
index ac5d5ef4..370255db 100644
--- a/dictgroupwidget.ui
+++ b/dictgroupwidget.ui
@@ -14,18 +14,6 @@
Form
-
- 2
-
-
- 4
-
-
- 2
-
-
- 3
-
-
@@ -91,6 +79,20 @@
+ -
+
+
-
+
+
+ Favorites folder:
+
+
+
+ -
+
+
+
+
diff --git a/favoritespanewidget.cc b/favoritespanewidget.cc
new file mode 100644
index 00000000..713a1361
--- /dev/null
+++ b/favoritespanewidget.cc
@@ -0,0 +1,1118 @@
+/* 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 );
+ m_favoritesTree->setModel( m_favoritesModel );
+
+ 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::DoubleClicked | 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 & ) ) );
+
+ listItemDelegate = new WordListItemDelegate( m_favoritesTree->itemDelegate() );
+ m_favoritesTree->setItemDelegate( listItemDelegate );
+}
+
+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;
+ }
+
+ FavoritesModel * model = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ QStringList selectedStrings = model->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;
+ }
+
+ 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;
+
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ md->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_favoritesMenu->exec( m_favoritesTree->mapToGlobal( pos ) );
+}
+
+void FavoritesPaneWidget::onSelectionChanged( QItemSelection const & selection )
+{
+ if ( selection.size() != 1 )
+ 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 )
+{
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ if( md->itemType( idx ) != TreeItem::Word )
+ {
+ // Item is not headword
+ return;
+ }
+
+ QString headword = md->data( idx, Qt::DisplayRole ).toString();
+ QString path = md->pathToItem( idx );
+
+ if( !headword.isEmpty() )
+ emit favoritesItemRequested( headword, path );
+}
+
+void FavoritesPaneWidget::addFolder()
+{
+ QModelIndexList selectedIdx = m_favoritesTree->selectionModel()->selectedIndexes();
+ if( selectedIdx.size() != 1 )
+ return;
+
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ QModelIndex folderIdx = md->addNewFolder( selectedIdx.front() );
+ if( folderIdx.isValid() )
+ m_favoritesTree->edit( folderIdx );
+}
+
+void FavoritesPaneWidget::addHeadword( QString const & path, QString const & headword )
+{
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ md->addNewHeadword( path, headword );
+}
+
+void FavoritesPaneWidget::getDataInXml( QByteArray & dataStr )
+{
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ md->getDataInXml( dataStr );
+}
+
+void FavoritesPaneWidget::getDataInPlainText( QString & dataStr )
+{
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ md->getDataInPlainText( dataStr );
+}
+
+bool FavoritesPaneWidget::setDataFromXml( QString const & dataStr )
+{
+ FavoritesModel * md = static_cast< FavoritesModel * >( m_favoritesTree->model() );
+ return md->setDataFromXml( dataStr );
+}
+
+/************************************************** 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 )
+{
+ readData();
+}
+
+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();
+
+ 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 );
+
+ 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();
+}
+
+void FavoritesModel::saveData()
+{
+ 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();
+
+ renameAtomically( tmpFile.fileName(), m_favoritesFilename );
+
+ 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 ) );
+ }
+ }
+}
+
+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();
+
+ 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 = parent( 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 = idx.row() + 1;
+
+ beginInsertRows( parentIdx, row, row );
+ TreeItem * newFolder = new TreeItem( name, parentItem, TreeItem::Folder );
+ parentItem->insertChild( row, newFolder );
+ endInsertRows();
+
+ 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();
+
+ 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();
+
+ 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();
+ return true;
+}
diff --git a/favoritespanewidget.hh b/favoritespanewidget.hh
new file mode 100644
index 00000000..f74ec817
--- /dev/null
+++ b/favoritespanewidget.hh
@@ -0,0 +1,236 @@
+/* This file is (c) 2017 Abs62
+ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
+
+#ifndef __FAVORITIESPANEWIDGET_HH__INCLUDED__
+#define __FAVORITIESPANEWIDGET_HH__INCLUDED__
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include "delegate.hh"
+
+class FavoritesModel;
+
+class FavoritesPaneWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ FavoritesPaneWidget( QWidget * parent = 0 ): QWidget( parent ),
+ itemSelectionChanged( false )
+ , listItemDelegate( 0 )
+ , m_favoritesModel( 0 )
+ {}
+
+ virtual ~FavoritesPaneWidget();
+
+ virtual QSize sizeHint() const
+ { return QSize( 204, 204 ); }
+
+ void setUp( Config::Class * cfg, QMenu * menu );
+
+ void addHeadword( QString const & path, QString const & headword );
+
+ // Export/import Favorites
+ void getDataInXml( QByteArray & dataStr );
+ void getDataInPlainText( QString & dataStr );
+ bool setDataFromXml( QString const & dataStr );
+
+signals:
+ void favoritesItemRequested( QString const & word, QString const & faforitesFolder );
+
+private slots:
+ void emitFavoritesItemRequested(QModelIndex const &);
+ void onSelectionChanged(QItemSelection const & selection);
+ void onItemClicked(QModelIndex const & idx);
+ void showCustomMenu(QPoint const & pos);
+ void deleteSelectedItems();
+ void copySelectedItems();
+ void addFolder();
+
+private:
+ virtual bool eventFilter( QObject *, QEvent * );
+
+ Config::Class * m_cfg ;
+ QTreeView * m_favoritesTree;
+ QMenu * m_favoritesMenu;
+ QAction * m_deleteSelectedAction;
+ QAction * m_separator;
+ QAction * m_copySelectedToClipboard;
+ QAction * m_addFolder;
+
+ QWidget favoritesPaneTitleBar;
+ QHBoxLayout favoritesPaneTitleBarLayout;
+ QLabel favoritesLabel;
+
+ /// needed to avoid multiple notifications
+ /// when selecting items via mouse and keyboard
+ bool itemSelectionChanged;
+
+ WordListItemDelegate * listItemDelegate;
+ FavoritesModel * m_favoritesModel;
+};
+
+
+class TreeItem
+{
+public:
+ enum Type { Word, Folder, Root };
+
+ TreeItem( const QVariant &data, TreeItem *parent = 0, Type type_ = Word );
+ ~TreeItem();
+
+ void appendChild( TreeItem * child );
+
+ void insertChild( int row, TreeItem * item );
+
+ // Remove child from list and delete it
+ void deleteChild( int row );
+
+ TreeItem * child( int row ) const;
+ int childCount() const;
+ QVariant data() const;
+ void setData( const QVariant & newData );
+ int row() const;
+ TreeItem * parent();
+
+ Type type() const
+ { return m_type; }
+
+ Qt::ItemFlags flags() const;
+
+ void setExpanded( bool expanded )
+ { m_expanded = expanded; }
+
+ bool isExpanded() const
+ { return m_expanded; }
+
+ // Full path from root folder
+ QString fullPath() const;
+
+ // Duplicate item with all childs
+ TreeItem * duplicateItem( TreeItem * newParent ) const;
+
+ // Check if item is ancestor of this element
+ bool haveAncestor( TreeItem * item );
+
+ // Check if same item already presented between childs
+ bool haveSameItem( TreeItem * item, bool allowSelf );
+
+ // Retrieve text from all childs
+ QStringList getTextFromAllChilds() const;
+
+private:
+ QList< TreeItem * > childItems;
+ QVariant itemData;
+ TreeItem *parentItem;
+ Type m_type;
+ bool m_expanded;
+};
+
+class FavoritesModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ explicit FavoritesModel( QString favoritesFilename, QObject * parent = 0 );
+ ~FavoritesModel();
+
+ QVariant data( const QModelIndex &index, int role ) const;
+ Qt::ItemFlags flags( const QModelIndex &index ) const;
+ QVariant headerData( int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole ) const;
+ QModelIndex index( int row, int column,
+ const QModelIndex &parent = QModelIndex() ) const;
+ QModelIndex parent( const QModelIndex &index ) const;
+ int rowCount( const QModelIndex &parent = QModelIndex() ) const;
+ int columnCount( const QModelIndex &parent = QModelIndex() ) const;
+ bool removeRows( int row, int count, const QModelIndex &parent );
+ bool setData( const QModelIndex &index, const QVariant &value, int role );
+
+ // Drag & drop support
+ Qt::DropActions supportedDropActions() const;
+ QStringList mimeTypes() const;
+ QMimeData *mimeData(const QModelIndexList &indexes) const;
+ bool dropMimeData(const QMimeData *data, Qt::DropAction action,
+ int row, int column, const QModelIndex &par);
+
+ // Restore nodes expanded state after data loading
+ void checkNodeForExpand( const TreeItem * item, const QModelIndex &parent );
+ void checkAllNodesForExpand();
+
+ // Retrieve text data for indexes
+ QStringList getTextForIndexes( QModelIndexList const & idxList ) const;
+
+ // Delete items for indexes
+ void removeItemsForIndexes( QModelIndexList const & idxList );
+
+ // Add new folder beside item and return its index
+ // or empty index if fail
+ QModelIndex addNewFolder( QModelIndex const & idx );
+
+ // Add new headword to given folder
+ // return false if it already exists there
+ bool addNewHeadword( QString const & path, QString const & headword );
+
+ // Return path in the tree to item
+ QString pathToItem( QModelIndex const & idx );
+
+ TreeItem::Type itemType( QModelIndex const & idx )
+ { return getItem( idx )->type(); }
+
+ // Export/import Favorites
+ void getDataInXml( QByteArray & dataStr );
+ void getDataInPlainText( QString & dataStr );
+ bool setDataFromXml( QString const & dataStr );
+
+public slots:
+ void itemCollapsed ( const QModelIndex & index );
+ void itemExpanded ( const QModelIndex & index );
+
+signals:
+ void expandItem( const QModelIndex & index );
+
+protected:
+ void readData();
+ void saveData();
+ void addFolder( TreeItem * parent, QDomNode & node );
+ void storeFolder( TreeItem * folder, QDomNode & node );
+
+ // Find item in folder
+ QModelIndex findItemInFolder( QString const & itemName, int itemType,
+ QModelIndex const & parentIdx );
+
+ // Find item by it params
+ QModelIndex findItem( QString const & path,
+ QString const & headword,
+ int type );
+
+ // Create items list from mime "plait/text" data
+ QList< QModelIndex > itemsListFromText( QString const & text );
+
+ TreeItem *getItem( const QModelIndex &index ) const;
+
+ // Find folder with given name or create it if folder not exist
+ QModelIndex forceFolder( QString const & name, QModelIndex const & parentIdx );
+
+ // Add headword to given folder
+ // return false if such headwordalready exists
+ bool addHeadword( QString const & word, QModelIndex const & parentIdx );
+
+ // Return tree level for item
+ int level( QModelIndex const & idx );
+
+private:
+ QString m_favoritesFilename;
+ TreeItem * rootItem;
+ QDomDocument dom;
+};
+
+#endif // __FAVORITIESPANEWIDGET_HH__INCLUDED__
diff --git a/goldendict.pro b/goldendict.pro
index cde83454..6fcfdbee 100644
--- a/goldendict.pro
+++ b/goldendict.pro
@@ -356,7 +356,8 @@ HEADERS += folding.hh \
slob.hh \
ripemd.hh \
gls.hh \
- splitfile.hh
+ splitfile.hh \
+ favoritespanewidget.hh
FORMS += groups.ui \
dictgroupwidget.ui \
@@ -482,7 +483,8 @@ SOURCES += folding.cc \
slob.cc \
ripemd.cc \
gls.cc \
- splitfile.cc
+ splitfile.cc \
+ favoritespanewidget.cc
win32 {
FORMS += texttospeechsource.ui
diff --git a/groups_widgets.cc b/groups_widgets.cc
index 8932e606..510e96c5 100644
--- a/groups_widgets.cc
+++ b/groups_widgets.cc
@@ -63,6 +63,8 @@ DictGroupWidget::DictGroupWidget( QWidget * parent,
ui.shortcut->setHotKey( Config::HotKey( group.shortcut ) );
+ ui.favoritesFolder->setText( group.favoritesFolder );
+
connect( ui.groupIcon, SIGNAL(activated(int)),this,SLOT(groupIconActivated(int)),
Qt::QueuedConnection );
@@ -129,6 +131,8 @@ Config::Group DictGroupWidget::makeGroup() const
g.shortcut = ui.shortcut->getHotKey().toKeySequence();
+ g.favoritesFolder = ui.favoritesFolder->text().replace( '\\', '/' );
+
return g.makeConfigGroup();
}
diff --git a/history.cc b/history.cc
index 2239afe5..866b3d1c 100644
--- a/history.cc
+++ b/history.cc
@@ -9,6 +9,8 @@
History::History( unsigned size, unsigned maxItemLength_ ): maxSize( size ),
maxItemLength( maxItemLength_ ), addingEnabled( true )
+, dirty( false )
+, timerId ( 0 )
{
}
diff --git a/icons/folder.png b/icons/folder.png
new file mode 100644
index 00000000..80d51b3e
Binary files /dev/null and b/icons/folder.png differ
diff --git a/icons/star.png b/icons/star.png
new file mode 100644
index 00000000..26d8eb0d
Binary files /dev/null and b/icons/star.png differ
diff --git a/instances.cc b/instances.cc
index a9af71ae..000bbd1b 100644
--- a/instances.cc
+++ b/instances.cc
@@ -15,6 +15,7 @@ Group::Group( Config::Group const & cfgGroup,
id( cfgGroup.id ),
name( cfgGroup.name ),
icon( cfgGroup.icon ),
+ favoritesFolder( cfgGroup.favoritesFolder ),
shortcut( cfgGroup.shortcut )
{
if ( !cfgGroup.iconData.isEmpty() )
@@ -82,6 +83,7 @@ Config::Group Group::makeConfigGroup()
result.name = name;
result.icon = icon;
result.shortcut = shortcut;
+ result.favoritesFolder = favoritesFolder;
if ( !iconData.isNull() )
{
diff --git a/instances.hh b/instances.hh
index 58dd31df..1cac20df 100644
--- a/instances.hh
+++ b/instances.hh
@@ -21,7 +21,7 @@ using std::vector;
struct Group
{
unsigned id;
- QString name, icon;
+ QString name, icon, favoritesFolder;
QIcon iconData;
QKeySequence shortcut;
vector< sptr< Dictionary::Class > > dictionaries;
diff --git a/mainwindow.cc b/mainwindow.cc
index 8e1cc592..c826eb75 100644
--- a/mainwindow.cc
+++ b/mainwindow.cc
@@ -238,6 +238,14 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
navToolbar->addAction( ui.print );
navToolbar->widgetForAction( ui.print )->setObjectName( "printButton" );
+ navToolbar->widgetForAction( navToolbar->addSeparator() )->setObjectName( "separatorBeforeAddToFavorites" );
+
+ addToFavorites = navToolbar->addAction( QIcon( ":/icons/star.png" ), tr( "Add current tab to Favorites" ) );
+ navToolbar->widgetForAction( addToFavorites )->setObjectName( "addToFavoritesButton" );
+
+ connect( addToFavorites, SIGNAL( triggered() ), this, SLOT( addCurrentTabToFavorites() ) );
+ connect( ui.actionAddToFavorites, SIGNAL( triggered() ), this, SLOT( addCurrentTabToFavorites() ) );
+
beforeOptionsSeparator = navToolbar->addSeparator();
navToolbar->widgetForAction( beforeOptionsSeparator )->setObjectName( "beforeOptionsSeparator" );
beforeOptionsSeparator->setVisible( cfg.preferences.hideMenubar);
@@ -246,6 +254,7 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
buttonMenu->addAction( ui.dictionaries );
buttonMenu->addAction( ui.preferences );
buttonMenu->addSeparator();
+ buttonMenu->addMenu( ui.menuFavorites );
buttonMenu->addMenu( ui.menuHistory );
buttonMenu->addSeparator();
buttonMenu->addMenu( ui.menuFile );
@@ -478,6 +487,8 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
ui.searchPane->toggleViewAction()->setShortcut( QKeySequence( "Ctrl+S" ) );
ui.menuView->addAction( ui.dictsPane->toggleViewAction() );
ui.dictsPane->toggleViewAction()->setShortcut( QKeySequence( "Ctrl+R" ) );
+ ui.menuView->addAction( ui.favoritesPane->toggleViewAction() );
+ ui.favoritesPane->toggleViewAction()->setShortcut( QKeySequence( "Ctrl+I" ) );
ui.menuView->addAction( ui.historyPane->toggleViewAction() );
ui.historyPane->toggleViewAction()->setShortcut( QKeySequence( "Ctrl+H" ) );
ui.menuView->addSeparator();
@@ -536,6 +547,19 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
connect( &dictionaryBar, SIGNAL( openDictionaryFolder( QString const & ) ),
this, SLOT( openDictionaryFolder( QString const & ) ) );
+ // Favorites
+
+ ui.favoritesPaneWidget->setUp( &cfg, ui.menuFavorites );
+
+ connect( ui.favoritesPane, SIGNAL( visibilityChanged( bool ) ),
+ this, SLOT( updateFavoritesMenu() ) );
+
+ connect( ui.menuFavorites, SIGNAL( aboutToShow() ),
+ this, SLOT( updateFavoritesMenu() ) );
+
+ connect( ui.favoritesPaneWidget, SIGNAL( favoritesItemRequested( QString, QString ) ),
+ this, SLOT( headwordFromFavorites( QString, QString ) ) );
+
// History
ui.historyPaneWidget->setUp( &cfg, &history, ui.menuHistory );
history.enableAdd( cfg.preferences.storeHistory );
@@ -972,6 +996,7 @@ void MainWindow::addGlobalAction( QAction * action, const char * slot )
ui.centralWidget->addAction( action );
ui.dictsPane->addAction( action );
ui.searchPaneWidget->addAction( action );
+ ui.favoritesPane->addAction( action );
ui.historyPane->addAction( action );
groupList->addAction( action );
translateBox->addAction( action );
@@ -2261,6 +2286,15 @@ bool MainWindow::eventFilter( QObject * obj, QEvent * ev )
return true;
}
+ // Handle Ctrl+I to show the Favorities Pane.
+ if ( ke->key() == Qt::Key_I && ke->modifiers() == Qt::ControlModifier )
+ {
+ if( ev->type() == QEvent::KeyPress )
+ on_showHideFavorites_triggered();
+ ev->accept();
+ return true;
+ }
+
// Handle F3/Shift+F3 shortcuts
if ( ke->key() == Qt::Key_F3 )
{
@@ -3606,6 +3640,18 @@ void MainWindow::headwordReceived( const QString & word, const QString & ID )
translateInputFinished( false, QString( "gdfrom-" )+ ID );
}
+void MainWindow::updateFavoritesMenu()
+{
+ if ( ui.favoritesPane->toggleViewAction()->isChecked() )
+ {
+ ui.showHideFavorites->setText( tr( "&Hide" ) );
+ }
+ else
+ {
+ ui.showHideFavorites->setText( tr( "&Show" ) );
+ }
+}
+
void MainWindow::updateHistoryMenu()
{
if ( ui.historyPane->toggleViewAction()->isChecked() )
@@ -3618,6 +3664,12 @@ void MainWindow::updateHistoryMenu()
}
}
+void MainWindow::on_showHideFavorites_triggered()
+{
+ ui.favoritesPane->toggleViewAction()->trigger();
+ ui.favoritesPane->raise(); // useful when the Pane is tabbed.
+}
+
void MainWindow::on_showHideHistory_triggered()
{
ui.historyPane->toggleViewAction()->trigger();
@@ -3759,6 +3811,145 @@ void MainWindow::on_importHistory_triggered()
mainStatusBar->showMessage( errStr, 10000, QPixmap( ":/icons/error.png" ) );
}
+void MainWindow::on_exportFavorites_triggered()
+{
+ QString exportPath;
+ if( cfg.historyExportPath.isEmpty() )
+ exportPath = QDir::homePath();
+ else
+ {
+ exportPath = QDir::fromNativeSeparators( cfg.historyExportPath );
+ if( !QDir( exportPath ).exists() )
+ exportPath = QDir::homePath();
+ }
+
+ QString fileName = QFileDialog::getSaveFileName( this, tr( "Export Favorites to file" ),
+ exportPath,
+ tr( "XML files (*.xml);;All files (*.*)" ) );
+ if( fileName.size() == 0)
+ return;
+
+ cfg.historyExportPath = QDir::toNativeSeparators( QFileInfo( fileName ).absoluteDir().absolutePath() );
+ QFile file( fileName );
+
+ for(;;)
+ {
+ if ( !file.open( QFile::WriteOnly | QIODevice::Text ) )
+ break;
+
+ QByteArray data;
+ ui.favoritesPaneWidget->getDataInXml( data );
+
+ if( file.write( data ) != data.size() )
+ break;
+
+ file.close();
+ mainStatusBar->showMessage( tr( "Favorites export complete" ), 5000 );
+ return;
+ }
+ QString errStr = QString( tr( "Export error: " ) ) + file.errorString();
+ file.close();
+ mainStatusBar->showMessage( errStr, 10000, QPixmap( ":/icons/error.png" ) );
+}
+
+void MainWindow::on_ExportFavoritesToList_triggered()
+{
+ QString exportPath;
+ if( cfg.historyExportPath.isEmpty() )
+ exportPath = QDir::homePath();
+ else
+ {
+ exportPath = QDir::fromNativeSeparators( cfg.historyExportPath );
+ if( !QDir( exportPath ).exists() )
+ exportPath = QDir::homePath();
+ }
+
+ QString fileName = QFileDialog::getSaveFileName( this, tr( "Export Favorites to file as plain list" ),
+ exportPath,
+ tr( "Text files (*.txt);;All files (*.*)" ) );
+ if( fileName.size() == 0)
+ return;
+
+ cfg.historyExportPath = QDir::toNativeSeparators( QFileInfo( fileName ).absoluteDir().absolutePath() );
+ QFile file( fileName );
+
+ for(;;)
+ {
+ if ( !file.open( QFile::WriteOnly | QIODevice::Text ) )
+ break;
+
+ // Write UTF-8 BOM
+ QByteArray line;
+ line.append( 0xEF ).append( 0xBB ).append( 0xBF );
+ if ( file.write( line ) != line.size() )
+ break;
+
+ // Write Favorites
+ QString data;
+ ui.favoritesPaneWidget->getDataInPlainText( data );
+
+ line = data.toUtf8();
+ if( file.write( line ) != line.size() )
+ break;
+
+ file.close();
+ mainStatusBar->showMessage( tr( "Favorites export complete" ), 5000 );
+ return;
+ }
+ QString errStr = QString( tr( "Export error: " ) ) + file.errorString();
+ file.close();
+ mainStatusBar->showMessage( errStr, 10000, QPixmap( ":/icons/error.png" ) );
+}
+
+void MainWindow::on_importFavorites_triggered()
+{
+ QString importPath;
+ if( cfg.historyExportPath.isEmpty() )
+ importPath = QDir::homePath();
+ else
+ {
+ importPath = QDir::fromNativeSeparators( cfg.historyExportPath );
+ if( !QDir( importPath ).exists() )
+ importPath = QDir::homePath();
+ }
+
+ QString fileName = QFileDialog::getOpenFileName( this, tr( "Import Favorites from file" ),
+ importPath,
+ tr( "XML files (*.xml);;All files (*.*)" ) );
+ if( fileName.size() == 0)
+ return;
+
+ QFileInfo fileInfo( fileName );
+ cfg.historyExportPath = QDir::toNativeSeparators( fileInfo.absoluteDir().absolutePath() );
+ QString errStr;
+ QFile file( fileName );
+
+ for(;;)
+ {
+ if ( !file.open( QFile::ReadOnly | QIODevice::Text ) )
+ break;
+
+ if( file.error() != QFile::NoError )
+ break;
+
+ QByteArray data = file.readAll();
+
+ if( !ui.favoritesPaneWidget->setDataFromXml( QString::fromUtf8( data.data(), data.size() ) ) )
+ break;
+
+ file.close();
+ mainStatusBar->showMessage( tr( "Favorites import complete" ), 5000 );
+ return;
+ }
+ if( file.error() != QFile::NoError )
+ errStr = QString( tr( "Import error: " ) ) + file.errorString();
+ else
+ errStr = QString( tr( "Data parsing error" ) );
+
+ file.close();
+ mainStatusBar->showMessage( errStr, 10000, QPixmap( ":/icons/error.png" ) );
+}
+
void MainWindow::fillWordListFromHistory()
{
ui.wordList->setUpdatesEnabled( false );
@@ -4240,6 +4431,18 @@ void MainWindow::showFTSIndexingName( QString const & name )
mainStatusBar->setBackgroundMessage( tr( "Now indexing for full-text search: " ) + name );
}
+void MainWindow::addCurrentTabToFavorites()
+{
+ QString folder;
+ Instances::Group const * igrp = groupInstances.findGroup( cfg.lastMainGroupId );
+ if( igrp )
+ folder = igrp->favoritesFolder;
+
+ QString headword = ui.tabWidget->tabText( ui.tabWidget->currentIndex() );
+
+ ui.favoritesPaneWidget->addHeadword( folder, headword );
+}
+
void MainWindow::setGroupByName( QString const & name, bool main_window )
{
if( main_window )
@@ -4262,6 +4465,27 @@ void MainWindow::setGroupByName( QString const & name, bool main_window )
}
}
+void MainWindow::headwordFromFavorites( QString const & headword,
+ QString const & favoritesFolder )
+{
+ if( !favoritesFolder.isEmpty() )
+ {
+ // Find group by it Favorites folder
+ for( Instances::Groups::size_type i = 0; i < groupInstances.size(); i++ )
+ {
+ if( groupInstances[ i ].favoritesFolder == favoritesFolder )
+ {
+ // Group found. Select it and stop search.
+ if( groupList->currentIndex() != (int)i )
+ groupList->setCurrentIndex( i );
+ break;
+ }
+ }
+ }
+
+ wordReceived( headword );
+}
+
#ifdef Q_OS_WIN32
bool MainWindow::handleGDMessage( MSG * message, long * result )
diff --git a/mainwindow.hh b/mainwindow.hh
index 4b2ce1ff..84cc6bbb 100644
--- a/mainwindow.hh
+++ b/mainwindow.hh
@@ -84,6 +84,7 @@ public slots:
void wordReceived( QString const & );
void headwordReceived( QString const &, QString const & );
void setExpandMode( bool expand );
+ void headwordFromFavorites( QString const &, QString const & );
private:
void addGlobalAction( QAction * action, const char * slot );
@@ -127,6 +128,7 @@ private:
QAction * beforeScanPopupSeparator, * afterScanPopupSeparator, * beforeOptionsSeparator;
QAction * zoomIn, * zoomOut, * zoomBase;
QAction * wordsZoomIn, * wordsZoomOut, * wordsZoomBase;
+ QAction * addToFavorites, * beforeAddToFavoritesSeparator;
QMenu trayIconMenu;
QMenu * tabMenu;
QAction * menuButtonAction;
@@ -276,6 +278,8 @@ private slots:
void showFTSIndexingName( QString const & name );
+ void addCurrentTabToFavorites();
+
private slots:
// Executed in response to a user click on an 'add tab' tool button
@@ -412,14 +416,20 @@ private slots:
void on_rescanFiles_triggered();
+ void on_showHideFavorites_triggered();
void on_showHideHistory_triggered();
void on_exportHistory_triggered();
void on_importHistory_triggered();
void on_alwaysOnTop_triggered( bool checked );
void focusWordList();
+ void on_exportFavorites_triggered();
+ void on_importFavorites_triggered();
+ void on_ExportFavoritesToList_triggered();
+
void updateSearchPaneAndBar( bool searchInDock );
+ void updateFavoritesMenu();
void updateHistoryMenu();
/// Add word to history
diff --git a/mainwindow.ui b/mainwindow.ui
index 71ba3540..c6890c49 100644
--- a/mainwindow.ui
+++ b/mainwindow.ui
@@ -138,11 +138,23 @@
+
+
@@ -299,6 +311,33 @@
+
+
+ Favor&ites Pane
+
+
+ 2
+
+
+
+
+ 2
+
+
+ 1
+
+
+ 2
+
+
+ 1
+
+ -
+
+
+
+
+
&History Pane
@@ -307,7 +346,7 @@
2
-
+
2
@@ -594,6 +633,37 @@
F1
+
+
+ Show
+
+
+ Ctrl+I
+
+
+
+
+ Export
+
+
+
+
+ Import
+
+
+
+
+ Add
+
+
+ Add current tab to Favorites
+
+
+
+
+ Export to list
+
+
@@ -625,6 +695,12 @@
QListWidget
+
+ FavoritesPaneWidget
+ QWidget
+
+ 1
+
translateLine
diff --git a/qt-style-st-babylon.css b/qt-style-st-babylon.css
index d80a7760..f438c21b 100644
--- a/qt-style-st-babylon.css
+++ b/qt-style-st-babylon.css
@@ -1,5 +1,5 @@
MainWindow #translateLine, ScanPopup #translateLine, MainWindow #searchPane #wordList, MainWindow #dictsPane #dictsList,
-MainWindow #historyPane #historyList
+MainWindow #historyPane #historyList, MainWindow #favoritesPane #favoritesTree
{
background: white;
}
diff --git a/qt-style-st-lingoes-blue.css b/qt-style-st-lingoes-blue.css
index ccb1ad96..c7e5b1e8 100644
--- a/qt-style-st-lingoes-blue.css
+++ b/qt-style-st-lingoes-blue.css
@@ -73,7 +73,7 @@ QMainWindow::separator {
margin: 2px;
}
-MainWindow #historyPane #historyList {
+MainWindow #historyPane #historyList, MainWindow #favoritesPane #favoritesTree {
background: #EAF0F8;
color: #52627C;
}
diff --git a/qt-style-st-lingvo.css b/qt-style-st-lingvo.css
index 04fa8ea9..64e87003 100644
--- a/qt-style-st-lingvo.css
+++ b/qt-style-st-lingvo.css
@@ -1,5 +1,5 @@
MainWindow #translateLine, ScanPopup #translateLine, MainWindow #wordList, MainWindow #dictsPane #dictsList,
-MainWindow #historyPane #historyList
+MainWindow #historyPane #historyList, MainWindow #favoritesPane #favoritesTree
{
background: white;
}
diff --git a/qt-style-st-modern.css b/qt-style-st-modern.css
index 04c925bc..0f70b1e8 100644
--- a/qt-style-st-modern.css
+++ b/qt-style-st-modern.css
@@ -1,5 +1,5 @@
MainWindow #translateLine, ScanPopup #translateLine, MainWindow #wordList, MainWindow #dictsPane #dictsList,
-MainWindow #historyPane #historyList
+MainWindow #historyPane #historyList, MainWindow #favoritesPane #favoritesTree
{
background: white;
}
diff --git a/qt-style.css b/qt-style.css
index 382dfec7..ea1120b6 100644
--- a/qt-style.css
+++ b/qt-style.css
@@ -1,4 +1,4 @@
-MainWindow #translateLine, ScanPopup #translateLine, MainWindow #wordList, MainWindow #dictsPane #dictsList, MainWindow #historyPane #historyList
+MainWindow #translateLine, ScanPopup #translateLine, MainWindow #wordList, MainWindow #dictsPane #dictsList, MainWindow #historyPane #historyList, MainWindow #favoritesPane #favoritesTree
{
background: #fefdeb;
color: black;
diff --git a/resources.qrc b/resources.qrc
index 9ffb7f99..ce07306e 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -80,5 +80,7 @@
icons/icon32_epwing.png
icons/icon32_slob.png
icons/icon32_gls.png
+ icons/star.png
+ icons/folder.png