diff --git a/include/eepp/scene/eventdispatcher.hpp b/include/eepp/scene/eventdispatcher.hpp index 11f521f7d..11eed7de3 100644 --- a/include/eepp/scene/eventdispatcher.hpp +++ b/include/eepp/scene/eventdispatcher.hpp @@ -3,9 +3,9 @@ #include #include +#include #include #include -#include using namespace EE::System; using namespace EE::Math; @@ -80,6 +80,8 @@ class EE_API EventDispatcher { const Uint32& getReleaseTrigger() const; + bool justPressTriggered( Uint32 flag ); + void setNodeDragging( Node* dragging ); bool isNodeDragging() const; diff --git a/include/eepp/ui/uifiledialog.hpp b/include/eepp/ui/uifiledialog.hpp index f4542511d..3b747ede4 100644 --- a/include/eepp/ui/uifiledialog.hpp +++ b/include/eepp/ui/uifiledialog.hpp @@ -23,7 +23,8 @@ class EE_API UIFileDialog : public UIWindow { SortAlphabetically = ( 1 << 2 ), AllowFolderSelect = ( 1 << 3 ), ShowOnlyFolders = ( 1 << 4 ), - ShowHidden = ( 1 << 5 ) + ShowHidden = ( 1 << 5 ), + AllowMultiFileSelection = ( 1 << 6 ), }; static const Uint32 DefaultFlags = UIFileDialog::Flags::FoldersFirst | @@ -52,9 +53,11 @@ class EE_API UIFileDialog : public UIWindow { std::string getCurPath() const; - std::string getCurFile() const; + std::string getCurFile( size_t index = 0 ) const; - std::string getFullPath(); + std::string getFullPath() const; + + std::vector getFullPaths() const; UIPushButton* getButtonOpen() const; @@ -72,17 +75,19 @@ class EE_API UIFileDialog : public UIWindow { void addFilePattern( std::string pattern, bool select = false ); - bool isSaveDialog(); + bool isSaveDialog() const; - bool getSortAlphabetically(); + bool getSortAlphabetically() const; - bool getFoldersFirst(); + bool getFoldersFirst() const; - bool getShowOnlyFolders(); + bool getShowOnlyFolders() const; - bool getShowHidden(); + bool getShowHidden() const; - bool allowFolderSelect(); + bool allowFolderSelect() const; + + bool allowMultiFileSelect() const; void setSortAlphabetically( const bool& sortAlphabetically ); @@ -94,6 +99,8 @@ class EE_API UIFileDialog : public UIWindow { void setShowHidden( const bool& showHidden ); + void setAllowsMultiFileSelect( bool allow ); + const KeyBindings::Shortcut& getCloseShortcut() const; void setFileName( const std::string& name ); @@ -167,11 +174,13 @@ class EE_API UIFileDialog : public UIWindow { void setCurPath( const std::string& path ); - const FileSystemModel::Node* getSelectionNode() const; + std::vector getSelectionNodes() const; - ModelIndex getSelectionModelIndex() const; + std::vector getSelectionModelIndex() const; std::string getSelectedDrive() const; + + std::string getFullPath( size_t index ) const; }; }} // namespace EE::UI diff --git a/include/eepp/ui/uimultimodelview.hpp b/include/eepp/ui/uimultimodelview.hpp index e79c593d3..41bbc2e12 100644 --- a/include/eepp/ui/uimultimodelview.hpp +++ b/include/eepp/ui/uimultimodelview.hpp @@ -45,6 +45,8 @@ class EE_API UIMultiModelView : public UIStackWidget { void setSingleClickNavigation( bool singleClickNavigation ); + void setMultiSelect( bool enable ); + protected: UIMultiModelView( const std::string& tag ); diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 2be39c036..80bef86a6 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/src/eepp/scene/eventdispatcher.cpp b/src/eepp/scene/eventdispatcher.cpp index fd837d30a..0166f7fb8 100644 --- a/src/eepp/scene/eventdispatcher.cpp +++ b/src/eepp/scene/eventdispatcher.cpp @@ -366,6 +366,10 @@ const Uint32& EventDispatcher::getReleaseTrigger() const { return mInput->getReleaseTrigger(); } +bool EventDispatcher::justPressTriggered( Uint32 flag ) { + return ( getPressTrigger() & flag ) && ( 0 == ( getLastPressTrigger() & flag ) ); +} + void EventDispatcher::setNodeDragging( Node* dragging ) { eeASSERT( mNodeDragging == nullptr || mNodeDragging == dragging || dragging == nullptr ); mNodeDragging = dragging; diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 4de67ae11..0f054377d 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -456,7 +456,9 @@ UITableRow* UIAbstractTableView::createRow() { rowWidget->reloadStyle( true, true, true ); rowWidget->on( Event::MouseDown, [this]( const Event* event ) { if ( !( event->asMouseEvent()->getFlags() & ( EE_BUTTON_LMASK | EE_BUTTON_RMASK ) ) || - !isRowSelection() ) + !isRowSelection() || + !getUISceneNode()->getEventDispatcher()->justPressTriggered( EE_BUTTON_LMASK | + EE_BUTTON_RMASK ) ) return; auto index = event->getNode()->asType()->getCurIndex(); if ( mSelectionKind == SelectionKind::Single && diff --git a/src/eepp/ui/uifiledialog.cpp b/src/eepp/ui/uifiledialog.cpp index b6e40413e..74087af31 100644 --- a/src/eepp/ui/uifiledialog.cpp +++ b/src/eepp/ui/uifiledialog.cpp @@ -169,20 +169,31 @@ UIFileDialog::UIFileDialog( Uint32 dialogFlags, const std::string& defaultFilePa } } ); mMultiView->setOnSelectionChange( [this] { - if ( mMultiView->getSelection().isEmpty() || mDisplayingDrives ) + if ( mMultiView->getSelection().isEmpty() || mDisplayingDrives || + ( isSaveDialog() && allowMultiFileSelect() ) ) return; - const FileSystemModel::Node* node = getSelectionNode(); - if ( !node ) { + auto nodes = getSelectionNodes(); + if ( nodes.empty() ) { Log::error( "UIFileDialog() - mMultiView->setOnSelectionChange - " "UIFileDialog::getSelectionNode() was empty, shouldn't " "be empty" ); return; } - if ( !isSaveDialog() ) { - if ( allowFolderSelect() || !FileSystem::isDirectory( node->fullPath() ) ) + if ( nodes.size() == 1 ) { + const FileSystemModel::Node* node = nodes[0]; + if ( !isSaveDialog() ) { + if ( allowFolderSelect() || !FileSystem::isDirectory( node->fullPath() ) ) + setFileName( node->getName() ); + } else if ( !FileSystem::isDirectory( node->fullPath() ) ) { setFileName( node->getName() ); - } else if ( !FileSystem::isDirectory( node->fullPath() ) ) { - setFileName( node->getName() ); + } + } else { + std::string names; + for ( size_t i = 0; i < nodes.size(); i++ ) { + auto node = nodes[i]; + names += node->getName() + ( ( i != nodes.size() - 1 ) ? "; " : "" ); + } + setFileName( names ); } } ); @@ -393,21 +404,29 @@ void UIFileDialog::setCurPath( const std::string& path ) { refreshFolder( true ); } -ModelIndex UIFileDialog::getSelectionModelIndex() const { +std::vector UIFileDialog::getSelectionModelIndex() const { if ( mMultiView->getSelection().isEmpty() ) return {}; - auto index = mMultiView->getSelection().first(); - auto* filterModel = (SortingProxyModel*)mMultiView->getModel().get(); - auto localIndex = filterModel->mapToSource( index ); - return localIndex; + std::vector indexes; + mMultiView->getSelection().forEachIndex( [this, &indexes]( const ModelIndex& index ) { + auto* filterModel = (SortingProxyModel*)mMultiView->getModel().get(); + auto localIndex = filterModel->mapToSource( index ); + indexes.emplace_back( std::move( localIndex ) ); + } ); + return indexes; } -const FileSystemModel::Node* UIFileDialog::getSelectionNode() const { +std::vector UIFileDialog::getSelectionNodes() const { if ( mMultiView->getSelection().isEmpty() || mDisplayingDrives ) - return nullptr; - auto localIndex = getSelectionModelIndex(); - const FileSystemModel::Node& node = mModel->node( localIndex ); - return &node; + return {}; + auto localIndexes = getSelectionModelIndex(); + std::vector nodes; + nodes.reserve( localIndexes.size() ); + for ( const auto& localIndex : localIndexes ) { + const FileSystemModel::Node& node = mModel->node( localIndex ); + nodes.push_back( &node ); + } + return nodes; } void UIFileDialog::openSaveClick() { @@ -443,7 +462,10 @@ void UIFileDialog::disableButtons() { std::string UIFileDialog::getSelectedDrive() const { if ( !mDisplayingDrives ) return ""; - ModelIndex index = getSelectionModelIndex(); + auto indexes = getSelectionModelIndex(); + if ( indexes.empty() ) + return ""; + ModelIndex index = indexes[0]; ModelIndex modelIndex( mDiskDrivesModel->index( index.row(), DiskDrivesModel::Name ) ); Variant var( mDiskDrivesModel->data( modelIndex ) ); return var.toString(); @@ -458,7 +480,10 @@ void UIFileDialog::openFileOrFolder( bool shouldOpenFolder = false ) { return; } - auto* node = getSelectionNode(); + auto nodes = getSelectionNodes(); + if ( nodes.empty() ) + return; + auto* node = nodes[0]; if ( !node ) { Log::error( "UIFileDialog::getSelectionNode() was empty, shouldn't be empty" ); return; @@ -543,21 +568,24 @@ void UIFileDialog::save() { } void UIFileDialog::open() { + auto fullPath( getFullPath() ); + if ( mMultiView->getSelection().isEmpty() && - !( allowFolderSelect() && FileSystem::isDirectory( getFullPath() ) ) && - !FileSystem::fileExists( getFullPath() ) ) + !( allowFolderSelect() && FileSystem::isDirectory( fullPath ) ) && + !FileSystem::fileExists( fullPath ) ) return; if ( mDisplayingDrives ) { if ( !allowFolderSelect() ) return; - if ( FileSystem::isDirectory( getFullPath() ) ) + if ( FileSystem::isDirectory( fullPath ) ) return; } - auto* node = !mMultiView->getSelection().isEmpty() ? getSelectionNode() : nullptr; + auto nodes = getSelectionNodes(); + auto* node = !nodes.empty() ? nodes[0] : nullptr; if ( !node ) { - node = mModel->getNodeFromPath( getFullPath() ); + node = mModel->getNodeFromPath( fullPath ); if ( !node ) { Log::warning( "UIFileDialog::open() - Should contain a valid path." ); @@ -567,11 +595,10 @@ void UIFileDialog::open() { if ( ( node && "" != node->getName() ) || allowFolderSelect() ) { if ( !allowFolderSelect() ) { - if ( FileSystem::isDirectory( getFullPath() ) ) + if ( FileSystem::isDirectory( fullPath ) ) return; } else { - if ( !FileSystem::isDirectory( getFullPath() ) && - !FileSystem::isDirectory( getCurPath() ) ) + if ( !FileSystem::isDirectory( fullPath ) && !FileSystem::isDirectory( getCurPath() ) ) return; } @@ -610,30 +637,34 @@ void UIFileDialog::addFilePattern( std::string pattern, bool select ) { } } -bool UIFileDialog::isSaveDialog() { +bool UIFileDialog::isSaveDialog() const { return 0 != ( mDialogFlags & SaveDialog ); } -bool UIFileDialog::getSortAlphabetically() { +bool UIFileDialog::getSortAlphabetically() const { return 0 != ( mDialogFlags & SortAlphabetically ); } -bool UIFileDialog::getFoldersFirst() { +bool UIFileDialog::getFoldersFirst() const { return 0 != ( mDialogFlags & FoldersFirst ); } -bool UIFileDialog::allowFolderSelect() { +bool UIFileDialog::allowFolderSelect() const { return 0 != ( mDialogFlags & AllowFolderSelect ); } -bool UIFileDialog::getShowOnlyFolders() { +bool UIFileDialog::getShowOnlyFolders() const { return 0 != ( mDialogFlags & ShowOnlyFolders ); } -bool UIFileDialog::getShowHidden() { +bool UIFileDialog::getShowHidden() const { return 0 != ( mDialogFlags & ShowHidden ); } +bool UIFileDialog::allowMultiFileSelect() const { + return 0 != ( mDialogFlags & AllowMultiFileSelection ); +} + void UIFileDialog::setSortAlphabetically( const bool& sortAlphabetically ) { BitOp::setBitFlagValue( &mDialogFlags, SortAlphabetically, sortAlphabetically ? 1 : 0 ); refreshFolder(); @@ -658,24 +689,48 @@ void UIFileDialog::setShowHidden( const bool& showHidden ) { refreshFolder(); } -std::string UIFileDialog::getFullPath() { +void UIFileDialog::setAllowsMultiFileSelect( bool allow ) { + BitOp::setBitFlagValue( &mDialogFlags, AllowMultiFileSelection, allow ? 1 : 0 ); + mMultiView->setMultiSelect( allow ); + mFile->setEnabled( !allow ); +} + +std::string UIFileDialog::getFullPath( size_t index ) const { if ( mDisplayingDrives ) return getCurFile(); std::string tPath = mCurPath; - FileSystem::dirAddSlashAtEnd( tPath ); - - tPath += getCurFile(); - + tPath += getCurFile( index ); return tPath; } +std::string UIFileDialog::getFullPath() const { + return getFullPath( 0 ); +} + +std::vector UIFileDialog::getFullPaths() const { + if ( mDisplayingDrives ) + return { getCurFile() }; + + std::vector paths; + auto nodes = getSelectionNodes(); + + for ( size_t i = 0; i < nodes.size(); i++ ) { + std::string tPath( mCurPath ); + FileSystem::dirAddSlashAtEnd( tPath ); + tPath += nodes[i]->getName(); + paths.emplace_back( std::move( tPath ) ); + } + + return paths; +} + std::string UIFileDialog::getCurPath() const { return mCurPath; } -std::string UIFileDialog::getCurFile() const { +std::string UIFileDialog::getCurFile( size_t index ) const { if ( mDialogFlags & SaveDialog ) return mFile->getText(); if ( mMultiView->getSelection().isEmpty() ) @@ -683,8 +738,10 @@ std::string UIFileDialog::getCurFile() const { if ( mDisplayingDrives ) { return getSelectedDrive(); } else { - auto* node = getSelectionNode(); - + auto nodes = getSelectionNodes(); + if ( nodes.empty() || index >= nodes.size() ) + return ""; + auto* node = nodes[index]; if ( !node ) { Log::error( "UIFileDialog::getCurFile() - UIFileDialog::getSelectionNode() was empty, " "shouldn't be empty" ); diff --git a/src/eepp/ui/uimultimodelview.cpp b/src/eepp/ui/uimultimodelview.cpp index e7cf42361..84cba8ede 100644 --- a/src/eepp/ui/uimultimodelview.cpp +++ b/src/eepp/ui/uimultimodelview.cpp @@ -101,4 +101,11 @@ void UIMultiModelView::setSingleClickNavigation( bool singleClickNavigation ) { mTable->setSingleClickNavigation( singleClickNavigation ); } +void UIMultiModelView::setMultiSelect( bool enable ) { + auto kind = + enable ? UIAbstractView::SelectionKind::Multiple : UIAbstractView::SelectionKind::Single; + mList->setSelectionKind( kind ); + mTable->setSelectionKind( kind ); +} + }} // namespace EE::UI diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index b7c7cdf1d..c2d54fd39 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -216,10 +216,13 @@ void App::openFileDialog() { dialog->setTitle( i18n( "open_file", "Open File" ) ); dialog->setCloseShortcut( KEY_ESCAPE ); dialog->setSingleClickNavigation( mConfig.editor.singleClickNavigation ); + dialog->setAllowsMultiFileSelect( true ); dialog->on( Event::OpenFile, [this]( const Event* event ) { - auto file = event->getNode()->asType()->getFullPath(); - mLastFileFolder = FileSystem::fileRemoveFileName( file ); - loadFileFromPath( file ); + auto files = event->getNode()->asType()->getFullPaths(); + for ( const auto& file : files ) { + mLastFileFolder = FileSystem::fileRemoveFileName( file ); + loadFileFromPath( file ); + } } ); dialog->on( Event::OnWindowClose, [this]( const Event* ) { if ( mSplitter && mSplitter->getCurWidget() && !SceneManager::instance()->isShuttingDown() )