/* * (c) LSIIT, UMR CNRS/UdS * Authors: O. Génevaux, F. Larue. * * See licence.txt for additional information. */ #include "UIMainWindow.h" #include "UIDataWidget.h" #include "UIDataWidgetItem.h" #include #include #include #include #include #include #include UIDataWidget::UIDataWidget( QWidget *parent ) : QTreeWidget( parent ) { connect( this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(updateVisibility(QTreeWidgetItem*)) ); connect( this, SIGNAL(itemSelectionChanged()), this, SLOT(updateSelection()) ); sortItems( 0, Qt::SortOrder::AscendingOrder ); } void UIDataWidget::updateDataCollapseState( QTreeWidgetItem* clickedItem ) { GenericUIData *m = ((UIDataWidgetItem*) clickedItem)->getUIData(); m->SetExpanded( clickedItem->isExpanded() ); } void UIDataWidget::updateContentBegin() { disconnect( this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(updateDataCollapseState(QTreeWidgetItem*)) ); disconnect( this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(updateDataCollapseState(QTreeWidgetItem*)) ); } void UIDataWidget::updateContentEnd() { connect( this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(updateDataCollapseState(QTreeWidgetItem*)) ); connect( this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(updateDataCollapseState(QTreeWidgetItem*)) ); } void UIDataWidget::updateVisibility( QTreeWidgetItem* clickedItem ) { // Disconnect the signal that triggers this slot to avoid multiple calls while modifying independent QTreeWidgetItem check state in the following code. disconnect( this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(updateVisibility(QTreeWidgetItem*)) ); // Keep track of items whose visibility state is modified. The clicked item is part of it. QList itemsWhoseVisibilityChanged; itemsWhoseVisibilityChanged.push_back( clickedItem ); // If the clicked item is set to visible without maintaining CTRL pressed, visibility of all other items is cleared. if( !(QApplication::keyboardModifiers() & Qt::ControlModifier) && clickedItem->checkState(0) == Qt::Checked ) forAllTreeItems( [&]( UIDataWidgetItem *item ){ if( item != clickedItem && item->checkState(0) == Qt::Checked ) { item->setCheckState( 0, Qt::Unchecked ); itemsWhoseVisibilityChanged.push_back( item ); } } ); // If the clicked item makes part of the selection, update visibility of the whole selection accordingly. QList selected = selectedItems(); if( clickedItem->isSelected() && !(QApplication::keyboardModifiers() & Qt::ControlModifier) ) { for( auto s : selected ) if( (s->flags() & Qt::ItemIsUserCheckable) && s->checkState(0) != clickedItem->checkState(0) ) { s->setCheckState( 0, clickedItem->checkState(0) ); itemsWhoseVisibilityChanged.push_back( s ); } // Tag items whose visibility must not be reset due to the click on the "eye" checkbox. if( selected.size() != 1 || !clickedItem->isSelected() ) { m_LockedSelection = selected; m_IsSelectionLocked = false; } } // Otherwise, if the clicked item is not selected and CTRL is not pressed, selection is reset to this single item. else { if( !(QApplication::keyboardModifiers() & Qt::ControlModifier) ) for( auto s : selected ) s->setSelected( false ); clickedItem->setSelected( true ); } // Inform the main window of the items whose visibility must be updated. emit visibilityChanged( itemsWhoseVisibilityChanged ); // Reconnect the triggering signal. connect( this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(updateVisibility(QTreeWidgetItem*)) ); } // Force the QTreeWidget to preserve the selection while clicking the "eye" icon of a single item among a multi-selection. void UIDataWidget::updateSelection() { if( !m_IsSelectionLocked ) { m_IsSelectionLocked = true; if( !m_LockedSelection.empty() ) { for( auto s : m_LockedSelection ) s->setSelected( true ); m_LockedSelection.clear(); } } } void UIDataWidget::updateSelection( GenericUIData *d, Qt::KeyboardModifiers modifiers ) { QList< GenericUIData* > currentSelection = selectedData(); UIDataWidgetItem *dItem = NULL; if( modifiers & Qt::CTRL ) { forAllTreeItems( [&]( UIDataWidgetItem *item ){ if( item->getUIData() == d ) { dItem = item; item->setSelected( !item->isSelected() ); } } ); } else { forAllTreeItems( [&]( UIDataWidgetItem *item ){ item->setSelected( item->getUIData() == d ); if( item->getUIData() == d ) dItem = item; } ); } scrollToItem( dItem ); } void UIDataWidget::mouseReleaseEvent( QMouseEvent *evt ) { if( evt->button() == Qt::LeftButton && (QApplication::keyboardModifiers() & Qt::AltModifier) ) { GenericUIData *m = ((UIDataWidgetItem*) itemAt(evt->pos()))->getUIData(); if( m ) { QString clickedType = m->GetTypeString(); bool allSelected = true; forAllTreeItems( [&]( UIDataWidgetItem* item ){ if( item->getUIData()->GetTypeString() == clickedType ) allSelected &= item->isSelected(); } ); forAllTreeItems( [&]( UIDataWidgetItem* item ){ if( item->getUIData()->GetTypeString() == clickedType ) item->setSelected( !allSelected ); } ); } } else QTreeWidget::mouseReleaseEvent( evt ); } QList UIDataWidget::dataWidgetsFromDropEvent( QDropEvent *evt ) const { QStringList pointerStrings = evt->mimeData()->text().split( ';' ); QList< UIDataWidgetItem* > widgetList; for( QString &ptrStr : pointerStrings ) { bool ok; UIDataWidgetItem *ptr = (UIDataWidgetItem*) ptrStr.toLongLong( &ok, 16 ); if( ok && ptr ) widgetList.push_back( ptr ); } return widgetList; } QList UIDataWidget::dataFromDropEvent( QDropEvent *evt ) const { QList< GenericUIData* > dataList; for( auto w : dataWidgetsFromDropEvent(evt) ) dataList.push_back( w->getUIData() ); return dataList; } void UIDataWidget::dragEnterEvent( QDragEnterEvent *evt ) { evt->acceptProposedAction(); } void UIDataWidget::dragMoveEvent( QDragMoveEvent *evt ) { evt->acceptProposedAction(); } void UIDataWidget::dropEvent( QDropEvent *evt ) { if( evt->source() == this ) { UIDataWidgetItem* dstItem = (UIDataWidgetItem*) itemAt( evt->pos() ); QList draggedItems = dataWidgetsFromDropEvent(evt); for( auto draggedItem : draggedItems ) if( (!dstItem && draggedItem->parent()) || dstItem != draggedItem->parent() ) { auto p = (QTreeWidgetItem*) dstItem; while( p ) { if( p == draggedItem ) return; p = p->parent(); } GenericUIData *prevParent = draggedItem->parent()? ((UIDataWidgetItem*)draggedItem->parent())->getUIData() : NULL; GenericUIData *newParent = dstItem? dstItem->getUIData() : NULL; if( draggedItem->parent() ) { draggedItem->parent()->removeChild( draggedItem ); if( !dstItem ) draggedItem->getUIData()->SetParent( NULL ); } else invisibleRootItem()->removeChild( draggedItem ); if( dstItem ) { dstItem->addChild( draggedItem ); draggedItem->getUIData()->SetParent( dstItem->getUIData() ); } else invisibleRootItem()->addChild( draggedItem ); emit parentDataChanged( draggedItem->getUIData(), prevParent, newParent ); } } else emit contentDropped( evt ); } void UIDataWidget::mouseDoubleClickEvent( QMouseEvent *evt ) { UIDataWidgetItem* item = (UIDataWidgetItem*) itemAt( evt->pos() ); if( item ) emit itemDoubleClicked( item->getUIData() ); else QTreeWidget::mouseDoubleClickEvent( evt ); } void UIDataWidget::mouseMoveEvent( QMouseEvent *evt ) { if( evt->buttons() & Qt::RightButton ) { QString mimeText; QList selection = selectedItems(); while( !selection.isEmpty() ) { mimeText += "0x" + QString::number( (qulonglong) selection.takeFirst(), 16 ); if( !selection.isEmpty() ) mimeText += ";"; } QMimeData *mimeData = new QMimeData(); mimeData->setText( mimeText ); QDrag *drag = new QDrag( this ); drag->setMimeData( mimeData ); Qt::DropAction dropAction = drag->exec( Qt::MoveAction ); } else QTreeWidget::mouseMoveEvent( evt ); } void UIDataWidget::contextMenuEvent( QContextMenuEvent *evt ) { typedef QMap< QString, QList > MngByTypeMap; QMenu menu; bool addFactoryDispModes = true; // Recovers the selection and sorts it by data type. QList selection = selectedData(); MngByTypeMap selectionSortedByType; for( GenericUIData *m : selection ) selectionSortedByType[m->GetTypeString()].push_back( m ); // Adds menu entries for the exports available for the selected items. for( ExportInterface *exporter : m_ExportPlugins ) { QList exportIds; QStringList exportNames; exporter->exportIds( exportIds ); exporter->exportNames( exportNames ); QList::iterator id = exportIds.begin(); QStringList::iterator name = exportNames.begin(); while( id != exportIds.end() ) { // Recovers the list of data types required by the export plug-in and checks if all of them // belong to the current selection. QVector requiredTypes; exporter->acceptedDataTypes( *id, requiredTypes ); bool exportDataTypesOK = true; for( QString& type : requiredTypes ) if( !selectionSortedByType.contains(type) ) { exportDataTypesOK = false; break; } // If it is the case, an entry for this export is added to the menu. if( exportDataTypesOK ) { ExportActionData exportData; exportData.id = *id; exportData.name = *name; exportData.plugin = exporter; QAction *actExport = new QAction( "Export "+(*name), &menu ); actExport->setData( QVariant::fromValue(exportData) ); connect( actExport, SIGNAL(triggered()), this, SLOT(launchExport()) ); menu.addAction( actExport ); } ++ id; ++ name; } } if( !menu.isEmpty() ) menu.exec( QCursor::pos() ); } void UIDataWidget::launchExport() { if( QAction *act = dynamic_cast(sender()) ) { ExportActionData exportData = act->data().value(); emit exportLaunched( exportData.plugin, exportData.id, exportData.name ); } } void UIDataWidget::editSelectedDataName() { QList selected = selectedItems(); if( selected.size() == 1 ) editItem( selected.front(), 0 ); } void UIDataWidget::keyPressEvent( QKeyEvent *evt ) { switch( evt->key() ) { case Qt::Key_Delete: { emit removalAsked(); return; } case Qt::Key_A: { if( evt->modifiers() == Qt::ControlModifier ) { selectAll(); return; } break; } case Qt::Key_F2: { QList selected = selectedItems(); if( selected.size() == 1 ) { editItem( selected.front(), 0 ); return; } break; } } QTreeWidget::keyPressEvent( evt ); } QList UIDataWidget::selectedData( const QStringList& typeFilters ) { QList selected = selectedItems(); QList mng; if( typeFilters.empty() ) for( auto s=selected.begin(); s!=selected.end(); ++s ) mng.push_back( ((UIDataWidgetItem*) *s)->getUIData() ); else for( auto s=selected.begin(); s!=selected.end(); ++s ) { UIDataWidgetItem* item = (UIDataWidgetItem*) *s; for( auto t=typeFilters.begin(); t!=typeFilters.end(); ++t ) if( item->getUIData()->GetTypeString() == *t ) { mng.push_back( item->getUIData() ); break; } } return mng; } void UIDataWidget::expandCollapseSelectedData() { for( auto selected : selectedItems() ) if( selected->isExpanded() ) forAllItemsInSubTree( (UIDataWidgetItem*) selected, [&]( UIDataWidgetItem *item ) { collapseItem( item ); } ); else forAllItemsInSubTree( (UIDataWidgetItem*) selected, [&]( UIDataWidgetItem *item ) { expandItem( item ); } ); } void UIDataWidget::selectSubTrees() { for( auto selected : selectedItems() ) forAllItemsInSubTree( (UIDataWidgetItem*) selected, [&]( UIDataWidgetItem *item ) { item->setSelected( true ); } ); } void UIDataWidget::selectSameTypesAsSelected() { QSet< QString > selectedTypes; for( auto selected : selectedItems() ) selectedTypes.insert( ((UIDataWidgetItem*) selected)->getUIData()->GetTypeString() ); forAllTreeItems( [&]( UIDataWidgetItem *item ) { if( selectedTypes.contains(item->getUIData()->GetTypeString()) ) item->setSelected( true ); } ); }