mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-28 17:24:22 +03:00
Introduce modal user and project settings panels with searchable tree navigation and live configuration updates. - centralize settings registration and presentation - add user and project settings commands and shortcuts - preserve operational actions in the existing settings menu - support boolean, choice, numeric, and action-based settings - restore renderer notifications and choice descriptions - persist settings when closing the panel - add translated settings strings for all supported locales - fix live color preview updates
1730 lines
78 KiB
C++
1730 lines
78 KiB
C++
#include "settingspanel.hpp"
|
|
#include "ecode.hpp"
|
|
#include "uitreeviewfs.hpp"
|
|
#include <deque>
|
|
|
|
using namespace EE::UI::Models;
|
|
|
|
namespace ecode {
|
|
|
|
class SettingsCategoryModel final : public Model {
|
|
public:
|
|
struct Node {
|
|
std::string id;
|
|
std::string text;
|
|
Node* parent{ nullptr };
|
|
std::vector<Node*> children;
|
|
std::vector<Node*> visibleChildren;
|
|
};
|
|
|
|
static std::shared_ptr<SettingsCategoryModel>
|
|
create( const std::vector<std::pair<std::string, std::vector<std::string>>>& categories,
|
|
const UnorderedMap<std::string, std::string>& ids ) {
|
|
return std::make_shared<SettingsCategoryModel>( categories, ids );
|
|
}
|
|
|
|
SettingsCategoryModel(
|
|
const std::vector<std::pair<std::string, std::vector<std::string>>>& categories,
|
|
const UnorderedMap<std::string, std::string>& ids ) {
|
|
mNodes.emplace_back();
|
|
mRoot = &mNodes.back();
|
|
for ( const auto& [parent, children] : categories ) {
|
|
std::string parentId;
|
|
if ( !children.empty() ) {
|
|
auto id = ids.find( parent + '/' + children.front() );
|
|
if ( id != ids.end() ) {
|
|
auto separator = id->second.find( '.' );
|
|
parentId = id->second.substr( 0, separator ) + ".*";
|
|
}
|
|
}
|
|
mNodes.push_back( { std::move( parentId ), parent, mRoot } );
|
|
auto* parentNode = &mNodes.back();
|
|
mRoot->children.push_back( parentNode );
|
|
for ( const auto& child : children ) {
|
|
auto id = ids.find( parent + '/' + child );
|
|
mNodes.push_back(
|
|
{ id == ids.end() ? std::string{} : id->second, child, parentNode } );
|
|
parentNode->children.push_back( &mNodes.back() );
|
|
}
|
|
}
|
|
filter( {}, {} );
|
|
}
|
|
|
|
size_t rowCount( const ModelIndex& parent = {} ) const {
|
|
auto* node = parent.isValid() ? static_cast<Node*>( parent.internalData() ) : mRoot;
|
|
return node->visibleChildren.size();
|
|
}
|
|
|
|
size_t columnCount( const ModelIndex& = {} ) const { return 1; }
|
|
|
|
ModelIndex index( int row, int column, const ModelIndex& parent = {} ) const {
|
|
auto* node = parent.isValid() ? static_cast<Node*>( parent.internalData() ) : mRoot;
|
|
if ( row < 0 || column != 0 || static_cast<size_t>( row ) >= node->visibleChildren.size() )
|
|
return {};
|
|
return createIndex( row, column, node->visibleChildren[row] );
|
|
}
|
|
|
|
ModelIndex parentIndex( const ModelIndex& index ) const {
|
|
if ( !index.isValid() )
|
|
return {};
|
|
auto* node = static_cast<Node*>( index.internalData() );
|
|
if ( !node->parent || node->parent == mRoot )
|
|
return {};
|
|
auto* parent = node->parent;
|
|
auto found =
|
|
std::find( mRoot->visibleChildren.begin(), mRoot->visibleChildren.end(), parent );
|
|
return found == mRoot->visibleChildren.end()
|
|
? ModelIndex{}
|
|
: createIndex( std::distance( mRoot->visibleChildren.begin(), found ), 0,
|
|
parent );
|
|
}
|
|
|
|
Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const {
|
|
if ( !index.isValid() || role != ModelRole::Display )
|
|
return {};
|
|
return Variant( static_cast<Node*>( index.internalData() )->text );
|
|
}
|
|
|
|
void filter( const std::string_view query,
|
|
const UnorderedSet<std::string>& matchingCategories ) {
|
|
mRoot->visibleChildren.clear();
|
|
for ( auto* parent : mRoot->children ) {
|
|
parent->visibleChildren.clear();
|
|
const bool parentMatches = query.empty() || String::icontains( parent->text, query );
|
|
for ( auto* child : parent->children ) {
|
|
if ( parentMatches || String::icontains( child->text, query ) ||
|
|
matchingCategories.contains( child->id ) )
|
|
parent->visibleChildren.push_back( child );
|
|
}
|
|
if ( !parent->visibleChildren.empty() )
|
|
mRoot->visibleChildren.push_back( parent );
|
|
}
|
|
invalidate( Model::UpdateFlag::InvalidateAllIndexes );
|
|
}
|
|
|
|
private:
|
|
std::deque<Node> mNodes;
|
|
Node* mRoot{ nullptr };
|
|
};
|
|
|
|
static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
|
|
<style>
|
|
<![CDATA[
|
|
.settings_panel #settings_filter {
|
|
margin-top: 8dp;
|
|
margin-bottom: 8dp;
|
|
}
|
|
.settings_panel #settings_rows {
|
|
padding-left: 16dp;
|
|
padding-right: 16dp;
|
|
margin-bottom: 8dp;
|
|
}
|
|
.settings_panel #settings_page_title {
|
|
font-size: 16dp;
|
|
padding-bottom: 2dp;
|
|
margin-bottom: 6dp;
|
|
font-weight: bold;
|
|
font-style: shadow;
|
|
border-bottom: 1dp solid var(--tab-line);
|
|
}
|
|
.settings_panel .settings_category_heading {
|
|
font-size: 15dp;
|
|
font-weight: bold;
|
|
margin: 12dp 8dp 8dp 0dp;
|
|
font-style: shadow;
|
|
border-bottom: 1dp solid var(--tab-line);
|
|
}
|
|
.settings_panel .settings_option {
|
|
border-bottom: 1dp solid var(--tab-line);
|
|
padding: 4dp 8dp 8dp 0dp;
|
|
margin-bottom: 12dp;
|
|
}
|
|
.settings_panel .settings_option_name {
|
|
font-weight: bold;
|
|
font-style: shadow;
|
|
}
|
|
.settings_panel .settings_option_description {
|
|
margin-top: 4dp;
|
|
word-wrap: true;
|
|
}
|
|
.settings_panel .settings_boolean_option .settings_option_name,
|
|
.settings_panel .settings_boolean_option .settings_option_description {
|
|
cursor: pointer;
|
|
}
|
|
.settings_panel .settings_option_control {
|
|
margin-left: 12dp;
|
|
layout-gravity: center_vertical;
|
|
}
|
|
.settings_panel .settings_bool,
|
|
.settings_panel .settings_action {
|
|
layout-width: wrap_content;
|
|
layout-height: wrap_content;
|
|
}
|
|
.settings_panel .settings_bool {
|
|
check-mode: button;
|
|
}
|
|
.settings_panel .settings_choice {
|
|
layout-width: 190dp;
|
|
layout-height: wrap_content;
|
|
}
|
|
.settings_panel .settings_integer {
|
|
layout-width: 110dp;
|
|
layout-height: wrap_content;
|
|
}
|
|
]]>
|
|
</style>
|
|
<vbox lw="mp" lh="mp" class="settings_panel">
|
|
<TextInput id="settings_filter" lw="mp" lh="wc" hint="@string(search_settings, Search settings...)" />
|
|
<Splitter id="settings_splitter" lw="mp" lh="0" lw8="1" orientation="horizontal" splitter-partition="160dp">
|
|
<TreeView id="settings_categories" lw="mp" lh="mp" />
|
|
<ScrollView id="settings_scroll" lw="0" lw8="1" lh="mp">
|
|
<vbox id="settings_rows" lw="mp" lh="wc">
|
|
<TextView id="settings_page_title" lw="mp" lh="wc" />
|
|
</vbox>
|
|
</ScrollView>
|
|
</Splitter>
|
|
</vbox>
|
|
)xml";
|
|
|
|
static constexpr const char* SETTINGS_ROW_LAYOUT = R"xml(
|
|
<vbox lw="mp" lh="wc" class="settings_option">
|
|
<hbox lw="mp" lh="wc" class="settings_option_content">
|
|
<vbox lw="0" lw8="1" lh="wc">
|
|
<TextView id="setting_name" lw="mp" lh="wc" class="settings_option_name" />
|
|
<TextView id="setting_description" lw="mp" lh="wc" class="settings_option_description" />
|
|
</vbox>
|
|
<hbox id="setting_control" lw="wc" lh="wc" class="settings_option_control" />
|
|
</hbox>
|
|
</vbox>
|
|
)xml";
|
|
|
|
static constexpr const char* SETTINGS_CATEGORY_HEADING_LAYOUT =
|
|
R"xml(<TextView lw="mp" lh="wc" class="settings_category_heading" visible="false" />)xml";
|
|
static constexpr const char* SETTINGS_BOOL_LAYOUT = R"xml(<CheckBox class="settings_bool" />)xml";
|
|
static constexpr const char* SETTINGS_CHOICE_LAYOUT =
|
|
R"xml(<DropDownList class="settings_choice" />)xml";
|
|
static constexpr const char* SETTINGS_INTEGER_LAYOUT =
|
|
R"xml(<SpinBox class="settings_integer" />)xml";
|
|
static constexpr const char* SETTINGS_ACTION_LAYOUT =
|
|
R"xml(<PushButton class="settings_action" />)xml";
|
|
|
|
SettingsPanel::SettingsPanel( App* app ) : mApp( app ) {}
|
|
|
|
SettingsPanel::PanelState& SettingsPanel::state( Scope scope ) {
|
|
return scope == Scope::User ? mUser : mProject;
|
|
}
|
|
|
|
void SettingsPanel::PanelState::reset() {
|
|
bindingGroup.clear();
|
|
connections.clear();
|
|
window = nullptr;
|
|
search = nullptr;
|
|
categories = nullptr;
|
|
scroll = nullptr;
|
|
settings = nullptr;
|
|
pageTitle = nullptr;
|
|
categoryModel.reset();
|
|
categoryItems.clear();
|
|
categoryIds.clear();
|
|
categorySearchText.clear();
|
|
categoryTitles.clear();
|
|
categoryHeadings.clear();
|
|
bindings.clear();
|
|
selectedCategory.clear();
|
|
categoryFilter.clear();
|
|
}
|
|
|
|
void SettingsPanel::show( Scope scope ) {
|
|
auto& panel = state( scope );
|
|
if ( panel.window ) {
|
|
panel.window->show();
|
|
panel.window->toFront();
|
|
panel.categories->setFocus();
|
|
return;
|
|
}
|
|
create( scope );
|
|
}
|
|
|
|
void SettingsPanel::create( Scope scope ) {
|
|
auto& panel = state( scope );
|
|
UIWindow::StyleConfig config{ UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL };
|
|
panel.window = UIWindow::NewOpt( UIWindow::SIMPLE_LAYOUT, config );
|
|
panel.window->setId( scope == Scope::User ? "settings_panel" : "project_settings_panel" );
|
|
panel.window->setTitle( scope == Scope::User
|
|
? mApp->i18n( "settings", "Settings" )
|
|
: mApp->i18n( "project_settings", "Project Settings" ) );
|
|
const auto sceneSize = mApp->getUISceneNode()->getPixelsSize();
|
|
panel.window->setPixelsSize( { eeclamp( sceneSize.getWidth() * 0.82f, 720.f, 1200.f ),
|
|
eeclamp( sceneSize.getHeight() * 0.82f, 520.f, 850.f ) } );
|
|
panel.window->setMinWindowSize( 640, 440 );
|
|
panel.window->setKeyBindingCommand( "closeWindow", [window = panel.window, this] {
|
|
if ( !SceneManager::instance()->isShuttingDown() ) {
|
|
window->closeWindow();
|
|
if ( mApp->getSplitter() && mApp->getSplitter()->getCurWidget() )
|
|
mApp->getSplitter()->getCurWidget()->setFocus();
|
|
}
|
|
} );
|
|
panel.window->getKeyBindings().addKeybind( { KEY_ESCAPE }, "closeWindow" );
|
|
auto* layout = mApp->getUISceneNode()->loadLayoutFromString( SETTINGS_PANEL_LAYOUT,
|
|
panel.window->getContainer() );
|
|
panel.search = layout->find<UITextInput>( "settings_filter" );
|
|
panel.categories = layout->find<UITreeView>( "settings_categories" );
|
|
panel.settings = layout->find<UILinearLayout>( "settings_rows" );
|
|
panel.pageTitle = layout->find<UITextView>( "settings_page_title" );
|
|
panel.scroll = layout->find<UIScrollView>( "settings_scroll" );
|
|
panel.scroll->setVerticalScrollMode( ScrollBarMode::Auto );
|
|
panel.scroll->setHorizontalScrollMode( ScrollBarMode::AlwaysOff );
|
|
if ( scope == Scope::User )
|
|
addUserSettings( panel );
|
|
else
|
|
addProjectSettings( panel );
|
|
setupCategories( panel );
|
|
filter( panel );
|
|
panel.connections += panel.search->connect(
|
|
Event::OnTextChanged, [this, &panel]( const Event* ) { filter( panel ); } );
|
|
panel.connections += panel.window->connect( Event::OnWindowReady, [&panel]( const Event* ) {
|
|
auto title = panel.categoryTitles.find( panel.selectedCategory );
|
|
if ( title != panel.categoryTitles.end() ) {
|
|
auto index = panel.categories->findRowWithText(
|
|
title->second.toUtf8(), true, UIAbstractView::FindRowWithTextMatchKind::Equals );
|
|
if ( index.isValid() )
|
|
panel.categories->setSelection( index );
|
|
}
|
|
panel.categories->setFocus();
|
|
} );
|
|
panel.connections +=
|
|
panel.window->connect( Event::OnWindowClose, [this, &panel, scope]( const Event* ) {
|
|
if ( scope == Scope::User )
|
|
mApp->saveConfig();
|
|
else
|
|
mApp->saveProject();
|
|
panel.reset();
|
|
} );
|
|
panel.window->center();
|
|
panel.window->showWhenReady();
|
|
panel.categories->setFocus();
|
|
}
|
|
|
|
void SettingsPanel::addCategory( PanelState& panel, const std::string& id, const String& parent,
|
|
const String& name ) {
|
|
const auto parentText = parent.toUtf8();
|
|
const auto nameText = name.toUtf8();
|
|
auto parentItems =
|
|
std::find_if( panel.categoryItems.begin(), panel.categoryItems.end(),
|
|
[&parentText]( const auto& item ) { return item.first == parentText; } );
|
|
if ( parentItems == panel.categoryItems.end() ) {
|
|
panel.categoryItems.emplace_back( parentText, std::vector<std::string>{ nameText } );
|
|
} else {
|
|
parentItems->second.emplace_back( nameText );
|
|
}
|
|
panel.categoryIds[parentText + '/' + nameText] = id;
|
|
panel.categorySearchText[id] = parent + " " + name;
|
|
panel.categoryTitles[id] = name;
|
|
auto* heading = mApp->getUISceneNode()
|
|
->loadLayoutFromString( SETTINGS_CATEGORY_HEADING_LAYOUT, panel.settings )
|
|
->asType<UITextView>();
|
|
heading->setText( name );
|
|
heading->setId( "settings_category_" + id );
|
|
panel.categoryHeadings[id] = heading;
|
|
if ( panel.selectedCategory.empty() )
|
|
panel.selectedCategory = id;
|
|
}
|
|
|
|
void SettingsPanel::setupCategories( PanelState& panel ) {
|
|
auto model = SettingsCategoryModel::create( panel.categoryItems, panel.categoryIds );
|
|
panel.categoryModel = model;
|
|
panel.categories->setHeadersVisible( false );
|
|
panel.categories->setAutoExpandOnSingleColumn( true );
|
|
panel.categories->setFocusOnSelection( true );
|
|
panel.categories->setModel( model );
|
|
panel.categories->expandAll();
|
|
panel.connections +=
|
|
panel.categories->connect( Event::OnSelectionChanged, [this, &panel]( const Event* ) {
|
|
auto index = panel.categories->getSelection().first();
|
|
if ( !index.isValid() )
|
|
return;
|
|
auto* node = static_cast<SettingsCategoryModel::Node*>( index.internalData() );
|
|
if ( !node || node->id.empty() )
|
|
return;
|
|
panel.selectedCategory = node->id;
|
|
panel.pageTitle->setText( node->text );
|
|
filter( panel );
|
|
panel.scroll->getVerticalScrollBar()->setValue( 0, false );
|
|
} );
|
|
}
|
|
|
|
UIWidget* SettingsPanel::createRow( PanelState& panel, SettingBinding& binding ) {
|
|
auto* row = mApp->getUISceneNode()->loadLayoutFromString( SETTINGS_ROW_LAYOUT, panel.settings );
|
|
row->setId( "setting_" + binding.id );
|
|
row->find<UITextView>( "setting_name" )->setText( binding.name );
|
|
auto* description = row->find<UITextView>( "setting_description" );
|
|
description->setText( binding.description );
|
|
binding.row = row;
|
|
return row;
|
|
}
|
|
|
|
void SettingsPanel::addBool( PanelState& panel, SettingBinding binding, bool* value,
|
|
std::function<void( bool )> apply ) {
|
|
auto* check = createBoolControl( panel, binding );
|
|
auto valueBinding =
|
|
UIDataBind<bool>::New( value, check, UIValueConverter<bool>::converterBool() );
|
|
valueBinding->onValueChangeCb = std::move( apply );
|
|
panel.bindingGroup += std::move( valueBinding );
|
|
panel.bindings.emplace_back( std::move( binding ) );
|
|
}
|
|
|
|
void SettingsPanel::addBool( PanelState& panel, SettingBinding binding, std::function<bool()> get,
|
|
std::function<void( bool )> set ) {
|
|
auto* check = createBoolControl( panel, binding );
|
|
check->setChecked( get() );
|
|
panel.connections +=
|
|
check->connect( Event::OnValueChange, [check, set = std::move( set )]( const Event* ) {
|
|
set( check->isChecked() );
|
|
} );
|
|
panel.bindings.emplace_back( std::move( binding ) );
|
|
}
|
|
|
|
UICheckBox* SettingsPanel::createBoolControl( PanelState& panel, SettingBinding& binding ) {
|
|
auto* row = createRow( panel, binding );
|
|
row->addClass( "settings_boolean_option" );
|
|
auto* check = mApp->getUISceneNode()
|
|
->loadLayoutFromString( SETTINGS_BOOL_LAYOUT,
|
|
row->find<UILinearLayout>( "setting_control" ) )
|
|
->asType<UICheckBox>();
|
|
auto toggle = [check]( const Event* ) { check->setChecked( !check->isChecked() ); };
|
|
panel.connections +=
|
|
row->find<UITextView>( "setting_name" )->connect( Event::MouseClick, toggle );
|
|
panel.connections += row->find<UITextView>( "setting_description" )
|
|
->connect( Event::MouseClick, std::move( toggle ) );
|
|
return check;
|
|
}
|
|
|
|
void SettingsPanel::addChoice( PanelState& panel, SettingBinding binding,
|
|
const std::vector<String>& choices, std::function<size_t()> get,
|
|
std::function<void( size_t )> set,
|
|
std::vector<String> choiceDescriptions ) {
|
|
auto* row = createRow( panel, binding );
|
|
auto* dropDown = mApp->getUISceneNode()
|
|
->loadLayoutFromString( SETTINGS_CHOICE_LAYOUT,
|
|
row->find<UILinearLayout>( "setting_control" ) )
|
|
->asType<UIDropDownList>();
|
|
dropDown->getListBox()->addListBoxItems( choices );
|
|
const size_t selected = get();
|
|
dropDown->getListBox()->setSelected( selected );
|
|
if ( selected < choiceDescriptions.size() )
|
|
dropDown->setTooltipText( choiceDescriptions[selected] );
|
|
panel.connections += dropDown->connect(
|
|
Event::OnValueChange, [dropDown, set = std::move( set ),
|
|
descriptions = std::move( choiceDescriptions )]( const Event* ) {
|
|
if ( !dropDown->getListBox()->hasSelection() )
|
|
return;
|
|
const size_t selected = dropDown->getListBox()->getItemSelectedIndex();
|
|
if ( selected < descriptions.size() )
|
|
dropDown->setTooltipText( descriptions[selected] );
|
|
set( selected );
|
|
} );
|
|
panel.bindings.emplace_back( std::move( binding ) );
|
|
}
|
|
|
|
void SettingsPanel::addInteger( PanelState& panel, SettingBinding binding, int min, int max,
|
|
int* value, std::function<void( int )> apply ) {
|
|
auto* row = createRow( panel, binding );
|
|
auto* spin = mApp->getUISceneNode()
|
|
->loadLayoutFromString( SETTINGS_INTEGER_LAYOUT,
|
|
row->find<UILinearLayout>( "setting_control" ) )
|
|
->asType<UISpinBox>();
|
|
spin->setMinValue( min )->setMaxValue( max );
|
|
auto valueBinding = UIDataBind<int>::New( value, spin );
|
|
valueBinding->onValueChangeCb = std::move( apply );
|
|
panel.bindingGroup += std::move( valueBinding );
|
|
panel.bindings.emplace_back( std::move( binding ) );
|
|
}
|
|
|
|
void SettingsPanel::addAction( PanelState& panel, SettingBinding binding, const String& buttonText,
|
|
std::function<void()> action ) {
|
|
auto* row = createRow( panel, binding );
|
|
auto* button = mApp->getUISceneNode()
|
|
->loadLayoutFromString( SETTINGS_ACTION_LAYOUT,
|
|
row->find<UILinearLayout>( "setting_control" ) )
|
|
->asType<UIPushButton>();
|
|
button->setText( buttonText );
|
|
panel.connections += button->connect(
|
|
Event::MouseClick, [action = std::move( action )]( const Event* ) { action(); } );
|
|
panel.bindings.emplace_back( std::move( binding ) );
|
|
}
|
|
|
|
void SettingsPanel::addUserSettings( PanelState& panel ) {
|
|
addCategory( panel, "general.behavior", mApp->i18n( "general", "General" ),
|
|
mApp->i18n( "behavior", "Behavior" ) );
|
|
addBool( panel,
|
|
{ "welcomeScreen", "general.behavior",
|
|
mApp->i18n( "welcome_screen_enable", "Enable Welcome Screen" ),
|
|
mApp->i18n( "welcome_screen_enable_desc",
|
|
"Show the welcome screen when no document or folder is open." ) },
|
|
&mApp->getConfig().ui.welcomeScreen );
|
|
addBool(
|
|
panel,
|
|
{ "openFilesInNewWindow", "general.behavior",
|
|
mApp->i18n( "open_files_in_new_window_enable", "Open Files in New Window" ),
|
|
mApp->i18n(
|
|
"open_files_in_new_window_desc",
|
|
"Open files received from the file explorer or command line in a new window." ) },
|
|
&mApp->getConfig().ui.openFilesInNewWindow );
|
|
addBool(
|
|
panel,
|
|
{ "openProjectInNewWindow", "general.behavior",
|
|
mApp->i18n( "open_project_in_new_window", "Open Project in New Window" ),
|
|
mApp->i18n( "open_project_in_new_window_tooltip",
|
|
"Open folders in a new window when another project is already loaded." ) },
|
|
&mApp->getConfig().ui.openProjectInNewWindow );
|
|
addBool( panel,
|
|
{ "nativeFileDialogs", "general.behavior",
|
|
mApp->i18n( "use_native_file_dialogs", "Enable Native File Dialogs" ),
|
|
mApp->i18n( "use_native_file_dialogs_tooltip",
|
|
"Use operating-system file dialogs when available." ) },
|
|
&mApp->getConfig().ui.nativeFileDialogs );
|
|
addBool( panel,
|
|
{ "imagesQuickPreview", "general.behavior",
|
|
mApp->i18n( "quick_preview_images", "Quick Preview Images" ),
|
|
mApp->i18n( "quick_preview_images_tooltip",
|
|
"Preview images without opening a permanent editor tab." ) },
|
|
&mApp->getConfig().ui.imagesQuickPreview );
|
|
|
|
addCategory( panel, "editor.appearance", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "appearance", "Appearance" ) );
|
|
std::vector<String> editorSchemeNames;
|
|
std::vector<std::string> editorSchemeIds;
|
|
for ( const auto& [name, scheme] : mApp->getSplitter()->getColorSchemes() ) {
|
|
editorSchemeNames.emplace_back( name );
|
|
editorSchemeIds.emplace_back( name );
|
|
}
|
|
auto selectedEditorScheme =
|
|
std::find( editorSchemeNames.begin(), editorSchemeNames.end(),
|
|
String( mApp->getSplitter()->getCurrentColorSchemeName() ) );
|
|
const size_t selectedEditorSchemeIndex =
|
|
selectedEditorScheme == editorSchemeNames.end()
|
|
? 0
|
|
: static_cast<size_t>( selectedEditorScheme - editorSchemeNames.begin() );
|
|
if ( !editorSchemeNames.empty() )
|
|
addChoice(
|
|
panel,
|
|
{ "editorColorScheme", "editor.appearance",
|
|
mApp->i18n( "syntax_color_scheme", "Syntax Color Scheme" ),
|
|
mApp->i18n( "syntax_color_scheme_desc",
|
|
"Choose the colors used for syntax highlighting and editor surfaces." ) },
|
|
editorSchemeNames, [selectedEditorSchemeIndex] { return selectedEditorSchemeIndex; },
|
|
[this, editorSchemeIds = std::move( editorSchemeIds )]( size_t selected ) {
|
|
mApp->getSplitter()->setColorScheme(
|
|
editorSchemeIds[std::min( selected, editorSchemeIds.size() - 1 )] );
|
|
} );
|
|
auto addEditorBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
|
|
const char* descriptionKey, const char* description,
|
|
bool CodeEditorConfig::* member, auto apply ) {
|
|
addBool( panel,
|
|
{ std::move( id ), "editor.appearance", mApp->i18n( nameKey, name ),
|
|
mApp->i18n( descriptionKey, description ) },
|
|
&( mApp->getConfig().editor.*member ), [this, apply]( bool value ) {
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value, apply]( UICodeEditor* editor ) { apply( editor, value ); } );
|
|
} );
|
|
};
|
|
addEditorBool( "showLineNumbers", "show_line_numbers", "Show Line Numbers",
|
|
"show_line_numbers_desc", "Display line numbers beside the editor.",
|
|
&CodeEditorConfig::showLineNumbers,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setShowLineNumber( value ); } );
|
|
addEditorBool(
|
|
"showWhiteSpaces", "show_white_spaces", "Show White Spaces", "show_white_spaces_desc",
|
|
"Display visible markers for whitespace characters.", &CodeEditorConfig::showWhiteSpaces,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setShowWhitespaces( value ); } );
|
|
addEditorBool(
|
|
"highlightCurrentLine", "highlight_current_line", "Highlight Current Line",
|
|
"highlight_current_line_desc", "Highlight the line containing the primary cursor.",
|
|
&CodeEditorConfig::highlightCurrentLine,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setHighlightCurrentLine( value ); } );
|
|
addBool( panel,
|
|
{ "showDocumentInfo", "editor.appearance",
|
|
mApp->i18n( "show_doc_info", "Show Document Info" ),
|
|
mApp->i18n( "show_doc_info_desc",
|
|
"Display document encoding and line-ending information." ) },
|
|
&mApp->getConfig().editor.showDocInfo, [this]( bool value ) {
|
|
if ( mApp->getDocInfo() )
|
|
mApp->getDocInfo()->setVisible( value );
|
|
} );
|
|
addEditorBool(
|
|
"showLineEndings", "show_line_endings", "Show Line Endings", "show_line_endings_desc",
|
|
"Display markers for line-ending characters.", &CodeEditorConfig::showLineEndings,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setShowLineEndings( value ); } );
|
|
addEditorBool(
|
|
"showIndentationGuides", "show_indentation_guides", "Show Indentation Guides",
|
|
"show_indentation_guides_desc", "Display vertical guides for indentation levels.",
|
|
&CodeEditorConfig::showIndentationGuides,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setShowIndentationGuides( value ); } );
|
|
addEditorBool( "minimap", "show_minimap", "Show Minimap", "show_minimap_desc",
|
|
"Display a compact overview of the document.", &CodeEditorConfig::minimap,
|
|
[]( UICodeEditor* editor, bool value ) { editor->showMinimap( value ); } );
|
|
addEditorBool(
|
|
"relativeLinePositions", "show_lines_relative_position", "Show Lines Relative Position",
|
|
"show_lines_relative_position_desc", "Show line numbers relative to the primary cursor.",
|
|
&CodeEditorConfig::linesRelativePosition,
|
|
[]( UICodeEditor* editor, bool value ) { editor->showLinesRelativePosition( value ); } );
|
|
addEditorBool(
|
|
"highlightMatchingBracket", "highlight_matching_brackets", "Highlight Matching Bracket",
|
|
"highlight_matching_brackets_desc",
|
|
"Highlight the bracket paired with the one beside the cursor.",
|
|
&CodeEditorConfig::highlightMatchingBracket,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setHighlightMatchingBracket( value ); } );
|
|
addEditorBool(
|
|
"highlightSelectionMatch", "highlight_selection_match", "Highlight Selection Match",
|
|
"highlight_selection_match_desc", "Highlight text matching the current selection.",
|
|
&CodeEditorConfig::highlightSelectionMatch,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setHighlightSelectionMatch( value ); } );
|
|
addEditorBool( "verticalScrollbar", "enable_vertical_scrollbar", "Enable Vertical Scrollbar",
|
|
"enable_vertical_scrollbar_desc", "Display the editor's vertical scrollbar.",
|
|
&CodeEditorConfig::verticalScrollbar, []( UICodeEditor* editor, bool value ) {
|
|
editor->setVerticalScrollBarEnabled( value );
|
|
} );
|
|
addEditorBool( "horizontalScrollbar", "enable_horizontal_scrollbar",
|
|
"Enable Horizontal Scrollbar", "enable_horizontal_scrollbar_desc",
|
|
"Display the editor's horizontal scrollbar.",
|
|
&CodeEditorConfig::horizontalScrollbar, []( UICodeEditor* editor, bool value ) {
|
|
editor->setHorizontalScrollBarEnabled( value );
|
|
} );
|
|
addEditorBool( "inlineColorBoxes", "enable_inline_color_boxes", "Enable Color Boxes",
|
|
"enable_inline_color_boxes_tooltip",
|
|
"Display inline color boxes beside recognized color values.",
|
|
&CodeEditorConfig::inlineColorBoxes, []( UICodeEditor* editor, bool value ) {
|
|
editor->setEnableInlineColorBoxes( value );
|
|
} );
|
|
addEditorBool( "colorPreview", "enable_color_preview", "Enable Color Preview",
|
|
"enable_color_preview_tooltip", "Preview selected color values in the editor.",
|
|
&CodeEditorConfig::colorPreview,
|
|
[]( UICodeEditor* editor, bool value ) { editor->setColorPreview( value ); } );
|
|
addEditorBool( "colorPickerSelection", "enable_color_picker", "Enable Color Picker",
|
|
"enable_color_picker_tooltip",
|
|
"Open the color picker for recognized color selections.",
|
|
&CodeEditorConfig::colorPickerSelection, []( UICodeEditor* editor, bool value ) {
|
|
editor->setEnableColorPickerOnSelection( value );
|
|
} );
|
|
|
|
addCategory( panel, "editor.navigation", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "navigation", "Navigation" ) );
|
|
addBool( panel,
|
|
{ "singleClickNavigation", "editor.navigation",
|
|
mApp->i18n( "treeview_single_click_nav", "Single Click Navigation" ),
|
|
mApp->i18n( "treeview_single_click_nav_tooltip",
|
|
"Open project-tree files with a single click." ) },
|
|
&mApp->getConfig().editor.singleClickNavigation, [this]( bool value ) {
|
|
if ( mApp->getProjectTreeView() )
|
|
mApp->getProjectTreeView()->setSingleClickNavigation( value );
|
|
} );
|
|
addBool( panel,
|
|
{ "syncProjectTree", "editor.navigation",
|
|
mApp->i18n( "sync_project_tree", "Synchronize Project Tree with Editor" ),
|
|
mApp->i18n( "sync_project_tree_tooltip",
|
|
"Select the active document in the project tree." ) },
|
|
&mApp->getConfig().editor.syncProjectTreeWithEditor );
|
|
addBool(
|
|
panel,
|
|
{ "restoreSelectionOnFocus", "editor.navigation",
|
|
mApp->i18n( "restore_editor_selection_on_focus", "Restore Editor Selection on Focus" ),
|
|
mApp->i18n( "restore_editor_selection_on_focus_tooltip",
|
|
"Restore each editor split's last cursor and selection when focused." ) },
|
|
&mApp->getConfig().editor.restoreEditorSelectionOnFocus,
|
|
[this]( bool value ) { mApp->getSplitter()->setRestoreEditorSelectionOnFocus( value ); } );
|
|
|
|
addCategory( panel, "editor.formatting", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "formatting", "Formatting" ) );
|
|
addChoice(
|
|
panel,
|
|
{ "lineWrapMode", "editor.formatting", mApp->i18n( "line_wrap", "Line Wrap" ),
|
|
mApp->i18n( "line_wrap_desc", "Controls how long lines wrap in the editor." ) },
|
|
{ mApp->i18n( "no_wrap", "No Wrap" ), mApp->i18n( "wrap_word", "Word Wrap" ),
|
|
mApp->i18n( "wrap_letter", "Letter Wrap" ) },
|
|
[this] {
|
|
switch ( mApp->getConfig().editor.wrapMode ) {
|
|
case LineWrapMode::NoWrap:
|
|
return size_t{ 0 };
|
|
case LineWrapMode::Letter:
|
|
return size_t{ 2 };
|
|
case LineWrapMode::Word:
|
|
default:
|
|
return size_t{ 1 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
auto mode = selected == 0 ? LineWrapMode::NoWrap
|
|
: selected == 2 ? LineWrapMode::Letter
|
|
: LineWrapMode::Word;
|
|
mApp->getConfig().editor.wrapMode = mode;
|
|
mApp->getSplitter()->forEachEditor(
|
|
[mode]( UICodeEditor* editor ) { editor->setLineWrapMode( mode ); } );
|
|
} );
|
|
addInteger(
|
|
panel,
|
|
{ "lineBreakingColumn", "editor.formatting",
|
|
mApp->i18n( "line_breaking_column", "Line Breaking Column" ),
|
|
mApp->i18n( "line_breaking_column_desc",
|
|
"Column used for wrapping and the editor width guide. Set 0 to disable." ) },
|
|
0, 1000, &mApp->getConfig().doc.lineBreakingColumn, [this]( int value ) {
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->setLineBreakingColumn( value ); } );
|
|
} );
|
|
addChoice(
|
|
panel,
|
|
{ "lineWrapType", "editor.formatting", mApp->i18n( "wrap_type", "Wrap Against" ),
|
|
mApp->i18n( "wrap_type_desc",
|
|
"Choose whether lines wrap at the viewport or line breaking column." ) },
|
|
{ mApp->i18n( "viewport", "Viewport" ),
|
|
mApp->i18n( "line_breaking_column", "Line Breaking Column" ) },
|
|
[this] { return mApp->getConfig().editor.wrapType == LineWrapType::Viewport ? 0 : 1; },
|
|
[this]( size_t selected ) {
|
|
auto value = selected == 0 ? LineWrapType::Viewport : LineWrapType::LineBreakingColumn;
|
|
mApp->getConfig().editor.wrapType = value;
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->setLineWrapType( value ); } );
|
|
} );
|
|
addBool( panel,
|
|
{ "wrapKeepIndentation", "editor.formatting",
|
|
mApp->i18n( "keep_indentation", "Keep Indentation" ),
|
|
mApp->i18n( "keep_indentation_desc",
|
|
"Preserve indentation on wrapped continuation lines." ) },
|
|
&mApp->getConfig().editor.wrapKeepIndentation, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachEditor( [value]( UICodeEditor* editor ) {
|
|
editor->setLineWrapKeepIndentation( value );
|
|
} );
|
|
} );
|
|
|
|
addCategory( panel, "editor.folding", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "code_folding", "Code Folding" ) );
|
|
addBool( panel,
|
|
{ "codeFoldingEnabled", "editor.folding", mApp->i18n( "enabled", "Enabled" ),
|
|
mApp->i18n( "code_folding_enabled_desc", "Enable syntax-aware code folding." ) },
|
|
&mApp->getConfig().editor.codeFoldingEnabled, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachDoc( [value]( TextDocument& doc ) {
|
|
doc.getFoldRangeService().setEnabled( value );
|
|
} );
|
|
} );
|
|
addBool( panel,
|
|
{ "codeFoldingAlwaysVisible", "editor.folding",
|
|
mApp->i18n( "code_folding_always_display_folds", "Folds Always Visible" ),
|
|
mApp->i18n( "code_folding_always_visible_desc",
|
|
"Always display fold controls in the editor gutter." ) },
|
|
&mApp->getConfig().editor.codeFoldingAlwaysVisible, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->setFoldsAlwaysVisible( value ); } );
|
|
} );
|
|
|
|
addCategory( panel, "editor.tabs", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "tab_bar", "Tab Bar" ) );
|
|
addBool( panel,
|
|
{ "hideTabBar", "editor.tabs", mApp->i18n( "hide_tabbar", "Hide Tab Bar" ),
|
|
mApp->i18n( "hide_tabbar_tooltip", "Always hide the tab bar." ) },
|
|
&mApp->getConfig().editor.hideTabBar,
|
|
[this]( bool value ) { mApp->getSplitter()->setHideTabBar( value ); } );
|
|
addBool( panel,
|
|
{ "hideTabBarOnSingleTab", "editor.tabs",
|
|
mApp->i18n( "hide_tabbar_on_single_tab", "Hide Tab Bar on Single Tab" ),
|
|
mApp->i18n( "hide_tabbar_on_single_tab_tooltip",
|
|
"Hide the tab bar when a tab widget contains only one item." ) },
|
|
&mApp->getConfig().editor.hideTabBarOnSingleTab,
|
|
[this]( bool value ) { mApp->getSplitter()->setHideTabBarOnSingleTab( value ); } );
|
|
addBool( panel,
|
|
{ "tabSwitcher", "editor.tabs",
|
|
mApp->i18n( "display_tab_switcher", "Display Tab Switcher" ),
|
|
mApp->i18n( "display_tab_switcher_tooltip",
|
|
"Display a tab switcher in the center of each tab widget." ) },
|
|
&mApp->getConfig().editor.tabSwitcher );
|
|
addBool( panel,
|
|
{ "openDocumentsInMainSplit", "editor.tabs",
|
|
mApp->i18n( "open_documents_in_main_split", "Open Documents in Main Split" ),
|
|
mApp->i18n( "open_documents_in_main_split_desc",
|
|
"Open externally requested documents in the main editor split." ) },
|
|
&mApp->getConfig().editor.openDocumentsInMainSplit );
|
|
addChoice(
|
|
panel,
|
|
{ "tabJumpMode", "editor.tabs", mApp->i18n( "tab_jump_mode", "Tab Jump Mode" ),
|
|
mApp->i18n( "tab_jump_mode_desc",
|
|
"Choose how keyboard navigation cycles through tabs." ) },
|
|
{ mApp->i18n( "linear", "Linear" ), mApp->i18n( "chronological", "Chronological" ) },
|
|
[this] {
|
|
return mApp->getConfig().editor.tabJumpMode == UITabWidget::TabJumpMode::Linear ? 0 : 1;
|
|
},
|
|
[this]( size_t selected ) {
|
|
mApp->getConfig().editor.tabJumpMode = selected == 0
|
|
? UITabWidget::TabJumpMode::Linear
|
|
: UITabWidget::TabJumpMode::Chronological;
|
|
},
|
|
{ mApp->i18n( "jump_mode_linear_tooltip",
|
|
"Linear Jump Mode will switch tabs in the order they are displayed" ),
|
|
mApp->i18n(
|
|
"jump_mode_chronological_tooltip",
|
|
"Chronological Jump Mode will switch tabs in the last focused / visited order." ) } );
|
|
addChoice(
|
|
panel,
|
|
{ "newTabPosition", "editor.tabs", mApp->i18n( "new_tab_position", "New Tab Position" ),
|
|
mApp->i18n( "new_tab_position_desc", "Choose where newly opened tabs are inserted." ) },
|
|
{ mApp->i18n( "new_tab_position_after_active", "After Active Tab" ),
|
|
mApp->i18n( "new_tab_position_last", "Last" ),
|
|
mApp->i18n( "new_tab_position_first", "First" ),
|
|
mApp->i18n( "new_tab_position_left_of_active", "Left of Active Tab" ) },
|
|
[this] {
|
|
switch ( mApp->getConfig().editor.newTabPosition ) {
|
|
case NewTabPosition::AfterActive:
|
|
return size_t{ 0 };
|
|
case NewTabPosition::First:
|
|
return size_t{ 2 };
|
|
case NewTabPosition::LeftOfActive:
|
|
return size_t{ 3 };
|
|
default:
|
|
return size_t{ 1 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr NewTabPosition::Position positions[] = {
|
|
NewTabPosition::AfterActive, NewTabPosition::Last, NewTabPosition::First,
|
|
NewTabPosition::LeftOfActive };
|
|
mApp->getConfig().editor.newTabPosition = positions[std::min( selected, size_t{ 3 } )];
|
|
} );
|
|
|
|
addCategory( panel, "editor.document", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "document_defaults", "Document Defaults" ) );
|
|
auto addDocumentBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
|
|
const char* descriptionKey, const char* description,
|
|
bool DocumentConfig::* member,
|
|
std::function<void( bool )> apply = {} ) {
|
|
addBool( panel,
|
|
{ std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
|
|
mApp->i18n( descriptionKey, description ) },
|
|
&( mApp->getConfig().doc.*member ), std::move( apply ) );
|
|
};
|
|
addDocumentBool( "autoDetectIndentType", "auto_detect_indent_type_and_width",
|
|
"Auto Detect Indent Type & Width", "auto_detect_indent_type_desc",
|
|
"Detect indentation settings when opening each document.",
|
|
&DocumentConfig::autoDetectIndentType );
|
|
addChoice(
|
|
panel,
|
|
{ "autoIndent", "editor.document", mApp->i18n( "auto_indent", "Auto-Indent" ),
|
|
mApp->i18n( "auto_indent_tooltip", "Control indentation added after pressing Enter." ) },
|
|
{ mApp->i18n( "auto_indent_none", "None" ),
|
|
mApp->i18n( "auto_indent_preserve", "Preserve" ),
|
|
mApp->i18n( "auto_indent_smart", "Smart" ) },
|
|
[this] { return static_cast<size_t>( mApp->getConfig().doc.autoIndent ); },
|
|
[this]( size_t selected ) {
|
|
auto value =
|
|
static_cast<TextDocument::AutoIndentConfig>( std::min( selected, size_t{ 2 } ) );
|
|
mApp->getConfig().doc.autoIndent = value;
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->getDocument().setAutoIndent( value ); } );
|
|
},
|
|
{ mApp->i18n( "auto_indent_none_desc", "No automatic indentation." ),
|
|
mApp->i18n( "auto_indent_preserve_desc",
|
|
"Preserve the indentation level of the previous line." ),
|
|
mApp->i18n( "auto_indent_smart_desc",
|
|
"Preserve indentation and indent between auto-closed brackets." ) } );
|
|
addDocumentBool( "indentSpaces", "indent_spaces", "Indent Using Spaces", "indent_spaces_desc",
|
|
"Insert spaces instead of tab characters for indentation.",
|
|
&DocumentConfig::indentSpaces );
|
|
addDocumentBool( "trimTrailingWhitespaces", "trim_trailing_whitespaces",
|
|
"Trim Trailing Whitespaces", "trim_trailing_whitespaces_desc",
|
|
"Remove trailing whitespace when saving files.",
|
|
&DocumentConfig::trimTrailingWhitespaces );
|
|
addDocumentBool( "forceNewLineAtEndOfFile", "force_new_line_at_end_of_file",
|
|
"Force New Line at End of File", "force_new_line_at_end_of_file_desc",
|
|
"Ensure saved files end with a newline.",
|
|
&DocumentConfig::forceNewLineAtEndOfFile );
|
|
addDocumentBool( "writeUnicodeBOM", "write_unicode_bom", "Write Unicode BOM",
|
|
"write_unicode_bom_desc", "Write a Unicode byte-order mark when saving.",
|
|
&DocumentConfig::writeUnicodeBOM );
|
|
addDocumentBool( "tabStops", "tab_stops", "Tab Stops", "tab_stops_desc",
|
|
"Align tabs to consistent positions on the tab grid.",
|
|
&DocumentConfig::tabStops, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->setTabStops( value ); } );
|
|
} );
|
|
addDocumentBool( "tabOut", "tab_out", "Tab Out", "tab_out_desc",
|
|
"Press Tab before a closing character to move past it.",
|
|
&DocumentConfig::tabOutEnabled, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachEditor( [value]( UICodeEditor* editor ) {
|
|
editor->getDocument().setTabOutEnabled( value );
|
|
} );
|
|
} );
|
|
addInteger( panel,
|
|
{ "indentWidth", "editor.document", mApp->i18n( "indent_width", "Indent Width" ),
|
|
mApp->i18n( "indent_width_desc", "Columns in one indentation level." ) },
|
|
1, 16, &mApp->getConfig().doc.indentWidth );
|
|
addInteger( panel,
|
|
{ "tabWidth", "editor.document", mApp->i18n( "tab_width", "Tab Width" ),
|
|
mApp->i18n( "tab_width_desc", "Columns used to display a tab character." ) },
|
|
1, 16, &mApp->getConfig().doc.tabWidth );
|
|
addChoice(
|
|
panel,
|
|
{ "lineEndings", "editor.document", mApp->i18n( "line_endings", "Line Endings" ),
|
|
mApp->i18n( "line_endings_desc", "Default line-ending sequence for new files." ) },
|
|
{ "Windows (CR/LF)", "Unix (LF)", "Macintosh (CR)" },
|
|
[this] {
|
|
switch ( mApp->getConfig().doc.lineEndings ) {
|
|
case TextFormat::LineEnding::CRLF:
|
|
return size_t{ 0 };
|
|
case TextFormat::LineEnding::CR:
|
|
return size_t{ 2 };
|
|
default:
|
|
return size_t{ 1 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
mApp->getConfig().doc.lineEndings =
|
|
selected == 0
|
|
? TextFormat::LineEnding::CRLF
|
|
: ( selected == 2 ? TextFormat::LineEnding::CR : TextFormat::LineEnding::LF );
|
|
} );
|
|
addBool( panel,
|
|
{ "autoCloseXMLTags", "editor.document",
|
|
mApp->i18n( "auto_close_xml_tags", "Auto Close XML Tags" ),
|
|
mApp->i18n( "auto_close_xml_tags_desc", "Automatically insert closing XML tags." ) },
|
|
&mApp->getConfig().editor.autoCloseXMLTags, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->setAutoCloseXMLTags( value ); } );
|
|
} );
|
|
auto addAutoClosePair = [this, &panel]( std::string id, const char* nameKey, const char* name,
|
|
std::string pair ) {
|
|
auto containsPair = [this, pair] {
|
|
auto pairs = String::split( mApp->getConfig().editor.autoCloseBrackets, ',' );
|
|
return std::find( pairs.begin(), pairs.end(), pair ) != pairs.end();
|
|
};
|
|
addBool( panel,
|
|
{ std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
|
|
mApp->i18n( "auto_close_pair_desc",
|
|
"Automatically insert the matching closing character." ) },
|
|
std::move( containsPair ), [this, pair = std::move( pair )]( bool enabled ) {
|
|
auto pairs = String::split( mApp->getConfig().editor.autoCloseBrackets, ',' );
|
|
auto found = std::find( pairs.begin(), pairs.end(), pair );
|
|
if ( enabled && found == pairs.end() )
|
|
pairs.emplace_back( pair );
|
|
else if ( !enabled && found != pairs.end() )
|
|
pairs.erase( found );
|
|
mApp->getConfig().editor.autoCloseBrackets = String::join( pairs, ',' );
|
|
auto closePairs =
|
|
mApp->makeAutoClosePairs( mApp->getConfig().editor.autoCloseBrackets );
|
|
mApp->getSplitter()->forEachEditor( [closePairs]( UICodeEditor* editor ) {
|
|
editor->getDocument().setAutoCloseBrackets( !closePairs.empty() );
|
|
editor->getDocument().setAutoCloseBracketsPairs( closePairs );
|
|
} );
|
|
} );
|
|
};
|
|
addAutoClosePair( "autoCloseBrackets", "brackets", "Brackets ()", "()" );
|
|
addAutoClosePair( "autoCloseCurlyBrackets", "curly_brackets", "Curly Brackets {}", "{}" );
|
|
addAutoClosePair( "autoCloseSquareBrackets", "square_brakcets", "Square Brackets []", "[]" );
|
|
addAutoClosePair( "autoCloseSingleQuotes", "single_quotes", "Single Quotes ''", "''" );
|
|
addAutoClosePair( "autoCloseDoubleQuotes", "double_quotes", "Double Quotes \"\"", "\"\"" );
|
|
addAutoClosePair( "autoCloseBackQuotes", "back_quotes", "Back Quotes ``", "``" );
|
|
addBool( panel,
|
|
{ "autoReloadOnDiskChange", "editor.document",
|
|
mApp->i18n( "autoreload_on_disk_change", "Auto-Reload on Disk Change" ),
|
|
mApp->i18n( "autoreload_on_disk_change_desc",
|
|
"Reload unmodified documents when their files change on disk." ) },
|
|
&mApp->getConfig().editor.autoReloadOnDiskChange );
|
|
|
|
addCategory( panel, "general.workspace", mApp->i18n( "general", "General" ),
|
|
mApp->i18n( "workspace", "Workspace" ) );
|
|
addBool( panel,
|
|
{ "sessionSnapshot", "general.workspace",
|
|
mApp->i18n( "session_snapshot", "Session Snapshot & Periodic Backup" ),
|
|
mApp->i18n( "session_snapshot_desc",
|
|
"Preserve unsaved document changes between sessions." ) },
|
|
&mApp->getConfig().workspace.sessionSnapshot );
|
|
addBool( panel,
|
|
{ "restoreLastSession", "general.workspace",
|
|
mApp->i18n( "restore_last_session", "Restore Last Session" ),
|
|
mApp->i18n( "restore_last_session_desc",
|
|
"Reopen the previous workspace and documents at startup." ) },
|
|
&mApp->getConfig().workspace.restoreLastSession );
|
|
addBool( panel,
|
|
{ "checkForUpdatesAtStartup", "general.workspace",
|
|
mApp->i18n( "check_updates_at_startup", "Check for Updates at Startup" ),
|
|
mApp->i18n( "check_updates_at_startup_desc",
|
|
"Check whether a newer ecode release is available after startup." ) },
|
|
&mApp->getConfig().workspace.checkForUpdatesAtStartup );
|
|
|
|
addCategory( panel, "appearance.theme", mApp->i18n( "appearance", "Appearance" ),
|
|
mApp->i18n( "theme_and_language", "Theme & Language" ) );
|
|
addChoice(
|
|
panel,
|
|
{ "uiColorScheme", "appearance.theme",
|
|
mApp->i18n( "ui_prefes_color_scheme", "UI Prefers Color Scheme" ),
|
|
mApp->i18n(
|
|
"ui_prefers_color_scheme_desc",
|
|
"Choose whether the interface follows the system, light, or dark appearance." ) },
|
|
{ mApp->i18n( "system", "System" ), mApp->i18n( "light", "Light" ),
|
|
mApp->i18n( "dark", "Dark" ) },
|
|
[this] {
|
|
switch ( mApp->getUIColorScheme() ) {
|
|
case ColorSchemeExtPreference::System:
|
|
return size_t{ 0 };
|
|
case ColorSchemeExtPreference::Light:
|
|
return size_t{ 1 };
|
|
default:
|
|
return size_t{ 2 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr ColorSchemeExtPreference values[] = { ColorSchemeExtPreference::System,
|
|
ColorSchemeExtPreference::Light,
|
|
ColorSchemeExtPreference::Dark };
|
|
mApp->setUIColorSchemeFromUserInteraction( values[std::min( selected, size_t{ 2 } )] );
|
|
},
|
|
{ mApp->i18n(
|
|
"prefers_color_scheme_system_tooltip",
|
|
"System options will try to pick the system-wide currently preferred color scheme." ),
|
|
mApp->i18n( "prefers_color_scheme_light_tooltip",
|
|
"Always use the light interface color scheme." ),
|
|
mApp->i18n( "prefers_color_scheme_dark_tooltip",
|
|
"Always use the dark interface color scheme." ) } );
|
|
std::vector<String> themeNames{ mApp->i18n( "default_theme", "Default Theme" ),
|
|
mApp->i18n( "syntax_color_scheme", "Syntax Color Scheme" ) };
|
|
std::vector<std::string> themeIds{ "default_theme", "syntax_color_scheme" };
|
|
for ( const auto& file :
|
|
FileSystem::filesInfoGetInPath( mApp->getThemesPath(), true, true, true ) ) {
|
|
if ( file.getExtension() != "css" )
|
|
continue;
|
|
auto name = FileSystem::fileRemoveExtension( file.getFileName() );
|
|
themeNames.emplace_back( name );
|
|
themeIds.emplace_back( std::move( name ) );
|
|
}
|
|
auto currentTheme =
|
|
mApp->getConfig().ui.theme.empty() ? "default_theme" : mApp->getConfig().ui.theme;
|
|
auto selectedTheme = std::find( themeIds.begin(), themeIds.end(), currentTheme );
|
|
const size_t selectedThemeIndex = selectedTheme == themeIds.end()
|
|
? 0
|
|
: static_cast<size_t>( selectedTheme - themeIds.begin() );
|
|
addChoice(
|
|
panel,
|
|
{ "uiTheme", "appearance.theme", mApp->i18n( "ui_thene", "UI Theme" ),
|
|
mApp->i18n( "ui_theme_desc",
|
|
"Choose the stylesheet used by the application interface." ) },
|
|
themeNames, [selectedThemeIndex] { return selectedThemeIndex; },
|
|
[this, themeIds = std::move( themeIds )]( size_t selected ) {
|
|
const auto& id = themeIds[std::min( selected, themeIds.size() - 1 )];
|
|
mApp->getConfig().ui.theme = id == "default_theme" ? "" : id;
|
|
mApp->setTheme(
|
|
id == "default_theme"
|
|
? mApp->getDefaultThemePath()
|
|
: ( id == "syntax_color_scheme" ? id : mApp->getThemesPath() + id + ".css" ) );
|
|
} );
|
|
std::map<std::string, std::string> languages;
|
|
for ( const auto& file :
|
|
FileSystem::filesInfoGetInPath( mApp->geti18nPath(), false, true, false, true ) ) {
|
|
if ( file.getExtension() != "xml" )
|
|
continue;
|
|
auto id = FileSystem::fileRemoveExtension( file.getFileName() );
|
|
std::string data;
|
|
FileSystem::fileGet( file.getFilepath(), data );
|
|
LuaPattern pattern( "title=\"(.-)\"" );
|
|
PatternMatcher::Range matches[2];
|
|
if ( pattern.matches( data, matches ) )
|
|
languages[data.substr( matches[1].start, matches[1].end - matches[1].start )] = id;
|
|
else
|
|
languages[id] = id;
|
|
}
|
|
std::vector<String> languageNames;
|
|
std::vector<std::string> languageIds;
|
|
for ( const auto& [name, id] : languages ) {
|
|
languageNames.emplace_back( name );
|
|
languageIds.emplace_back( id );
|
|
}
|
|
auto currentLanguage =
|
|
mApp->getConfig().ui.language.empty() ? "en" : mApp->getConfig().ui.language;
|
|
auto selectedLanguage = std::find( languageIds.begin(), languageIds.end(), currentLanguage );
|
|
const size_t selectedLanguageIndex =
|
|
selectedLanguage == languageIds.end()
|
|
? 0
|
|
: static_cast<size_t>( selectedLanguage - languageIds.begin() );
|
|
if ( !languageIds.empty() )
|
|
addChoice(
|
|
panel,
|
|
{ "uiLanguage", "appearance.theme", mApp->i18n( "ui_language", "UI Language" ),
|
|
mApp->i18n( "ui_language_desc",
|
|
"Choose the interface language. Restart required." ) },
|
|
languageNames, [selectedLanguageIndex] { return selectedLanguageIndex; },
|
|
[this, languageIds = std::move( languageIds )]( size_t selected ) {
|
|
mApp->getConfig().ui.language =
|
|
languageIds[std::min( selected, languageIds.size() - 1 )];
|
|
} );
|
|
|
|
addCategory( panel, "appearance.fonts", mApp->i18n( "appearance", "Appearance" ),
|
|
mApp->i18n( "fonts_and_scale", "Fonts & Scale" ) );
|
|
addAction(
|
|
panel,
|
|
{ "uiFont", "appearance.fonts",
|
|
mApp->i18n( "ui_font_and_size_ellipsis", "UI Font & Size..." ),
|
|
mApp->i18n( "ui_font_desc", "Choose the proportional font used by the interface." ) },
|
|
mApp->i18n( "choose_font", "Choose Font..." ),
|
|
[this] { mApp->runCommand( "sans-serif-font" ); } );
|
|
addAction(
|
|
panel,
|
|
{ "editorFont", "appearance.fonts",
|
|
mApp->i18n( "editor_font_and_size_ellipsis", "Editor Font & Size..." ),
|
|
mApp->i18n( "editor_font_desc", "Choose the monospace font used by code editors." ) },
|
|
mApp->i18n( "choose_font", "Choose Font..." ),
|
|
[this] { mApp->runCommand( "editor-font" ); } );
|
|
addAction(
|
|
panel,
|
|
{ "terminalFont", "appearance.fonts",
|
|
mApp->i18n( "terminal_font_and_size_ellipsis", "Terminal Font & Size..." ),
|
|
mApp->i18n( "terminal_font_desc", "Choose the monospace font used by terminals." ) },
|
|
mApp->i18n( "choose_font", "Choose Font..." ),
|
|
[this] { mApp->runCommand( "terminal-font" ); } );
|
|
addAction( panel,
|
|
{ "fallbackFont", "appearance.fonts",
|
|
mApp->i18n( "fallback_font_ellipsis", "Fallback Font..." ),
|
|
mApp->i18n( "fallback_font_desc", "Choose the font used for missing glyphs." ) },
|
|
mApp->i18n( "choose_font", "Choose Font..." ),
|
|
[this] { mApp->runCommand( "fallback-font" ); } );
|
|
addAction(
|
|
panel,
|
|
{ "uiFontSize", "appearance.fonts", mApp->i18n( "ui_font_size", "UI Font Size" ),
|
|
mApp->i18n( "ui_font_size_desc", "Set the font size used by the application UI." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setUIFontSize(); } );
|
|
addAction(
|
|
panel,
|
|
{ "panelFontSize", "appearance.fonts",
|
|
mApp->i18n( "ui_panel_font_size", "Panel Font Size" ),
|
|
mApp->i18n( "ui_panel_font_size_desc", "Set the font size used by side panels." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setUIPanelFontSize(); } );
|
|
addAction( panel,
|
|
{ "editorFontSize", "appearance.fonts",
|
|
mApp->i18n( "editor_font_size", "Editor Font Size" ),
|
|
mApp->i18n( "editor_font_size_desc", "Set the default code editor font size." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setEditorFontSize(); } );
|
|
addAction( panel,
|
|
{ "terminalFontSize", "appearance.fonts",
|
|
mApp->i18n( "terminal_font_size", "Terminal Font Size" ),
|
|
mApp->i18n( "terminal_font_size_desc",
|
|
"Set the default integrated terminal font size." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setTerminalFontSize(); } );
|
|
addAction( panel,
|
|
{ "uiScaleFactor", "appearance.fonts",
|
|
mApp->i18n( "ui_scale_factor", "UI Scale Factor" ),
|
|
mApp->i18n( "ui_scale_factor_desc", "Scale the complete user interface." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setUIScaleFactor(); } );
|
|
addBool( panel,
|
|
{ "editorFontInInputFields", "appearance.fonts",
|
|
mApp->i18n( "editor_font_in_input_fields", "Editor Font in Input Fields" ),
|
|
mApp->i18n( "editor_font_in_input_fields_desc",
|
|
"Use the editor font for text input controls." ) },
|
|
&mApp->getConfig().ui.editorFontInInputFields );
|
|
addChoice(
|
|
panel,
|
|
{ "fontHinting", "appearance.fonts", mApp->i18n( "ui_font_hint", "Font Hinting" ),
|
|
mApp->i18n( "ui_font_hint_desc", "Control glyph alignment to the pixel grid." ) },
|
|
{ mApp->i18n( "none", "None" ), mApp->i18n( "slight", "Slight" ),
|
|
mApp->i18n( "full", "Full" ) },
|
|
[this] { return static_cast<size_t>( mApp->getConfig().ui.fontHinting ); },
|
|
[this]( size_t selected ) {
|
|
static constexpr FontHinting values[] = { FontHinting::None, FontHinting::Slight,
|
|
FontHinting::Full };
|
|
auto value = values[std::min( selected, size_t{ 2 } )];
|
|
mApp->getConfig().ui.fontHinting = value;
|
|
defaultResourceScope().getFontService().setHinting( value );
|
|
mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, []( UIWidget* widget ) {
|
|
widget->asType<UITerminal>()->syncFontRenderingConfig();
|
|
} );
|
|
} );
|
|
addChoice(
|
|
panel,
|
|
{ "fontAntialiasing", "appearance.fonts",
|
|
mApp->i18n( "ui_font_antialiasing", "Font Anti-Aliasing" ),
|
|
mApp->i18n( "ui_font_antialiasing_desc", "Choose how glyph edges are smoothed." ) },
|
|
{ mApp->i18n( "none", "None" ), mApp->i18n( "grayscale", "Grayscale" ),
|
|
mApp->i18n( "subpixel", "Subpixel" ) },
|
|
[this] { return static_cast<size_t>( mApp->getConfig().ui.fontAntialiasing ); },
|
|
[this]( size_t selected ) {
|
|
static constexpr FontAntialiasing values[] = {
|
|
FontAntialiasing::None, FontAntialiasing::Grayscale, FontAntialiasing::Subpixel };
|
|
auto value = values[std::min( selected, size_t{ 2 } )];
|
|
mApp->getConfig().ui.fontAntialiasing = value;
|
|
defaultResourceScope().getFontService().setAntialiasing( value );
|
|
mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, []( UIWidget* widget ) {
|
|
widget->asType<UITerminal>()->syncFontRenderingConfig();
|
|
} );
|
|
} );
|
|
auto addFontFeature = [this, &panel]( std::string id, const char* nameKey, const char* name,
|
|
const char* descriptionKey, const char* description,
|
|
Uint32 feature, bool editorFeature ) {
|
|
addBool(
|
|
panel,
|
|
{ std::move( id ), "appearance.fonts", mApp->i18n( nameKey, name ),
|
|
mApp->i18n( descriptionKey, description ) },
|
|
[this, feature, editorFeature] {
|
|
const Uint32 features = editorFeature ? mApp->getConfig().editor.fontFeatures
|
|
: mApp->getConfig().ui.fontFeatures;
|
|
return ( features & feature ) != 0;
|
|
},
|
|
[this, feature, editorFeature]( bool enabled ) {
|
|
Uint32& features = editorFeature ? mApp->getConfig().editor.fontFeatures
|
|
: mApp->getConfig().ui.fontFeatures;
|
|
if ( enabled )
|
|
features |= feature;
|
|
else
|
|
features &= ~feature;
|
|
if ( editorFeature ) {
|
|
mApp->getSplitter()->forEachEditor( [features]( UICodeEditor* editor ) {
|
|
editor->setLigatureFeatures( features );
|
|
} );
|
|
} else {
|
|
mApp->getUISceneNode()->setDefaultTextHints( features );
|
|
}
|
|
} );
|
|
};
|
|
for ( bool editorFeature : { false, true } ) {
|
|
const std::string prefix = editorFeature ? "editor" : "ui";
|
|
addFontFeature( prefix + "StandardLigatures", "standard_ligatures",
|
|
"Standard Ligatures (liga)", "standard_ligatures_desc",
|
|
"Typographic combinations such as fi, fl, and ffi, depending on the font.",
|
|
TextHints::StandardLigatures, editorFeature );
|
|
addFontFeature( prefix + "ContextualAlternates", "contextual_alternates",
|
|
"Contextual Alternates (calt)", "contextual_alternates_desc",
|
|
"Context-dependent alternatives, including many programming ligatures.",
|
|
TextHints::ContextualAlternates, editorFeature );
|
|
addFontFeature( prefix + "ContextualLigatures", "contextual_ligatures",
|
|
"Contextual Ligatures (clig)", "contextual_ligatures_desc",
|
|
"Ligatures applied in specific contexts to improve readability.",
|
|
TextHints::ContextualLigatures, editorFeature );
|
|
addFontFeature( prefix + "DiscretionaryLigatures", "discretionary_ligatures",
|
|
"Discretionary Ligatures (dlig)", "discretionary_ligatures_desc",
|
|
"Optional decorative or stylistic ligatures provided by the font.",
|
|
TextHints::DiscretionaryLigatures, editorFeature );
|
|
}
|
|
|
|
addCategory( panel, "editor.advanced", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "advanced", "Advanced" ) );
|
|
addAction( panel,
|
|
{ "lineSpacing", "editor.advanced", mApp->i18n( "line_spacing", "Line Spacing" ),
|
|
mApp->i18n( "line_spacing_desc",
|
|
"Set additional vertical spacing between editor lines." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setLineSpacing(); } );
|
|
addAction( panel,
|
|
{ "cursorBlinkingTime", "editor.advanced",
|
|
mApp->i18n( "cursor_blinking_time", "Cursor Blinking Time" ),
|
|
mApp->i18n( "cursor_blinking_time_desc", "Set the text cursor blink interval." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setCursorBlinkingTime(); } );
|
|
addAction( panel,
|
|
{ "indentTabCharacter", "editor.advanced",
|
|
mApp->i18n( "indent_tab_character", "Indent Tab Character" ),
|
|
mApp->i18n( "indent_tab_character_desc",
|
|
"Choose the character inserted when indenting with Tab." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setIndentTabCharacter(); } );
|
|
addChoice(
|
|
panel,
|
|
{ "indentTabAlignment", "editor.advanced",
|
|
mApp->i18n( "indent_tab_alignment", "Indent Tab Alignment" ),
|
|
mApp->i18n( "indent_tab_alignment_desc",
|
|
"Align text within the visual width of indentation tabs." ) },
|
|
{ mApp->i18n( "left", "Left" ), mApp->i18n( "center", "Center" ),
|
|
mApp->i18n( "right", "Right" ) },
|
|
[this] {
|
|
return mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Left
|
|
? size_t{ 0 }
|
|
: ( mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Center
|
|
? size_t{ 1 }
|
|
: size_t{ 2 } );
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr CharacterAlignment values[] = {
|
|
CharacterAlignment::Left, CharacterAlignment::Center, CharacterAlignment::Right };
|
|
auto value = values[std::min( selected, size_t{ 2 } )];
|
|
mApp->getConfig().editor.tabIndentAlignment = value;
|
|
mApp->getSplitter()->forEachEditor(
|
|
[value]( UICodeEditor* editor ) { editor->setTabIndentAlignment( value ); } );
|
|
} );
|
|
addAction( panel,
|
|
{ "foldRefreshFrequency", "editor.advanced",
|
|
mApp->i18n( "folds_refresh_freq", "Folds Refresh Frequency" ),
|
|
mApp->i18n( "folds_refresh_freq_desc",
|
|
"Set how frequently code folding ranges are recalculated." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setFoldRefreshFreq(); } );
|
|
addAction( panel,
|
|
{ "tabOutCharacters", "editor.advanced",
|
|
mApp->i18n( "set_tab_out_characters", "Set Tab Out Characters" ),
|
|
mApp->i18n( "set_tab_out_characters_message",
|
|
"Set the characters the cursor can move past when pressing Tab." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setTabOutChars(); } );
|
|
|
|
addCategory( panel, "window.screenshots", mApp->i18n( "window", "Window" ),
|
|
mApp->i18n( "screenshots", "Screenshots" ) );
|
|
addAction( panel,
|
|
{ "screenshotSavePath", "window.screenshots",
|
|
mApp->i18n( "set_screenshot_save_path", "Screenshot Save Path" ),
|
|
mApp->i18n( "screenshot_save_path_desc", "Choose where screenshots are saved." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setScreenshotSavePath(); } );
|
|
addAction( panel,
|
|
{ "screenshotFilenamePattern", "window.screenshots",
|
|
mApp->i18n( "set_screenshot_filename_pattern", "Screenshot Filename Pattern" ),
|
|
mApp->i18n( "screenshot_filename_pattern_desc",
|
|
"Configure the timestamp-based screenshot filename pattern." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setScreenshotFilenamePattern(); } );
|
|
addAction(
|
|
panel,
|
|
{ "screenshotSaveFormat", "window.screenshots",
|
|
mApp->i18n( "set_screenshot_save_format", "Screenshot Save Format" ),
|
|
mApp->i18n( "screenshot_save_format_desc", "Choose the image format for screenshots." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->getSettingsActions()->setScreenshotSaveFormat(); } );
|
|
|
|
addCategory( panel, "window.renderer", mApp->i18n( "window", "Window" ),
|
|
mApp->i18n( "renderer", "Renderer" ) );
|
|
addBool(
|
|
panel,
|
|
{ "vsync", "window.renderer", mApp->i18n( "vsync", "VSync" ),
|
|
mApp->i18n( "vsync_desc",
|
|
"Synchronize rendering with the display refresh rate. Restart required." ) },
|
|
&mApp->getConfig().context.VSync, [this]( bool ) {
|
|
mApp->saveConfig();
|
|
mApp->getNotificationCenter()->addNotification(
|
|
mApp->i18n( "vsync_changed",
|
|
"Vsync configuration changed.\nRestart ecode to see the changes." )
|
|
.unescape() );
|
|
} );
|
|
addInteger( panel,
|
|
{ "frameRateLimit", "window.renderer",
|
|
mApp->i18n( "frame_rate_limit", "Frame Rate Limit" ),
|
|
mApp->i18n( "frame_rate_limit_desc",
|
|
"Limit rendered frames per second. Set 0 to disable." ) },
|
|
0, 1000, &mApp->getConfig().context.FrameRateLimit, [this]( int value ) {
|
|
mApp->saveConfig();
|
|
mApp->getWindow()->setFrameRateLimit( value );
|
|
mApp->getNotificationCenter()->addNotification(
|
|
mApp->i18n( "frame_rate_limit_applied", "Frame Rate Limit Applied" ) );
|
|
} );
|
|
std::vector<GraphicsLibraryVersion> rendererVersions =
|
|
Renderer::getAvailableGraphicsLibraryVersions();
|
|
std::vector<String> rendererVersionNames;
|
|
rendererVersionNames.reserve( rendererVersions.size() );
|
|
for ( const auto version : rendererVersions )
|
|
rendererVersionNames.emplace_back( Renderer::graphicsLibraryVersionToString( version ) );
|
|
auto selectedRendererVersion = std::find( rendererVersions.begin(), rendererVersions.end(),
|
|
mApp->getConfig().context.Version );
|
|
const size_t selectedRendererVersionIndex =
|
|
selectedRendererVersion == rendererVersions.end()
|
|
? 0
|
|
: static_cast<size_t>( selectedRendererVersion - rendererVersions.begin() );
|
|
if ( !rendererVersions.empty() )
|
|
addChoice(
|
|
panel,
|
|
{ "rendererVersion", "window.renderer",
|
|
mApp->i18n( "ui_renderer_version", "Renderer Version" ),
|
|
mApp->i18n( "ui_renderer_version_desc",
|
|
"Select the graphics API version used by ecode. Restart required." ) },
|
|
rendererVersionNames,
|
|
[selectedRendererVersionIndex] { return selectedRendererVersionIndex; },
|
|
[this, rendererVersions = std::move( rendererVersions )]( size_t selected ) {
|
|
mApp->getConfig().context.Version =
|
|
rendererVersions[std::min( selected, rendererVersions.size() - 1 )];
|
|
mApp->saveConfig();
|
|
mApp->getNotificationCenter()->addNotification(
|
|
mApp->i18n( "glversion_changed",
|
|
"Renderer version changed.\nRestart ecode to see the changes." )
|
|
.unescape() );
|
|
} );
|
|
addChoice(
|
|
panel,
|
|
{ "multisamples", "window.renderer",
|
|
mApp->i18n( "ui_multisamples_level", "Multisample Anti-Aliasing Level" ),
|
|
mApp->i18n( "ui_multisamples_level_desc",
|
|
"Set renderer multisampling. Restart required." ) },
|
|
{ "0", "2", "4", "8", "16" },
|
|
[this] {
|
|
static constexpr Uint32 values[] = { 0, 2, 4, 8, 16 };
|
|
auto found = std::find( std::begin( values ), std::end( values ),
|
|
mApp->getConfig().context.Multisamples );
|
|
return found == std::end( values ) ? size_t{ 0 }
|
|
: size_t( found - std::begin( values ) );
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr Uint32 values[] = { 0, 2, 4, 8, 16 };
|
|
mApp->getConfig().context.Multisamples = values[std::min( selected, size_t{ 4 } )];
|
|
mApp->saveConfig();
|
|
mApp->getNotificationCenter()->addNotification(
|
|
mApp->i18n( "multisamples_changed", "Multisample Anti-Aliasing Level "
|
|
"applied.\nRestart ecode to see the changes." )
|
|
.unescape() );
|
|
},
|
|
{ mApp->i18n( "multisamples_disabled_desc", "Disable multisample anti-aliasing." ),
|
|
mApp->i18n( "multisamples_2x_desc", "Use 2x multisample anti-aliasing." ),
|
|
mApp->i18n( "multisamples_4x_desc", "Use 4x multisample anti-aliasing." ),
|
|
mApp->i18n( "multisamples_8x_desc", "Use 8x multisample anti-aliasing." ),
|
|
mApp->i18n( "multisamples_16x_desc", "Use 16x multisample anti-aliasing." ) } );
|
|
|
|
addCategory( panel, "terminal.behavior", mApp->i18n( "terminal", "Terminal" ),
|
|
mApp->i18n( "behavior", "Behavior" ) );
|
|
addChoice(
|
|
panel,
|
|
{ "newTerminalOrientation", "terminal.behavior",
|
|
mApp->i18n( "new_terminal_behavior", "New Terminal Behavior" ),
|
|
mApp->i18n( "new_terminal_behavior_desc",
|
|
"Choose where newly created terminal sessions are opened." ) },
|
|
{ mApp->i18n( "open_in_same_tabbar", "Open in Current Tab Bar" ),
|
|
mApp->i18n( "open_in_vertical_split", "Open in New Vertical Split" ),
|
|
mApp->i18n( "open_in_horizontal_split", "Open in New Horizontal Split" ),
|
|
mApp->i18n( "open_in_statusbar_panel", "Open in Status Bar Panel" ) },
|
|
[this] {
|
|
switch ( mApp->getConfig().term.newTerminalOrientation ) {
|
|
case NewTerminalOrientation::Vertical:
|
|
return size_t{ 1 };
|
|
case NewTerminalOrientation::Horizontal:
|
|
return size_t{ 2 };
|
|
case NewTerminalOrientation::StatusBarPanel:
|
|
return size_t{ 3 };
|
|
default:
|
|
return size_t{ 0 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr NewTerminalOrientation::Orientation orientations[] = {
|
|
NewTerminalOrientation::Same, NewTerminalOrientation::Vertical,
|
|
NewTerminalOrientation::Horizontal, NewTerminalOrientation::StatusBarPanel };
|
|
mApp->getConfig().term.newTerminalOrientation =
|
|
orientations[std::min( selected, size_t{ 3 } )];
|
|
} );
|
|
addBool( panel,
|
|
{ "terminalExclusiveMode", "terminal.behavior",
|
|
mApp->i18n( "enable_exclusive_mode_by_default", "Enable Exclusive Mode by Default" ),
|
|
mApp->i18n( "enable_exclusive_mode_by_default_tooltip",
|
|
"Disable global keybindings in newly created terminals." ) },
|
|
&mApp->getConfig().term.exclusiveMode );
|
|
addBool( panel,
|
|
{ "closeTerminalTabOnExit", "terminal.behavior",
|
|
mApp->i18n( "close_terminal_tab_on_exit", "Close Terminal Tab on Exit" ),
|
|
mApp->i18n( "close_terminal_tab_on_exit_tooltip",
|
|
"Close a terminal tab when its main process exits." ) },
|
|
&mApp->getConfig().term.closeTerminalTabOnExit, [this]( bool value ) {
|
|
mApp->getSplitter()->forEachWidgetType(
|
|
UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
|
|
widget->asType<UITerminal>()->getTerm()->setKeepAlive( !value );
|
|
} );
|
|
} );
|
|
addBool( panel,
|
|
{ "warnBeforeClosingTerminal", "terminal.behavior",
|
|
mApp->i18n( "warn_before_closing_tab", "Warn Before Closing Tab" ),
|
|
mApp->i18n( "warn_before_closing_tab_tooltip",
|
|
"Ask before closing a terminal while a program is running." ) },
|
|
&mApp->getConfig().term.warnBeforeClosingTab );
|
|
addAction( panel,
|
|
{ "terminalShell", "terminal.behavior",
|
|
mApp->i18n( "configure_terminal_shell", "Configure Terminal Shell" ),
|
|
mApp->i18n( "configure_terminal_shell_desc",
|
|
"Set the shell executable and command-line arguments." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->runCommand( "configure-terminal-shell" ); } );
|
|
addAction( panel,
|
|
{ "terminalScrollback", "terminal.behavior",
|
|
mApp->i18n( "configure_terminal_scrollback", "Configure Terminal Scrollback" ),
|
|
mApp->i18n( "configure_terminal_scrollback_desc",
|
|
"Set the number of terminal history lines retained." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->runCommand( "configure-terminal-scrollback" ); } );
|
|
addAction( panel,
|
|
{ "terminalWorkingDirectory", "terminal.behavior",
|
|
mApp->i18n( "configure_terminal_working_dir",
|
|
"Configure Terminal Default Working Directory" ),
|
|
mApp->i18n( "configure_terminal_working_dir_desc",
|
|
"Choose the working directory used by new terminals." ) },
|
|
mApp->i18n( "configure", "Configure..." ),
|
|
[this] { mApp->runCommand( "configure-terminal-working-dir" ); } );
|
|
|
|
addCategory( panel, "terminal.appearance", mApp->i18n( "terminal", "Terminal" ),
|
|
mApp->i18n( "appearance", "Appearance" ) );
|
|
std::vector<String> terminalSchemeNames;
|
|
std::vector<std::string> terminalSchemeIds;
|
|
for ( const auto& [name, scheme] : mApp->getTerminalManager()->getTerminalColorSchemes() ) {
|
|
terminalSchemeNames.emplace_back( name );
|
|
terminalSchemeIds.emplace_back( name );
|
|
}
|
|
auto selectedTerminalScheme =
|
|
std::find( terminalSchemeNames.begin(), terminalSchemeNames.end(),
|
|
String( mApp->getTerminalManager()->getTerminalCurrentColorScheme() ) );
|
|
const size_t selectedTerminalSchemeIndex =
|
|
selectedTerminalScheme == terminalSchemeNames.end()
|
|
? 0
|
|
: static_cast<size_t>( selectedTerminalScheme - terminalSchemeNames.begin() );
|
|
if ( !terminalSchemeNames.empty() )
|
|
addChoice(
|
|
panel,
|
|
{ "terminalColorScheme", "terminal.appearance",
|
|
mApp->i18n( "terminal_color_scheme", "Terminal Color Scheme" ),
|
|
mApp->i18n( "terminal_color_scheme_desc",
|
|
"Choose the colors used by integrated terminals." ) },
|
|
terminalSchemeNames,
|
|
[selectedTerminalSchemeIndex] { return selectedTerminalSchemeIndex; },
|
|
[this, terminalSchemeIds = std::move( terminalSchemeIds )]( size_t selected ) {
|
|
mApp->getTerminalManager()->setTerminalColorScheme(
|
|
terminalSchemeIds[std::min( selected, terminalSchemeIds.size() - 1 )] );
|
|
} );
|
|
addChoice(
|
|
panel,
|
|
{ "terminalScrollbarType", "terminal.appearance",
|
|
mApp->i18n( "scrollbar_type", "Scrollbar Type" ),
|
|
mApp->i18n( "scrollbar_type_desc",
|
|
"Place the terminal scrollbar over or outside its content." ) },
|
|
{ mApp->i18n( "overlay", "Overlay" ), mApp->i18n( "outside", "Outside" ) },
|
|
[this] { return mApp->getConfig().term.scrollBarType == ScrollViewType::Overlay ? 0 : 1; },
|
|
[this]( size_t selected ) {
|
|
auto value = selected == 0 ? ScrollViewType::Overlay : ScrollViewType::Outside;
|
|
mApp->getConfig().term.scrollBarType = value;
|
|
mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
|
|
widget->asType<UITerminal>()->setScrollViewType( value );
|
|
} );
|
|
},
|
|
{ mApp->i18n( "scroll_overlay_tooltip", "Scrollbar appears over content." ),
|
|
mApp->i18n( "scroll_outside_tooltip",
|
|
"Scrollbar has its own space, never covers content." ) } );
|
|
addChoice(
|
|
panel,
|
|
{ "terminalCursorStyle", "terminal.appearance",
|
|
mApp->i18n( "cursor_style", "Cursor Style" ),
|
|
mApp->i18n( "cursor_style_desc", "Choose the terminal cursor shape and animation." ) },
|
|
{ mApp->i18n( "blinking_block", "Blinking Block" ),
|
|
mApp->i18n( "steady_block", "Steady Block" ),
|
|
mApp->i18n( "blink_underline", "Blink Underline" ),
|
|
mApp->i18n( "steady_underline", "Steady Underline" ),
|
|
mApp->i18n( "blink_bar", "Blink Bar" ), mApp->i18n( "steady_bar", "Steady Bar" ) },
|
|
[this] {
|
|
static constexpr TerminalCursorMode modes[] = {
|
|
TerminalCursorMode::BlinkingBlock, TerminalCursorMode::SteadyBlock,
|
|
TerminalCursorMode::BlinkUnderline, TerminalCursorMode::SteadyUnderline,
|
|
TerminalCursorMode::BlinkBar, TerminalCursorMode::SteadyBar };
|
|
auto found = std::find( std::begin( modes ), std::end( modes ),
|
|
mApp->getConfig().term.cursorStyle );
|
|
return found == std::end( modes ) ? size_t{ 0 } : size_t( found - std::begin( modes ) );
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr TerminalCursorMode modes[] = {
|
|
TerminalCursorMode::BlinkingBlock, TerminalCursorMode::SteadyBlock,
|
|
TerminalCursorMode::BlinkUnderline, TerminalCursorMode::SteadyUnderline,
|
|
TerminalCursorMode::BlinkBar, TerminalCursorMode::SteadyBar };
|
|
auto value = modes[std::min( selected, size_t{ 5 } )];
|
|
mApp->getConfig().term.cursorStyle = value;
|
|
mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
|
|
widget->asType<UITerminal>()->getTerm()->setCursorMode( value );
|
|
} );
|
|
} );
|
|
addChoice(
|
|
panel,
|
|
{ "terminalScrollbarMode", "terminal.appearance",
|
|
mApp->i18n( "scrollbar_mode", "Scrollbar Mode" ),
|
|
mApp->i18n( "scrollbar_mode_desc", "Control when the terminal scrollbar is visible." ) },
|
|
{ mApp->i18n( "auto", "Auto" ), mApp->i18n( "always_visible", "Always Visible" ),
|
|
mApp->i18n( "always_hidden", "Always Hidden" ) },
|
|
[this] {
|
|
return mApp->getConfig().term.scrollBarMode == ScrollBarMode::Auto
|
|
? 0
|
|
: ( mApp->getConfig().term.scrollBarMode == ScrollBarMode::AlwaysOn ? 1
|
|
: 2 );
|
|
},
|
|
[this]( size_t selected ) {
|
|
auto value = selected == 0 ? ScrollBarMode::Auto
|
|
: ( selected == 1 ? ScrollBarMode::AlwaysOn
|
|
: ScrollBarMode::AlwaysOff );
|
|
mApp->getConfig().term.scrollBarMode = value;
|
|
mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
|
|
widget->asType<UITerminal>()->setVerticalScrollMode( value );
|
|
} );
|
|
},
|
|
{ mApp->i18n( "scrollbar_mode_auto_tooltip",
|
|
"With an overlay scrollbar, show it when the mouse moves over the terminal. "
|
|
"With an outside scrollbar, show it when there is a scrollable area." ),
|
|
mApp->i18n( "scrollbar_mode_always_visible_tooltip",
|
|
"Keep the terminal scrollbar visible." ),
|
|
mApp->i18n( "scrollbar_mode_always_hidden_tooltip",
|
|
"Keep the terminal scrollbar hidden." ) } );
|
|
}
|
|
|
|
void SettingsPanel::addProjectSettings( PanelState& panel ) {
|
|
addCategory( panel, "editor.document", mApp->i18n( "editor", "Editor" ),
|
|
mApp->i18n( "document", "Document" ) );
|
|
addBool( panel,
|
|
{ "useGlobalSettings", "editor.document",
|
|
mApp->i18n( "use_global_settings", "Use Global Settings" ),
|
|
mApp->i18n( "use_global_settings_desc",
|
|
"Inherit document defaults from the user settings." ) },
|
|
&mApp->getProjectConfig().useGlobalSettings );
|
|
auto addProjectBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
|
|
const char* descriptionKey, const char* description,
|
|
bool DocumentConfig::* member ) {
|
|
addBool( panel,
|
|
{ std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
|
|
mApp->i18n( descriptionKey, description ) },
|
|
&( mApp->getProjectConfig().doc.*member ) );
|
|
};
|
|
addProjectBool( "trimTrailingWhitespaces", "trim_trailing_whitespaces",
|
|
"Trim Trailing Whitespaces", "trim_trailing_whitespaces_desc",
|
|
"Remove trailing whitespace when saving files.",
|
|
&DocumentConfig::trimTrailingWhitespaces );
|
|
addProjectBool( "forceNewLineAtEndOfFile", "force_new_line_at_end_of_file",
|
|
"Force New Line at End of File", "force_new_line_at_end_of_file_desc",
|
|
"Ensure saved files end with a newline.",
|
|
&DocumentConfig::forceNewLineAtEndOfFile );
|
|
addProjectBool( "autoDetectIndentType", "auto_detect_indent_type_and_width",
|
|
"Auto Detect Indent Type & Width", "auto_detect_indent_type_desc",
|
|
"Detect indentation settings when opening each document.",
|
|
&DocumentConfig::autoDetectIndentType );
|
|
addChoice(
|
|
panel,
|
|
{ "autoIndent", "editor.document", mApp->i18n( "auto_indent", "Auto-Indent" ),
|
|
mApp->i18n( "auto_indent_tooltip", "Control indentation added after pressing Enter." ) },
|
|
{ mApp->i18n( "auto_indent_none", "None" ),
|
|
mApp->i18n( "auto_indent_preserve", "Preserve" ),
|
|
mApp->i18n( "auto_indent_smart", "Smart" ) },
|
|
[this] { return static_cast<size_t>( mApp->getProjectConfig().doc.autoIndent ); },
|
|
[this]( size_t selected ) {
|
|
mApp->getProjectConfig().doc.autoIndent =
|
|
static_cast<TextDocument::AutoIndentConfig>( std::min( selected, size_t{ 2 } ) );
|
|
},
|
|
{ mApp->i18n( "auto_indent_none_desc", "No automatic indentation." ),
|
|
mApp->i18n( "auto_indent_preserve_desc",
|
|
"Preserve the indentation level of the previous line." ),
|
|
mApp->i18n( "auto_indent_smart_desc",
|
|
"Preserve indentation and indent between auto-closed brackets." ) } );
|
|
addProjectBool( "indentSpaces", "indent_spaces", "Indent Using Spaces", "indent_spaces_desc",
|
|
"Insert spaces instead of tab characters for indentation.",
|
|
&DocumentConfig::indentSpaces );
|
|
addProjectBool( "writeUnicodeBOM", "write_unicode_bom", "Write Unicode BOM",
|
|
"write_unicode_bom_desc", "Write a Unicode byte-order mark when saving.",
|
|
&DocumentConfig::writeUnicodeBOM );
|
|
addInteger( panel,
|
|
{ "indentWidth", "editor.document", mApp->i18n( "indent_width", "Indent Width" ),
|
|
mApp->i18n( "indent_width_desc",
|
|
"Number of columns inserted for one indentation level." ) },
|
|
1, 16, &mApp->getProjectConfig().doc.indentWidth );
|
|
addInteger(
|
|
panel,
|
|
{ "tabWidth", "editor.document", mApp->i18n( "tab_width", "Tab Width" ),
|
|
mApp->i18n( "tab_width_desc", "Number of columns used to display a tab character." ) },
|
|
1, 16, &mApp->getProjectConfig().doc.tabWidth );
|
|
addInteger( panel,
|
|
{ "lineBreakingColumn", "editor.document",
|
|
mApp->i18n( "line_breaking_column", "Line Breaking Column" ),
|
|
mApp->i18n( "line_breaking_column_desc",
|
|
"Column used for wrapping and the editor width guide." ) },
|
|
0, 1000, &mApp->getProjectConfig().doc.lineBreakingColumn );
|
|
addChoice(
|
|
panel,
|
|
{ "lineEndings", "editor.document", mApp->i18n( "line_endings", "Line Endings" ),
|
|
mApp->i18n( "line_endings_desc", "Default line-ending sequence for new files." ) },
|
|
{ "Windows (CR/LF)", "Unix (LF)", "Macintosh (CR)" },
|
|
[this] {
|
|
switch ( mApp->getProjectConfig().doc.lineEndings ) {
|
|
case TextFormat::LineEnding::CRLF:
|
|
return size_t{ 0 };
|
|
case TextFormat::LineEnding::CR:
|
|
return size_t{ 2 };
|
|
default:
|
|
return size_t{ 1 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
mApp->getProjectConfig().doc.lineEndings =
|
|
selected == 0
|
|
? TextFormat::LineEnding::CRLF
|
|
: ( selected == 2 ? TextFormat::LineEnding::CR : TextFormat::LineEnding::LF );
|
|
} );
|
|
|
|
addCategory( panel, "languages.file_associations", mApp->i18n( "languages", "Languages" ),
|
|
mApp->i18n( "file_associations", "File Associations" ) );
|
|
addChoice(
|
|
panel,
|
|
{ "hExtLanguageType", "languages.file_associations",
|
|
mApp->i18n( "treat_h_files_as", "Treat .h Files As" ),
|
|
mApp->i18n( "treat_h_files_as_desc",
|
|
"Choose the default language for ambiguous .h header files." ) },
|
|
{ mApp->i18n( "auto-detect", "Auto-Detect" ), "C", "C++", "Objective-C", "Objective-C++" },
|
|
[this] {
|
|
switch ( mApp->getProjectConfig().hExtLanguageType ) {
|
|
case HExtLanguageType::C:
|
|
return size_t{ 1 };
|
|
case HExtLanguageType::CPP:
|
|
return size_t{ 2 };
|
|
case HExtLanguageType::ObjectiveC:
|
|
return size_t{ 3 };
|
|
case HExtLanguageType::ObjectiveCPP:
|
|
return size_t{ 4 };
|
|
default:
|
|
return size_t{ 0 };
|
|
}
|
|
},
|
|
[this]( size_t selected ) {
|
|
static constexpr HExtLanguageType values[] = {
|
|
HExtLanguageType::AutoDetect, HExtLanguageType::C, HExtLanguageType::CPP,
|
|
HExtLanguageType::ObjectiveC, HExtLanguageType::ObjectiveCPP };
|
|
auto value = values[std::min( selected, size_t{ 4 } )];
|
|
mApp->getProjectConfig().hExtLanguageType = value;
|
|
mApp->getSplitter()->forEachEditor( [this, value]( UICodeEditor* editor ) {
|
|
auto& document = editor->getDocument();
|
|
document.setHExtLanguageType( value );
|
|
if ( document.getFileInfo().getExtension() == "h" ) {
|
|
editor->resetSyntaxDefinition();
|
|
}
|
|
} );
|
|
} );
|
|
}
|
|
|
|
void SettingsPanel::filter( PanelState& panel ) {
|
|
String query = panel.search ? panel.search->getText() : String{};
|
|
query.trim().toLower();
|
|
if ( !query.empty() ) {
|
|
panel.pageTitle->setText( mApp->i18n( "search_results", "Search Results" ) );
|
|
} else if ( auto title = panel.categoryTitles.find( panel.selectedCategory );
|
|
title != panel.categoryTitles.end() ) {
|
|
panel.pageTitle->setText( title->second );
|
|
}
|
|
UnorderedSet<std::string> matchingCategories;
|
|
const std::string queryUtf8 = query.toUtf8();
|
|
const bool aggregate = String::endsWith( panel.selectedCategory, ".*" );
|
|
const std::string aggregatePrefix =
|
|
aggregate ? panel.selectedCategory.substr( 0, panel.selectedCategory.size() - 1 )
|
|
: std::string{};
|
|
if ( !query.empty() ) {
|
|
for ( const auto& binding : panel.bindings ) {
|
|
if ( String::icontains( binding.name, query ) ||
|
|
String::icontains( binding.description, query ) ||
|
|
String::icontains( binding.id, queryUtf8 ) )
|
|
matchingCategories.insert( binding.category );
|
|
}
|
|
}
|
|
if ( queryUtf8 != panel.categoryFilter ) {
|
|
panel.categoryFilter = queryUtf8;
|
|
if ( auto model = std::static_pointer_cast<SettingsCategoryModel>( panel.categoryModel ) )
|
|
model->filter( queryUtf8, matchingCategories );
|
|
}
|
|
panel.categories->expandAll();
|
|
for ( auto& [category, heading] : panel.categoryHeadings )
|
|
heading->setVisible( query.empty() && aggregate &&
|
|
String::startsWith( category, aggregatePrefix ) );
|
|
for ( auto& binding : panel.bindings ) {
|
|
const auto categoryName = panel.categorySearchText.find( binding.category );
|
|
const bool categoryMatches = categoryName != panel.categorySearchText.end() &&
|
|
String::icontains( categoryName->second, query );
|
|
const bool matches =
|
|
query.empty()
|
|
? binding.category == panel.selectedCategory ||
|
|
( aggregate && String::startsWith( binding.category, aggregatePrefix ) )
|
|
: categoryMatches || String::icontains( binding.name, query ) ||
|
|
String::icontains( binding.description, query ) ||
|
|
String::icontains( binding.id, queryUtf8 );
|
|
binding.row->setVisible( matches );
|
|
}
|
|
}
|
|
|
|
} // namespace ecode
|