diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index bb3411b3e..f3768d993 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -77,6 +77,7 @@ class EE_API Event { OnModelEvent, OnResourceChange, OnActiveWidgetChange, + OnWindowReady, UserEvent, NoEvent = eeINDEX_NOT_FOUND }; diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp index 673eb2981..2cc0fce85 100644 --- a/include/eepp/ui/abstract/uiabstracttableview.hpp +++ b/include/eepp/ui/abstract/uiabstracttableview.hpp @@ -167,6 +167,9 @@ class EE_API UIAbstractTableView : public UIAbstractView { virtual void onOpenModelIndex( const ModelIndex& index, const Event* triggerEvent = nullptr ); + virtual void onOpenMenuModelIndex( const ModelIndex& index, + const Event* triggerEvent = nullptr ); + virtual void onSortColumn( const size_t& colIndex ); virtual Uint32 onTextInput( const TextInputEvent& event ); diff --git a/include/eepp/ui/abstract/uiabstractview.hpp b/include/eepp/ui/abstract/uiabstractview.hpp index c9cb12753..a2ebc8c48 100644 --- a/include/eepp/ui/abstract/uiabstractview.hpp +++ b/include/eepp/ui/abstract/uiabstractview.hpp @@ -11,7 +11,7 @@ using namespace EE::UI::Models; namespace EE { namespace UI { namespace Abstract { -enum class ModelEventType { Open, OpenTree, CloseTree }; +enum class ModelEventType { Open, OpenTree, CloseTree, OpenMenu }; class EE_API ModelEvent : public Event { public: diff --git a/include/eepp/ui/uiwindow.hpp b/include/eepp/ui/uiwindow.hpp index 5bb418f81..2b0146106 100644 --- a/include/eepp/ui/uiwindow.hpp +++ b/include/eepp/ui/uiwindow.hpp @@ -96,6 +96,8 @@ class EE_API UIWindow : public UIWidget { virtual bool hide(); + void showWhenReady(); + virtual void scheduledUpdate( const Time& time ); virtual void closeWindow(); @@ -161,6 +163,8 @@ class EE_API UIWindow : public UIWidget { Sizef getSizeWithoutDecoration(); + Sizef getMinWindowTitleSizeRequired(); + typedef std::function KeyBindingCommand; KeyBindings& getKeyBindings(); @@ -217,6 +221,7 @@ class EE_API UIWindow : public UIWidget { Vector2f mResizePos; bool mFrameBufferBound; + bool mShowWhenReady{ false }; KeyBindings mKeyBindings; std::map mKeyBindingCommands; @@ -279,13 +284,15 @@ class EE_API UIWindow : public UIWidget { virtual void postDraw(); - virtual Sizei getFrameBufferSize(); + Sizei getFrameBufferSize(); virtual void onWindowReady(); void onContainerPositionChange( const Event* Event ); void setupModal(); + + void forcedApplyStyle(); }; }} // namespace EE::UI diff --git a/src/eepp/ui/abstract/uiabstracttableview.cpp b/src/eepp/ui/abstract/uiabstracttableview.cpp index 9298d5a35..47b47cac8 100644 --- a/src/eepp/ui/abstract/uiabstracttableview.cpp +++ b/src/eepp/ui/abstract/uiabstracttableview.cpp @@ -393,6 +393,14 @@ void UIAbstractTableView::bindNavigationClick( UIWidget* widget ) { onOpenModelIndex( idx, event ); } } ); + + widget->addEventListener( Event::MouseClick, [&]( const Event* event ) { + auto mouseEvent = static_cast( event ); + auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); + if ( mouseEvent->getFlags() & EE_BUTTON_RMASK ) { + onOpenMenuModelIndex( idx, event ); + } + } ); } UIWidget* UIAbstractTableView::createCell( UIWidget* rowWidget, const ModelIndex& index ) { @@ -495,6 +503,12 @@ void UIAbstractTableView::onOpenModelIndex( const ModelIndex& index, const Event sendEvent( &event ); } +void UIAbstractTableView::onOpenMenuModelIndex( const ModelIndex& index, + const Event* triggerEvent ) { + ModelEvent event( getModel(), index, this, ModelEventType::OpenMenu, triggerEvent ); + sendEvent( &event ); +} + void UIAbstractTableView::onSortColumn( const size_t& colIndex ) { Model* model = getModel(); if ( !model ) diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 43325912e..409b0ecb9 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -186,41 +186,45 @@ SyntaxDefinitionManager::SyntaxDefinitionManager() { "//" } ); // TypeScript - add( { "TypeScript", - { "%.ts$", "%.tsx$", "%.d.ts$" }, - { - { { "//.-\n" }, "comment" }, - { { "/%*", "%*/" }, "comment" }, - { { "\"", "\"", "\\" }, "string" }, - { { "'", "'", "\\" }, "string" }, - { { "`", "`", "\\" }, "string" }, - { { "0x[%da-fA-F]+" }, "number" }, - { { "-?%d+[%d%.eE]*" }, "number" }, - { { "-?%.?%d+" }, "number" }, - { { "[%+%-=/%*%^%%<>!~|&]" }, "operator" }, - { { "[%a_][%w_]*%s*%f[(]" }, "function" }, - { { "[%a_][%w_]*" }, "symbol" }, - }, - { { "arguments", "keyword2" }, { "async", "keyword" }, { "await", "keyword" }, - { "break", "keyword" }, { "case", "keyword" }, { "catch", "keyword" }, - { "class", "keyword" }, { "const", "keyword" }, { "continue", "keyword" }, - { "debugger", "keyword" }, { "default", "keyword" }, { "delete", "keyword" }, - { "do", "keyword" }, { "else", "keyword" }, { "export", "keyword" }, - { "extends", "keyword" }, { "false", "literal" }, { "finally", "keyword" }, - { "for", "keyword" }, { "function", "keyword" }, { "get", "keyword" }, - { "if", "keyword" }, { "import", "keyword" }, { "in", "keyword" }, - { "Infinity", "keyword2" }, { "instanceof", "keyword" }, { "let", "keyword" }, - { "NaN", "keyword2" }, { "new", "keyword" }, { "null", "literal" }, - { "return", "keyword" }, { "set", "keyword" }, { "super", "keyword" }, - { "static", "keyword" }, { "switch", "keyword" }, { "this", "keyword2" }, - { "throw", "keyword" }, { "true", "literal" }, { "try", "keyword" }, - { "typeof", "keyword" }, { "undefined", "literal" }, { "var", "keyword" }, - { "void", "keyword" }, { "while", "keyword" }, { "with", "keyword" }, - { "yield", "keyword" }, { "implements", "keyword" }, { "Array", "keyword2" }, - { "any", "keyword" }, { "from", "keyword" }, { "public", "keyword" }, - { "private", "keyword" }, { "declare", "keyword" }, { "namespace", "keyword" }, - { "protected", "keyword" }, { "enum", "keyword" } }, - "//" } ); + add( + { "TypeScript", + { "%.ts$", "%.tsx$", "%.d.ts$" }, + { + { { "//.-\n" }, "comment" }, + { { "/%*", "%*/" }, "comment" }, + { { "\"", "\"", "\\" }, "string" }, + { { "'", "'", "\\" }, "string" }, + { { "`", "`", "\\" }, "string" }, + { { "0x[%da-fA-F]+" }, "number" }, + { { "-?%d+[%d%.eE]*" }, "number" }, + { { "-?%.?%d+" }, "number" }, + { { "[%+%-=/%*%^%%<>!~|&]" }, "operator" }, + { { "[%a_][%w_]*%s*%f[(]" }, "function" }, + { { "[%a_][%w_]*" }, "symbol" }, + }, + { { "any", "keyword2" }, { "arguments", "keyword2" }, { "as", "keyword2" }, + { "async", "keyword" }, { "await", "keyword" }, { "boolean", "keyword2" }, + { "break", "keyword" }, { "case", "keyword" }, { "catch", "keyword" }, + { "class", "keyword" }, { "const", "keyword" }, { "constructor", "keyword2" }, + { "continue", "keyword" }, { "debugger", "keyword" }, { "declare", "keyword2" }, + { "default", "keyword" }, { "delete", "keyword" }, { "do", "keyword" }, + { "else", "keyword" }, { "enum", "keyword2" }, { "export", "keyword" }, + { "extends", "keyword" }, { "false", "literal" }, { "finally", "keyword" }, + { "for", "keyword" }, { "from", "keyword" }, { "function", "keyword" }, + { "get", "keyword" }, { "if", "keyword" }, { "implements", "keyword2" }, + { "import", "keyword" }, { "in", "keyword" }, { "Infinity", "keyword2" }, + { "instanceof", "keyword" }, { "interface", "keyword2" }, { "let", "keyword" }, + { "module", "keyword2" }, { "new", "keyword" }, { "null", "literal" }, + { "number", "keyword2" }, { "of", "keyword2" }, { "package", "keyword2" }, + { "private", "keyword2" }, { "protected", "keyword2" }, { "public", "keyword2" }, + { "require", "keyword2" }, { "return", "keyword" }, { "set", "keyword" }, + { "static", "keyword" }, { "string", "keyword2" }, { "super", "keyword" }, + { "switch", "keyword" }, { "symbol", "keyword2" }, { "this", "keyword2" }, + { "throw", "keyword" }, { "true", "literal" }, { "try", "keyword" }, + { "type", "keyword2" }, { "typeof", "keyword" }, { "undefined", "literal" }, + { "var", "keyword" }, { "void", "keyword" }, { "while", "keyword" }, + { "with", "keyword" }, { "yield", "keyword" } }, + "//" } ); // Python add( { "Python", diff --git a/src/eepp/ui/models/modelselection.cpp b/src/eepp/ui/models/modelselection.cpp index 2e903223e..175a9ed6c 100644 --- a/src/eepp/ui/models/modelselection.cpp +++ b/src/eepp/ui/models/modelselection.cpp @@ -24,8 +24,10 @@ void ModelSelection::set( const ModelIndex& index ) { } void ModelSelection::set( const std::vector& indexes, bool notify ) { +#ifdef EE_DEBUG for ( auto& index : indexes ) eeASSERT( index.isValid() ); +#endif mIndexes.clear(); mIndexes = indexes; if ( notify ) diff --git a/src/eepp/ui/uifiledialog.cpp b/src/eepp/ui/uifiledialog.cpp index a45a0f6ce..f78f13d47 100644 --- a/src/eepp/ui/uifiledialog.cpp +++ b/src/eepp/ui/uifiledialog.cpp @@ -250,6 +250,8 @@ UIFileDialog::~UIFileDialog() {} void UIFileDialog::onWindowReady() { updateClickStep(); + UIWindow::onWindowReady(); + sendCommonEvent( Event::OnWindowReady ); } Uint32 UIFileDialog::getType() const { diff --git a/src/eepp/ui/uimessagebox.cpp b/src/eepp/ui/uimessagebox.cpp index 5dba60e38..cb6ae9c47 100644 --- a/src/eepp/ui/uimessagebox.cpp +++ b/src/eepp/ui/uimessagebox.cpp @@ -92,6 +92,7 @@ UIMessageBox::UIMessageBox( const Type& type, const String& message, const Uint3 applyDefaultTheme(); setMinWindowSize( mLayoutCont->getSize() ); + center(); } @@ -104,7 +105,8 @@ void UIMessageBox::setTheme( UITheme* theme ) { mButtonOK->setTheme( theme ); mButtonCancel->setTheme( theme ); - if ( "Retry" != mButtonOK->getText() ) { + if ( getUISceneNode()->getTranslatorString( "@string/msg_box_retry", "Retry" ) != + mButtonOK->getText() ) { Drawable* okIcon = getUISceneNode()->findIconDrawable( "ok", PixelDensity::dpToPxI( 16 ) ); Drawable* cancelIcon = getUISceneNode()->findIconDrawable( "cancel", PixelDensity::dpToPxI( 16 ) ); @@ -186,9 +188,26 @@ UITextInput* UIMessageBox::getTextInput() const { } void UIMessageBox::onWindowReady() { - UIWindow::onWindowReady(); - setMinWindowSize( mLayoutCont->getSize() ); + forcedApplyStyle(); + + Sizef size( mLayoutCont->getSize() ); + Sizef minWinTitle( getMinWindowTitleSizeRequired() ); + if ( size.x < minWinTitle.x ) { + size.x = minWinTitle.x; + mLayoutCont->setLayoutWidthPolicy( SizePolicy::MatchParent ); + mLayoutCont->getFirstChild()->asType()->setLayoutWidthPolicy( + SizePolicy::MatchParent ); + mTextBox->setLayoutWidthPolicy( SizePolicy::MatchParent ); + } + setMinWindowSize( size ); center(); + + if ( mShowWhenReady ) { + mShowWhenReady = false; + show(); + } + + sendCommonEvent( Event::OnWindowReady ); } }} // namespace EE::UI diff --git a/src/eepp/ui/uitableview.cpp b/src/eepp/ui/uitableview.cpp index e99ae119c..1824daf44 100644 --- a/src/eepp/ui/uitableview.cpp +++ b/src/eepp/ui/uitableview.cpp @@ -263,6 +263,11 @@ Uint32 UITableView::onKeyDown( const KeyEvent& event ) { onOpenModelIndex( curIndex, &event ); return 1; } + case KEY_MENU: { + if ( curIndex.isValid() ) + onOpenMenuModelIndex( curIndex, &event ); + return 1; + } default: break; } diff --git a/src/eepp/ui/uitreeview.cpp b/src/eepp/ui/uitreeview.cpp index 312916620..c08110a2c 100644 --- a/src/eepp/ui/uitreeview.cpp +++ b/src/eepp/ui/uitreeview.cpp @@ -129,6 +129,14 @@ void UITreeView::bindNavigationClick( UIWidget* widget ) { } } } ); + + widget->addEventListener( Event::MouseClick, [&]( const Event* event ) { + auto mouseEvent = static_cast( event ); + auto idx = mouseEvent->getNode()->getParent()->asType()->getCurIndex(); + if ( mouseEvent->getFlags() & EE_BUTTON_RMASK ) { + onOpenMenuModelIndex( idx, event ); + } + } ); } UIWidget* UITreeView::setupCell( UITableCell* widget, UIWidget* rowWidget, @@ -611,6 +619,11 @@ Uint32 UITreeView::onKeyDown( const KeyEvent& event ) { } return 1; } + case KEY_MENU: { + if ( curIndex.isValid() ) + onOpenMenuModelIndex( curIndex, &event ); + return 1; + } default: break; } diff --git a/src/eepp/ui/uiwindow.cpp b/src/eepp/ui/uiwindow.cpp index 670c15776..4872076b6 100644 --- a/src/eepp/ui/uiwindow.cpp +++ b/src/eepp/ui/uiwindow.cpp @@ -77,7 +77,6 @@ UIWindow::UIWindow( UIWindow::WindowBaseContainerType type, const StyleConfig& w mContainer->setParent( this ); mContainer->clipEnable(); mContainer->enableReportSizeChangeToChilds(); - mContainer->setSize( getSize() ); mContainer->addEventListener( Event::OnPositionChange, cb::Make1( this, &UIWindow::onContainerPositionChange ) ); @@ -432,7 +431,7 @@ Sizei UIWindow::getFrameBufferSize() { : mSize.ceil().asInt(); } -void UIWindow::onWindowReady() { +void UIWindow::forcedApplyStyle() { if ( NULL != mStyle ) { mStyle->setForceReapplyProperties( true ); mStyle->setDisableAnimations( true ); @@ -441,6 +440,15 @@ void UIWindow::onWindowReady() { } } +void UIWindow::onWindowReady() { + forcedApplyStyle(); + + if ( mShowWhenReady ) { + mShowWhenReady = false; + show(); + } +} + void UIWindow::createModalNode() { Node* node = mSceneNode; @@ -1105,6 +1113,11 @@ bool UIWindow::hide() { return false; } +void UIWindow::showWhenReady() { + hide(); + mShowWhenReady = true; +} + void UIWindow::onAlphaChange() { if ( mStyleConfig.WinFlags & UI_WIN_SHARE_ALPHA_WITH_CHILDS ) { Node* CurChild = mChild; @@ -1157,6 +1170,7 @@ void UIWindow::setTitle( const String& text ) { mTitle->setHorizontalAlign( getHorizontalAlign() ); mTitle->setVerticalAlign( getVerticalAlign() ); mTitle->setEnabled( false ); + mTitle->clipEnable(); mTitle->setVisible( !( mStyleConfig.WinFlags & UI_WIN_NO_DECORATION ) ); } @@ -1388,6 +1402,28 @@ Sizef UIWindow::getSizeWithoutDecoration() { return size; } +Sizef UIWindow::getMinWindowTitleSizeRequired() { + Sizef size( PixelDensity::pxToDp( mTitle->getTextWidth() + mTitle->getFontSize() * 4 ) + + mPadding.Left + mPadding.Right, + 0 ); + + if ( NULL != mWindowDecoration ) { + size.x += mBorderLeft->getSize().getWidth() + mBorderRight->getSize().getWidth(); + size.y += mWindowDecoration->getSize().getHeight(); + } + + if ( NULL != mButtonClose ) + size.x += mButtonClose->getSize().getWidth(); + + if ( NULL != mButtonMaximize ) + size.x += mButtonMaximize->getSize().getWidth(); + + if ( NULL != mButtonMinimize ) + size.x += mButtonMinimize->getSize().getWidth(); + + return size; +} + const Sizef& UIWindow::getMinWindowSize() { return mStyleConfig.MinWindowSize; } @@ -1638,7 +1674,7 @@ void UIWindow::preDraw() {} void UIWindow::postDraw() {} Uint32 UIWindow::onKeyDown( const KeyEvent& event ) { - std::string cmd = mKeyBindings.getCommandFromKeyBind( {event.getKeyCode(), event.getMod()} ); + std::string cmd = mKeyBindings.getCommandFromKeyBind( { event.getKeyCode(), event.getMod() } ); if ( !cmd.empty() ) { executeKeyBindingCommand( cmd ); return 0; diff --git a/src/tools/codeeditor/appconfig.cpp b/src/tools/codeeditor/appconfig.cpp index 4612d1bab..43e00220f 100644 --- a/src/tools/codeeditor/appconfig.cpp +++ b/src/tools/codeeditor/appconfig.cpp @@ -44,6 +44,9 @@ void AppConfig::load( std::string& confPath, std::string& keybindingsPath, window.pixelDensity = iniState.getValueF( "window", "pixeldensity" ); window.winIcon = ini.getValue( "window", "winicon", resPath + "assets/icon/ee.png" ); window.panelPartition = iniState.getValue( "window", "panel_partition", "15%" ); + window.displayIndex = iniState.getValueI( "window", "display_index", 0 ); + window.position.x = iniState.getValueI( "window", "x", -1 ); + window.position.y = iniState.getValueI( "window", "y", -1 ); editor.showLineNumbers = ini.getValueB( "editor", "show_line_numbers", true ); editor.showWhiteSpaces = ini.getValueB( "editor", "show_white_spaces", true ); editor.highlightMatchingBracket = @@ -94,12 +97,17 @@ void AppConfig::save( const std::vector& recentFiles, editor.colorScheme = colorSchemeName; window.size = win->getLastWindowedSize(); window.maximized = win->isMaximized(); + window.displayIndex = win->getCurrentDisplayIndex(); + window.position = win->getPosition() - win->getBorderSize().getSize(); ini.setValue( "editor", "colorscheme", editor.colorScheme ); iniState.setValueI( "window", "width", window.size.getWidth() ); iniState.setValueI( "window", "height", window.size.getHeight() ); iniState.setValueB( "window", "maximized", window.maximized ); iniState.setValueF( "window", "pixeldensity", window.pixelDensity ); iniState.setValue( "window", "panel_partition", panelPartition ); + iniState.setValueI( "window", "display_index", window.displayIndex ); + iniState.setValueI( "window", "x", window.position.x ); + iniState.setValueI( "window", "y", window.position.y ); iniState.setValue( "files", "recentfiles", String::join( urlEncode( recentFiles ), ';' ) ); iniState.setValue( "folders", "recentfolders", String::join( urlEncode( recentFolders ), ';' ) ); diff --git a/src/tools/codeeditor/appconfig.hpp b/src/tools/codeeditor/appconfig.hpp index fac4e3a10..7a5060523 100644 --- a/src/tools/codeeditor/appconfig.hpp +++ b/src/tools/codeeditor/appconfig.hpp @@ -29,6 +29,8 @@ struct WindowConfig { std::string winIcon; bool maximized{ false }; std::string panelPartition; + int displayIndex{ 0 }; + Vector2i position{ -1, -1 }; }; struct CodeEditorConfig { diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index be1c21a19..3cb87eb59 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -37,7 +37,7 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { msgBox = nullptr; } ); msgBox->setTitle( "Close " + mWindowTitle + "?" ); msgBox->center(); - msgBox->show(); + msgBox->showWhenReady(); return false; } else { if ( !mCurrentProject.empty() ) @@ -505,7 +505,7 @@ UIMenu* App::createWindowMenu() { msgBox->getTextInput()->setText( String::format( "%.2f", mConfig.window.pixelDensity ) ); msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->show(); + msgBox->showWhenReady(); msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { msgBox->closeWindow(); Float val; @@ -533,7 +533,7 @@ UIMenu* App::createWindowMenu() { msgBox->setTitle( mWindowTitle ); msgBox->getTextInput()->setText( mConfig.editor.fontSize.toString() ); msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->show(); + msgBox->showWhenReady(); msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { mConfig.editor.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { @@ -547,7 +547,7 @@ UIMenu* App::createWindowMenu() { msgBox->setTitle( mWindowTitle ); msgBox->getTextInput()->setText( mConfig.ui.fontSize.toString() ); msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->show(); + msgBox->showWhenReady(); msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { mConfig.ui.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); Float fontSize = mConfig.ui.fontSize.asDp( 0, Sizef(), mDisplayDPI ); @@ -707,7 +707,7 @@ UIMenu* App::createViewMenu() { msgBox->getTextInput()->setAllowOnlyNumbers( true, false ); msgBox->getTextInput()->setText( String::toString( mConfig.editor.lineBreakingColumn ) ); - msgBox->show(); + msgBox->showWhenReady(); msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { int val; if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { @@ -1168,7 +1168,7 @@ void App::closeFolder() { msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { msgBox = nullptr; } ); msgBox->setTitle( "Close Folder?" ); msgBox->center(); - msgBox->show(); + msgBox->showWhenReady(); } else { closeEditors(); } @@ -1712,6 +1712,142 @@ void App::loadDirTree( const std::string& path ) { SyntaxDefinitionManager::instance()->getExtensionsPatternsSupported() ); } +UIMessageBox* errorMsgBox( const String& msg ) { + UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK, msg ); + msgBox->setTitle( "Error" ); + msgBox->showWhenReady(); + return msgBox; +} + +UIMessageBox* fileAlreadyExistsMsgBox() { + return errorMsgBox( "File already exists!" ); +} + +std::string getNewFilePath( const FileInfo& file, UIMessageBox* msgBox ) { + auto folderName( msgBox->getTextInput()->getText() ); + auto folderPath( file.getDirectoryPath() ); + FileSystem::dirAddSlashAtEnd( folderPath ); + return folderPath + folderName; +} + +UIMessageBox* newInputMsgBox( const String& title, const String& msg ) { + UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, msg ); + msgBox->setTitle( title ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + return msgBox; +} + +void renameFile( const FileInfo& file ) { + if ( !file.exists() ) + return; + UIMessageBox* msgBox = + newInputMsgBox( "Rename file \"" + file.getFileName() + "\"", "Enter new file name:" ); + msgBox->getTextInput()->setText( file.getFileName() ); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [file, msgBox]( const Event* ) { + auto newFilePath( getNewFilePath( file, msgBox ) ); + if ( !FileSystem::fileExists( newFilePath ) ) { + if ( 0 != std::rename( file.getFilepath().c_str(), newFilePath.c_str() ) ) + errorMsgBox( "Error renaming file." ); + msgBox->closeWindow(); + } else { + fileAlreadyExistsMsgBox(); + } + } ); +} + +void App::createProjectTreeMenu( const FileInfo& file ) { + if ( mProjectTreeMenu && mProjectTreeMenu->isVisible() ) + mProjectTreeMenu->close(); + mProjectTreeMenu = UIPopUpMenu::New(); + if ( file.isDirectory() ) { + mProjectTreeMenu->add( "New File...", findIcon( "file-add" ) )->setId( "new_file" ); + mProjectTreeMenu->add( "New Folder...", findIcon( "folder-add" ) )->setId( "new_folder" ); + } else { + mProjectTreeMenu->add( "Open File", findIcon( "document-open" ) )->setId( "open_file" ); + mProjectTreeMenu->add( "New File in directory...", findIcon( "file-add" ) ) + ->setId( "new_file_in_place" ); + mProjectTreeMenu->add( "Duplicate File...", findIcon( "file-copy" ) ) + ->setId( "duplicate_file" ); + } + mProjectTreeMenu->add( "Rename", findIcon( "edit" ) )->setId( "rename" ); + mProjectTreeMenu->add( "Remove...", findIcon( "delete-bin" ), "F2" )->setId( "remove" ); + mProjectTreeMenu->addEventListener( Event::OnItemClicked, [&, file]( const Event* event ) { + if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) + return; + UIMenuItem* item = event->getNode()->asType(); + std::string txt( item->getId() ); + + if ( "new_file" == txt || "new_file_in_place" == txt ) { + UIMessageBox* msgBox = newInputMsgBox( "Create new file", "Enter new file name:" ); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [file, msgBox]( const Event* ) { + auto newFilePath( getNewFilePath( file, msgBox ) ); + if ( !FileSystem::fileExists( newFilePath ) ) { + if ( !FileSystem::fileWrite( newFilePath, nullptr, 0 ) ) + errorMsgBox( "Couldn't create file." ); + msgBox->closeWindow(); + } else { + fileAlreadyExistsMsgBox(); + } + } ); + } else if ( "new_folder" == txt ) { + UIMessageBox* msgBox = newInputMsgBox( "Create new folder", "Enter new folder name:" ); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [file, msgBox]( const Event* ) { + auto newFolderPath( getNewFilePath( file, msgBox ) ); + if ( !FileSystem::fileExists( newFolderPath ) ) { + if ( !FileSystem::makeDir( newFolderPath ) ) + errorMsgBox( "Couldn't create directory." ); + msgBox->closeWindow(); + } else { + fileAlreadyExistsMsgBox(); + } + } ); + } else if ( "open_file" == txt ) { + loadFileFromPath( file.getFilepath() ); + } else if ( "remove" == txt ) { + if ( file.isDirectory() && !FileSystem::filesGetInPath( file.getFilepath() ).empty() ) { + errorMsgBox( "Cannot remove non-empty directory." ); + return; + } + + UIMessageBox* msgBox = + UIMessageBox::New( UIMessageBox::OK_CANCEL, + "Do you really want to remove \"" + file.getFileName() + "\"?" ); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [file, msgBox]( const Event* ) { + if ( !FileSystem::fileRemove( file.getFilepath() ) ) { + errorMsgBox( String::format( "Couldn't remove %s.", + file.isDirectory() ? "directory" : "file" ) ); + } + msgBox->closeWindow(); + } ); + msgBox->setTitle( "Remove file?" ); + msgBox->center(); + msgBox->showWhenReady(); + } else if ( "duplicate_file" == txt ) { + UIMessageBox* msgBox = newInputMsgBox( "Duplicate file \"" + file.getFileName() + "\"", + "Enter duplicate file name:" ); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [file, msgBox]( const Event* ) { + auto newFilePath( getNewFilePath( file, msgBox ) ); + if ( !FileSystem::fileExists( newFilePath ) ) { + if ( !FileSystem::fileCopy( file.getFilepath(), newFilePath ) ) + errorMsgBox( "Error copying file." ); + msgBox->closeWindow(); + } else { + fileAlreadyExistsMsgBox(); + } + } ); + } else if ( "rename" == txt ) { + renameFile( file ); + } + } ); + + Vector2f pos( mWindow->getInput()->getMousePosf() ); + mProjectTreeMenu->nodeToWorldTranslation( pos ); + UIMenu::findBestMenuPos( pos, mProjectTreeMenu ); + mProjectTreeMenu->setPixelsPosition( pos ); + mProjectTreeMenu->show(); +} + void App::initProjectTreeView( const std::string& path ) { mProjectTreeView = mUISceneNode->find( "project_view" ); mProjectTreeView->setColumnsHidden( @@ -1728,24 +1864,43 @@ void App::initProjectTreeView( const std::string& path ) { mProjectTreeView->setSingleClickNavigation( mConfig.editor.singleClickTreeNavigation ); mProjectTreeView->addEventListener( Event::OnModelEvent, [&]( const Event* event ) { const ModelEvent* modelEvent = static_cast( event ); - if ( modelEvent->getModelEventType() == ModelEventType::Open ) { + ModelEventType type = modelEvent->getModelEventType(); + if ( type == ModelEventType::Open || type == ModelEventType::OpenMenu ) { Variant vPath( modelEvent->getModel()->data( modelEvent->getModelIndex(), ModelRole::Custom ) ); if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) { std::string path( vPath.asCStr() ); - UITab* tab = mEditorSplitter->isDocumentOpen( path ); - if ( !tab ) { - FileInfo fileInfo( path ); - if ( fileInfo.exists() && fileInfo.isRegularFile() ) - loadFileFromPath( path ); - } else { - tab->getTabWidget()->setTabSelected( tab ); + if ( type == ModelEventType::Open ) { + UITab* tab = mEditorSplitter->isDocumentOpen( path ); + if ( !tab ) { + FileInfo fileInfo( path ); + if ( fileInfo.exists() && fileInfo.isRegularFile() ) + loadFileFromPath( path ); + } else { + tab->getTabWidget()->setTabSelected( tab ); + } + } else { // ModelEventType::OpenMenu + bool focusOnSelection = mProjectTreeView->getFocusOnSelection(); + mProjectTreeView->setFocusOnSelection( false ); + mProjectTreeView->getSelection().set( modelEvent->getModelIndex() ); + mProjectTreeView->setFocusOnSelection( focusOnSelection ); + createProjectTreeMenu( FileInfo( path ) ); } } } } ); mProjectTreeView->addEventListener( Event::KeyDown, [&]( const Event* event ) { const KeyEvent* keyEvent = static_cast( event ); + if ( keyEvent->getKeyCode() == KEY_F2 ) { + ModelIndex modelIndex = mProjectTreeView->getSelection().first(); + if ( !modelIndex.isValid() ) + return 0; + Variant vPath( mProjectTreeView->getModel()->data( modelIndex, ModelRole::Custom ) ); + if ( vPath.isValid() && vPath.is( Variant::Type::cstr ) ) + renameFile( FileInfo( vPath.asCStr() ) ); + return 1; + } + if ( mEditorSplitter->getCurEditor() ) { std::string cmd = mEditorSplitter->getCurEditor()->getKeyBindings().getCommandFromKeyBind( @@ -1754,6 +1909,8 @@ void App::initProjectTreeView( const std::string& path ) { mEditorSplitter->getCurEditor()->getDocument().execute( cmd ); } } + + return 1; } ); if ( !path.empty() && FileSystem::fileExists( path ) ) { @@ -1828,13 +1985,16 @@ FontTrueType* App::loadFont( const std::string& name, std::string fontPath, } void App::init( const std::string& file, const Float& pidelDensity ) { + loadConfig(); + DisplayManager* displayManager = Engine::instance()->getDisplayManager(); - Display* currentDisplay = displayManager->getDisplayIndex( 0 ); + Display* currentDisplay = displayManager->getDisplayIndex( + mConfig.window.displayIndex < displayManager->getDisplayCount() + ? mConfig.window.displayIndex + : 0 ); mDisplayDPI = currentDisplay->getDPI(); mResPath = Sys::getProcessPath(); - loadConfig(); - mConfig.window.pixelDensity = pidelDensity > 0 ? pidelDensity : ( mConfig.window.pixelDensity > 0 ? mConfig.window.pixelDensity @@ -1857,6 +2017,10 @@ void App::init( const std::string& file, const Float& pidelDensity ) { mWindow = engine->createWindow( winSettings, contextSettings ); if ( mWindow->isOpen() ) { + if ( mConfig.window.position != Vector2i( -1, -1 ) && + mConfig.window.displayIndex < displayManager->getDisplayCount() ) + mWindow->setPosition( mConfig.window.position.x, mConfig.window.position.y ); + loadKeybindings(); PixelDensity::setPixelDensity( mConfig.window.pixelDensity ); @@ -2111,6 +2275,7 @@ void App::init( const std::string& file, const Float& pidelDensity ) { { "cut", 0xf0c1 }, { "copy", 0xecd5 }, { "paste", 0xeb91 }, + { "edit", 0xec86 }, { "split-horizontal", 0xf17a }, { "split-vertical", 0xf17b }, { "find-replace", 0xed2b }, @@ -2118,9 +2283,12 @@ void App::init( const std::string& file, const Float& pidelDensity ) { { "folder-open", 0xed70 }, { "folder-add", 0xed5a }, { "file", 0xecc3 }, + { "file-add", 0xecc9 }, + { "file-copy", 0xecd3 }, { "file-code", 0xecd1 }, { "file-edit", 0xecdb }, { "font-size", 0xed8d }, + { "delete-bin", 0xec1e }, { "zoom-in", 0xf2db }, { "zoom-out", 0xf2dd }, { "zoom-reset", 0xeb47 }, diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index 6511a370a..61c71605e 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -90,6 +90,7 @@ class App : public UICodeEditorSplitter::Client { UIPopUpMenu* mViewMenu{ nullptr }; UIPopUpMenu* mWindowMenu{ nullptr }; UIPopUpMenu* mToolsMenu{ nullptr }; + UIPopUpMenu* mProjectTreeMenu{ nullptr }; UISplitter* mProjectSplitter{ nullptr }; UITabWidget* mSidePanel{ nullptr }; UICodeEditorSplitter* mEditorSplitter{ nullptr }; @@ -240,6 +241,8 @@ class App : public UICodeEditorSplitter::Client { void createDocAlert( UICodeEditor* editor ); void syncProjectTreeWithEditor( UICodeEditor* editor ); + + void createProjectTreeMenu( const FileInfo& file ); }; #endif // EE_TOOLS_CODEEDITOR_HPP