Serenity Operating System
at master 183 lines 8.7 kB view raw
1/* 2 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "CharacterMapWidget.h" 9#include "CharacterSearchWidget.h" 10#include <AK/StringUtils.h> 11#include <Applications/CharacterMap/CharacterMapWindowGML.h> 12#include <LibConfig/Client.h> 13#include <LibDesktop/Launcher.h> 14#include <LibGUI/Action.h> 15#include <LibGUI/Application.h> 16#include <LibGUI/Clipboard.h> 17#include <LibGUI/FontPicker.h> 18#include <LibGUI/Icon.h> 19#include <LibGUI/InputBox.h> 20#include <LibGUI/ItemListModel.h> 21#include <LibGUI/Label.h> 22#include <LibGUI/ListView.h> 23#include <LibGUI/Menu.h> 24#include <LibGUI/TextBox.h> 25#include <LibGUI/Toolbar.h> 26#include <LibUnicode/CharacterTypes.h> 27 28CharacterMapWidget::CharacterMapWidget() 29{ 30 load_from_gml(character_map_window_gml).release_value_but_fixme_should_propagate_errors(); 31 32 m_toolbar = find_descendant_of_type_named<GUI::Toolbar>("toolbar"); 33 m_font_name_label = find_descendant_of_type_named<GUI::Label>("font_name"); 34 m_glyph_map = find_descendant_of_type_named<GUI::GlyphMapWidget>("glyph_map"); 35 m_output_box = find_descendant_of_type_named<GUI::TextBox>("output_box"); 36 m_copy_output_button = find_descendant_of_type_named<GUI::Button>("copy_output_button"); 37 m_statusbar = find_descendant_of_type_named<GUI::Statusbar>("statusbar"); 38 m_unicode_block_listview = find_descendant_of_type_named<GUI::ListView>("unicode_block_listview"); 39 40 m_choose_font_action = GUI::Action::create("Choose Font...", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-font-editor.png"sv).release_value_but_fixme_should_propagate_errors(), [&](auto&) { 41 auto font_picker = GUI::FontPicker::construct(window(), &font(), false); 42 if (font_picker->exec() == GUI::Dialog::ExecResult::OK) { 43 auto& font = *font_picker->font(); 44 Config::write_string("CharacterMap"sv, "History"sv, "Font"sv, font.qualified_name()); 45 set_font(font); 46 } 47 }); 48 49 m_copy_selection_action = GUI::CommonActions::make_copy_action([&](GUI::Action&) { 50 auto selection = m_glyph_map->selection(); 51 StringBuilder builder; 52 for (auto code_point = selection.start(); code_point < selection.start() + selection.size(); ++code_point) { 53 if (!m_glyph_map->font().contains_glyph(code_point)) 54 continue; 55 builder.append_code_point(code_point); 56 } 57 GUI::Clipboard::the().set_plain_text(builder.to_deprecated_string()); 58 }); 59 m_copy_selection_action->set_status_tip("Copy the highlighted characters to the clipboard"); 60 61 m_previous_glyph_action = GUI::Action::create("Previous character", { Mod_Alt, Key_Left }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv).release_value_but_fixme_should_propagate_errors(), [&](auto&) { 62 m_glyph_map->select_previous_existing_glyph(); 63 }); 64 m_previous_glyph_action->set_status_tip("Seek the previous visible glyph"); 65 66 m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv).release_value_but_fixme_should_propagate_errors(), [&](auto&) { 67 m_glyph_map->select_next_existing_glyph(); 68 }); 69 m_next_glyph_action->set_status_tip("Seek the next visible glyph"); 70 71 m_go_to_glyph_action = GUI::Action::create("Go to glyph...", { Mod_Ctrl, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-to.png"sv).release_value_but_fixme_should_propagate_errors(), [&](auto&) { 72 DeprecatedString input; 73 if (GUI::InputBox::show(window(), input, "Hexadecimal:"sv, "Go to glyph"sv, GUI::InputType::NonemptyText) == GUI::InputBox::ExecResult::OK) { 74 auto maybe_code_point = AK::StringUtils::convert_to_uint_from_hex(input); 75 if (!maybe_code_point.has_value()) 76 return; 77 auto code_point = maybe_code_point.value(); 78 code_point = clamp(code_point, m_range.first, m_range.last); 79 m_glyph_map->set_focus(true); 80 m_glyph_map->set_active_glyph(code_point); 81 m_glyph_map->scroll_to_glyph(code_point); 82 } 83 }); 84 m_go_to_glyph_action->set_status_tip("Go to the specified code point"); 85 86 m_find_glyphs_action = GUI::Action::create("&Find glyphs...", { Mod_Ctrl, Key_F }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find.png"sv).release_value_but_fixme_should_propagate_errors(), [&](auto&) { 87 if (m_find_window.is_null()) { 88 m_find_window = GUI::Window::construct(window()); 89 auto search_widget = m_find_window->set_main_widget<CharacterSearchWidget>().release_value_but_fixme_should_propagate_errors(); 90 search_widget->on_character_selected = [&](auto code_point) { 91 m_glyph_map->set_active_glyph(code_point); 92 m_glyph_map->scroll_to_glyph(code_point); 93 }; 94 m_find_window->set_icon(GUI::Icon::try_create_default_icon("find"sv).value().bitmap_for_size(16)); 95 m_find_window->set_title("Find a character"); 96 m_find_window->resize(300, 400); 97 m_find_window->set_window_mode(GUI::WindowMode::Modeless); 98 } 99 m_find_window->show(); 100 m_find_window->move_to_front(); 101 m_find_window->find_descendant_of_type_named<GUI::TextBox>("search_input")->set_focus(true); 102 }); 103 104 m_toolbar->add_action(*m_choose_font_action); 105 m_toolbar->add_separator(); 106 m_toolbar->add_action(*m_copy_selection_action); 107 m_toolbar->add_separator(); 108 m_toolbar->add_action(*m_previous_glyph_action); 109 m_toolbar->add_action(*m_next_glyph_action); 110 m_toolbar->add_action(*m_go_to_glyph_action); 111 m_toolbar->add_action(*m_find_glyphs_action); 112 113 m_glyph_map->on_active_glyph_changed = [&](int) { 114 update_statusbar(); 115 }; 116 117 m_glyph_map->on_glyph_double_clicked = [&](int code_point) { 118 StringBuilder builder; 119 builder.append(m_output_box->text()); 120 builder.append_code_point(code_point); 121 m_output_box->set_text(builder.string_view()); 122 }; 123 124 m_copy_output_button->on_click = [&](auto) { 125 GUI::Clipboard::the().set_plain_text(m_output_box->text()); 126 }; 127 128 auto unicode_blocks = Unicode::block_display_names(); 129 m_unicode_block_listview->on_selection_change = [this, unicode_blocks] { 130 auto index = m_unicode_block_listview->selection().first(); 131 if (index.row() > 0) 132 m_range = unicode_blocks[index.row() - 1].code_point_range; 133 else 134 m_range = { 0x0000, 0x10FFFF }; 135 m_glyph_map->set_active_range(m_range); 136 }; 137 138 m_unicode_block_list.append("Show All"); 139 for (auto& block : unicode_blocks) 140 m_unicode_block_list.append(block.display_name); 141 142 m_unicode_block_model = GUI::ItemListModel<DeprecatedString>::create(m_unicode_block_list); 143 m_unicode_block_listview->set_model(*m_unicode_block_model); 144 m_unicode_block_listview->set_activates_on_selection(true); 145 m_unicode_block_listview->horizontal_scrollbar().set_visible(false); 146 m_unicode_block_listview->set_cursor(m_unicode_block_model->index(0, 0), GUI::AbstractView::SelectionUpdate::Set); 147 148 did_change_font(); 149 update_statusbar(); 150} 151 152void CharacterMapWidget::initialize_menubar(GUI::Window& window) 153{ 154 auto& file_menu = window.add_menu("&File"); 155 file_menu.add_action(GUI::CommonActions::make_quit_action([](GUI::Action&) { 156 GUI::Application::the()->quit(); 157 })); 158 159 auto& help_menu = window.add_menu("&Help"); 160 help_menu.add_action(GUI::CommonActions::make_command_palette_action(&window)); 161 help_menu.add_action(GUI::CommonActions::make_help_action([&](auto&) { 162 Desktop::Launcher::open(URL::create_with_file_scheme("/usr/share/man/man1/CharacterMap.md"), "/bin/Help"); 163 })); 164 help_menu.add_action(GUI::CommonActions::make_about_action("Character Map", GUI::Icon::default_icon("app-character-map"sv), &window)); 165} 166 167void CharacterMapWidget::did_change_font() 168{ 169 // No need to track glyph modifications by cloning 170 m_glyph_map->GUI::AbstractScrollableWidget::set_font(font()); 171 m_font_name_label->set_text(font().human_readable_name()); 172 m_output_box->set_font(font()); 173} 174 175void CharacterMapWidget::update_statusbar() 176{ 177 auto code_point = m_glyph_map->active_glyph(); 178 StringBuilder builder; 179 builder.appendff("U+{:04X}", code_point); 180 if (auto display_name = Unicode::code_point_display_name(code_point); display_name.has_value()) 181 builder.appendff(" - {}", display_name.value()); 182 m_statusbar->set_text(builder.to_deprecated_string()); 183}