diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index d3301ec82..d663223fa 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1,6 +1,7 @@ #include "ecode.hpp" #include "featureshealth.hpp" #include "iconmanager.hpp" +#include "keybindingshelper.hpp" #include "pathhelper.hpp" #include "plugins/autocomplete/autocompleteplugin.hpp" #include "plugins/formatter/formatterplugin.hpp" @@ -8,6 +9,7 @@ #include "plugins/linter/linterplugin.hpp" #include "plugins/lsp/lspclientplugin.hpp" #include "plugins/xmltools/xmltoolsplugin.hpp" +#include "settingsactions.hpp" #include "settingsmenu.hpp" #include "uibuildsettings.hpp" #include "uiwelcomescreen.hpp" @@ -744,14 +746,13 @@ void App::onTextDropped( String text ) { App::App( const size_t& jobs, const std::vector& args ) : mArgs( args ), #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN - mThreadPool( - ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) { -} + mThreadPool( ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) #elif defined( __EMSCRIPTEN_PTHREADS__ ) - mThreadPool( - ThreadPool::createShared( jobs > 0 ? jobs : eemin( 8, Sys::getCPUCount() ) ) ) { -} + mThreadPool( ThreadPool::createShared( jobs > 0 ? jobs : eemin( 8, Sys::getCPUCount() ) ) ) #endif + , + mSettingsActions( std::make_unique( this ) ) { +} App::~App() { if ( mProjectBuildManager ) @@ -960,266 +961,10 @@ UITreeView* App::getProjectTreeView() const { return mProjectTreeView; } -void App::checkForUpdatesResponse( Http::Response response, bool fromStartup ) { - auto updatesError = [this, fromStartup]() { - if ( fromStartup ) - return; - UIMessageBox* msg = UIMessageBox::New( - UIMessageBox::OK, i18n( "error_checking_version", "Failed checking for updates." ) ); - msg->setTitle( "Error" ); - msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msg->showWhenReady(); - }; - - if ( response.getStatus() != Http::Response::Status::Ok || response.getBody().empty() ) { - updatesError(); - return; - } - - auto addStartUpCheckbox = [this]( UIMessageBox* msg ) { - msg->setId( "check_for_updates" ); - msg->on( Event::OnWindowReady, [this, msg]( const Event* ) { - msg->setVisible( false ); - UICheckBox* cbox = UICheckBox::New(); - cbox->addClass( "check_at_startup" ); - cbox->setParent( msg->getLayoutCont()->getFirstChild() ); - cbox->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ); - cbox->setText( i18n( "check_for_new_updates_at_startup", - "Always check for new updates at startup." ) ); - cbox->setChecked( mConfig.workspace.checkForUpdatesAtStartup ); - cbox->toPosition( 1 ); - cbox->runOnMainThread( [msg]() { - msg->setMinWindowSize( msg->getLayoutCont()->getSize() ); - msg->center(); - msg->show(); - } ); - cbox->on( Event::OnValueChange, [this, cbox]( const Event* ) { - mConfig.workspace.checkForUpdatesAtStartup = cbox->isChecked(); - } ); - } ); - }; - - json j; - try { - j = json::parse( response.getBody(), nullptr, true, true ); - - if ( j.contains( "tag_name" ) ) { - auto tagName( j["tag_name"].get() ); - auto versionNum = ecode::Version::getVersionNumFromTag( tagName ); - if ( versionNum > ecode::Version::getVersionNum() ) { - auto name( j.value( "name", tagName ) ); - UIMessageBox* msg = UIMessageBox::New( - UIMessageBox::OK_CANCEL, - name + i18n( "ecode_updates_available", - " is available!\nDo you want to download it now?" ) - .unescape() ); - - auto url( j.value( "html_url", "https://github.com/SpartanJ/ecode/releases/" ) ); - msg->on( Event::OnConfirm, [url, msg]( const Event* ) { - Engine::instance()->openURI( url ); - msg->closeWindow(); - } ); - msg->setTitle( "ecode" ); - msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); - addStartUpCheckbox( msg ); - } else if ( versionNum < ecode::Version::getVersionNum() ) { - if ( fromStartup ) - return; - UIMessageBox* msg = UIMessageBox::New( - UIMessageBox::OK, - i18n( "ecode_unreleased_version", - "You are running an unreleased version of ecode!\nCurrent version: " ) - .unescape() + - ecode::Version::getVersionNumString() ); - msg->setTitle( "ecode" ); - msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); - addStartUpCheckbox( msg ); - } else { - if ( fromStartup ) - return; - UIMessageBox* msg = UIMessageBox::New( - UIMessageBox::OK, i18n( "ecode_no_updates_available", - "There are currently no updates available." ) ); - msg->setTitle( "ecode" ); - msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); - addStartUpCheckbox( msg ); - } - } else { - updatesError(); - } - } catch ( ... ) { - updatesError(); - } -} - -void App::checkForUpdates( bool fromStartup ) { - Http::getAsync( - [this, fromStartup]( const Http&, Http::Request&, Http::Response& response ) { - mUISceneNode->runOnMainThread( [this, response, fromStartup]() { - checkForUpdatesResponse( response, fromStartup ); - } ); - }, - "https://api.github.com/repos/SpartanJ/ecode/releases/latest", Seconds( 30 ) ); -} - -void App::aboutEcode() { - String msg( ecode::Version::getVersionFullName() + " (codename: \"" + - ecode::Version::getCodename() + "\")" ); - UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK, msg ); - UIImage* image = UIImage::New(); - image->setParent( msgBox->getContainer()->getFirstChild() ); - auto tf = TextureFactory::instance(); - Texture* tex = tf->getByName( "ecode-logo" ); - if ( tex == nullptr ) { - tex = tf->loadFromFile( mResPath + "icon/ecode.png" ); - if ( tex ) - tex->setName( "ecode-logo" ); - } - image->setDrawable( tex ); - image->setLayoutGravity( UI_NODE_ALIGN_CENTER ); - image->setGravity( UI_NODE_ALIGN_CENTER ); - image->setScaleType( UIScaleType::FitInside ); - image->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); - image->setSize( { 128, 128 } ); - image->toBack(); - msgBox->setTitle( i18n( "about_ecode", "About ecode..." ) ); - msgBox->showWhenReady(); -} - -void App::ecodeSource() { - Engine::instance()->openURI( "https://github.com/SpartanJ/ecode" ); -} - -void App::setUIScaleFactor() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, - i18n( "set_ui_scale_factor", "Set the UI scale factor (pixel density):\nMinimum value is " - "1, and maximum 6. Requires restart." ) ); - msgBox->setTitle( mWindowTitle ); - msgBox->getTextInput()->setText( String::fromFloat( mConfig.windowState.pixelDensity ) ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - msgBox->closeWindow(); - Float val; - if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 1 && - val <= 6 ) { - if ( mConfig.windowState.pixelDensity != val ) { - mConfig.windowState.pixelDensity = val; - UIMessageBox* msg = UIMessageBox::New( - UIMessageBox::OK, - i18n( "new_ui_scale_factor", "New UI scale factor assigned.\nPlease " - "restart the application." ) ); - msg->showWhenReady(); - setFocusEditorOnClose( msg ); - } else if ( mSplitter && mSplitter->getCurWidget() ) { - mSplitter->getCurWidget()->setFocus(); - } - } else { - UIMessageBox* msg = UIMessageBox::New( UIMessageBox::OK, "Invalid value!" ); - msg->showWhenReady(); - setFocusEditorOnClose( msg ); - } - } ); -} - PluginManager* App::getPluginManager() const { return mPluginManager.get(); } -void App::setEditorFontSize() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, i18n( "set_editor_font_size", "Set the editor font size:" ) ); - msgBox->setTitle( mWindowTitle ); - msgBox->getTextInput()->setText( mConfig.editor.fontSize.toString() ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.editor.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); - mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { - editor->setFontSize( mConfig.editor.fontSize.asPixels( 0, Sizef(), mDisplayDPI ) ); - } ); - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setTerminalFontSize() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, i18n( "set_terminal_font_size", "Set the terminal font size:" ) ); - msgBox->setTitle( mWindowTitle ); - msgBox->getTextInput()->setText( mConfig.term.fontSize.toString() ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.term.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); - mSplitter->forEachWidget( [this]( UIWidget* widget ) { - if ( widget && widget->isType( UI_TYPE_TERMINAL ) ) - widget->asType()->setFontSize( - mConfig.term.fontSize.asPixels( 0, Sizef(), mDisplayDPI ) ); - } ); - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setUIFontSize() { - UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, - i18n( "set_ui_font_size", "Set the UI font size:" ) ); - msgBox->setTitle( mWindowTitle ); - msgBox->getTextInput()->setText( mConfig.ui.fontSize.toString() ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.ui.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); - Float fontSize = mConfig.ui.fontSize.asPixels( 0, Sizef(), mDisplayDPI ); - UIThemeManager* manager = mUISceneNode->getUIThemeManager(); - manager->setDefaultFontSize( fontSize ); - manager->getDefaultTheme()->setDefaultFontSize( fontSize ); - mUISceneNode->forEachNode( [this]( Node* node ) { - if ( node->isType( UI_TYPE_TEXTVIEW ) ) { - UITextView* textView = node->asType(); - if ( !textView->getUIStyle()->hasProperty( PropertyId::FontSize ) ) { - textView->setFontSize( - mConfig.ui.fontSize.asPixels( node->getParent()->getPixelsSize().getWidth(), - Sizef(), mUISceneNode->getDPI() ) ); - } - } - } ); - msgBox->closeWindow(); - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setUIPanelFontSize() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, i18n( "set_side_panel_font_size", "Set side panel font size:" ) ); - msgBox->setTitle( mWindowTitle ); - msgBox->getTextInput()->setText( mConfig.ui.panelFontSize.toString() ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.ui.panelFontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); - - // Update the CSS - auto selsFound = mUISceneNode->getStyleSheet().findStyleFromSelectorName( - "#project_view > treeview::row > treeview::cell > treeview::cell::text" ); - if ( !selsFound.empty() ) { - for ( auto sel : selsFound ) - sel->updatePropertyValue( "font-size", mConfig.ui.panelFontSize.toString() ); - mUISceneNode->getStyleSheet().refreshCacheFromStyles( selsFound ); - } - - UITreeView* treeView = mUISceneNode->find( "project_view" ); - if ( !treeView ) { - msgBox->closeWindow(); - return; - } - treeView->reloadStyle( true, true, true, true ); - treeView->updateContentSize(); - msgBox->closeWindow(); - } ); - setFocusEditorOnClose( msgBox ); -} - void App::setFocusEditorOnClose( UIMessageBox* msgBox ) { msgBox->on( Event::OnClose, [this]( const Event* ) { if ( mSplitter && mSplitter->getCurWidget() ) @@ -1276,120 +1021,6 @@ const std::string& App::getWindowTitle() const { return mWindowTitle; } -void App::setLineBreakingColumn() { - 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->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - int val; - if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { - mConfig.doc.lineBreakingColumn = val; - mSplitter->forEachEditor( - [val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } ); - msgBox->closeWindow(); - } - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setLineSpacing() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, - i18n( "set_line_spacing", "Set Line Spacing:\nSet 0 to disable it.\n" ).unescape() ); - msgBox->setTitle( mWindowTitle ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->getTextInput()->setText( mConfig.editor.lineSpacing.toString() ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.editor.lineSpacing = StyleSheetLength( msgBox->getTextInput()->getText() ); - mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { - editor->setLineSpacing( mConfig.editor.lineSpacing ); - } ); - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setCursorBlinkingTime() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, - i18n( "set_cursor_blinking_time", "Set Cursor Blinking Time:\nSet 0 to disable it.\n" ) - .unescape() ); - msgBox->setTitle( mWindowTitle ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->getTextInput()->setText( mConfig.editor.cursorBlinkingTime.toString() ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.editor.cursorBlinkingTime = - Time::fromString( msgBox->getTextInput()->getText().toUtf8() ); - mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { - editor->setCursorBlinkTime( mConfig.editor.cursorBlinkingTime ); - } ); - msgBox->closeWindow(); - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setIndentTabCharacter() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::COMBOBOX, - i18n( "set_indent_tab_character", "Set the tab indentation guide character displayed." ) ); - msgBox->setId( "indent_tab_window" ); - msgBox->setTitle( mWindowTitle ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->getComboBox()->getDropDownList()->getListBox()->addClass( "indent_tab_listbox_item" ); - msgBox->getComboBox()->getDropDownList()->getListBox()->addListBoxItems( - { u8"»", u8"→", u8"⇒", u8"↪", u8"⇢", u8"↣" } ); - msgBox->getComboBox()->setText( String::fromUtf8( - mConfig.editor.tabIndentCharacter.empty() ? u8"»" : mConfig.editor.tabIndentCharacter ) ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - auto txt( msgBox->getComboBox()->getText() ); - if ( txt.size() == 1 ) { - mConfig.editor.tabIndentCharacter = txt.toUtf8(); - mSplitter->forEachEditor( - [txt]( UICodeEditor* editor ) { editor->setTabIndentCharacter( txt[0] ); } ); - msgBox->closeWindow(); - } else { - UIMessageBox* msgBoxAlert = - UIMessageBox::New( UIMessageBox::OK, i18n( "it_must_be_a_single_character", - "It must be a single character" ) ); - msgBoxAlert->setTitle( mWindowTitle ); - msgBoxAlert->setCloseShortcut( { KEY_ESCAPE, 0 } ); - } - } ); - setFocusEditorOnClose( msgBox ); -} - -void App::setFoldRefreshFreq() { - UIMessageBox* msgBox = UIMessageBox::New( - UIMessageBox::INPUT, - i18n( "set_fold_refresh_frequency", - "Set code folds refresh frequency:\nIt should be bigger than 1 second.\nFolds are " - "only refreshed after any document modification." ) - .unescape() ); - msgBox->setTitle( mWindowTitle ); - msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); - msgBox->getTextInput()->setText( mConfig.editor.codeFoldingRefreshFreq.toString() ); - msgBox->showWhenReady(); - msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { - mConfig.editor.codeFoldingRefreshFreq = - Time::fromString( msgBox->getTextInput()->getText().toUtf8() ); - if ( mConfig.editor.codeFoldingRefreshFreq < Seconds( 1 ) ) - mConfig.editor.codeFoldingRefreshFreq = Seconds( 1 ); - mSplitter->forEachEditor( [this]( UICodeEditor* editor ) { - editor->setFoldsRefreshTime( mConfig.editor.codeFoldingRefreshFreq ); - } ); - msgBox->closeWindow(); - } ); - setFocusEditorOnClose( msgBox ); -} - void App::loadFileFromPathOrFocus( const std::string& path ) { UITab* tab = mSplitter->isDocumentOpen( path ); if ( !tab ) { @@ -1418,178 +1049,6 @@ void App::debugDrawData() { mUISceneNode->setDrawDebugData( !mUISceneNode->getDrawDebugData() ); } -static void updateKeybindings( IniFile& ini, const std::string& group, Input* input, - std::unordered_map& keybindings, - const std::unordered_map& defKeybindings, - bool forceRebind, - const std::map& migrateKeyindings, - IniFile& iniState ) { - KeyBindings bindings( input ); - bool added = false; - bool migrated = false; - - if ( ini.findKey( group ) != IniFile::noID ) { - keybindings = ini.getKeyUnorderedMap( group ); - } else { - for ( const auto& it : defKeybindings ) - ini.setValue( group, it.first, it.second ); - added = true; - } - std::unordered_map invertedKeybindings; - for ( const auto& key : keybindings ) - invertedKeybindings[key.second] = key.first; - - if ( !added && forceRebind ) { - for ( const auto& migrate : migrateKeyindings ) { - auto foundCmd = invertedKeybindings.find( migrate.first ); - if ( foundCmd != invertedKeybindings.end() && foundCmd->second == migrate.second ) { - std::string shortcut; - for ( const auto& defKb : defKeybindings ) { - if ( defKb.second == foundCmd->first ) { - shortcut = defKb.first; - break; - } - } - if ( !shortcut.empty() && - !iniState.keyValueExists( "migrated_keybindings_" + group, migrate.first ) ) { - ini.setValue( group, shortcut, foundCmd->first ); - ini.deleteValue( group, migrate.second ); - keybindings.erase( migrate.second ); - invertedKeybindings[foundCmd->first] = shortcut; - iniState.setValue( "migrated_keybindings_" + group, migrate.first, - migrate.second ); - added = true; - migrated = true; - } - } - } - } - - if ( defKeybindings.size() != keybindings.size() || forceRebind ) { - for ( const auto& key : defKeybindings ) { - auto foundCmd = invertedKeybindings.find( key.second ); - auto& shortcutStr = key.first; - if ( foundCmd == invertedKeybindings.end() && - keybindings.find( shortcutStr ) == keybindings.end() ) { - keybindings[shortcutStr] = key.second; - invertedKeybindings[key.second] = shortcutStr; - ini.setValue( group, shortcutStr, key.second ); - added = true; - } else if ( foundCmd == invertedKeybindings.end() ) { - // Override the shortcut if the command that holds that - // shortcut does not exists anymore - auto kb = keybindings.find( shortcutStr ); - if ( kb != keybindings.end() ) { - bool found = false; - for ( const auto& val : defKeybindings ) - if ( val.second == kb->second ) - found = true; - if ( !found ) { - keybindings[shortcutStr] = key.second; - invertedKeybindings[key.second] = shortcutStr; - ini.setValue( group, shortcutStr, key.second ); - added = true; - } - } - } - } - } - - if ( migrated ) - iniState.writeFile(); - if ( added ) - ini.writeFile(); -} - -static void updateKeybindings( IniFile& ini, const std::string& group, Input* input, - std::unordered_map& keybindings, - std::unordered_map& invertedKeybindings, - const std::map& defKeybindings, - bool forceRebind, - const std::map& migrateKeyindings, - IniFile& iniState ) { - KeyBindings bindings( input ); - bool added = false; - bool migrated = false; - - if ( ini.findKey( group ) != IniFile::noID ) { - keybindings = ini.getKeyUnorderedMap( group ); - } else { - for ( const auto& it : defKeybindings ) - ini.setValue( group, bindings.getShortcutString( it.first ), it.second ); - added = true; - } - for ( const auto& key : keybindings ) - invertedKeybindings[key.second] = key.first; - - if ( !added && forceRebind ) { - for ( const auto& migrate : migrateKeyindings ) { - auto foundCmd = invertedKeybindings.find( migrate.first ); - if ( foundCmd != invertedKeybindings.end() && foundCmd->second == migrate.second ) { - KeyBindings::Shortcut shortcut; - for ( const auto& defKb : defKeybindings ) { - if ( defKb.second == foundCmd->first ) { - shortcut = defKb.first; - break; - } - } - if ( !iniState.keyValueExists( "migrated_keybindings_" + group, migrate.first ) ) { - if ( !shortcut.empty() ) { - auto newShortcutStr = bindings.getShortcutString( shortcut ); - ini.setValue( group, newShortcutStr, foundCmd->first ); - invertedKeybindings[foundCmd->first] = newShortcutStr; - } else { - invertedKeybindings.erase( foundCmd->first ); - } - ini.deleteValue( group, migrate.second ); - keybindings.erase( migrate.second ); - iniState.setValue( "migrated_keybindings_" + group, migrate.first, - migrate.second ); - added = true; - migrated = true; - } - } - } - } - - bool keybindingsWereEmpty = keybindings.empty(); - - if ( defKeybindings.size() != keybindings.size() || forceRebind ) { - for ( auto& key : defKeybindings ) { - auto foundCmd = invertedKeybindings.find( key.second ); - auto shortcutStr = bindings.getShortcutString( key.first ); - - if ( ( foundCmd == invertedKeybindings.end() || keybindingsWereEmpty ) && - keybindings.find( shortcutStr ) == keybindings.end() ) { - keybindings[shortcutStr] = key.second; - invertedKeybindings[key.second] = shortcutStr; - ini.setValue( group, shortcutStr, key.second ); - added = true; - } else if ( foundCmd == invertedKeybindings.end() ) { - // Override the shortcut if the command that holds that - // shortcut does not exists anymore - auto kb = keybindings.find( shortcutStr ); - if ( kb != keybindings.end() ) { - bool found = false; - for ( const auto& val : defKeybindings ) - if ( val.second == kb->second ) - found = true; - if ( !found ) { - keybindings[shortcutStr] = key.second; - invertedKeybindings[key.second] = shortcutStr; - ini.setValue( group, shortcutStr, key.second ); - added = true; - } - } - } - } - } - if ( migrated ) - iniState.writeFile(); - if ( added ) - ini.writeFile(); -} - void App::loadKeybindings() { if ( !mKeybindings.empty() ) return; @@ -1616,17 +1075,19 @@ void App::loadKeybindings() { if ( KEYMOD_NONE != defModKeyCode ) KeyMod::setDefaultModifier( defModKeyCode ); - updateKeybindings( ini, "editor", mWindow->getInput(), mKeybindings, mKeybindingsInvert, - getDefaultKeybindings(), forceRebind, getMigrateKeybindings(), - mConfig.iniState ); + KeybindingsHelper::updateKeybindings( ini, "editor", mWindow->getInput(), mKeybindings, + mKeybindingsInvert, getDefaultKeybindings(), forceRebind, + getMigrateKeybindings(), mConfig.iniState ); - updateKeybindings( ini, "global_search", mWindow->getInput(), mGlobalSearchKeybindings, - GlobalSearchController::getDefaultKeybindings(), forceRebind, - getMigrateKeybindings(), mConfig.iniState ); + KeybindingsHelper::updateKeybindings( ini, "global_search", mWindow->getInput(), + mGlobalSearchKeybindings, + GlobalSearchController::getDefaultKeybindings(), + forceRebind, getMigrateKeybindings(), mConfig.iniState ); - updateKeybindings( ini, "document_search", mWindow->getInput(), mDocumentSearchKeybindings, - DocSearchController::getDefaultKeybindings(), forceRebind, - getMigrateKeybindings(), mConfig.iniState ); + KeybindingsHelper::updateKeybindings( ini, "document_search", mWindow->getInput(), + mDocumentSearchKeybindings, + DocSearchController::getDefaultKeybindings(), forceRebind, + getMigrateKeybindings(), mConfig.iniState ); auto localKeybindings = getLocalKeybindings(); for ( const auto& kb : localKeybindings ) { @@ -4014,7 +3475,7 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe #endif if ( mConfig.workspace.checkForUpdatesAtStartup ) - checkForUpdates( true ); + mSettingsActions->checkForUpdates( true ); mUISceneNode->setInterval( [this] { diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index b4f589722..59dca10bd 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -10,6 +10,7 @@ #include "plugins/pluginmanager.hpp" #include "projectbuild.hpp" #include "projectdirectorytree.hpp" +#include "settingsactions.hpp" #include "statusappoutputcontroller.hpp" #include "statusbuildoutputcontroller.hpp" #include "statusterminalcontroller.hpp" @@ -163,14 +164,6 @@ class App : public UICodeEditorSplitter::Client { void debugDrawData(); - void setUIFontSize(); - - void setEditorFontSize(); - - void setTerminalFontSize(); - - void setUIScaleFactor(); - void toggleSidePanel(); UIMainLayout* getMainLayout() const { return mMainLayout; } @@ -279,21 +272,24 @@ class App : public UICodeEditorSplitter::Client { [this] { mUniversalLocator->showWorkspaceSymbol(); } ); t.setCommand( "open-document-symbol-search", [this] { mUniversalLocator->showDocumentSymbol(); } ); - t.setCommand( "editor-set-line-breaking-column", [this] { setLineBreakingColumn(); } ); - t.setCommand( "editor-set-line-spacing", [this] { setLineSpacing(); } ); - t.setCommand( "editor-set-cursor-blinking-time", [this] { setCursorBlinkingTime(); } ); - t.setCommand( "editor-set-indent-tab-character", [this] { setIndentTabCharacter(); } ); - t.setCommand( "check-for-updates", [this] { checkForUpdates(); } ); - t.setCommand( "about-ecode", [this] { aboutEcode(); } ); - t.setCommand( "ecode-source", [this] { ecodeSource(); } ); - t.setCommand( "ui-scale-factor", [this] { setUIScaleFactor(); } ); + t.setCommand( "editor-set-line-breaking-column", + [this] { mSettingsActions->setLineBreakingColumn(); } ); + t.setCommand( "editor-set-line-spacing", [this] { mSettingsActions->setLineSpacing(); } ); + t.setCommand( "editor-set-cursor-blinking-time", + [this] { mSettingsActions->setCursorBlinkingTime(); } ); + t.setCommand( "editor-set-indent-tab-character", + [this] { mSettingsActions->setIndentTabCharacter(); } ); + t.setCommand( "check-for-updates", [this] { mSettingsActions->checkForUpdates(); } ); + t.setCommand( "about-ecode", [this] { mSettingsActions->aboutEcode(); } ); + t.setCommand( "ecode-source", [this] { mSettingsActions->ecodeSource(); } ); + t.setCommand( "ui-scale-factor", [this] { mSettingsActions->setUIScaleFactor(); } ); t.setCommand( "show-side-panel", [this] { switchSidePanel(); } ); t.setCommand( "toggle-status-bar", [this] { switchStatusBar(); } ); t.setCommand( "toggle-menu-bar", [this] { switchMenuBar(); } ); - t.setCommand( "editor-font-size", [this] { setEditorFontSize(); } ); - t.setCommand( "terminal-font-size", [this] { setTerminalFontSize(); } ); - t.setCommand( "ui-font-size", [this] { setUIFontSize(); } ); - t.setCommand( "ui-panel-font-size", [this] { setUIPanelFontSize(); } ); + t.setCommand( "editor-font-size", [this] { mSettingsActions->setEditorFontSize(); } ); + t.setCommand( "terminal-font-size", [this] { mSettingsActions->setTerminalFontSize(); } ); + t.setCommand( "ui-font-size", [this] { mSettingsActions->setUIFontSize(); } ); + t.setCommand( "ui-panel-font-size", [this] { mSettingsActions->setUIPanelFontSize(); } ); t.setCommand( "serif-font", [this] { openFontDialog( mConfig.ui.serifFont, false ); } ); t.setCommand( "monospace-font", [this] { openFontDialog( mConfig.ui.monospaceFont, true ); } ); @@ -312,7 +308,7 @@ class App : public UICodeEditorSplitter::Client { if ( mTerminalManager ) mTerminalManager->configureTerminalScrollback(); } ); - t.setCommand( "check-for-updates", [this] { checkForUpdates( false ); } ); + t.setCommand( "check-for-updates", [this] { mSettingsActions->checkForUpdates( false ); } ); t.setCommand( "create-new-window", [] { std::string processPath = Sys::getProcessFilePath(); if ( !processPath.empty() ) { @@ -330,20 +326,6 @@ class App : public UICodeEditorSplitter::Client { UISceneNode* getUISceneNode() const { return mUISceneNode; } - void setLineBreakingColumn(); - - void setLineSpacing(); - - void setCursorBlinkingTime(); - - void setIndentTabCharacter(); - - void setFoldRefreshFreq(); - - void checkForUpdates( bool fromStartup = false ); - - void aboutEcode(); - void updateRecentFiles(); void updateRecentFolders(); @@ -389,10 +371,6 @@ class App : public UICodeEditorSplitter::Client { void updateTerminalMenu(); - void ecodeSource(); - - void setUIPanelFontSize(); - void refreshFolderView(); bool isFileVisibleInTreeView( const std::string& filePath ); @@ -475,6 +453,8 @@ class App : public UICodeEditorSplitter::Client { void createDocDoesNotExistsInFSAlert( UICodeEditor* editor ); + SettingsActions* getSettingsActions() { return mSettingsActions.get(); } + protected: std::vector mArgs; EE::Window::Window* mWindow{ nullptr }; @@ -562,6 +542,7 @@ class App : public UICodeEditorSplitter::Client { StyleSheet mAppStyleSheet; UIMessageBox* mCloseMsgBox{ nullptr }; UIMenuBar* mMenuBar{ nullptr }; + std::unique_ptr mSettingsActions; void saveAllProcess(); @@ -642,8 +623,6 @@ class App : public UICodeEditorSplitter::Client { void onPluginEnabled( Plugin* plugin ); - void checkForUpdatesResponse( Http::Response response, bool fromStartup ); - std::string getLastUsedFolder(); void insertRecentFolder( const std::string& rpath ); diff --git a/src/tools/ecode/keybindingshelper.cpp b/src/tools/ecode/keybindingshelper.cpp new file mode 100644 index 000000000..c5de74da3 --- /dev/null +++ b/src/tools/ecode/keybindingshelper.cpp @@ -0,0 +1,175 @@ +#include "keybindingshelper.hpp" + +namespace ecode { + +void KeybindingsHelper::updateKeybindings( + IniFile& ini, const std::string& group, Input* input, + std::unordered_map& keybindings, + const std::unordered_map& defKeybindings, bool forceRebind, + const std::map& migrateKeyindings, IniFile& iniState ) { + KeyBindings bindings( input ); + bool added = false; + bool migrated = false; + + if ( ini.findKey( group ) != IniFile::noID ) { + keybindings = ini.getKeyUnorderedMap( group ); + } else { + for ( const auto& it : defKeybindings ) + ini.setValue( group, it.first, it.second ); + added = true; + } + std::unordered_map invertedKeybindings; + for ( const auto& key : keybindings ) + invertedKeybindings[key.second] = key.first; + + if ( !added && forceRebind ) { + for ( const auto& migrate : migrateKeyindings ) { + auto foundCmd = invertedKeybindings.find( migrate.first ); + if ( foundCmd != invertedKeybindings.end() && foundCmd->second == migrate.second ) { + std::string shortcut; + for ( const auto& defKb : defKeybindings ) { + if ( defKb.second == foundCmd->first ) { + shortcut = defKb.first; + break; + } + } + if ( !shortcut.empty() && + !iniState.keyValueExists( "migrated_keybindings_" + group, migrate.first ) ) { + ini.setValue( group, shortcut, foundCmd->first ); + ini.deleteValue( group, migrate.second ); + keybindings.erase( migrate.second ); + invertedKeybindings[foundCmd->first] = shortcut; + iniState.setValue( "migrated_keybindings_" + group, migrate.first, + migrate.second ); + added = true; + migrated = true; + } + } + } + } + + if ( defKeybindings.size() != keybindings.size() || forceRebind ) { + for ( const auto& key : defKeybindings ) { + auto foundCmd = invertedKeybindings.find( key.second ); + auto& shortcutStr = key.first; + if ( foundCmd == invertedKeybindings.end() && + keybindings.find( shortcutStr ) == keybindings.end() ) { + keybindings[shortcutStr] = key.second; + invertedKeybindings[key.second] = shortcutStr; + ini.setValue( group, shortcutStr, key.second ); + added = true; + } else if ( foundCmd == invertedKeybindings.end() ) { + // Override the shortcut if the command that holds that + // shortcut does not exists anymore + auto kb = keybindings.find( shortcutStr ); + if ( kb != keybindings.end() ) { + bool found = false; + for ( const auto& val : defKeybindings ) + if ( val.second == kb->second ) + found = true; + if ( !found ) { + keybindings[shortcutStr] = key.second; + invertedKeybindings[key.second] = shortcutStr; + ini.setValue( group, shortcutStr, key.second ); + added = true; + } + } + } + } + } + + if ( migrated ) + iniState.writeFile(); + if ( added ) + ini.writeFile(); +} + +void KeybindingsHelper::updateKeybindings( + IniFile& ini, const std::string& group, Input* input, + std::unordered_map& keybindings, + std::unordered_map& invertedKeybindings, + const std::map& defKeybindings, bool forceRebind, + const std::map& migrateKeyindings, IniFile& iniState ) { + KeyBindings bindings( input ); + bool added = false; + bool migrated = false; + + if ( ini.findKey( group ) != IniFile::noID ) { + keybindings = ini.getKeyUnorderedMap( group ); + } else { + for ( const auto& it : defKeybindings ) + ini.setValue( group, bindings.getShortcutString( it.first ), it.second ); + added = true; + } + for ( const auto& key : keybindings ) + invertedKeybindings[key.second] = key.first; + + if ( !added && forceRebind ) { + for ( const auto& migrate : migrateKeyindings ) { + auto foundCmd = invertedKeybindings.find( migrate.first ); + if ( foundCmd != invertedKeybindings.end() && foundCmd->second == migrate.second ) { + KeyBindings::Shortcut shortcut; + for ( const auto& defKb : defKeybindings ) { + if ( defKb.second == foundCmd->first ) { + shortcut = defKb.first; + break; + } + } + if ( !iniState.keyValueExists( "migrated_keybindings_" + group, migrate.first ) ) { + if ( !shortcut.empty() ) { + auto newShortcutStr = bindings.getShortcutString( shortcut ); + ini.setValue( group, newShortcutStr, foundCmd->first ); + invertedKeybindings[foundCmd->first] = newShortcutStr; + } else { + invertedKeybindings.erase( foundCmd->first ); + } + ini.deleteValue( group, migrate.second ); + keybindings.erase( migrate.second ); + iniState.setValue( "migrated_keybindings_" + group, migrate.first, + migrate.second ); + added = true; + migrated = true; + } + } + } + } + + bool keybindingsWereEmpty = keybindings.empty(); + + if ( defKeybindings.size() != keybindings.size() || forceRebind ) { + for ( auto& key : defKeybindings ) { + auto foundCmd = invertedKeybindings.find( key.second ); + auto shortcutStr = bindings.getShortcutString( key.first ); + + if ( ( foundCmd == invertedKeybindings.end() || keybindingsWereEmpty ) && + keybindings.find( shortcutStr ) == keybindings.end() ) { + keybindings[shortcutStr] = key.second; + invertedKeybindings[key.second] = shortcutStr; + ini.setValue( group, shortcutStr, key.second ); + added = true; + } else if ( foundCmd == invertedKeybindings.end() ) { + // Override the shortcut if the command that holds that + // shortcut does not exists anymore + auto kb = keybindings.find( shortcutStr ); + if ( kb != keybindings.end() ) { + bool found = false; + for ( const auto& val : defKeybindings ) + if ( val.second == kb->second ) + found = true; + if ( !found ) { + keybindings[shortcutStr] = key.second; + invertedKeybindings[key.second] = shortcutStr; + ini.setValue( group, shortcutStr, key.second ); + added = true; + } + } + } + } + } + if ( migrated ) + iniState.writeFile(); + if ( added ) + ini.writeFile(); +} + +} // namespace ecode diff --git a/src/tools/ecode/keybindingshelper.hpp b/src/tools/ecode/keybindingshelper.hpp new file mode 100644 index 000000000..52c1102ce --- /dev/null +++ b/src/tools/ecode/keybindingshelper.hpp @@ -0,0 +1,31 @@ +#ifndef KEYBINDINGS_HELPER_HPP +#define KEYBINDINGS_HELPER_HPP + +#include +#include +#include + +using namespace EE::System; +using namespace EE::UI; + +namespace ecode { + +class KeybindingsHelper { + public: + static void updateKeybindings( + IniFile& ini, const std::string& group, Input* input, + std::unordered_map& keybindings, + const std::unordered_map& defKeybindings, bool forceRebind, + const std::map& migrateKeyindings, IniFile& iniState ); + + static void updateKeybindings( + IniFile& ini, const std::string& group, Input* input, + std::unordered_map& keybindings, + std::unordered_map& invertedKeybindings, + const std::map& defKeybindings, bool forceRebind, + const std::map& migrateKeyindings, IniFile& iniState ); +}; + +} // namespace ecode + +#endif diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp new file mode 100644 index 000000000..7ea2c7654 --- /dev/null +++ b/src/tools/ecode/settingsactions.cpp @@ -0,0 +1,388 @@ +#include "ecode.hpp" +#include "settingsactions.hpp" +#include "version.hpp" + +namespace ecode { + +void SettingsActions::checkForUpdatesResponse( Http::Response response, bool fromStartup ) { + auto updatesError = [this, fromStartup]() { + if ( fromStartup ) + return; + UIMessageBox* msg = UIMessageBox::New( + UIMessageBox::OK, i18n( "error_checking_version", "Failed checking for updates." ) ); + msg->setTitle( "Error" ); + msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msg->showWhenReady(); + }; + + if ( response.getStatus() != Http::Response::Status::Ok || response.getBody().empty() ) { + updatesError(); + return; + } + + auto addStartUpCheckbox = [this]( UIMessageBox* msg ) { + msg->setId( "check_for_updates" ); + msg->on( Event::OnWindowReady, [this, msg]( const Event* ) { + msg->setVisible( false ); + UICheckBox* cbox = UICheckBox::New(); + cbox->addClass( "check_at_startup" ); + cbox->setParent( msg->getLayoutCont()->getFirstChild() ); + cbox->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent ); + cbox->setText( i18n( "check_for_new_updates_at_startup", + "Always check for new updates at startup." ) ); + cbox->setChecked( mApp->getConfig().workspace.checkForUpdatesAtStartup ); + cbox->toPosition( 1 ); + cbox->runOnMainThread( [msg]() { + msg->setMinWindowSize( msg->getLayoutCont()->getSize() ); + msg->center(); + msg->show(); + } ); + cbox->on( Event::OnValueChange, [this, cbox]( const Event* ) { + mApp->getConfig().workspace.checkForUpdatesAtStartup = cbox->isChecked(); + } ); + } ); + }; + + json j; + try { + j = json::parse( response.getBody(), nullptr, true, true ); + + if ( j.contains( "tag_name" ) ) { + auto tagName( j["tag_name"].get() ); + auto versionNum = ecode::Version::getVersionNumFromTag( tagName ); + if ( versionNum > ecode::Version::getVersionNum() ) { + auto name( j.value( "name", tagName ) ); + UIMessageBox* msg = UIMessageBox::New( + UIMessageBox::OK_CANCEL, + name + i18n( "ecode_updates_available", + " is available!\nDo you want to download it now?" ) + .unescape() ); + + auto url( j.value( "html_url", "https://github.com/SpartanJ/ecode/releases/" ) ); + msg->on( Event::OnConfirm, [url, msg]( const Event* ) { + Engine::instance()->openURI( url ); + msg->closeWindow(); + } ); + msg->setTitle( "ecode" ); + msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); + addStartUpCheckbox( msg ); + } else if ( versionNum < ecode::Version::getVersionNum() ) { + if ( fromStartup ) + return; + UIMessageBox* msg = UIMessageBox::New( + UIMessageBox::OK, + i18n( "ecode_unreleased_version", + "You are running an unreleased version of ecode!\nCurrent version: " ) + .unescape() + + ecode::Version::getVersionNumString() ); + msg->setTitle( "ecode" ); + msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); + addStartUpCheckbox( msg ); + } else { + if ( fromStartup ) + return; + UIMessageBox* msg = UIMessageBox::New( + UIMessageBox::OK, i18n( "ecode_no_updates_available", + "There are currently no updates available." ) ); + msg->setTitle( "ecode" ); + msg->setCloseShortcut( { KEY_ESCAPE, 0 } ); + addStartUpCheckbox( msg ); + } + } else { + updatesError(); + } + } catch ( ... ) { + updatesError(); + } +} + +void SettingsActions::checkForUpdates( bool fromStartup ) { + Http::getAsync( + [this, fromStartup]( const Http&, Http::Request&, Http::Response& response ) { + mApp->getUISceneNode()->runOnMainThread( [this, response, fromStartup]() { + checkForUpdatesResponse( response, fromStartup ); + } ); + }, + "https://api.github.com/repos/SpartanJ/ecode/releases/latest", Seconds( 30 ) ); +} + +void SettingsActions::aboutEcode() { + String msg( ecode::Version::getVersionFullName() + " (codename: \"" + + ecode::Version::getCodename() + "\")" ); + UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK, msg ); + UIImage* image = UIImage::New(); + image->setParent( msgBox->getContainer()->getFirstChild() ); + auto tf = TextureFactory::instance(); + Texture* tex = tf->getByName( "ecode-logo" ); + if ( tex == nullptr ) { + tex = tf->loadFromFile( mApp->resPath() + "icon/ecode.png" ); + if ( tex ) + tex->setName( "ecode-logo" ); + } + image->setDrawable( tex ); + image->setLayoutGravity( UI_NODE_ALIGN_CENTER ); + image->setGravity( UI_NODE_ALIGN_CENTER ); + image->setScaleType( UIScaleType::FitInside ); + image->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed ); + image->setSize( { 128, 128 } ); + image->toBack(); + msgBox->setTitle( i18n( "about_ecode", "About ecode..." ) ); + msgBox->showWhenReady(); +} + +void SettingsActions::ecodeSource() { + Engine::instance()->openURI( "https://github.com/SpartanJ/ecode" ); +} + +void SettingsActions::setLineBreakingColumn() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_line_breaking_column", "Set Line Breaking Column:\nSet 0 to disable it.\n" ) + .unescape() ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setAllowOnlyNumbers( true, false ); + msgBox->getTextInput()->setText( String::toString( mApp->getConfig().doc.lineBreakingColumn ) ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + int val; + if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { + mApp->getConfig().doc.lineBreakingColumn = val; + mApp->getSplitter()->forEachEditor( + [val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } ); + msgBox->closeWindow(); + } + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setLineSpacing() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_line_spacing", "Set Line Spacing:\nSet 0 to disable it.\n" ).unescape() ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setText( mApp->getConfig().editor.lineSpacing.toString() ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().editor.lineSpacing = + StyleSheetLength( msgBox->getTextInput()->getText() ); + mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setLineSpacing( mApp->getConfig().editor.lineSpacing ); + } ); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setCursorBlinkingTime() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_cursor_blinking_time", "Set Cursor Blinking Time:\nSet 0 to disable it.\n" ) + .unescape() ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setText( mApp->getConfig().editor.cursorBlinkingTime.toString() ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().editor.cursorBlinkingTime = + Time::fromString( msgBox->getTextInput()->getText().toUtf8() ); + mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setCursorBlinkTime( mApp->getConfig().editor.cursorBlinkingTime ); + } ); + msgBox->closeWindow(); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setIndentTabCharacter() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::COMBOBOX, + i18n( "set_indent_tab_character", "Set the tab indentation guide character displayed." ) ); + msgBox->setId( "indent_tab_window" ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getComboBox()->getDropDownList()->getListBox()->addClass( "indent_tab_listbox_item" ); + msgBox->getComboBox()->getDropDownList()->getListBox()->addListBoxItems( + { u8"»", u8"→", u8"⇒", u8"↪", u8"⇢", u8"↣" } ); + msgBox->getComboBox()->setText( + String::fromUtf8( mApp->getConfig().editor.tabIndentCharacter.empty() + ? u8"»" + : mApp->getConfig().editor.tabIndentCharacter ) ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + auto txt( msgBox->getComboBox()->getText() ); + if ( txt.size() == 1 ) { + mApp->getConfig().editor.tabIndentCharacter = txt.toUtf8(); + mApp->getSplitter()->forEachEditor( + [txt]( UICodeEditor* editor ) { editor->setTabIndentCharacter( txt[0] ); } ); + msgBox->closeWindow(); + } else { + UIMessageBox* msgBoxAlert = + UIMessageBox::New( UIMessageBox::OK, i18n( "it_must_be_a_single_character", + "It must be a single character" ) ); + msgBoxAlert->setTitle( mApp->getWindowTitle() ); + msgBoxAlert->setCloseShortcut( { KEY_ESCAPE, 0 } ); + } + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setFoldRefreshFreq() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_fold_refresh_frequency", + "Set code folds refresh frequency:\nIt should be bigger than 1 second.\nFolds are " + "only refreshed after any document modification." ) + .unescape() ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->getTextInput()->setText( mApp->getConfig().editor.codeFoldingRefreshFreq.toString() ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().editor.codeFoldingRefreshFreq = + Time::fromString( msgBox->getTextInput()->getText().toUtf8() ); + if ( mApp->getConfig().editor.codeFoldingRefreshFreq < Seconds( 1 ) ) + mApp->getConfig().editor.codeFoldingRefreshFreq = Seconds( 1 ); + mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setFoldsRefreshTime( mApp->getConfig().editor.codeFoldingRefreshFreq ); + } ); + msgBox->closeWindow(); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setUIScaleFactor() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, + i18n( "set_ui_scale_factor", "Set the UI scale factor (pixel density):\nMinimum value is " + "1, and maximum 6. Requires restart." ) ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->getTextInput()->setText( + String::fromFloat( mApp->getConfig().windowState.pixelDensity ) ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + msgBox->closeWindow(); + Float val; + if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 1 && + val <= 6 ) { + if ( mApp->getConfig().windowState.pixelDensity != val ) { + mApp->getConfig().windowState.pixelDensity = val; + UIMessageBox* msg = UIMessageBox::New( + UIMessageBox::OK, + i18n( "new_ui_scale_factor", "New UI scale factor assigned.\nPlease " + "restart the application." ) ); + msg->showWhenReady(); + mApp->setFocusEditorOnClose( msg ); + } else if ( mApp->getSplitter() && mApp->getSplitter()->getCurWidget() ) { + mApp->getSplitter()->getCurWidget()->setFocus(); + } + } else { + UIMessageBox* msg = UIMessageBox::New( UIMessageBox::OK, "Invalid value!" ); + msg->showWhenReady(); + mApp->setFocusEditorOnClose( msg ); + } + } ); +} + +void SettingsActions::setEditorFontSize() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, i18n( "set_editor_font_size", "Set the editor font size:" ) ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->getTextInput()->setText( mApp->getConfig().editor.fontSize.toString() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().editor.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); + mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) { + editor->setFontSize( + mApp->getConfig().editor.fontSize.asPixels( 0, Sizef(), mApp->getDisplayDPI() ) ); + } ); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setTerminalFontSize() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, i18n( "set_terminal_font_size", "Set the terminal font size:" ) ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->getTextInput()->setText( mApp->getConfig().term.fontSize.toString() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().term.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); + mApp->getSplitter()->forEachWidget( [this]( UIWidget* widget ) { + if ( widget && widget->isType( UI_TYPE_TERMINAL ) ) + widget->asType()->setFontSize( + mApp->getConfig().term.fontSize.asPixels( 0, Sizef(), mApp->getDisplayDPI() ) ); + } ); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setUIFontSize() { + UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, + i18n( "set_ui_font_size", "Set the UI font size:" ) ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->getTextInput()->setText( mApp->getConfig().ui.fontSize.toString() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().ui.fontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); + Float fontSize = + mApp->getConfig().ui.fontSize.asPixels( 0, Sizef(), mApp->getDisplayDPI() ); + UIThemeManager* manager = mApp->getUISceneNode()->getUIThemeManager(); + manager->setDefaultFontSize( fontSize ); + manager->getDefaultTheme()->setDefaultFontSize( fontSize ); + mApp->getUISceneNode()->forEachNode( [this]( Node* node ) { + if ( node->isType( UI_TYPE_TEXTVIEW ) ) { + UITextView* textView = node->asType(); + if ( !textView->getUIStyle()->hasProperty( PropertyId::FontSize ) ) { + textView->setFontSize( mApp->getConfig().ui.fontSize.asPixels( + node->getParent()->getPixelsSize().getWidth(), Sizef(), + mApp->getUISceneNode()->getDPI() ) ); + } + } + } ); + msgBox->closeWindow(); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +void SettingsActions::setUIPanelFontSize() { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::INPUT, i18n( "set_side_panel_font_size", "Set side panel font size:" ) ); + msgBox->setTitle( mApp->getWindowTitle() ); + msgBox->getTextInput()->setText( mApp->getConfig().ui.panelFontSize.toString() ); + msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } ); + msgBox->showWhenReady(); + msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) { + mApp->getConfig().ui.panelFontSize = StyleSheetLength( msgBox->getTextInput()->getText() ); + + // Update the CSS + auto selsFound = mApp->getUISceneNode()->getStyleSheet().findStyleFromSelectorName( + "#project_view > treeview::row > treeview::cell > treeview::cell::text" ); + if ( !selsFound.empty() ) { + for ( auto sel : selsFound ) + sel->updatePropertyValue( "font-size", + mApp->getConfig().ui.panelFontSize.toString() ); + mApp->getUISceneNode()->getStyleSheet().refreshCacheFromStyles( selsFound ); + } + + UITreeView* treeView = mApp->getUISceneNode()->find( "project_view" ); + if ( !treeView ) { + msgBox->closeWindow(); + return; + } + treeView->reloadStyle( true, true, true, true ); + treeView->updateContentSize(); + msgBox->closeWindow(); + } ); + mApp->setFocusEditorOnClose( msgBox ); +} + +String SettingsActions::i18n( const std::string& key, const String& def ) { + return mApp->i18n( key, def ); +} + +} // namespace ecode diff --git a/src/tools/ecode/settingsactions.hpp b/src/tools/ecode/settingsactions.hpp new file mode 100644 index 000000000..57bcc4fd7 --- /dev/null +++ b/src/tools/ecode/settingsactions.hpp @@ -0,0 +1,51 @@ +#ifndef SETTINGSACTIONS_HPP +#define SETTINGSACTIONS_HPP + +#include + +namespace ecode { + +class App; + +class SettingsActions { + public: + explicit SettingsActions( App* app ) : mApp( app ) {} + + void checkForUpdates( bool fromStartup = false ); + + void aboutEcode(); + + void ecodeSource(); + + void setLineBreakingColumn(); + + void setLineSpacing(); + + void setCursorBlinkingTime(); + + void setIndentTabCharacter(); + + void setFoldRefreshFreq(); + + void setUIScaleFactor(); + + void setUIFontSize(); + + void setEditorFontSize(); + + void setTerminalFontSize(); + + void setUIPanelFontSize(); + + private: + App* mApp{ nullptr }; + + String i18n( const std::string& key, const String& def ); + + void checkForUpdatesResponse( Http::Response response, bool fromStartup ); + +}; + +} // namespace ecode + +#endif diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 86a2d268b..acc9a7ad2 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -712,13 +712,13 @@ UIMenu* SettingsMenu::createDocumentMenu() { mApp->getConfig().workspace.sessionSnapshot = item->isActive(); } } else if ( "line_breaking_column" == id ) { - mApp->setLineBreakingColumn(); + mApp->getSettingsActions()->setLineBreakingColumn(); } else if ( "line_spacing" == id ) { - mApp->setLineSpacing(); + mApp->getSettingsActions()->setLineSpacing(); } else if ( "cursor_blinking_time" == id ) { - mApp->setCursorBlinkingTime(); + mApp->getSettingsActions()->setCursorBlinkingTime(); } else if ( "indent_tab_character" == id ) { - mApp->setIndentTabCharacter(); + mApp->getSettingsActions()->setIndentTabCharacter(); } } ); @@ -1431,7 +1431,7 @@ UIMenu* SettingsMenu::createViewMenu() { editor->setFoldsAlwaysVisible( enabled ); } ); } else if ( "folds_refresh_freq" == item->getId() ) { - mApp->setFoldRefreshFreq(); + mApp->getSettingsActions()->setFoldRefreshFreq(); } } ); }