Serenity Operating System
at master 275 lines 12 kB view raw
1/* 2 * Copyright (c) 2018-2021, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "TerminalSettingsWidget.h" 8#include <AK/Assertions.h> 9#include <AK/JsonObject.h> 10#include <AK/QuickSort.h> 11#include <Applications/TerminalSettings/TerminalSettingsMainGML.h> 12#include <Applications/TerminalSettings/TerminalSettingsViewGML.h> 13#include <LibConfig/Client.h> 14#include <LibCore/DirIterator.h> 15#include <LibGUI/Application.h> 16#include <LibGUI/Button.h> 17#include <LibGUI/CheckBox.h> 18#include <LibGUI/FontPicker.h> 19#include <LibGUI/ItemListModel.h> 20#include <LibGUI/Label.h> 21#include <LibGUI/MessageBox.h> 22#include <LibGUI/OpacitySlider.h> 23#include <LibGUI/RadioButton.h> 24#include <LibGUI/SpinBox.h> 25#include <LibGUI/Widget.h> 26#include <LibGfx/Font/Font.h> 27#include <LibGfx/Font/FontDatabase.h> 28#include <LibKeyboard/CharacterMap.h> 29#include <LibVT/TerminalWidget.h> 30#include <spawn.h> 31 32TerminalSettingsMainWidget::TerminalSettingsMainWidget() 33{ 34 load_from_gml(terminal_settings_main_gml).release_value_but_fixme_should_propagate_errors(); 35 36 auto& beep_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("beep_bell_radio"); 37 auto& visual_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("visual_bell_radio"); 38 auto& no_bell_radio = *find_descendant_of_type_named<GUI::RadioButton>("no_bell_radio"); 39 40 m_bell_mode = parse_bell(Config::read_string("Terminal"sv, "Window"sv, "Bell"sv)); 41 m_original_bell_mode = m_bell_mode; 42 43 switch (m_bell_mode) { 44 case VT::TerminalWidget::BellMode::Visible: 45 visual_bell_radio.set_checked(true, GUI::AllowCallback::No); 46 break; 47 case VT::TerminalWidget::BellMode::AudibleBeep: 48 beep_bell_radio.set_checked(true, GUI::AllowCallback::No); 49 break; 50 case VT::TerminalWidget::BellMode::Disabled: 51 no_bell_radio.set_checked(true, GUI::AllowCallback::No); 52 break; 53 } 54 55 beep_bell_radio.on_checked = [this](bool) { 56 m_bell_mode = VT::TerminalWidget::BellMode::AudibleBeep; 57 Config::write_string("Terminal"sv, "Window"sv, "Bell"sv, stringify_bell(m_bell_mode)); 58 set_modified(true); 59 }; 60 visual_bell_radio.on_checked = [this](bool) { 61 m_bell_mode = VT::TerminalWidget::BellMode::Visible; 62 Config::write_string("Terminal"sv, "Window"sv, "Bell"sv, stringify_bell(m_bell_mode)); 63 set_modified(true); 64 }; 65 no_bell_radio.on_checked = [this](bool) { 66 m_bell_mode = VT::TerminalWidget::BellMode::Disabled; 67 Config::write_string("Terminal"sv, "Window"sv, "Bell"sv, stringify_bell(m_bell_mode)); 68 set_modified(true); 69 }; 70 71 m_confirm_close = Config::read_bool("Terminal"sv, "Terminal"sv, "ConfirmClose"sv, true); 72 m_orignal_confirm_close = m_confirm_close; 73 auto& confirm_close_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("terminal_confirm_close"); 74 confirm_close_checkbox.on_checked = [&](bool confirm_close) { 75 m_confirm_close = confirm_close; 76 Config::write_bool("Terminal"sv, "Terminal"sv, "ConfirmClose"sv, confirm_close); 77 set_modified(true); 78 }; 79 confirm_close_checkbox.set_checked(m_confirm_close, GUI::AllowCallback::No); 80} 81 82TerminalSettingsViewWidget::TerminalSettingsViewWidget() 83{ 84 load_from_gml(terminal_settings_view_gml).release_value_but_fixme_should_propagate_errors(); 85 86 auto& slider = *find_descendant_of_type_named<GUI::HorizontalOpacitySlider>("background_opacity_slider"); 87 m_opacity = Config::read_i32("Terminal"sv, "Window"sv, "Opacity"sv); 88 m_original_opacity = m_opacity; 89 slider.set_value(m_opacity); 90 slider.on_change = [this](int value) { 91 m_opacity = value; 92 Config::write_i32("Terminal"sv, "Window"sv, "Opacity"sv, static_cast<i32>(m_opacity)); 93 set_modified(true); 94 }; 95 96 auto& font_button = *find_descendant_of_type_named<GUI::Button>("terminal_font_button"); 97 auto& font_text = *find_descendant_of_type_named<GUI::Label>("terminal_font_label"); 98 auto font_name = Config::read_string("Terminal"sv, "Text"sv, "Font"sv); 99 if (font_name.is_empty()) 100 m_font = Gfx::FontDatabase::the().default_fixed_width_font(); 101 else 102 m_font = Gfx::FontDatabase::the().get_by_name(font_name); 103 m_original_font = m_font; 104 font_text.set_text(m_font->human_readable_name()); 105 font_text.set_font(m_font); 106 font_button.on_click = [&](auto) { 107 auto picker = GUI::FontPicker::construct(window(), m_font.ptr(), true); 108 if (picker->exec() == GUI::Dialog::ExecResult::OK) { 109 m_font = picker->font(); 110 font_text.set_text(m_font->human_readable_name()); 111 font_text.set_font(m_font); 112 Config::write_string("Terminal"sv, "Text"sv, "Font"sv, m_font->qualified_name()); 113 set_modified(true); 114 } 115 }; 116 117 auto& font_selection = *find_descendant_of_type_named<GUI::Widget>("terminal_font_selection"); 118 auto& use_default_font_button = *find_descendant_of_type_named<GUI::CheckBox>("terminal_font_defaulted"); 119 use_default_font_button.on_checked = [&, font_name](bool use_default_font) { 120 if (use_default_font) { 121 font_selection.set_enabled(false); 122 m_font = Gfx::FontDatabase::the().default_fixed_width_font(); 123 font_text.set_text(m_font->human_readable_name()); 124 font_text.set_font(m_font); 125 Config::write_string("Terminal"sv, "Text"sv, "Font"sv, m_font->qualified_name()); 126 } else { 127 font_selection.set_enabled(true); 128 m_font = font_name.is_empty() 129 ? Gfx::FontDatabase::the().default_fixed_width_font() 130 : Gfx::FontDatabase::the().get_by_name(font_name); 131 Config::write_string("Terminal"sv, "Text"sv, "Font"sv, m_font->qualified_name()); 132 } 133 set_modified(true); 134 }; 135 // The "use default font" setting is not stored itself - we automatically set it if the actually present font is the default, 136 // whether that was filled in by the above defaulting code or by the user. 137 use_default_font_button.set_checked(m_font == Gfx::FontDatabase::the().default_fixed_width_font(), GUI::AllowCallback::No); 138 font_selection.set_enabled(!use_default_font_button.is_checked()); 139 140 auto& terminal_cursor_block = *find_descendant_of_type_named<GUI::RadioButton>("terminal_cursor_block"); 141 auto& terminal_cursor_underline = *find_descendant_of_type_named<GUI::RadioButton>("terminal_cursor_underline"); 142 auto& terminal_cursor_bar = *find_descendant_of_type_named<GUI::RadioButton>("terminal_cursor_bar"); 143 144 auto& terminal_cursor_blinking = *find_descendant_of_type_named<GUI::CheckBox>("terminal_cursor_blinking"); 145 146 m_cursor_shape = VT::TerminalWidget::parse_cursor_shape(Config::read_string("Terminal"sv, "Cursor"sv, "Shape"sv)).value_or(VT::CursorShape::Block); 147 m_original_cursor_shape = m_cursor_shape; 148 149 m_cursor_is_blinking_set = Config::read_bool("Terminal"sv, "Cursor"sv, "Blinking"sv, true); 150 m_original_cursor_is_blinking_set = m_cursor_is_blinking_set; 151 152 switch (m_cursor_shape) { 153 case VT::CursorShape::Underline: 154 terminal_cursor_underline.set_checked(true); 155 break; 156 case VT::CursorShape::Bar: 157 terminal_cursor_bar.set_checked(true); 158 break; 159 default: 160 terminal_cursor_block.set_checked(true); 161 } 162 163 terminal_cursor_blinking.on_checked = [&](bool is_checked) { 164 set_modified(true); 165 m_cursor_is_blinking_set = is_checked; 166 Config::write_bool("Terminal"sv, "Cursor"sv, "Blinking"sv, is_checked); 167 }; 168 terminal_cursor_blinking.set_checked(Config::read_bool("Terminal"sv, "Cursor"sv, "Blinking"sv, true)); 169 170 terminal_cursor_block.on_checked = [&](bool) { 171 set_modified(true); 172 m_cursor_shape = VT::CursorShape::Block; 173 Config::write_string("Terminal"sv, "Cursor"sv, "Shape"sv, "Block"sv); 174 }; 175 terminal_cursor_block.set_checked(Config::read_string("Terminal"sv, "Cursor"sv, "Shape"sv) == "Block"sv); 176 177 terminal_cursor_underline.on_checked = [&](bool) { 178 set_modified(true); 179 m_cursor_shape = VT::CursorShape::Underline; 180 Config::write_string("Terminal"sv, "Cursor"sv, "Shape"sv, "Underline"sv); 181 }; 182 terminal_cursor_underline.set_checked(Config::read_string("Terminal"sv, "Cursor"sv, "Shape"sv) == "Underline"sv); 183 184 terminal_cursor_bar.on_checked = [&](bool) { 185 set_modified(true); 186 m_cursor_shape = VT::CursorShape::Bar; 187 Config::write_string("Terminal"sv, "Cursor"sv, "Shape"sv, "Bar"sv); 188 }; 189 terminal_cursor_bar.set_checked(Config::read_string("Terminal"sv, "Cursor"sv, "Shape"sv) == "Bar"sv); 190 191 m_max_history_size = Config::read_i32("Terminal"sv, "Terminal"sv, "MaxHistorySize"sv); 192 m_original_max_history_size = m_max_history_size; 193 auto& history_size_spinbox = *find_descendant_of_type_named<GUI::SpinBox>("history_size_spinbox"); 194 history_size_spinbox.set_value(m_max_history_size, GUI::AllowCallback::No); 195 history_size_spinbox.on_change = [this](int value) { 196 m_max_history_size = value; 197 Config::write_i32("Terminal"sv, "Terminal"sv, "MaxHistorySize"sv, static_cast<i32>(m_max_history_size)); 198 set_modified(true); 199 }; 200 201 m_show_scrollbar = Config::read_bool("Terminal"sv, "Terminal"sv, "ShowScrollBar"sv, true); 202 m_original_show_scrollbar = m_show_scrollbar; 203 auto& show_scrollbar_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("terminal_show_scrollbar"); 204 show_scrollbar_checkbox.on_checked = [&](bool show_scrollbar) { 205 m_show_scrollbar = show_scrollbar; 206 Config::write_bool("Terminal"sv, "Terminal"sv, "ShowScrollBar"sv, show_scrollbar); 207 set_modified(true); 208 }; 209 show_scrollbar_checkbox.set_checked(m_show_scrollbar, GUI::AllowCallback::No); 210} 211 212VT::TerminalWidget::BellMode TerminalSettingsMainWidget::parse_bell(StringView bell_string) 213{ 214 if (bell_string == "AudibleBeep") 215 return VT::TerminalWidget::BellMode::AudibleBeep; 216 if (bell_string == "Visible") 217 return VT::TerminalWidget::BellMode::Visible; 218 if (bell_string == "Disabled") 219 return VT::TerminalWidget::BellMode::Disabled; 220 VERIFY_NOT_REACHED(); 221} 222 223DeprecatedString TerminalSettingsMainWidget::stringify_bell(VT::TerminalWidget::BellMode bell_mode) 224{ 225 if (bell_mode == VT::TerminalWidget::BellMode::AudibleBeep) 226 return "AudibleBeep"; 227 if (bell_mode == VT::TerminalWidget::BellMode::Disabled) 228 return "Disabled"; 229 if (bell_mode == VT::TerminalWidget::BellMode::Visible) 230 return "Visible"; 231 VERIFY_NOT_REACHED(); 232} 233 234void TerminalSettingsMainWidget::apply_settings() 235{ 236 m_original_bell_mode = m_bell_mode; 237 m_orignal_confirm_close = m_confirm_close; 238 write_back_settings(); 239} 240void TerminalSettingsMainWidget::write_back_settings() const 241{ 242 Config::write_bool("Terminal"sv, "Terminal"sv, "ConfirmClose"sv, m_orignal_confirm_close); 243 Config::write_string("Terminal"sv, "Window"sv, "Bell"sv, stringify_bell(m_original_bell_mode)); 244} 245 246void TerminalSettingsMainWidget::cancel_settings() 247{ 248 write_back_settings(); 249} 250 251void TerminalSettingsViewWidget::apply_settings() 252{ 253 m_original_opacity = m_opacity; 254 m_original_font = m_font; 255 m_original_cursor_shape = m_cursor_shape; 256 m_original_cursor_is_blinking_set = m_cursor_is_blinking_set; 257 m_original_max_history_size = m_max_history_size; 258 m_original_show_scrollbar = m_show_scrollbar; 259 write_back_settings(); 260} 261 262void TerminalSettingsViewWidget::write_back_settings() const 263{ 264 Config::write_i32("Terminal"sv, "Window"sv, "Opacity"sv, static_cast<i32>(m_original_opacity)); 265 Config::write_string("Terminal"sv, "Text"sv, "Font"sv, m_original_font->qualified_name()); 266 Config::write_string("Terminal"sv, "Cursor"sv, "Shape"sv, VT::TerminalWidget::stringify_cursor_shape(m_original_cursor_shape)); 267 Config::write_bool("Terminal"sv, "Cursor"sv, "Blinking"sv, m_original_cursor_is_blinking_set); 268 Config::write_i32("Terminal"sv, "Terminal"sv, "MaxHistorySize"sv, static_cast<i32>(m_original_max_history_size)); 269 Config::write_bool("Terminal"sv, "Terminal"sv, "ShowScrollBar"sv, m_original_show_scrollbar); 270} 271 272void TerminalSettingsViewWidget::cancel_settings() 273{ 274 write_back_settings(); 275}