/* * Copyright (c) 2022, Sam Atkins * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include "CharacterMapWidget.h" #include "CharacterSearchWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CharacterMapWidget::CharacterMapWidget() { load_from_gml(character_map_window_gml); m_toolbar = find_descendant_of_type_named("toolbar"); m_font_name_label = find_descendant_of_type_named("font_name"); m_glyph_map = find_descendant_of_type_named("glyph_map"); m_output_box = find_descendant_of_type_named("output_box"); m_copy_output_button = find_descendant_of_type_named("copy_output_button"); m_statusbar = find_descendant_of_type_named("statusbar"); m_unicode_block_listview = find_descendant_of_type_named("unicode_block_listview"); m_choose_font_action = GUI::Action::create("Choose Font...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-font-editor.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) { auto font_picker = GUI::FontPicker::construct(window(), &font(), false); if (font_picker->exec() == GUI::Dialog::ExecResult::OK) { auto& font = *font_picker->font(); Config::write_string("CharacterMap", "History", "Font", font.qualified_name()); set_font(font); } }); m_copy_selection_action = GUI::CommonActions::make_copy_action([&](GUI::Action&) { auto selection = m_glyph_map->selection(); StringBuilder builder; for (auto code_point = selection.start(); code_point < selection.start() + selection.size(); ++code_point) { if (!m_glyph_map->font().contains_glyph(code_point)) continue; builder.append_code_point(code_point); } GUI::Clipboard::the().set_plain_text(builder.to_string()); }); m_copy_selection_action->set_status_tip("Copy the highlighted characters to the clipboard"); m_previous_glyph_action = GUI::Action::create("Previous character", { Mod_Alt, Key_Left }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) { m_glyph_map->select_previous_existing_glyph(); }); m_previous_glyph_action->set_status_tip("Seek the previous visible glyph"); m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) { m_glyph_map->select_next_existing_glyph(); }); m_next_glyph_action->set_status_tip("Seek the next visible glyph"); m_go_to_glyph_action = GUI::Action::create("Go to glyph...", { Mod_Ctrl, Key_G }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-to.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) { String input; if (GUI::InputBox::show(window(), input, "Hexadecimal:", "Go to glyph") == GUI::InputBox::ExecResult::OK && !input.is_empty()) { auto maybe_code_point = AK::StringUtils::convert_to_uint_from_hex(input); if (!maybe_code_point.has_value()) return; auto code_point = maybe_code_point.value(); code_point = clamp(code_point, m_range.first, m_range.last); m_glyph_map->set_focus(true); m_glyph_map->set_active_glyph(code_point); m_glyph_map->scroll_to_glyph(code_point); } }); m_go_to_glyph_action->set_status_tip("Go to the specified code point"); m_find_glyphs_action = GUI::Action::create("&Find glyphs...", { Mod_Ctrl, Key_F }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/find.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) { if (m_find_window.is_null()) { m_find_window = GUI::Window::construct(window()); auto& search_widget = m_find_window->set_main_widget(); search_widget.on_character_selected = [&](auto code_point) { m_glyph_map->set_active_glyph(code_point); m_glyph_map->scroll_to_glyph(code_point); }; m_find_window->set_icon(GUI::Icon::try_create_default_icon("find").value().bitmap_for_size(16)); m_find_window->set_title("Find a character"); m_find_window->resize(300, 400); } m_find_window->show(); m_find_window->move_to_front(); m_find_window->find_descendant_of_type_named("search_input")->set_focus(true); }); m_toolbar->add_action(*m_choose_font_action); m_toolbar->add_separator(); m_toolbar->add_action(*m_copy_selection_action); m_toolbar->add_separator(); m_toolbar->add_action(*m_previous_glyph_action); m_toolbar->add_action(*m_next_glyph_action); m_toolbar->add_action(*m_go_to_glyph_action); m_toolbar->add_action(*m_find_glyphs_action); m_glyph_map->on_active_glyph_changed = [&](int) { update_statusbar(); }; m_glyph_map->on_glyph_double_clicked = [&](int code_point) { StringBuilder builder; builder.append(m_output_box->text()); builder.append_code_point(code_point); m_output_box->set_text(builder.string_view()); }; m_copy_output_button->on_click = [&](auto) { GUI::Clipboard::the().set_plain_text(m_output_box->text()); }; auto unicode_blocks = Unicode::block_display_names(); m_unicode_block_listview->on_selection_change = [this, unicode_blocks] { auto index = m_unicode_block_listview->selection().first(); if (index.row() > 0) m_range = unicode_blocks[index.row() - 1].code_point_range; else m_range = { 0x0000, 0x10FFFF }; m_glyph_map->set_active_range(m_range); }; m_unicode_block_list.append("Show All"); for (auto& block : unicode_blocks) m_unicode_block_list.append(block.display_name); m_unicode_block_model = GUI::ItemListModel::create(m_unicode_block_list); m_unicode_block_listview->set_model(*m_unicode_block_model); m_unicode_block_listview->set_activates_on_selection(true); m_unicode_block_listview->horizontal_scrollbar().set_visible(false); m_unicode_block_listview->set_cursor(m_unicode_block_model->index(0, 0), GUI::AbstractView::SelectionUpdate::Set); did_change_font(); update_statusbar(); } void CharacterMapWidget::initialize_menubar(GUI::Window& window) { auto& file_menu = window.add_menu("&File"); file_menu.add_action(GUI::CommonActions::make_quit_action([](GUI::Action&) { GUI::Application::the()->quit(); })); auto& help_menu = window.add_menu("&Help"); help_menu.add_action(GUI::CommonActions::make_help_action([&](auto&) { Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/CharacterMap.md"), "/bin/Help"); })); help_menu.add_action(GUI::CommonActions::make_about_action("Character Map", GUI::Icon::default_icon("app-character-map"), &window)); } void CharacterMapWidget::did_change_font() { m_glyph_map->set_font(font()); m_font_name_label->set_text(font().human_readable_name()); m_output_box->set_font(font()); } void CharacterMapWidget::update_statusbar() { auto code_point = m_glyph_map->active_glyph(); StringBuilder builder; builder.appendff("U+{:04X}", code_point); if (auto display_name = Unicode::code_point_display_name(code_point); display_name.has_value()) builder.appendff(" - {}", display_name.value()); m_statusbar->set_text(builder.to_string()); }