diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index 16c349126..9baa81c66 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -46,6 +46,8 @@ class EE_API UISceneNode : public SceneNode { void setTranslator( Translator translator ); + const Translator& getTranslator() const; + Translator& getTranslator(); String getTranslatorString( const std::string& str ); diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 6d9a3c82d..fab6c2049 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -162,6 +162,7 @@ void Text::setString( const String& string ) { mColorsNeedUpdate = true; mGeometryNeedUpdate = true; mCachedWidthNeedUpdate = true; + mContainsColorEmoji = false; if ( FontManager::instance()->getColorEmojiFont() != nullptr ) { if ( mFont->getType() == FontType::TTF ) { FontTrueType* fontTrueType = static_cast( mFont ); diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index c968f9754..02b045bdb 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -1004,8 +1004,7 @@ SyntaxDefinitionManager::SyntaxDefinitionManager() { { { "^%[.-%]" }, "keyword2" }, { { "%s%[.-%]" }, "keyword2" }, { { "=" }, "operator" }, - { { "https?://(([%w_.~!*:@&+$/?%%#-]-)(%w[-.%w]*%.)(%w%w%w?%w?)(:?)(%d*)(/" - "?)([%w_.~!*:@&+$/?%%#=-]*))" }, + { { "https?://[%w_.~!*:@&+$/?%%#-]-%w[-.%w]*%.%w%w%w?%w?:?%d*/?[%w_.~!*:@&+$/?%%#=-]*" }, "link" }, { { "[a-z]+" }, "symbol" } }, { { "true", "literal" }, { "false", "literal" } }, @@ -1888,8 +1887,7 @@ SyntaxDefinitionManager::SyntaxDefinitionManager() { { { "%f[%S]%-?%d+[%d%.eE]*f?" }, "number" }, { { "%f[%S]%-?%.?%d+f?" }, "number" }, { { "!!float", "\n", "\\" }, "number" }, - { { "https?://(([%w_.~!*:@&+$/?%%#-]-)(%w[-.%w]*%.)(%w%w%w?%w?)(:?)(%d*)(/" - "?)([%w_.~!*:@&+$/?%%#=-]*))" }, + { { "https?://[%w_.~!*:@&+$/?%%#-]-%w[-.%w]*%.%w%w%w?%w?:?%d*/?[%w_.~!*:@&+$/?%%#=-]*" }, "link" }, { { "%-%-%-" }, "literal" }, }, diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index c91cac996..da1106c48 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -312,6 +312,7 @@ void UICodeEditor::scheduledUpdate( const Time& ) { if ( !( getUISceneNode()->getWindow()->getInput()->getPressTrigger() & EE_BUTTON_LMASK ) ) { if ( mMinimapDragging ) { mMinimapDragging = false; + getEventDispatcher()->setNodeDragging( NULL ); mVScrollBar->setEnabled( true ); getUISceneNode()->setCursor( Cursor::Arrow ); updateMipmapHover( getUISceneNode()->getWindow()->getInput()->getMousePosf() ); @@ -954,8 +955,8 @@ Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags if ( mMinimapEnabled ) { Rectf rect( getMinimapRect( getScreenStart() ) ); - if ( ( flags & EE_BUTTON_LMASK ) && !mVScrollBar->isDragging() && !mMinimapDragging && - rect.contains( position.asFloat() ) ) { + if ( ( flags & EE_BUTTON_LMASK ) && !getEventDispatcher()->isNodeDragging() && + !mMinimapDragging && rect.contains( position.asFloat() ) ) { if ( mMouseDown ) return 1; updateMipmapHover( position.asFloat() ); @@ -968,9 +969,9 @@ Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags mMinimapScrollOffset = calculateMinimapClickedLine( position ) - getVisibleLineRange().first; mMinimapDragging = true; - getUISceneNode()->getWindow()->getInput()->captureMouse( true ); - getUISceneNode()->setCursor( Cursor::Arrow ); + getEventDispatcher()->setNodeDragging( this ); mVScrollBar->setEnabled( false ); + getUISceneNode()->setCursor( Cursor::Arrow ); return 1; } else if ( mMinimapDragging ) { if ( mMouseDown ) { @@ -1081,6 +1082,7 @@ Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags ) if ( flags & EE_BUTTON_LMASK ) { if ( mMinimapDragging ) { mMinimapDragging = false; + getEventDispatcher()->setNodeDragging( NULL ); mVScrollBar->setEnabled( true ); getUISceneNode()->setCursor( Cursor::Arrow ); updateMipmapHover( position.asFloat() ); diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index a3ad05822..f7f518774 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -106,6 +106,14 @@ void UISceneNode::setTranslator( Translator translator ) { mTranslator = translator; } +const Translator& UISceneNode::getTranslator() const { + return mTranslator; +} + +Translator& UISceneNode::getTranslator() { + return mTranslator; +} + String UISceneNode::getTranslatorString( const std::string& str ) { if ( String::startsWith( str, "@string/" ) ) { String tstr = mTranslator.getString( str.substr( 8 ) ); diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index 1b0c89b19..1070f582f 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -190,7 +190,8 @@ struct ProjectPath { }; void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, - const std::string& configPath ) { + const std::string& configPath, + const ProjectDocumentConfig& docConfig ) { FileSystem::dirAddSlashAtEnd( projectFolder ); std::vector editors = editorSplitter->getAllEditors(); std::vector paths; @@ -211,11 +212,23 @@ void AppConfig::saveProject( std::string projectFolder, UICodeEditorSplitter* ed !editorSplitter->getTabWidgets().empty() ? editorSplitter->getTabWidgets()[0]->getTabSelectedIndex() : 0 ); + ini.setValueB( "document", "use_global_settings", docConfig.useGlobalSettings ); + ini.setValueB( "document", "trim_trailing_whitespaces", docConfig.doc.trimTrailingWhitespaces ); + ini.setValueB( "document", "force_new_line_at_end_of_file", + docConfig.doc.forceNewLineAtEndOfFile ); + ini.setValueB( "document", "auto_detect_indent_type", docConfig.doc.autoDetectIndentType ); + ini.setValueB( "document", "write_bom", docConfig.doc.writeUnicodeBOM ); + ini.setValueI( "document", "indent_width", docConfig.doc.indentWidth ); + ini.setValueB( "document", "indent_spaces", docConfig.doc.indentSpaces ); + ini.setValueB( "document", "windows_line_endings", docConfig.doc.windowsLineEndings ); + ini.setValueI( "document", "tab_width", docConfig.doc.tabWidth ); + ini.setValueI( "document", "line_breaking_column", docConfig.doc.lineBreakingColumn ); ini.writeFile(); } void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, - const std::string& configPath, std::shared_ptr pool ) { + const std::string& configPath, ProjectDocumentConfig& docConfig, + std::shared_ptr pool ) { FileSystem::dirAddSlashAtEnd( projectFolder ); std::string projectsPath( configPath + "projects" + FileSystem::getOSSlash() ); MD5::Result hash = MD5::fromString( projectFolder ); @@ -253,4 +266,19 @@ void AppConfig::loadProject( std::string projectFolder, UICodeEditorSplitter* ed editorSplitter->switchToTab( currentPage ); } ); } + + docConfig.useGlobalSettings = ini.getValueB( "document", "use_global_settings", true ); + docConfig.doc.trimTrailingWhitespaces = + ini.getValueB( "document", "trim_trailing_whitespaces", false ); + docConfig.doc.forceNewLineAtEndOfFile = + ini.getValueB( "document", "force_new_line_at_end_of_file", false ); + docConfig.doc.autoDetectIndentType = + ini.getValueB( "document", "auto_detect_indent_type", true ); + docConfig.doc.writeUnicodeBOM = ini.getValueB( "document", "write_bom", false ); + docConfig.doc.indentWidth = ini.getValueI( "document", "indent_width", 4 ); + docConfig.doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false ); + docConfig.doc.windowsLineEndings = ini.getValueB( "document", "windows_line_endings", false ); + docConfig.doc.tabWidth = eemax( 2, ini.getValueI( "document", "tab_width", 4 ) ); + docConfig.doc.lineBreakingColumn = + eemax( 0, ini.getValueI( "document", "line_breaking_column", 100 ) ); } diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 5d32abebc..5cadcd8d5 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -73,6 +73,13 @@ struct DocumentConfig { int lineBreakingColumn{ 100 }; }; +struct ProjectDocumentConfig { + bool useGlobalSettings{ true }; + DocumentConfig doc; + ProjectDocumentConfig() {} + ProjectDocumentConfig( const DocumentConfig& doc ) { this->doc = doc; } +}; + struct AppConfig { WindowConfig window; CodeEditorConfig editor; @@ -92,10 +99,11 @@ struct AppConfig { EE::Window::Window* win, const std::string& colorSchemeName ); void saveProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, - const std::string& configPath ); + const std::string& configPath, const ProjectDocumentConfig& docConfig ); void loadProject( std::string projectFolder, UICodeEditorSplitter* editorSplitter, - const std::string& configPath, std::shared_ptr pool ); + const std::string& configPath, ProjectDocumentConfig& docConfig, + std::shared_ptr pool ); }; #endif // APPCONFIG_HPP diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 9fb1ed9e6..257c89228 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -16,20 +16,24 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { mEditorSplitter->getCurEditor()->isDirty() ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, - "Do you really want to close the code editor?\nAll changes will be lost." ); + i18n( "confirm_ecode_exit", + "Do you really want to close the code editor?\nAll changes will be lost." ) + .unescape() ); msgBox->addEventListener( Event::MsgBoxConfirmClick, [&]( const Event* ) { if ( !mCurrentProject.empty() ) - mConfig.saveProject( mCurrentProject, mEditorSplitter, mConfigPath ); + mConfig.saveProject( mCurrentProject, mEditorSplitter, mConfigPath, + mProjectDocConfig ); mWindow->close(); } ); msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { msgBox = nullptr; } ); - msgBox->setTitle( "Close " + mWindowTitle + "?" ); + msgBox->setTitle( String::format( i18n( "close_title", "Close %s?" ).toUtf8().c_str(), + mWindowTitle.c_str() ) ); msgBox->center(); msgBox->showWhenReady(); return false; } else { if ( !mCurrentProject.empty() ) - mConfig.saveProject( mCurrentProject, mEditorSplitter, mConfigPath ); + mConfig.saveProject( mCurrentProject, mEditorSplitter, mConfigPath, mProjectDocConfig ); return true; } } @@ -135,7 +139,7 @@ void App::openFileDialog() { UIFileDialog* dialog = UIFileDialog::New( UIFileDialog::DefaultFlags, "*", mLastFileFolder.empty() ? "." : mLastFileFolder ); dialog->setWinFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); - dialog->setTitle( "Open File" ); + dialog->setTitle( i18n( "open_file", "Open File" ) ); dialog->setCloseShortcut( KEY_ESCAPE ); dialog->addEventListener( Event::OpenFile, [&]( const Event* event ) { auto file = event->getNode()->asType()->getFullPath(); @@ -157,7 +161,7 @@ void App::openFolderDialog() { UIFileDialog::ShowOnlyFolders, "*", "." ); dialog->setWinFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); - dialog->setTitle( "Open Folder" ); + dialog->setTitle( i18n( "open_folder", "Open Folder" ) ); dialog->setCloseShortcut( KEY_ESCAPE ); dialog->addEventListener( Event::OpenFile, [&]( const Event* event ) { String path( event->getNode()->asType()->getFullPath() ); @@ -699,7 +703,6 @@ UIMenu* App::createViewMenu() { ->setActive( mConfig.editor.syncProjectTreeWithEditor ) ->setTooltipText( "Syncronizes the current focused document as the selected\nfile in the " "directory tree." ); - mViewMenu->add( "Line Breaking Column" ); mViewMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) @@ -774,25 +777,6 @@ UIMenu* App::createViewMenu() { mProjectTreeView->setSingleClickNavigation( mConfig.editor.singleClickTreeNavigation ); } else if ( item->getText() == "Synchronize project tree with editor" ) { mConfig.editor.syncProjectTreeWithEditor = item->asType()->isActive(); - } else if ( item->getText() == "Line Breaking Column" ) { - UIMessageBox* msgBox = - UIMessageBox::New( UIMessageBox::INPUT, "Set Line Breaking Column:\n" - "Set 0 to disable it.\n" ); - msgBox->setTitle( mWindowTitle ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->getTextInput()->setAllowOnlyNumbers( true, false ); - msgBox->getTextInput()->setText( String::toString( mConfig.doc.lineBreakingColumn ) ); - msgBox->showWhenReady(); - msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { - int val; - if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { - mConfig.doc.lineBreakingColumn = val; - mEditorSplitter->forEachEditor( - [val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } ); - msgBox->closeWindow(); - } - } ); - setFocusEditorOnClose( msgBox ); } else { String text = String( event->getNode()->asType()->getText() ).toLower(); String::replaceAll( text, " ", "-" ); @@ -817,8 +801,8 @@ Drawable* App::findIcon( const std::string& name ) { return nullptr; } -String App::i18n( const std::string& name, const String& def ) { - return mUISceneNode->getTranslatorString( name, def ); +String App::i18n( const std::string& key, const String& def ) { + return mUISceneNode->getTranslatorStringFromKey( key, def ); } UIMenu* App::createEditMenu() { @@ -842,15 +826,16 @@ UIMenu* App::createEditMenu() { getKeybind( "find-replace" ) ) ->setId( "find-replace" ); menu->addSeparator(); - menu->add( i18n( "key_bindings", "Key Bindings" ), findIcon( "keybindings" ), - getKeybind( "keybindings" ) ) - ->setId( "keybindings" ); menu->add( i18n( "open_containing_folder", "Open Containing Folder..." ), findIcon( "folder-open" ), getKeybind( "open-containing-folder" ) ) ->setId( "open-containing-folder" ); menu->add( i18n( "copy_file_path", "Copy File Path" ), findIcon( "copy" ), getKeybind( "copy-file-path" ) ) ->setId( "copy-file-path" ); + menu->addSeparator(); + menu->add( i18n( "key_bindings", "Key Bindings" ), findIcon( "keybindings" ), + getKeybind( "keybindings" ) ) + ->setId( "keybindings" ); menu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; @@ -882,18 +867,25 @@ makeAutoClosePairs( const std::string& strPairs ) { } UIMenu* App::createDocumentMenu() { + auto shouldCloseCb = []( UIMenuItem* ) -> bool { return false; }; + mDocMenu = UIPopUpMenu::New(); // **** CURRENT DOCUMENT **** - mDocMenu->add( "Current Document" )->setTextAlign( UI_HALIGN_CENTER ); + mDocMenu->add( i18n( "current_document", "Current Document" ) ) + ->setTextAlign( UI_HALIGN_CENTER ); - mDocMenu->addCheckBox( "Auto Detect Indent Type & Width", mConfig.doc.autoDetectIndentType ) + mDocMenu + ->addCheckBox( + i18n( "auto_detect_indent_type_and_width", "Auto Detect Indent Type & Width" ), + mConfig.doc.autoDetectIndentType ) ->setId( "auto_indent_cur" ); UIPopUpMenu* tabTypeMenu = UIPopUpMenu::New(); - tabTypeMenu->addRadioButton( "Tabs" )->setId( "tabs" ); - tabTypeMenu->addRadioButton( "Spaces" )->setId( "spaces" ); - mDocMenu->addSubMenu( "Indentation Type", nullptr, tabTypeMenu )->setId( "indent_type_cur" ); + tabTypeMenu->addRadioButton( i18n( "tabs", "Tabs" ) )->setId( "tabs" ); + tabTypeMenu->addRadioButton( i18n( "spaces", "Spaces" ) )->setId( "spaces" ); + mDocMenu->addSubMenu( i18n( "indentation_type", "Indentation Type" ), nullptr, tabTypeMenu ) + ->setId( "indent_type_cur" ); tabTypeMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { const String& text = event->getNode()->asType()->getId(); if ( mEditorSplitter->getCurEditor() ) { @@ -913,7 +905,8 @@ UIMenu* App::createDocumentMenu() { w ) ->setId( String::format( "indent_width_%zu", w ) ) ->setData( w ); - mDocMenu->addSubMenu( "Indent Width", nullptr, indentWidthMenu )->setId( "indent_width_cur" ); + mDocMenu->addSubMenu( i18n( "indent_width", "Indent Width" ), nullptr, indentWidthMenu ) + ->setId( "indent_width_cur" ); indentWidthMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( mEditorSplitter->getCurEditor() ) { int width = event->getNode()->getData(); @@ -929,7 +922,8 @@ UIMenu* App::createDocumentMenu() { mEditorSplitter->getCurEditor()->getTabWidth() == w ) ->setId( String::format( "tab_width_%zu", w ) ) ->setData( w ); - mDocMenu->addSubMenu( "Tab Width", nullptr, tabWidthMenu )->setId( "tab_width_cur" ); + mDocMenu->addSubMenu( i18n( "tab_width", "Tab Width" ), nullptr, tabWidthMenu ) + ->setId( "tab_width_cur" ); tabWidthMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( mEditorSplitter->getCurEditor() ) { int width = event->getNode()->getData(); @@ -942,7 +936,8 @@ UIMenu* App::createDocumentMenu() { ->setId( "windows" ); lineEndingsMenu->addRadioButton( "Unix (LF)", !mConfig.doc.windowsLineEndings ) ->setId( "unix" ); - mDocMenu->addSubMenu( "Line Endings", nullptr, lineEndingsMenu )->setId( "line_endings_cur" ); + mDocMenu->addSubMenu( i18n( "line_endings", "Line Endings" ), nullptr, lineEndingsMenu ) + ->setId( "line_endings_cur" ); lineEndingsMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { bool winLe = event->getNode()->asType()->getId() == "windows"; if ( mEditorSplitter->getCurEditor() ) { @@ -952,19 +947,23 @@ UIMenu* App::createDocumentMenu() { } } ); - mDocMenu->addCheckBox( "Read Only" )->setId( "read_only" ); + mDocMenu->addCheckBox( i18n( "read_only", "Read Only" ) )->setId( "read_only" ); - mDocMenu->addCheckBox( "Trim Trailing Whitespaces", mConfig.doc.trimTrailingWhitespaces ) + mDocMenu + ->addCheckBox( i18n( "trim_trailing_whitespaces", "Trim Trailing Whitespaces" ), + mConfig.doc.trimTrailingWhitespaces ) ->setId( "trim_whitespaces_cur" ); - mDocMenu->addCheckBox( "Force New Line at End of File", mConfig.doc.forceNewLineAtEndOfFile ) + mDocMenu + ->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ), + mConfig.doc.forceNewLineAtEndOfFile ) ->setId( "force_nl_cur" ); - mDocMenu->addCheckBox( "Write Unicode BOM", mConfig.doc.writeUnicodeBOM ) + mDocMenu + ->addCheckBox( i18n( "write_unicode_bom", "Write Unicode BOM" ), + mConfig.doc.writeUnicodeBOM ) ->setId( "write_bom_cur" ); - mDocMenu->addSeparator(); - mDocMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( !mEditorSplitter->getCurEditor() || event->getNode()->isType( UI_TYPE_MENU_SEPARATOR ) || @@ -989,21 +988,28 @@ UIMenu* App::createDocumentMenu() { } } ); - // **** GLOBAL OPTIONS **** - UIPopUpMenu* globalMenu = UIPopUpMenu::New(); - mDocMenu->addSubMenu( "Global Settings", findIcon( "global_settings" ), globalMenu ); + // **** GLOBAL SETTINGS **** + mDocMenu->addSeparator(); - globalMenu->addCheckBox( "Auto Detect Indent Type & Width", mConfig.doc.autoDetectIndentType ) + UIPopUpMenu* globalMenu = UIPopUpMenu::New(); + mDocMenu->addSubMenu( i18n( "global_settings", "Global Settings" ), + findIcon( "global-settings" ), globalMenu ); + + globalMenu + ->addCheckBox( + i18n( "auto_detect_indent_type_and_width", "Auto Detect Indent Type & Width" ), + mConfig.doc.autoDetectIndentType ) ->setId( "auto_indent" ); UIPopUpMenu* tabTypeMenuGlobal = UIPopUpMenu::New(); - tabTypeMenuGlobal->addRadioButton( "Tabs" ) + tabTypeMenuGlobal->addRadioButton( i18n( "tabs", "Tabs" ) ) ->setActive( !mConfig.doc.indentSpaces ) ->setId( "tabs" ); - tabTypeMenuGlobal->addRadioButton( "Spaces" ) + tabTypeMenuGlobal->addRadioButton( i18n( "spaces", "Spaces" ) ) ->setActive( mConfig.doc.indentSpaces ) ->setId( "spaces" ); - globalMenu->addSubMenu( "Indentation Type", nullptr, tabTypeMenuGlobal ) + globalMenu + ->addSubMenu( i18n( "indentation_type", "Indentation Type" ), nullptr, tabTypeMenuGlobal ) ->setId( "indent_type" ); tabTypeMenuGlobal->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { const String& text = event->getNode()->asType()->getId(); @@ -1015,7 +1021,7 @@ UIMenu* App::createDocumentMenu() { indentWidthMenuGlobal->addRadioButton( String::toString( w ), mConfig.doc.indentWidth == w ) ->setId( String::format( "indent_width_%d", w ) ) ->setData( w ); - globalMenu->addSubMenu( "Indent Width", nullptr, indentWidthMenuGlobal ) + globalMenu->addSubMenu( i18n( "indent_width", "Indent Width" ), nullptr, indentWidthMenuGlobal ) ->setId( "indent_width" ); indentWidthMenuGlobal->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { int width = event->getNode()->getData(); @@ -1027,7 +1033,8 @@ UIMenu* App::createDocumentMenu() { tabWidthMenuGlobal->addRadioButton( String::toString( w ), mConfig.doc.tabWidth == w ) ->setId( String::format( "tab_width_%d", w ) ) ->setData( w ); - globalMenu->addSubMenu( "Tab Width", nullptr, tabWidthMenuGlobal )->setId( "tab_width_cur" ); + globalMenu->addSubMenu( i18n( "tab_width", "Tab Width" ), nullptr, tabWidthMenuGlobal ) + ->setId( "tab_width_cur" ); tabWidthMenuGlobal->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { int width = event->getNode()->getData(); mConfig.doc.tabWidth = width; @@ -1038,7 +1045,7 @@ UIMenu* App::createDocumentMenu() { ->setId( "windows" ); lineEndingsGlobalMenu->addRadioButton( "Unix (LF)", !mConfig.doc.windowsLineEndings ) ->setId( "unix" ); - globalMenu->addSubMenu( "Line Endings", nullptr, lineEndingsGlobalMenu ) + globalMenu->addSubMenu( i18n( "line_endings", "Line Endings" ), nullptr, lineEndingsGlobalMenu ) ->setId( "line_endings" ); lineEndingsGlobalMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { bool winLe = event->getNode()->asType()->getId() == "windows"; @@ -1046,30 +1053,42 @@ UIMenu* App::createDocumentMenu() { } ); UIPopUpMenu* bracketsMenu = UIPopUpMenu::New(); - globalMenu->addSubMenu( "Auto-Close Brackets & Tags", nullptr, bracketsMenu ); + globalMenu->addSubMenu( i18n( "auto_close_brackets_and_tags", "Auto-Close Brackets & Tags" ), + nullptr, bracketsMenu ); auto& closeBrackets = mConfig.editor.autoCloseBrackets; - auto shouldCloseCb = []( UIMenuItem* ) -> bool { return false; }; - bracketsMenu->addCheckBox( "Brackets ()", closeBrackets.find( '(' ) != std::string::npos ) + bracketsMenu + ->addCheckBox( i18n( "brackets", "Brackets ()" ), + closeBrackets.find( '(' ) != std::string::npos ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "()" ); - bracketsMenu->addCheckBox( "Curly Brackets {}", closeBrackets.find( '{' ) != std::string::npos ) + bracketsMenu + ->addCheckBox( i18n( "curly_brackets", "Curly Brackets {}" ), + closeBrackets.find( '{' ) != std::string::npos ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "{}" ); bracketsMenu - ->addCheckBox( "Square Brackets []", closeBrackets.find( '[' ) != std::string::npos ) + ->addCheckBox( i18n( "square_brakcets", "Square Brackets []" ), + closeBrackets.find( '[' ) != std::string::npos ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "[]" ); - bracketsMenu->addCheckBox( "Single Quotes ''", closeBrackets.find( '\'' ) != std::string::npos ) + bracketsMenu + ->addCheckBox( i18n( "single_quotes", "Single Quotes ''" ), + closeBrackets.find( '\'' ) != std::string::npos ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "''" ); bracketsMenu - ->addCheckBox( "Double Quotes \"\"", closeBrackets.find( '"' ) != std::string::npos ) + ->addCheckBox( i18n( "double_quotes", "Double Quotes \"\"" ), + closeBrackets.find( '"' ) != std::string::npos ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "\"\"" ); - bracketsMenu->addCheckBox( "Back Quotes ``", closeBrackets.find( '`' ) != std::string::npos ) + bracketsMenu + ->addCheckBox( i18n( "back_quotes", "Back Quotes ``" ), + closeBrackets.find( '`' ) != std::string::npos ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "``" ); - bracketsMenu->addCheckBox( "Auto Close XML Tags", mConfig.editor.autoCloseXMLTags ) + bracketsMenu + ->addCheckBox( i18n( "auto_close_xml_tags", "Auto Close XML Tags" ), + mConfig.editor.autoCloseXMLTags ) ->setOnShouldCloseCb( shouldCloseCb ) ->setId( "XML" ); bracketsMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { @@ -1100,15 +1119,26 @@ UIMenu* App::createDocumentMenu() { } } ); - globalMenu->addCheckBox( "Trim Trailing Whitespaces", mConfig.doc.trimTrailingWhitespaces ) + globalMenu + ->addCheckBox( i18n( "trim_trailing_whitespaces", "Trim Trailing Whitespaces" ), + mConfig.doc.trimTrailingWhitespaces ) ->setId( "trim_whitespaces" ); - globalMenu->addCheckBox( "Force New Line at End of File", mConfig.doc.forceNewLineAtEndOfFile ) + globalMenu + ->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ), + mConfig.doc.forceNewLineAtEndOfFile ) ->setId( "force_nl" ); - globalMenu->addCheckBox( "Write Unicode BOM", mConfig.doc.writeUnicodeBOM ) + globalMenu + ->addCheckBox( i18n( "write_unicode_bom", "Write Unicode BOM" ), + mConfig.doc.writeUnicodeBOM ) ->setId( "write_bom" ); + globalMenu->addSeparator(); + + globalMenu->add( i18n( "line_breaking_column", "Line Breaking Column" ) ) + ->setId( "line_breaking_column" ); + globalMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( !mEditorSplitter->getCurEditor() || event->getNode()->isType( UI_TYPE_MENU_SEPARATOR ) || @@ -1127,12 +1157,250 @@ UIMenu* App::createDocumentMenu() { } else if ( "auto_indent" == id ) { mConfig.doc.autoDetectIndentType = item->isActive(); } + } else if ( "line_breaking_column" == id ) { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, i18n( "set_line_breaking_column", + "Set Line Breaking Column:\nSet 0 to disable it.\n" ) + .unescape() ); + msgBox->setTitle( mWindowTitle ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setAllowOnlyNumbers( true, false ); + msgBox->getTextInput()->setText( String::toString( mConfig.doc.lineBreakingColumn ) ); + msgBox->showWhenReady(); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { + int val; + if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { + mConfig.doc.lineBreakingColumn = val; + mEditorSplitter->forEachEditor( + [val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } ); + msgBox->closeWindow(); + } + } ); + setFocusEditorOnClose( msgBox ); } } ); + mDocMenu->addSeparator(); + + // **** PROJECT SETTINGS **** + mProjectDocConfig = mConfig.doc; + mProjectMenu = UIPopUpMenu::New(); + mProjectMenu + ->addCheckBox( i18n( "use_global_settings", "Use Global Settings" ), + mProjectDocConfig.useGlobalSettings ) + ->setOnShouldCloseCb( shouldCloseCb ) + ->setId( "use_global_settings" ); + + mProjectMenu + ->addCheckBox( + i18n( "auto_detect_indent_type_and_width", "Auto Detect Indent Type & Width" ), + mConfig.doc.autoDetectIndentType ) + ->setId( "auto_indent" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + + UIPopUpMenu* tabTypeMenuProject = UIPopUpMenu::New(); + tabTypeMenuProject->addRadioButton( i18n( "tabs", "Tabs" ) ) + ->setActive( !mProjectDocConfig.doc.indentSpaces ) + ->setId( "tabs" ); + tabTypeMenuProject->addRadioButton( i18n( "spaces", "Spaces" ) ) + ->setActive( mProjectDocConfig.doc.indentSpaces ) + ->setId( "spaces" ); + mProjectMenu + ->addSubMenu( i18n( "indentation_type", "Indentation Type" ), nullptr, tabTypeMenuProject ) + ->setId( "indent_type" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + tabTypeMenuProject->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + const String& text = event->getNode()->asType()->getId(); + mProjectDocConfig.doc.indentSpaces = text != "tabs"; + } ); + + UIPopUpMenu* indentWidthMenuProject = UIPopUpMenu::New(); + for ( int w = 2; w <= 12; w++ ) + indentWidthMenuProject + ->addRadioButton( String::toString( w ), mProjectDocConfig.doc.indentWidth == w ) + ->setId( String::format( "indent_width_%d", w ) ) + ->setData( w ); + mProjectMenu + ->addSubMenu( i18n( "indent_width", "Indent Width" ), nullptr, indentWidthMenuProject ) + ->setId( "indent_width" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + indentWidthMenuProject->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + int width = event->getNode()->getData(); + mProjectDocConfig.doc.indentWidth = width; + } ); + + UIPopUpMenu* tabWidthMenuProject = UIPopUpMenu::New(); + for ( int w = 2; w <= 12; w++ ) + tabWidthMenuProject + ->addRadioButton( String::toString( w ), mProjectDocConfig.doc.tabWidth == w ) + ->setId( String::format( "tab_width_%d", w ) ) + ->setData( w ); + mProjectMenu->addSubMenu( i18n( "tab_width", "Tab Width" ), nullptr, tabWidthMenuProject ) + ->setId( "tab_width" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + tabWidthMenuProject->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + int width = event->getNode()->getData(); + mProjectDocConfig.doc.tabWidth = width; + } ); + + UIPopUpMenu* lineEndingsProjectMenu = UIPopUpMenu::New(); + lineEndingsProjectMenu + ->addRadioButton( "Windows (CR/LF)", mProjectDocConfig.doc.windowsLineEndings ) + ->setId( "windows" ); + lineEndingsProjectMenu->addRadioButton( "Unix (LF)", !mProjectDocConfig.doc.windowsLineEndings ) + ->setId( "unix" ); + mProjectMenu + ->addSubMenu( i18n( "line_endings", "Line Endings" ), nullptr, lineEndingsProjectMenu ) + ->setId( "line_endings" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + lineEndingsProjectMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + bool winLe = event->getNode()->asType()->getId() == "windows"; + mProjectDocConfig.doc.windowsLineEndings = winLe; + } ); + + mProjectMenu + ->addCheckBox( i18n( "trim_trailing_whitespaces", "Trim Trailing Whitespaces" ), + mConfig.doc.trimTrailingWhitespaces ) + ->setId( "trim_whitespaces" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + + mProjectMenu + ->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ), + mConfig.doc.forceNewLineAtEndOfFile ) + ->setId( "force_nl" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + + mProjectMenu + ->addCheckBox( i18n( "write_unicode_bom", "Write Unicode BOM" ), + mConfig.doc.writeUnicodeBOM ) + ->setId( "write_bom" ) + ->setEnabled( !mProjectDocConfig.useGlobalSettings ); + + mProjectMenu->addSeparator(); + + mProjectMenu->add( i18n( "line_breaking_column", "Line Breaking Column" ) ) + ->setId( "line_breaking_column" ); + + mProjectMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { + if ( !mEditorSplitter->getCurEditor() || + event->getNode()->isType( UI_TYPE_MENU_SEPARATOR ) || + event->getNode()->isType( UI_TYPE_MENUSUBMENU ) ) + return; + const String& id = event->getNode()->getId(); + + if ( event->getNode()->isType( UI_TYPE_MENUCHECKBOX ) ) { + UIMenuCheckBox* item = event->getNode()->asType(); + if ( "use_global_settings" == id ) { + mProjectDocConfig.useGlobalSettings = item->isActive(); + updateProjectSettingsMenu(); + } else if ( "trim_whitespaces" == id ) { + mProjectDocConfig.doc.trimTrailingWhitespaces = item->isActive(); + } else if ( "force_nl" == id ) { + mProjectDocConfig.doc.forceNewLineAtEndOfFile = item->isActive(); + } else if ( "write_bom" == id ) { + mProjectDocConfig.doc.writeUnicodeBOM = item->isActive(); + } else if ( "auto_indent" == id ) { + mProjectDocConfig.doc.autoDetectIndentType = item->isActive(); + } + } else if ( "line_breaking_column" == id ) { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, i18n( "set_line_breaking_column", + "Set Line Breaking Column:\nSet 0 to disable it.\n" ) + .unescape() ); + msgBox->setTitle( mWindowTitle ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setAllowOnlyNumbers( true, false ); + msgBox->getTextInput()->setText( + String::toString( mProjectDocConfig.doc.lineBreakingColumn ) ); + msgBox->showWhenReady(); + msgBox->addEventListener( Event::MsgBoxConfirmClick, [&, msgBox]( const Event* ) { + int val; + if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { + mProjectDocConfig.doc.lineBreakingColumn = val; + mEditorSplitter->forEachEditor( + [val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } ); + msgBox->closeWindow(); + } + } ); + setFocusEditorOnClose( msgBox ); + } + } ); + + mDocMenu + ->addSubMenu( i18n( "folder_project_settings", "Folder/Project Settings" ), + findIcon( "folder-user" ), mProjectMenu ) + ->setId( "project_settings" ); + return mDocMenu; } +void App::updateProjectSettingsMenu() { + mDocMenu->getItemId( "project_settings" )->setEnabled( !mCurrentProject.empty() ); + + for ( size_t i = 0; i < mProjectMenu->getCount(); i++ ) { + mProjectMenu->getItem( i )->setEnabled( !mCurrentProject.empty() && + !mProjectDocConfig.useGlobalSettings ); + } + + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { + editor->setLineBreakingColumn( !mCurrentProject.empty() && + !mProjectDocConfig.useGlobalSettings + ? mProjectDocConfig.doc.lineBreakingColumn + : mConfig.doc.lineBreakingColumn ); + } ); + + mProjectMenu->getItemId( "trim_whitespaces" ) + ->asType() + ->setActive( mProjectDocConfig.doc.trimTrailingWhitespaces ); + + mProjectMenu->getItemId( "force_nl" ) + ->asType() + ->setActive( mProjectDocConfig.doc.forceNewLineAtEndOfFile ); + + mProjectMenu->getItemId( "write_bom" ) + ->asType() + ->setActive( mProjectDocConfig.doc.writeUnicodeBOM ); + + mProjectMenu->getItemId( "auto_indent" ) + ->asType() + ->setActive( mProjectDocConfig.doc.autoDetectIndentType ); + + auto* curIndent = + mProjectMenu->find( "indent_width" ) + ->asType() + ->getSubMenu() + ->find( String::format( "indent_width_%d", mProjectDocConfig.doc.indentWidth ) ); + + if ( curIndent ) + curIndent->asType()->setActive( true ); + + mProjectMenu->find( "indent_type" ) + ->asType() + ->getSubMenu() + ->find( !mProjectDocConfig.doc.indentSpaces ? "tabs" : "spaces" ) + ->asType() + ->setActive( true ); + + mProjectMenu->find( "tab_width" ) + ->asType() + ->getSubMenu() + ->find( String::format( "tab_width_%d", mProjectDocConfig.doc.tabWidth ) ) + ->asType() + ->setActive( true ); + + mProjectMenu->find( "line_endings" ) + ->asType() + ->getSubMenu() + ->find( mProjectDocConfig.doc.windowsLineEndings ? "windows" : "unix" ) + ->asType() + ->setActive( true ); + + mProjectMenu->getItemId( "use_global_settings" ) + ->setEnabled( true ) + ->asType() + ->setActive( mProjectDocConfig.useGlobalSettings ); +} + void App::updateDocumentMenu() { if ( !mEditorSplitter->getCurEditor() ) return; @@ -1338,7 +1606,7 @@ std::vector App::getUnlockedCommands() { } void App::closeEditors() { - mConfig.saveProject( mCurrentProject, mEditorSplitter, mConfigPath ); + mConfig.saveProject( mCurrentProject, mEditorSplitter, mConfigPath, mProjectDocConfig ); std::vector editors = mEditorSplitter->getAllEditors(); for ( auto editor : editors ) { UITabWidget* tabWidget = mEditorSplitter->tabWidgetFromEditor( editor ); @@ -1348,8 +1616,12 @@ void App::closeEditors() { mDirTree = nullptr; if ( mFileSystemListener ) mFileSystemListener->setDirTree( mDirTree ); + // Force to update the closed tabs. SceneManager::instance()->update(); + + mProjectDocConfig = ProjectDocumentConfig( mConfig.doc ); + updateProjectSettingsMenu(); } void App::closeFolder() { @@ -1488,7 +1760,9 @@ NotificationCenter* App::getNotificationCenter() const { void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { const CodeEditorConfig& config = mConfig.editor; - const DocumentConfig& docc = mConfig.doc; + const DocumentConfig& docc = !mCurrentProject.empty() && !mProjectDocConfig.useGlobalSettings + ? mProjectDocConfig.doc + : mConfig.doc; editor->setFontSize( config.fontSize.asDp( 0, Sizef(), mUISceneNode->getDPI() ) ); editor->setEnableColorPickerOnSelection( true ); editor->setColorScheme( mEditorSplitter->getCurrentColorScheme() ); @@ -1895,8 +2169,8 @@ UIMenu* App::createColorSchemeMenu() { const auto& colorSchemes = mEditorSplitter->getColorSchemes(); for ( auto& colorScheme : colorSchemes ) { - menu->addRadioButton( - colorScheme.first, mEditorSplitter->getCurrentColorSchemeName() == colorScheme.first ); + menu->addRadioButton( colorScheme.first, + mEditorSplitter->getCurrentColorSchemeName() == colorScheme.first ); if ( mColorSchemeMenues.size() == 1 && menu->getCount() == 1 ) { menu->reloadStyle( true, true ); @@ -2294,7 +2568,7 @@ void App::loadFolder( const std::string& path ) { mCurrentProject = rpath; loadDirTree( rpath ); - mConfig.loadProject( rpath, mEditorSplitter, mConfigPath, mThreadPool ); + mConfig.loadProject( rpath, mEditorSplitter, mConfigPath, mProjectDocConfig, mThreadPool ); mFileSystemModel = FileSystemModel::New( rpath, FileSystemModel::Mode::FilesAndDirectories, { true, true, true } ); @@ -2312,6 +2586,7 @@ void App::loadFolder( const std::string& path ) { mRecentFolders.resize( 10 ); updateRecentFolders(); + updateProjectSettingsMenu(); if ( mEditorSplitter->getCurEditor() ) mEditorSplitter->getCurEditor()->setFocus(); @@ -2677,7 +2952,8 @@ void App::init( std::string file, const Float& pidelDensity, const std::string& { "layout-left", 0xee94 }, { "layout-right", 0xee9b }, { "color-scheme", 0xebd4 }, - { "global_settings", 0xedcf }, + { "global-settings", 0xedcf }, + { "folder-user", 0xed84 }, }; for ( const auto& icon : icons ) iconTheme->add( UIGlyphIcon::New( icon.first, iconFont, icon.second ) ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 8300dc300..54569c848 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -97,6 +97,7 @@ class App : public UICodeEditorSplitter::Client { UIPopUpMenu* mWindowMenu{ nullptr }; UIPopUpMenu* mToolsMenu{ nullptr }; UIPopUpMenu* mProjectTreeMenu{ nullptr }; + UIPopUpMenu* mProjectMenu{ nullptr }; UISplitter* mProjectSplitter{ nullptr }; UITabWidget* mSidePanel{ nullptr }; UICodeEditorSplitter* mEditorSplitter{ nullptr }; @@ -116,6 +117,7 @@ class App : public UICodeEditorSplitter::Client { std::shared_ptr mFileSystemModel; size_t mMenuIconSize; bool mDirTreeReady{ false }; + ProjectDocumentConfig mProjectDocConfig; std::unordered_set mTmpDocs; std::string mCurrentProject; FontTrueType* mFont{ nullptr }; @@ -188,7 +190,9 @@ class App : public UICodeEditorSplitter::Client { Drawable* findIcon( const std::string& name ); - String i18n( const std::string& name, const String& def ); + String i18n( const std::string& key, const String& def ); + + void updateProjectSettingsMenu(); UIMenu* createDocumentMenu();