2020-01-18 05:38:21 -03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2022-02-10 16:28:48 -03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2020-01-18 05:38:21 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 05:38:21 -03:00
*/
2019-02-02 05:05:14 -02:00
# include "FontEditor.h"
2019-04-03 10:23:13 -03:00
# include "GlyphEditorWidget.h"
2021-04-10 14:47:23 -03:00
# include "NewFontDialog.h"
2022-02-09 21:45:15 -03:00
# include <AK/Array.h>
2019-09-04 17:50:40 -03:00
# include <AK/StringBuilder.h>
2022-02-13 17:18:39 -03:00
# include <AK/StringUtils.h>
2021-04-06 13:04:21 -03:00
# include <Applications/FontEditor/FontEditorWindowGML.h>
2021-11-14 10:26:10 -03:00
# include <LibConfig/Client.h>
2021-04-10 14:40:59 -03:00
# include <LibDesktop/Launcher.h>
2021-04-06 13:04:21 -03:00
# include <LibGUI/Action.h>
2021-04-10 14:40:59 -03:00
# include <LibGUI/Application.h>
2020-05-08 19:06:56 -03:00
# include <LibGUI/BoxLayout.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Button.h>
# include <LibGUI/CheckBox.h>
2021-04-10 14:40:59 -03:00
# include <LibGUI/Clipboard.h>
# include <LibGUI/ComboBox.h>
2021-04-06 13:04:21 -03:00
# include <LibGUI/FilePicker.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/GroupBox.h>
2021-09-09 08:43:19 -03:00
# include <LibGUI/InputBox.h>
2021-09-23 21:07:34 -03:00
# include <LibGUI/ItemListModel.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Label.h>
2022-02-13 17:18:39 -03:00
# include <LibGUI/ListView.h>
2021-04-10 14:40:59 -03:00
# include <LibGUI/Menu.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/Menubar.h>
2020-05-08 19:06:56 -03:00
# include <LibGUI/MessageBox.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/Painter.h>
# include <LibGUI/SpinBox.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/Statusbar.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/TextBox.h>
2021-04-13 11:18:20 -03:00
# include <LibGUI/ToolbarContainer.h>
2020-05-08 19:06:56 -03:00
# include <LibGUI/Window.h>
2020-12-31 10:01:59 -03:00
# include <LibGfx/BitmapFont.h>
2021-11-14 09:27:17 -03:00
# include <LibGfx/Emoji.h>
2021-09-23 21:07:34 -03:00
# include <LibGfx/FontStyleMapping.h>
2020-05-08 19:06:56 -03:00
# include <LibGfx/Palette.h>
2021-05-20 08:00:04 -03:00
# include <LibGfx/TextDirection.h>
2021-11-18 19:39:54 -03:00
# include <LibUnicode/CharacterTypes.h>
2019-02-02 05:05:14 -02:00
2022-03-02 21:55:14 -03:00
static constexpr Array pangrams = {
" quick fox jumps nightly above wizard " ,
" five quacking zephyrs jolt my wax bed " ,
" pack my box with five dozen liquor jugs " ,
" quick brown fox jumps over the lazy dog " ,
" waxy and quivering jocks fumble the pizza " ,
" ~#:[@_1%]*{$2.3}/4^(5'6 \" )-&|7+8!=<9,0 \\ >?; " ,
" byxfjärmat föl gick på duvshowen " ,
2022-03-05 17:05:48 -03:00
" " ,
" float Fox.quick(h){ is_brown && it_jumps_over(doges.lazy) } " ,
" <fox color= \" brown \" speed= \" quick \" jumps= \" over \" >lazy dog</fox> "
2022-03-02 21:55:14 -03:00
} ;
2021-04-06 13:04:21 -03:00
static RefPtr < GUI : : Window > create_font_preview_window ( FontEditorWidget & editor )
2019-02-02 05:05:14 -02:00
{
2021-10-02 08:32:58 -03:00
auto window = GUI : : Window : : construct ( & editor ) ;
2021-04-06 13:04:21 -03:00
window - > set_window_type ( GUI : : WindowType : : ToolWindow ) ;
2021-09-22 10:25:25 -03:00
window - > set_title ( " Preview " ) ;
2021-04-06 13:04:21 -03:00
window - > resize ( 400 , 150 ) ;
window - > set_minimum_size ( 200 , 100 ) ;
window - > center_within ( * editor . window ( ) ) ;
auto & main_widget = window - > set_main_widget < GUI : : Widget > ( ) ;
main_widget . set_fill_with_background_color ( true ) ;
main_widget . set_layout < GUI : : VerticalBoxLayout > ( ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-16 21:11:38 -03:00
main_widget . layout ( ) - > set_margins ( 2 ) ;
2021-04-06 13:04:21 -03:00
main_widget . layout ( ) - > set_spacing ( 4 ) ;
auto & preview_box = main_widget . add < GUI : : GroupBox > ( ) ;
preview_box . set_layout < GUI : : VerticalBoxLayout > ( ) ;
Userland+LibGUI: Add shorthand versions of the Margins constructor
This allows for typing [8] instead of [8, 8, 8, 8] to specify the same
margin on all edges, for example. The constructors follow CSS' style of
specifying margins. The added constructors are:
- Margins(int all): Sets the same margin on all edges.
- Margins(int vertical, int horizontal): Sets the first argument to top
and bottom margins, and the second argument to left and right margins.
- Margins(int top, int vertical, int bottom): Sets the first argument to
the top margin, the second argument to the left and right margins,
and the third argument to the bottom margin.
2021-08-16 21:11:38 -03:00
preview_box . layout ( ) - > set_margins ( 8 ) ;
2021-04-06 13:04:21 -03:00
auto & preview_label = preview_box . add < GUI : : Label > ( ) ;
preview_label . set_font ( editor . edited_font ( ) ) ;
editor . on_initialize = [ & ] {
preview_label . set_font ( editor . edited_font ( ) ) ;
2019-03-06 08:52:41 -03:00
} ;
2021-04-22 14:41:43 -03:00
auto & textbox_button_container = main_widget . add < GUI : : Widget > ( ) ;
textbox_button_container . set_layout < GUI : : HorizontalBoxLayout > ( ) ;
textbox_button_container . set_fixed_height ( 22 ) ;
auto & preview_textbox = textbox_button_container . add < GUI : : TextBox > ( ) ;
preview_textbox . set_text ( pangrams [ 0 ] ) ;
2021-04-10 14:40:59 -03:00
preview_textbox . set_placeholder ( " Preview text " ) ;
2020-10-24 12:03:39 -03:00
2021-04-06 13:04:21 -03:00
preview_textbox . on_change = [ & ] {
2021-04-10 14:40:59 -03:00
auto preview = String : : formatted ( " {} \n {} " ,
preview_textbox . text ( ) ,
2021-11-29 11:00:15 -03:00
Unicode : : to_unicode_uppercase_full ( preview_textbox . text ( ) ) ) ;
2021-04-10 14:40:59 -03:00
preview_label . set_text ( preview ) ;
2020-10-24 12:03:39 -03:00
} ;
2021-04-22 14:41:43 -03:00
auto & reload_button = textbox_button_container . add < GUI : : Button > ( ) ;
2021-11-06 12:25:29 -03:00
reload_button . set_icon ( Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/reload.png " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-04-22 14:41:43 -03:00
reload_button . set_fixed_width ( 22 ) ;
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
reload_button . on_click = [ & ] ( auto ) {
2022-02-09 21:45:15 -03:00
static size_t i = 1 ;
if ( i > = pangrams . size ( ) )
2021-04-22 14:41:43 -03:00
i = 0 ;
preview_textbox . set_text ( pangrams [ i ] ) ;
i + + ;
} ;
2021-04-06 13:04:21 -03:00
return window ;
}
2021-11-29 11:07:47 -03:00
FontEditorWidget : : FontEditorWidget ( )
2021-04-06 13:04:21 -03:00
{
load_from_gml ( font_editor_window_gml ) ;
2021-04-13 11:18:20 -03:00
auto & toolbar = * find_descendant_of_type_named < GUI : : Toolbar > ( " toolbar " ) ;
2021-11-29 11:26:24 -03:00
auto & glyph_mode_toolbar = * find_descendant_of_type_named < GUI : : Toolbar > ( " glyph_mode_toolbar " ) ;
auto & glyph_transform_toolbar = * find_descendant_of_type_named < GUI : : Toolbar > ( " glyph_transform_toolbar " ) ;
2021-04-06 13:04:21 -03:00
auto & glyph_map_container = * find_descendant_of_type_named < GUI : : Widget > ( " glyph_map_container " ) ;
2021-11-29 11:26:24 -03:00
2021-09-22 10:25:25 -03:00
m_statusbar = * find_descendant_of_type_named < GUI : : Statusbar > ( " statusbar " ) ;
2021-04-06 13:04:21 -03:00
m_glyph_editor_container = * find_descendant_of_type_named < GUI : : Widget > ( " glyph_editor_container " ) ;
m_left_column_container = * find_descendant_of_type_named < GUI : : Widget > ( " left_column_container " ) ;
m_glyph_editor_width_spinbox = * find_descendant_of_type_named < GUI : : SpinBox > ( " glyph_editor_width_spinbox " ) ;
2021-04-18 15:12:52 -03:00
m_glyph_editor_present_checkbox = * find_descendant_of_type_named < GUI : : CheckBox > ( " glyph_editor_present_checkbox " ) ;
2021-04-06 13:04:21 -03:00
m_name_textbox = * find_descendant_of_type_named < GUI : : TextBox > ( " name_textbox " ) ;
m_family_textbox = * find_descendant_of_type_named < GUI : : TextBox > ( " family_textbox " ) ;
m_presentation_spinbox = * find_descendant_of_type_named < GUI : : SpinBox > ( " presentation_spinbox " ) ;
2021-04-10 14:40:59 -03:00
m_weight_combobox = * find_descendant_of_type_named < GUI : : ComboBox > ( " weight_combobox " ) ;
2021-09-23 21:07:34 -03:00
m_slope_combobox = * find_descendant_of_type_named < GUI : : ComboBox > ( " slope_combobox " ) ;
2021-04-06 13:04:21 -03:00
m_spacing_spinbox = * find_descendant_of_type_named < GUI : : SpinBox > ( " spacing_spinbox " ) ;
m_mean_line_spinbox = * find_descendant_of_type_named < GUI : : SpinBox > ( " mean_line_spinbox " ) ;
m_baseline_spinbox = * find_descendant_of_type_named < GUI : : SpinBox > ( " baseline_spinbox " ) ;
m_fixed_width_checkbox = * find_descendant_of_type_named < GUI : : CheckBox > ( " fixed_width_checkbox " ) ;
m_font_metadata_groupbox = * find_descendant_of_type_named < GUI : : GroupBox > ( " font_metadata_groupbox " ) ;
2022-02-13 17:18:39 -03:00
m_unicode_block_listview = * find_descendant_of_type_named < GUI : : ListView > ( " unicode_block_listview " ) ;
2022-02-16 12:12:46 -03:00
m_search_textbox = * find_descendant_of_type_named < GUI : : TextBox > ( " search_textbox " ) ;
m_unicode_block_container = * find_descendant_of_type_named < GUI : : Widget > ( " unicode_block_container " ) ;
2021-04-06 13:04:21 -03:00
m_glyph_editor_widget = m_glyph_editor_container - > add < GlyphEditorWidget > ( ) ;
2022-01-09 13:15:23 -03:00
m_glyph_map_widget = glyph_map_container . add < GUI : : GlyphMapWidget > ( ) ;
2021-04-06 13:04:21 -03:00
2021-11-06 12:25:29 -03:00
m_new_action = GUI : : Action : : create ( " &New Font... " , { Mod_Ctrl , Key_N } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/filetype-font.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2021-11-29 11:44:26 -03:00
if ( ! request_close ( ) )
return ;
2021-04-10 14:47:23 -03:00
auto new_font_wizard = NewFontDialog : : construct ( window ( ) ) ;
if ( new_font_wizard - > exec ( ) = = GUI : : Dialog : : ExecOK ) {
auto metadata = new_font_wizard - > new_font_metadata ( ) ;
2021-11-29 11:48:02 -03:00
auto new_font = Gfx : : BitmapFont : : create ( metadata . glyph_height , metadata . glyph_width , metadata . is_fixed_width , 0x110000 ) ;
2021-04-10 14:47:23 -03:00
new_font - > set_name ( metadata . name ) ;
new_font - > set_family ( metadata . family ) ;
new_font - > set_presentation_size ( metadata . presentation_size ) ;
new_font - > set_weight ( metadata . weight ) ;
2021-09-23 21:07:34 -03:00
new_font - > set_slope ( metadata . slope ) ;
2021-04-10 14:47:23 -03:00
new_font - > set_baseline ( metadata . baseline ) ;
new_font - > set_mean_line ( metadata . mean_line ) ;
2021-08-26 18:25:02 -03:00
window ( ) - > set_modified ( true ) ;
initialize ( { } , move ( new_font ) ) ;
2021-04-10 14:47:23 -03:00
}
} ) ;
2021-04-22 14:53:17 -03:00
m_new_action - > set_status_tip ( " Create a new font " ) ;
2021-04-10 14:47:23 -03:00
m_open_action = GUI : : CommonActions : : make_open_action ( [ & ] ( auto & ) {
2021-10-13 17:09:38 -03:00
if ( ! request_close ( ) )
return ;
2021-04-06 13:04:21 -03:00
Optional < String > open_path = GUI : : FilePicker : : get_open_filepath ( window ( ) , { } , " /res/fonts/ " ) ;
if ( ! open_path . has_value ( ) )
return ;
2019-03-06 08:52:41 -03:00
2021-10-13 17:13:48 -03:00
open_file ( open_path . value ( ) ) ;
2021-04-06 13:04:21 -03:00
} ) ;
2021-04-10 14:40:59 -03:00
m_save_action = GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
2021-08-26 18:25:02 -03:00
if ( m_path . is_empty ( ) )
2021-04-26 10:11:14 -03:00
m_save_as_action - > activate ( ) ;
2021-08-26 18:25:02 -03:00
else
2022-01-16 11:36:43 -03:00
save_file ( m_path ) ;
2021-04-06 13:04:21 -03:00
} ) ;
2021-04-10 14:40:59 -03:00
m_save_as_action = GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
2021-08-26 18:25:02 -03:00
LexicalPath lexical_path ( m_path . is_empty ( ) ? " Untitled.font " : m_path ) ;
2021-04-10 14:40:59 -03:00
Optional < String > save_path = GUI : : FilePicker : : get_save_filepath ( window ( ) , lexical_path . title ( ) , lexical_path . extension ( ) ) ;
if ( ! save_path . has_value ( ) )
return ;
2022-01-16 11:36:43 -03:00
save_file ( save_path . value ( ) ) ;
2021-04-10 14:40:59 -03:00
} ) ;
m_cut_action = GUI : : CommonActions : : make_cut_action ( [ & ] ( auto & ) {
2022-01-04 14:24:06 -03:00
cut_selected_glyphs ( ) ;
2021-04-06 13:04:21 -03:00
} ) ;
2021-04-10 14:40:59 -03:00
m_copy_action = GUI : : CommonActions : : make_copy_action ( [ & ] ( auto & ) {
2022-01-04 14:24:06 -03:00
copy_selected_glyphs ( ) ;
2021-04-06 13:04:21 -03:00
} ) ;
2021-04-10 14:40:59 -03:00
m_paste_action = GUI : : CommonActions : : make_paste_action ( [ & ] ( auto & ) {
2022-01-04 14:24:06 -03:00
paste_glyphs ( ) ;
2021-04-06 13:04:21 -03:00
} ) ;
2021-11-20 11:22:01 -03:00
m_paste_action - > set_enabled ( GUI : : Clipboard : : the ( ) . fetch_mime_type ( ) = = " glyph/x-fonteditor " ) ;
2021-09-22 10:25:25 -03:00
m_delete_action = GUI : : CommonActions : : make_delete_action ( [ this ] ( auto & ) {
2022-01-04 14:24:06 -03:00
delete_selected_glyphs ( ) ;
2021-04-06 13:04:21 -03:00
} ) ;
2021-04-22 15:12:53 -03:00
m_undo_action = GUI : : CommonActions : : make_undo_action ( [ & ] ( auto & ) {
undo ( ) ;
} ) ;
m_redo_action = GUI : : CommonActions : : make_redo_action ( [ & ] ( auto & ) {
redo ( ) ;
} ) ;
2021-11-06 12:25:29 -03:00
m_open_preview_action = GUI : : Action : : create ( " &Preview Font " , { Mod_Ctrl , Key_P } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/find.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2021-04-06 13:04:21 -03:00
if ( ! m_font_preview_window )
m_font_preview_window = create_font_preview_window ( * this ) ;
m_font_preview_window - > show ( ) ;
m_font_preview_window - > move_to_front ( ) ;
} ) ;
2021-04-10 14:40:59 -03:00
m_open_preview_action - > set_checked ( false ) ;
2021-04-22 14:53:17 -03:00
m_open_preview_action - > set_status_tip ( " Preview the current font " ) ;
2022-02-16 12:14:06 -03:00
bool show_metadata = Config : : read_bool ( " FontEditor " , " Layout " , " ShowMetadata " , true ) ;
set_show_font_metadata ( show_metadata ) ;
2021-04-22 14:53:17 -03:00
m_show_metadata_action = GUI : : Action : : create_checkable ( " Font &Metadata " , { Mod_Ctrl , Key_M } , [ & ] ( auto & action ) {
2021-04-10 14:40:59 -03:00
set_show_font_metadata ( action . is_checked ( ) ) ;
2022-02-16 12:14:06 -03:00
Config : : write_bool ( " FontEditor " , " Layout " , " ShowMetadata " , action . is_checked ( ) ) ;
2021-04-10 14:40:59 -03:00
} ) ;
2022-02-16 12:14:06 -03:00
m_show_metadata_action - > set_checked ( show_metadata ) ;
2021-04-22 14:53:17 -03:00
m_show_metadata_action - > set_status_tip ( " Show or hide metadata about the current font " ) ;
2022-02-16 12:14:06 -03:00
bool show_unicode_blocks = Config : : read_bool ( " FontEditor " , " Layout " , " ShowUnicodeBlocks " , true ) ;
set_show_unicode_blocks ( show_unicode_blocks ) ;
2022-02-13 17:18:39 -03:00
m_show_unicode_blocks_action = GUI : : Action : : create_checkable ( " &Unicode Blocks " , { Mod_Ctrl , Key_U } , [ & ] ( auto & action ) {
set_show_unicode_blocks ( action . is_checked ( ) ) ;
2022-02-16 12:14:06 -03:00
Config : : write_bool ( " FontEditor " , " Layout " , " ShowUnicodeBlocks " , action . is_checked ( ) ) ;
2022-02-13 17:18:39 -03:00
} ) ;
2022-02-16 12:14:06 -03:00
m_show_unicode_blocks_action - > set_checked ( show_unicode_blocks ) ;
2022-02-13 17:18:39 -03:00
m_show_unicode_blocks_action - > set_status_tip ( " Show or hide the Unicode block list " ) ;
2022-02-16 12:14:06 -03:00
2021-11-06 12:25:29 -03:00
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 & ) {
2021-09-09 08:43:19 -03:00
String input ;
2022-02-13 17:18:39 -03:00
if ( GUI : : InputBox : : show ( window ( ) , input , " Hexadecimal: " , " Go to glyph " ) = = GUI : : InputBox : : ExecOK & & ! 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 ) ;
2021-09-09 08:43:19 -03:00
m_glyph_map_widget - > set_focus ( true ) ;
2022-01-04 14:24:06 -03:00
m_glyph_map_widget - > set_active_glyph ( code_point ) ;
2021-09-09 08:43:19 -03:00
m_glyph_map_widget - > scroll_to_glyph ( code_point ) ;
}
} ) ;
m_go_to_glyph_action - > set_status_tip ( " Go to the specified code point " ) ;
2021-11-06 12:25:29 -03:00
m_previous_glyph_action = GUI : : Action : : create ( " Pre&vious Glyph " , { Mod_Alt , Key_Left } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/go-back.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2022-01-11 17:50:59 -03:00
m_glyph_map_widget - > select_previous_existing_glyph ( ) ;
2021-09-09 08:43:19 -03:00
} ) ;
m_previous_glyph_action - > set_status_tip ( " Seek the previous visible glyph " ) ;
2021-11-06 12:25:29 -03:00
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 & ) {
2022-01-11 17:50:59 -03:00
m_glyph_map_widget - > select_next_existing_glyph ( ) ;
2021-09-09 08:43:19 -03:00
} ) ;
m_next_glyph_action - > set_status_tip ( " Seek the next visible glyph " ) ;
2021-04-06 13:04:21 -03:00
2021-04-10 14:40:59 -03:00
toolbar . add_action ( * m_new_action ) ;
toolbar . add_action ( * m_open_action ) ;
toolbar . add_action ( * m_save_action ) ;
2021-04-06 13:04:21 -03:00
toolbar . add_separator ( ) ;
2021-04-10 14:40:59 -03:00
toolbar . add_action ( * m_cut_action ) ;
toolbar . add_action ( * m_copy_action ) ;
toolbar . add_action ( * m_paste_action ) ;
toolbar . add_action ( * m_delete_action ) ;
2021-04-06 13:04:21 -03:00
toolbar . add_separator ( ) ;
2021-04-22 15:12:53 -03:00
toolbar . add_action ( * m_undo_action ) ;
toolbar . add_action ( * m_redo_action ) ;
toolbar . add_separator ( ) ;
2021-04-10 14:40:59 -03:00
toolbar . add_action ( * m_open_preview_action ) ;
2021-09-09 08:43:19 -03:00
toolbar . add_separator ( ) ;
toolbar . add_action ( * m_previous_glyph_action ) ;
toolbar . add_action ( * m_next_glyph_action ) ;
toolbar . add_action ( * m_go_to_glyph_action ) ;
2021-04-10 14:40:59 -03:00
2021-11-14 10:26:10 -03:00
i32 scale = Config : : read_i32 ( " FontEditor " , " GlyphEditor " , " Scale " , 10 ) ;
m_scale_five_action = GUI : : Action : : create_checkable ( " 500% " , { Mod_Ctrl , Key_1 } , [ this ] ( auto & ) {
set_scale_and_save ( 5 ) ;
2021-04-22 15:04:19 -03:00
} ) ;
2021-11-14 10:26:10 -03:00
m_scale_five_action - > set_checked ( scale = = 5 ) ;
2021-04-22 15:04:19 -03:00
m_scale_five_action - > set_status_tip ( " Scale the editor in proportion to the current font " ) ;
2021-11-14 10:26:10 -03:00
m_scale_ten_action = GUI : : Action : : create_checkable ( " 1000% " , { Mod_Ctrl , Key_2 } , [ this ] ( auto & ) {
set_scale_and_save ( 10 ) ;
2021-04-22 15:04:19 -03:00
} ) ;
2021-11-14 10:26:10 -03:00
m_scale_ten_action - > set_checked ( scale = = 10 ) ;
2021-04-22 15:04:19 -03:00
m_scale_ten_action - > set_status_tip ( " Scale the editor in proportion to the current font " ) ;
2021-11-14 10:26:10 -03:00
m_scale_fifteen_action = GUI : : Action : : create_checkable ( " 1500% " , { Mod_Ctrl , Key_3 } , [ this ] ( auto & ) {
set_scale_and_save ( 15 ) ;
2021-04-22 15:04:19 -03:00
} ) ;
2021-11-14 10:26:10 -03:00
m_scale_fifteen_action - > set_checked ( scale = = 15 ) ;
2021-04-22 15:04:19 -03:00
m_scale_fifteen_action - > set_status_tip ( " Scale the editor in proportion to the current font " ) ;
m_glyph_editor_scale_actions . add_action ( * m_scale_five_action ) ;
m_glyph_editor_scale_actions . add_action ( * m_scale_ten_action ) ;
m_glyph_editor_scale_actions . add_action ( * m_scale_fifteen_action ) ;
m_glyph_editor_scale_actions . set_exclusive ( true ) ;
2021-11-29 11:26:24 -03:00
m_paint_glyph_action = GUI : : Action : : create_checkable ( " Paint Glyph " , { Mod_Ctrl , KeyCode : : Key_J } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/pixelpaint/pen.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
m_glyph_editor_widget - > set_mode ( GlyphEditorWidget : : Paint ) ;
} ) ;
m_paint_glyph_action - > set_checked ( true ) ;
2021-04-22 15:15:21 -03:00
2021-11-29 11:26:24 -03:00
m_move_glyph_action = GUI : : Action : : create_checkable ( " Move Glyph " , { Mod_Ctrl , KeyCode : : Key_K } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/selection-move.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
m_glyph_editor_widget - > set_mode ( GlyphEditorWidget : : Move ) ;
} ) ;
2021-11-19 22:38:24 -03:00
2021-11-29 11:26:24 -03:00
m_glyph_tool_actions . add_action ( * m_move_glyph_action ) ;
m_glyph_tool_actions . add_action ( * m_paint_glyph_action ) ;
m_glyph_tool_actions . set_exclusive ( true ) ;
glyph_mode_toolbar . add_action ( * m_paint_glyph_action ) ;
glyph_mode_toolbar . add_action ( * m_move_glyph_action ) ;
2022-01-03 07:19:05 -03:00
m_rotate_counterclockwise_action = GUI : : CommonActions : : make_rotate_counterclockwise_action ( [ & ] ( auto & ) {
2021-11-29 11:26:24 -03:00
m_glyph_editor_widget - > rotate_90 ( GlyphEditorWidget : : Counterclockwise ) ;
} ) ;
2021-11-19 22:38:24 -03:00
2022-01-03 07:19:05 -03:00
m_rotate_clockwise_action = GUI : : CommonActions : : make_rotate_clockwise_action ( [ & ] ( auto & ) {
2021-11-29 11:26:24 -03:00
m_glyph_editor_widget - > rotate_90 ( GlyphEditorWidget : : Clockwise ) ;
} ) ;
m_flip_horizontal_action = GUI : : Action : : create ( " Flip Horizontally " , { Mod_Ctrl | Mod_Shift , Key_A } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/edit-flip-horizontal.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2021-11-19 22:38:24 -03:00
m_glyph_editor_widget - > flip_horizontally ( ) ;
2021-11-29 11:26:24 -03:00
} ) ;
2021-11-19 22:38:24 -03:00
2021-11-29 11:26:24 -03:00
m_flip_vertical_action = GUI : : Action : : create ( " Flip Vertically " , { Mod_Ctrl | Mod_Shift , Key_S } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/edit-flip-vertical.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
m_glyph_editor_widget - > flip_vertically ( ) ;
} ) ;
m_copy_character_action = GUI : : Action : : create ( " Cop&y as Character " , { Mod_Ctrl | Mod_Shift , Key_C } , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/edit-copy.png " ) . release_value_but_fixme_should_propagate_errors ( ) , [ & ] ( auto & ) {
2021-11-19 23:00:58 -03:00
StringBuilder glyph_builder ;
glyph_builder . append_code_point ( m_glyph_editor_widget - > glyph ( ) ) ;
GUI : : Clipboard : : the ( ) . set_plain_text ( glyph_builder . build ( ) ) ;
2021-11-29 11:26:24 -03:00
} ) ;
glyph_transform_toolbar . add_action ( * m_flip_horizontal_action ) ;
glyph_transform_toolbar . add_action ( * m_flip_vertical_action ) ;
glyph_transform_toolbar . add_action ( * m_rotate_counterclockwise_action ) ;
glyph_transform_toolbar . add_action ( * m_rotate_clockwise_action ) ;
2021-11-19 23:00:58 -03:00
2021-11-29 14:06:10 -03:00
GUI : : Clipboard : : the ( ) . on_change = [ & ] ( String const & data_type ) {
2021-04-10 14:40:59 -03:00
m_paste_action - > set_enabled ( data_type = = " glyph/x-fonteditor " ) ;
} ;
2019-02-02 05:05:14 -02:00
2021-09-22 10:25:25 -03:00
m_glyph_editor_widget - > on_glyph_altered = [ this ] ( int glyph ) {
2019-04-06 10:28:06 -03:00
m_glyph_map_widget - > update_glyph ( glyph ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2019-03-06 08:52:41 -03:00
} ;
2021-08-26 13:01:52 -03:00
m_glyph_editor_widget - > on_undo_event = [ this ] {
2021-04-22 15:12:53 -03:00
m_undo_stack - > push ( make < GlyphUndoCommand > ( * m_undo_glyph ) ) ;
} ;
2022-01-04 14:24:06 -03:00
m_glyph_map_widget - > on_active_glyph_changed = [ this ] ( int glyph ) {
2021-04-22 15:12:53 -03:00
if ( m_undo_glyph )
m_undo_glyph - > set_code_point ( glyph ) ;
2019-02-02 05:05:14 -02:00
m_glyph_editor_widget - > set_glyph ( glyph ) ;
2021-09-21 18:24:09 -03:00
auto glyph_width = m_edited_font - > raw_glyph_width ( glyph ) ;
if ( m_edited_font - > is_fixed_width ( ) )
m_glyph_editor_present_checkbox - > set_checked ( glyph_width > 0 , GUI : : AllowCallback : : No ) ;
else
m_glyph_editor_width_spinbox - > set_value ( glyph_width , GUI : : AllowCallback : : No ) ;
2021-04-22 14:53:17 -03:00
update_statusbar ( ) ;
2021-04-06 13:04:21 -03:00
} ;
m_name_textbox - > on_change = [ & ] {
m_edited_font - > set_name ( m_name_textbox - > text ( ) ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2021-04-06 13:04:21 -03:00
} ;
m_family_textbox - > on_change = [ & ] {
m_edited_font - > set_family ( m_family_textbox - > text ( ) ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2019-02-02 05:05:14 -02:00
} ;
2021-09-22 10:25:25 -03:00
m_fixed_width_checkbox - > on_checked = [ this ] ( bool checked ) {
2019-05-24 12:11:42 -03:00
m_edited_font - > set_fixed_width ( checked ) ;
2022-01-04 14:24:06 -03:00
auto glyph_width = m_edited_font - > raw_glyph_width ( m_glyph_map_widget - > active_glyph ( ) ) ;
2021-04-18 15:12:52 -03:00
m_glyph_editor_width_spinbox - > set_visible ( ! checked ) ;
2021-09-21 18:24:09 -03:00
m_glyph_editor_width_spinbox - > set_value ( glyph_width , GUI : : AllowCallback : : No ) ;
2021-04-18 15:12:52 -03:00
m_glyph_editor_present_checkbox - > set_visible ( checked ) ;
2021-09-21 18:24:09 -03:00
m_glyph_editor_present_checkbox - > set_checked ( glyph_width > 0 , GUI : : AllowCallback : : No ) ;
2019-03-06 08:52:41 -03:00
m_glyph_editor_widget - > update ( ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2019-03-06 08:52:41 -03:00
} ;
2021-09-22 10:25:25 -03:00
m_glyph_editor_width_spinbox - > on_change = [ this ] ( int value ) {
2021-09-21 18:42:25 -03:00
m_undo_stack - > push ( make < GlyphUndoCommand > ( * m_undo_glyph ) ) ;
2022-01-04 14:24:06 -03:00
m_edited_font - > set_glyph_width ( m_glyph_map_widget - > active_glyph ( ) , value ) ;
2019-04-09 11:29:00 -03:00
m_glyph_editor_widget - > update ( ) ;
2022-01-04 14:24:06 -03:00
m_glyph_map_widget - > update_glyph ( m_glyph_map_widget - > active_glyph ( ) ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-22 14:53:17 -03:00
update_statusbar ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2019-03-06 08:52:41 -03:00
} ;
2021-09-22 10:25:25 -03:00
m_glyph_editor_present_checkbox - > on_checked = [ this ] ( bool checked ) {
2021-09-21 18:42:25 -03:00
m_undo_stack - > push ( make < GlyphUndoCommand > ( * m_undo_glyph ) ) ;
2022-01-04 14:24:06 -03:00
m_edited_font - > set_glyph_width ( m_glyph_map_widget - > active_glyph ( ) , checked ? m_edited_font - > glyph_fixed_width ( ) : 0 ) ;
2021-04-18 15:12:52 -03:00
m_glyph_editor_widget - > update ( ) ;
2022-01-04 14:24:06 -03:00
m_glyph_map_widget - > update_glyph ( m_glyph_map_widget - > active_glyph ( ) ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-22 14:53:17 -03:00
update_statusbar ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2021-04-18 15:12:52 -03:00
} ;
AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-05 15:34:31 -03:00
m_weight_combobox - > on_change = [ this ] ( auto & , auto & ) {
2021-09-23 21:07:34 -03:00
m_edited_font - > set_weight ( Gfx : : name_to_weight ( m_weight_combobox - > text ( ) ) ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2021-04-10 14:40:59 -03:00
} ;
2021-09-23 21:07:34 -03:00
for ( auto & it : Gfx : : font_weight_names )
m_font_weight_list . append ( it . name ) ;
m_weight_combobox - > set_model ( * GUI : : ItemListModel < String > : : create ( m_font_weight_list ) ) ;
m_slope_combobox - > on_change = [ this ] ( auto & , auto & ) {
m_edited_font - > set_slope ( Gfx : : name_to_slope ( m_slope_combobox - > text ( ) ) ) ;
did_modify_font ( ) ;
} ;
for ( auto & it : Gfx : : font_slope_names )
m_font_slope_list . append ( it . name ) ;
m_slope_combobox - > set_model ( * GUI : : ItemListModel < String > : : create ( m_font_slope_list ) ) ;
2021-04-10 14:40:59 -03:00
2021-09-22 10:25:25 -03:00
m_presentation_spinbox - > on_change = [ this ] ( int value ) {
2020-10-24 12:03:39 -03:00
m_edited_font - > set_presentation_size ( value ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2020-10-24 12:03:39 -03:00
} ;
2021-09-22 10:25:25 -03:00
m_spacing_spinbox - > on_change = [ this ] ( int value ) {
2019-12-30 07:40:30 -03:00
m_edited_font - > set_glyph_spacing ( value ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2019-12-30 07:40:30 -03:00
} ;
2021-09-22 10:25:25 -03:00
m_baseline_spinbox - > on_change = [ this ] ( int value ) {
2020-09-19 13:01:32 -03:00
m_edited_font - > set_baseline ( value ) ;
2020-09-20 16:45:21 -03:00
m_glyph_editor_widget - > update ( ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2020-09-19 13:01:32 -03:00
} ;
2021-09-22 10:25:25 -03:00
m_mean_line_spinbox - > on_change = [ this ] ( int value ) {
2020-10-05 12:19:37 -03:00
m_edited_font - > set_mean_line ( value ) ;
m_glyph_editor_widget - > update ( ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
2021-04-26 10:11:14 -03:00
did_modify_font ( ) ;
2020-10-05 12:19:37 -03:00
} ;
2022-02-13 17:18:39 -03:00
auto unicode_blocks = Unicode : : block_display_names ( ) ;
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 < String > : : create ( m_unicode_block_list ) ;
2022-02-16 12:12:46 -03:00
m_filter_model = MUST ( GUI : : FilteringProxyModel : : create ( * m_unicode_block_model ) ) ;
m_filter_model - > set_filter_term ( " " ) ;
2022-02-26 09:33:32 -03:00
m_unicode_block_listview - > on_selection_change = [ this , unicode_blocks ] {
auto index = m_unicode_block_listview - > selection ( ) . first ( ) ;
2022-02-16 12:12:46 -03:00
auto mapped_index = m_filter_model - > map ( index ) ;
if ( mapped_index . row ( ) > 0 )
m_range = unicode_blocks [ mapped_index . row ( ) - 1 ] . code_point_range ;
else
m_range = { 0x0000 , 0x10FFFF } ;
m_glyph_map_widget - > set_active_range ( m_range ) ;
} ;
m_unicode_block_listview - > set_model ( * m_filter_model ) ;
2022-02-13 17:18:39 -03:00
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 ) ;
2022-02-16 12:12:46 -03:00
m_search_textbox - > on_return_pressed = [ this ] {
if ( ! m_unicode_block_listview - > selection ( ) . is_empty ( ) )
m_unicode_block_listview - > activate_selected ( ) ;
} ;
m_search_textbox - > on_down_pressed = [ this ] {
m_unicode_block_listview - > move_cursor ( GUI : : AbstractView : : CursorMovement : : Down , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
} ;
m_search_textbox - > on_up_pressed = [ this ] {
m_unicode_block_listview - > move_cursor ( GUI : : AbstractView : : CursorMovement : : Up , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
} ;
m_search_textbox - > on_change = [ this ] {
m_filter_model - > set_filter_term ( m_search_textbox - > text ( ) ) ;
if ( m_filter_model - > row_count ( ) ! = 0 )
m_unicode_block_listview - > set_cursor ( m_filter_model - > index ( 0 , 0 ) , GUI : : AbstractView : : SelectionUpdate : : Set ) ;
} ;
2021-09-22 10:25:25 -03:00
GUI : : Application : : the ( ) - > on_action_enter = [ this ] ( GUI : : Action & action ) {
2021-04-22 14:53:17 -03:00
auto text = action . status_tip ( ) ;
if ( text . is_empty ( ) )
text = Gfx : : parse_ampersand_string ( action . text ( ) ) ;
2021-09-22 10:25:25 -03:00
m_statusbar - > set_override_text ( move ( text ) ) ;
2021-04-22 14:53:17 -03:00
} ;
2021-09-22 10:25:25 -03:00
GUI : : Application : : the ( ) - > on_action_leave = [ this ] ( GUI : : Action & ) {
m_statusbar - > set_override_text ( { } ) ;
2021-04-22 14:53:17 -03:00
} ;
2021-11-14 10:26:10 -03:00
set_scale ( scale ) ;
2019-02-02 05:05:14 -02:00
}
2021-11-29 14:06:10 -03:00
void FontEditorWidget : : initialize ( String const & path , RefPtr < Gfx : : BitmapFont > & & edited_font )
2021-04-06 13:04:21 -03:00
{
if ( m_edited_font = = edited_font )
return ;
m_path = path ;
m_edited_font = edited_font ;
2022-01-09 14:29:06 -03:00
m_glyph_map_widget - > set_font ( * m_edited_font ) ;
2021-04-06 13:04:21 -03:00
m_glyph_editor_widget - > initialize ( * m_edited_font ) ;
2021-11-29 12:05:57 -03:00
did_resize_glyph_editor ( ) ;
2021-09-21 18:24:09 -03:00
2021-04-18 15:12:52 -03:00
m_glyph_editor_width_spinbox - > set_visible ( ! m_edited_font - > is_fixed_width ( ) ) ;
2021-09-21 18:24:09 -03:00
m_glyph_editor_width_spinbox - > set_max ( m_edited_font - > max_glyph_width ( ) , GUI : : AllowCallback : : No ) ;
2022-01-04 14:24:06 -03:00
m_glyph_editor_width_spinbox - > set_value ( m_edited_font - > raw_glyph_width ( m_glyph_map_widget - > active_glyph ( ) ) , GUI : : AllowCallback : : No ) ;
2021-09-21 18:24:09 -03:00
2021-04-18 15:12:52 -03:00
m_glyph_editor_present_checkbox - > set_visible ( m_edited_font - > is_fixed_width ( ) ) ;
2022-01-04 14:24:06 -03:00
m_glyph_editor_present_checkbox - > set_checked ( m_edited_font - > contains_raw_glyph ( m_glyph_map_widget - > active_glyph ( ) ) , GUI : : AllowCallback : : No ) ;
2021-09-21 18:24:09 -03:00
m_fixed_width_checkbox - > set_checked ( m_edited_font - > is_fixed_width ( ) , GUI : : AllowCallback : : No ) ;
2021-04-06 13:04:21 -03:00
2021-09-21 18:24:09 -03:00
m_name_textbox - > set_text ( m_edited_font - > name ( ) , GUI : : AllowCallback : : No ) ;
m_family_textbox - > set_text ( m_edited_font - > family ( ) , GUI : : AllowCallback : : No ) ;
2021-04-06 13:04:21 -03:00
2021-09-21 18:24:09 -03:00
m_presentation_spinbox - > set_value ( m_edited_font - > presentation_size ( ) , GUI : : AllowCallback : : No ) ;
m_spacing_spinbox - > set_value ( m_edited_font - > glyph_spacing ( ) , GUI : : AllowCallback : : No ) ;
2021-04-22 15:20:57 -03:00
2021-09-21 18:24:09 -03:00
m_mean_line_spinbox - > set_range ( 0 , max ( m_edited_font - > glyph_height ( ) - 2 , 0 ) , GUI : : AllowCallback : : No ) ;
m_baseline_spinbox - > set_range ( 0 , max ( m_edited_font - > glyph_height ( ) - 2 , 0 ) , GUI : : AllowCallback : : No ) ;
m_mean_line_spinbox - > set_value ( m_edited_font - > mean_line ( ) , GUI : : AllowCallback : : No ) ;
m_baseline_spinbox - > set_value ( m_edited_font - > baseline ( ) , GUI : : AllowCallback : : No ) ;
2021-04-06 13:04:21 -03:00
2021-04-10 14:40:59 -03:00
int i = 0 ;
2021-09-23 21:07:34 -03:00
for ( auto & it : Gfx : : font_weight_names ) {
if ( it . style = = m_edited_font - > weight ( ) ) {
2021-11-29 16:43:02 -03:00
m_weight_combobox - > set_selected_index ( i , GUI : : AllowCallback : : No ) ;
2021-04-10 14:40:59 -03:00
break ;
}
i + + ;
}
2021-09-23 21:07:34 -03:00
i = 0 ;
for ( auto & it : Gfx : : font_slope_names ) {
if ( it . style = = m_edited_font - > slope ( ) ) {
2021-11-29 16:43:02 -03:00
m_slope_combobox - > set_selected_index ( i , GUI : : AllowCallback : : No ) ;
2021-09-23 21:07:34 -03:00
break ;
}
i + + ;
}
2021-04-10 14:40:59 -03:00
2021-08-30 15:12:48 -03:00
deferred_invoke ( [ this ] {
2022-01-16 12:32:52 -03:00
auto glyph = m_glyph_map_widget - > active_glyph ( ) ;
2021-04-10 14:40:59 -03:00
m_glyph_map_widget - > set_focus ( true ) ;
2022-01-16 12:32:52 -03:00
m_glyph_map_widget - > scroll_to_glyph ( glyph ) ;
m_glyph_editor_widget - > set_glyph ( glyph ) ;
2021-04-26 10:11:14 -03:00
update_title ( ) ;
2021-04-10 14:40:59 -03:00
} ) ;
2021-04-07 13:05:02 -03:00
2021-04-22 15:12:53 -03:00
m_undo_stack = make < GUI : : UndoStack > ( ) ;
2022-01-04 14:24:06 -03:00
m_undo_glyph = adopt_ref ( * new UndoGlyph ( m_glyph_map_widget - > active_glyph ( ) , * m_edited_font ) ) ;
2021-08-26 18:25:02 -03:00
m_undo_stack - > on_state_change = [ this ] {
m_undo_action - > set_enabled ( m_undo_stack - > can_undo ( ) ) ;
m_redo_action - > set_enabled ( m_undo_stack - > can_redo ( ) ) ;
did_modify_font ( ) ;
} ;
2021-09-21 18:42:25 -03:00
m_undo_action - > set_enabled ( false ) ;
m_redo_action - > set_enabled ( false ) ;
2021-04-22 15:12:53 -03:00
2021-11-29 12:11:51 -03:00
update_statusbar ( ) ;
2021-04-06 13:04:21 -03:00
if ( on_initialize )
on_initialize ( ) ;
}
2021-07-21 16:21:03 -03:00
void FontEditorWidget : : initialize_menubar ( GUI : : Window & window )
2021-04-10 14:40:59 -03:00
{
2021-07-21 16:21:03 -03:00
auto & file_menu = window . add_menu ( " &File " ) ;
2021-05-01 05:45:39 -03:00
file_menu . add_action ( * m_new_action ) ;
file_menu . add_action ( * m_open_action ) ;
file_menu . add_action ( * m_save_action ) ;
file_menu . add_action ( * m_save_as_action ) ;
file_menu . add_separator ( ) ;
file_menu . add_action ( GUI : : CommonActions : : make_quit_action ( [ this ] ( auto & ) {
2021-04-26 10:11:14 -03:00
if ( ! request_close ( ) )
return ;
2021-04-10 14:40:59 -03:00
GUI : : Application : : the ( ) - > quit ( ) ;
} ) ) ;
2021-07-21 16:21:03 -03:00
auto & edit_menu = window . add_menu ( " &Edit " ) ;
2021-04-22 15:12:53 -03:00
edit_menu . add_action ( * m_undo_action ) ;
edit_menu . add_action ( * m_redo_action ) ;
edit_menu . add_separator ( ) ;
2021-04-10 14:40:59 -03:00
edit_menu . add_action ( * m_cut_action ) ;
edit_menu . add_action ( * m_copy_action ) ;
edit_menu . add_action ( * m_paste_action ) ;
edit_menu . add_action ( * m_delete_action ) ;
2021-09-09 08:43:19 -03:00
edit_menu . add_separator ( ) ;
2021-11-29 11:26:24 -03:00
edit_menu . add_action ( * m_copy_character_action ) ;
edit_menu . add_separator ( ) ;
2021-09-09 08:43:19 -03:00
edit_menu . add_action ( * m_previous_glyph_action ) ;
edit_menu . add_action ( * m_next_glyph_action ) ;
edit_menu . add_action ( * m_go_to_glyph_action ) ;
2021-04-10 14:40:59 -03:00
2021-07-21 16:21:03 -03:00
auto & view_menu = window . add_menu ( " &View " ) ;
2021-04-10 14:40:59 -03:00
view_menu . add_action ( * m_open_preview_action ) ;
view_menu . add_separator ( ) ;
view_menu . add_action ( * m_show_metadata_action ) ;
2022-02-13 17:18:39 -03:00
view_menu . add_action ( * m_show_unicode_blocks_action ) ;
2021-04-22 15:04:19 -03:00
view_menu . add_separator ( ) ;
auto & scale_menu = view_menu . add_submenu ( " &Scale " ) ;
scale_menu . add_action ( * m_scale_five_action ) ;
scale_menu . add_action ( * m_scale_ten_action ) ;
scale_menu . add_action ( * m_scale_fifteen_action ) ;
2021-04-10 14:40:59 -03:00
2021-07-21 16:21:03 -03:00
auto & help_menu = window . add_menu ( " &Help " ) ;
2021-04-10 14:40:59 -03:00
help_menu . add_action ( GUI : : CommonActions : : make_help_action ( [ ] ( auto & ) {
Desktop : : Launcher : : open ( URL : : create_with_file_protocol ( " /usr/share/man/man1/FontEditor.md " ) , " /bin/Help " ) ;
} ) ) ;
2021-07-21 16:21:03 -03:00
help_menu . add_action ( GUI : : CommonActions : : make_about_action ( " Font Editor " , GUI : : Icon : : default_icon ( " app-font-editor " ) , & window ) ) ;
2021-04-10 14:40:59 -03:00
}
2022-01-16 11:36:43 -03:00
bool FontEditorWidget : : save_file ( String const & path )
2020-07-10 22:20:39 -03:00
{
2021-09-09 08:41:08 -03:00
auto saved_font = m_edited_font - > masked_character_set ( ) ;
auto ret_val = saved_font - > write_to_file ( path ) ;
2020-07-10 22:20:39 -03:00
if ( ! ret_val ) {
2020-07-15 23:45:11 -03:00
GUI : : MessageBox : : show ( window ( ) , " The font file could not be saved. " , " Save failed " , GUI : : MessageBox : : Type : : Error ) ;
2020-07-10 22:20:39 -03:00
return false ;
}
m_path = path ;
2022-01-04 14:04:28 -03:00
m_undo_stack - > set_current_unmodified ( ) ;
2021-08-26 18:25:02 -03:00
window ( ) - > set_modified ( false ) ;
2021-04-26 10:11:14 -03:00
update_title ( ) ;
2020-07-10 22:20:39 -03:00
return true ;
}
2021-04-06 13:04:21 -03:00
void FontEditorWidget : : set_show_font_metadata ( bool show )
{
if ( m_font_metadata = = show )
return ;
m_font_metadata = show ;
m_font_metadata_groupbox - > set_visible ( m_font_metadata ) ;
}
2021-04-22 15:12:53 -03:00
2022-02-13 17:18:39 -03:00
void FontEditorWidget : : set_show_unicode_blocks ( bool show )
{
if ( m_unicode_blocks = = show )
return ;
m_unicode_blocks = show ;
2022-02-16 12:14:06 -03:00
m_unicode_block_container - > set_visible ( m_unicode_blocks ) ;
2022-02-13 17:18:39 -03:00
}
2021-11-29 11:07:47 -03:00
bool FontEditorWidget : : open_file ( String const & path )
2021-10-13 17:13:48 -03:00
{
auto bitmap_font = Gfx : : BitmapFont : : load_from_file ( path ) ;
if ( ! bitmap_font ) {
String message = String : : formatted ( " Couldn't load font: {} \n " , path ) ;
GUI : : MessageBox : : show ( window ( ) , message , " Font Editor " , GUI : : MessageBox : : Type : : Error ) ;
2021-11-29 11:07:47 -03:00
return false ;
2021-10-13 17:13:48 -03:00
}
2021-11-29 11:07:47 -03:00
auto new_font = bitmap_font - > unmasked_character_set ( ) ;
2021-10-13 17:13:48 -03:00
window ( ) - > set_modified ( false ) ;
initialize ( path , move ( new_font ) ) ;
2021-11-29 11:07:47 -03:00
return true ;
2021-10-13 17:13:48 -03:00
}
2021-04-22 15:12:53 -03:00
void FontEditorWidget : : undo ( )
{
if ( ! m_undo_stack - > can_undo ( ) )
return ;
m_undo_stack - > undo ( ) ;
2021-09-21 18:42:25 -03:00
auto glyph = m_undo_glyph - > restored_code_point ( ) ;
auto glyph_width = m_undo_glyph - > restored_width ( ) ;
2022-01-04 14:24:06 -03:00
m_glyph_map_widget - > set_active_glyph ( glyph ) ;
2021-09-21 18:42:25 -03:00
m_glyph_map_widget - > scroll_to_glyph ( glyph ) ;
if ( m_edited_font - > is_fixed_width ( ) ) {
m_glyph_editor_present_checkbox - > set_checked ( glyph_width > 0 , GUI : : AllowCallback : : No ) ;
} else {
m_glyph_editor_width_spinbox - > set_value ( glyph_width , GUI : : AllowCallback : : No ) ;
}
2022-01-04 14:24:06 -03:00
m_edited_font - > set_glyph_width ( m_glyph_map_widget - > active_glyph ( ) , glyph_width ) ;
2021-04-22 15:12:53 -03:00
m_glyph_editor_widget - > update ( ) ;
2021-09-21 18:42:25 -03:00
m_glyph_map_widget - > update_glyph ( glyph ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
update_statusbar ( ) ;
2021-04-22 15:12:53 -03:00
}
void FontEditorWidget : : redo ( )
{
if ( ! m_undo_stack - > can_redo ( ) )
return ;
m_undo_stack - > redo ( ) ;
2021-09-21 18:42:25 -03:00
auto glyph = m_undo_glyph - > restored_code_point ( ) ;
auto glyph_width = m_undo_glyph - > restored_width ( ) ;
2022-01-04 14:24:06 -03:00
m_glyph_map_widget - > set_active_glyph ( glyph ) ;
2021-09-21 18:42:25 -03:00
m_glyph_map_widget - > scroll_to_glyph ( glyph ) ;
if ( m_edited_font - > is_fixed_width ( ) ) {
m_glyph_editor_present_checkbox - > set_checked ( glyph_width > 0 , GUI : : AllowCallback : : No ) ;
} else {
m_glyph_editor_width_spinbox - > set_value ( glyph_width , GUI : : AllowCallback : : No ) ;
}
2022-01-04 14:24:06 -03:00
m_edited_font - > set_glyph_width ( m_glyph_map_widget - > active_glyph ( ) , glyph_width ) ;
2021-04-22 15:12:53 -03:00
m_glyph_editor_widget - > update ( ) ;
2021-09-21 18:42:25 -03:00
m_glyph_map_widget - > update_glyph ( glyph ) ;
2021-09-22 10:25:25 -03:00
update_preview ( ) ;
update_statusbar ( ) ;
2021-04-22 15:12:53 -03:00
}
2021-04-26 10:11:14 -03:00
bool FontEditorWidget : : request_close ( )
{
2021-08-26 18:25:02 -03:00
if ( ! window ( ) - > is_modified ( ) )
2021-04-26 10:11:14 -03:00
return true ;
2022-01-04 14:04:28 -03:00
auto result = GUI : : MessageBox : : ask_about_unsaved_changes ( window ( ) , m_path , m_undo_stack - > last_unmodified_timestamp ( ) ) ;
2021-04-26 10:11:14 -03:00
if ( result = = GUI : : MessageBox : : ExecYes ) {
m_save_action - > activate ( ) ;
2021-08-26 18:25:02 -03:00
if ( ! window ( ) - > is_modified ( ) )
2021-04-26 10:11:14 -03:00
return true ;
}
if ( result = = GUI : : MessageBox : : ExecNo )
return true ;
return false ;
}
void FontEditorWidget : : update_title ( )
{
StringBuilder title ;
2021-08-26 18:25:02 -03:00
if ( m_path . is_empty ( ) )
title . append ( " Untitled " ) ;
else
title . append ( m_path ) ;
title . append ( " [*] - Font Editor " ) ;
window ( ) - > set_title ( title . to_string ( ) ) ;
2021-04-26 10:11:14 -03:00
}
void FontEditorWidget : : did_modify_font ( )
{
2021-08-26 18:25:02 -03:00
if ( ! window ( ) | | window ( ) - > is_modified ( ) )
2021-04-26 10:11:14 -03:00
return ;
2021-08-26 18:25:02 -03:00
window ( ) - > set_modified ( true ) ;
2021-04-26 10:11:14 -03:00
update_title ( ) ;
2021-04-22 15:12:53 -03:00
}
2021-09-22 10:25:25 -03:00
void FontEditorWidget : : update_statusbar ( )
{
2022-01-04 14:24:06 -03:00
auto glyph = m_glyph_map_widget - > active_glyph ( ) ;
2021-09-22 10:25:25 -03:00
StringBuilder builder ;
builder . appendff ( " U+{:04X} ( " , glyph ) ;
2022-01-18 10:32:39 -03:00
if ( auto abbreviation = Unicode : : code_point_abbreviation ( glyph ) ; abbreviation . has_value ( ) ) {
builder . append ( * abbreviation ) ;
2021-09-22 10:25:25 -03:00
} else if ( Gfx : : get_char_bidi_class ( glyph ) = = Gfx : : BidirectionalClass : : STRONG_RTL ) {
// FIXME: This is a necessary hack, as RTL text will mess up the painting of the statusbar text.
// For now, replace RTL glyphs with U+FFFD, the replacement character.
builder . append_code_point ( 0xFFFD ) ;
} else {
builder . append_code_point ( glyph ) ;
}
builder . append ( " ) " ) ;
2021-11-18 19:39:54 -03:00
auto glyph_name = Unicode : : code_point_display_name ( glyph ) ;
if ( glyph_name . has_value ( ) ) {
builder . appendff ( " {} " , glyph_name . value ( ) ) ;
}
2021-11-29 10:43:32 -03:00
if ( m_edited_font - > contains_raw_glyph ( glyph ) )
2021-09-22 10:25:25 -03:00
builder . appendff ( " [{}x{}] " , m_edited_font - > raw_glyph_width ( glyph ) , m_edited_font - > glyph_height ( ) ) ;
2021-11-14 09:27:17 -03:00
else if ( Gfx : : Emoji : : emoji_for_code_point ( glyph ) )
builder . appendff ( " [emoji] " ) ;
2021-09-22 10:25:25 -03:00
m_statusbar - > set_text ( builder . to_string ( ) ) ;
}
void FontEditorWidget : : update_preview ( )
{
if ( m_font_preview_window )
m_font_preview_window - > update ( ) ;
}
2021-10-13 17:16:25 -03:00
void FontEditorWidget : : drop_event ( GUI : : DropEvent & event )
{
event . accept ( ) ;
if ( event . mime_data ( ) . has_urls ( ) ) {
auto urls = event . mime_data ( ) . urls ( ) ;
if ( urls . is_empty ( ) )
return ;
window ( ) - > move_to_front ( ) ;
if ( ! request_close ( ) )
return ;
open_file ( urls . first ( ) . path ( ) ) ;
}
}
2021-11-29 12:05:57 -03:00
void FontEditorWidget : : did_resize_glyph_editor ( )
{
2022-01-09 14:41:13 -03:00
constexpr int button_width = 22 ;
constexpr int buttons_per_bar = 4 ;
constexpr int spacing = ( buttons_per_bar - 1 ) * 2 + 10 ;
constexpr int glyph_toolbars_width = button_width * buttons_per_bar + spacing ;
2021-11-29 12:05:57 -03:00
m_glyph_editor_container - > set_fixed_size ( m_glyph_editor_widget - > preferred_width ( ) , m_glyph_editor_widget - > preferred_height ( ) ) ;
m_left_column_container - > set_fixed_width ( max ( m_glyph_editor_widget - > preferred_width ( ) , glyph_toolbars_width ) ) ;
}
2021-11-14 10:26:10 -03:00
void FontEditorWidget : : set_scale ( i32 scale )
{
m_glyph_editor_widget - > set_scale ( scale ) ;
}
void FontEditorWidget : : set_scale_and_save ( i32 scale )
{
set_scale ( scale ) ;
Config : : write_i32 ( " FontEditor " , " GlyphEditor " , " Scale " , scale ) ;
did_resize_glyph_editor ( ) ;
}
2022-01-16 11:36:43 -03:00
2022-01-04 14:24:06 -03:00
void FontEditorWidget : : copy_selected_glyphs ( )
{
ByteBuffer buffer ;
int first_glyph = - 1 ;
size_t glyph_count = 0 ;
auto append_glyph_to_buffer = [ & ] ( int glyph ) {
if ( ! edited_font ( ) . contains_glyph ( glyph ) )
return ;
if ( first_glyph = = - 1 | | glyph < first_glyph )
first_glyph = glyph ;
buffer . append ( { ( char * ) & glyph , sizeof ( int ) } ) ;
auto bitmap = edited_font ( ) . raw_glyph ( glyph ) . glyph_bitmap ( ) ;
buffer . append ( ( u8 ) bitmap . width ( ) ) ;
buffer . append ( ( u8 ) bitmap . height ( ) ) ;
for ( int x = 0 ; x < bitmap . width ( ) ; x + + ) {
for ( int y = 0 ; y < bitmap . height ( ) ; y + + )
buffer . append ( bitmap . bit_at ( x , y ) ) ;
}
glyph_count + + ;
} ;
auto selection = m_glyph_map_widget - > selection ( ) . normalized ( ) ;
for ( int i = selection . start ( ) ; i < selection . start ( ) + selection . size ( ) ; i + + )
append_glyph_to_buffer ( i ) ;
HashMap < String , String > metadata ;
metadata . set ( " first_glyph " , String : : number ( first_glyph ) ) ;
metadata . set ( " count " , String : : number ( glyph_count ) ) ;
GUI : : Clipboard : : the ( ) . set_data ( buffer . bytes ( ) , " glyph/x-fonteditor " , metadata ) ;
}
void FontEditorWidget : : cut_selected_glyphs ( )
{
copy_selected_glyphs ( ) ;
delete_selected_glyphs ( ) ;
}
void FontEditorWidget : : paste_glyphs ( )
{
auto [ data , mime_type , metadata ] = GUI : : Clipboard : : the ( ) . fetch_data_and_type ( ) ;
if ( ! mime_type . starts_with ( " glyph/ " ) )
return ;
;
auto glyph_count = metadata . get ( " count " ) . value_or ( " 0 " ) . to_uint ( ) . value_or ( 0 ) ;
if ( glyph_count = = 0 )
return ;
// FIXME: This is a hack to avoid regression and still doesn't support
// multiple glyphs. It should have done proper undo stack integration.
if ( glyph_count = = 1 ) {
if ( m_glyph_editor_widget - > on_undo_event )
m_glyph_editor_widget - > on_undo_event ( ) ;
}
auto first_glyph = metadata . get ( " first_glyph " ) . value_or ( " 0 " ) . to_uint ( ) . value_or ( 0 ) ;
InputMemoryStream stream ( data . bytes ( ) ) ;
for ( size_t s = 0 ; s < glyph_count ; s + + ) {
int copied_glyph { } ;
char width { } ;
char height { } ;
stream > > Bytes { ( char * ) & copied_glyph , sizeof ( int ) } > > width > > height ;
if ( stream . has_any_error ( ) ) {
dbgln ( " Failed to read glyph from clipboard, aborting! " ) ;
return ;
}
int glyph = m_glyph_map_widget - > active_glyph ( ) + ( copied_glyph - first_glyph ) ;
auto bitmap = edited_font ( ) . raw_glyph ( glyph ) . glyph_bitmap ( ) ;
m_edited_font - > set_glyph_width ( glyph , min ( width , edited_font ( ) . max_glyph_width ( ) ) ) ;
for ( int x = 0 ; x < min ( width , edited_font ( ) . max_glyph_width ( ) ) ; x + + ) {
for ( int y = 0 ; y < min ( height , edited_font ( ) . glyph_height ( ) ) ; y + + ) {
char byte ;
stream > > byte ;
if ( stream . has_any_error ( ) ) {
dbgln ( " Failed to read glyph from clipboard, aborting! " ) ;
return ;
}
bitmap . set_bit_at ( x , y , byte ) ;
}
}
if ( m_glyph_editor_widget - > on_glyph_altered )
m_glyph_editor_widget - > on_glyph_altered ( glyph ) ;
}
2022-01-09 15:02:16 -03:00
if ( m_edited_font - > is_fixed_width ( ) )
m_glyph_editor_present_checkbox - > set_checked ( m_edited_font - > contains_raw_glyph ( m_glyph_map_widget - > active_glyph ( ) ) , GUI : : AllowCallback : : No ) ;
else
m_glyph_editor_width_spinbox - > set_value ( m_edited_font - > raw_glyph_width ( m_glyph_map_widget - > active_glyph ( ) ) , GUI : : AllowCallback : : No ) ;
2022-01-04 14:24:06 -03:00
m_glyph_editor_widget - > update ( ) ;
2022-01-09 15:02:16 -03:00
update_statusbar ( ) ;
2022-01-04 14:24:06 -03:00
}
void FontEditorWidget : : delete_selected_glyphs ( )
{
auto delete_glyph = [ & ] ( int glyph ) {
auto bitmap = m_edited_font - > raw_glyph ( glyph ) . glyph_bitmap ( ) ;
m_edited_font - > set_glyph_width ( glyph , 0 ) ;
for ( int x = 0 ; x < m_edited_font - > max_glyph_width ( ) ; x + + )
for ( int y = 0 ; y < m_edited_font - > glyph_height ( ) ; y + + )
bitmap . set_bit_at ( x , y , false ) ;
if ( m_glyph_editor_widget - > on_glyph_altered )
m_glyph_editor_widget - > on_glyph_altered ( glyph ) ;
} ;
auto selection = m_glyph_map_widget - > selection ( ) . normalized ( ) ;
// FIXME: This is a hack to avoid regression and still doesn't support
// multiple glyphs. It should have done proper undo stack integration.
if ( selection . size ( ) = = 1 ) {
if ( m_glyph_editor_widget - > on_undo_event )
m_glyph_editor_widget - > on_undo_event ( ) ;
}
for ( int i = selection . start ( ) ; i < selection . start ( ) + selection . size ( ) ; i + + )
delete_glyph ( i ) ;
2022-01-09 15:02:16 -03:00
if ( m_edited_font - > is_fixed_width ( ) )
m_glyph_editor_present_checkbox - > set_checked ( false , GUI : : AllowCallback : : No ) ;
else
m_glyph_editor_width_spinbox - > set_value ( 0 , GUI : : AllowCallback : : No ) ;
m_glyph_editor_widget - > update ( ) ;
update_statusbar ( ) ;
2022-01-04 14:24:06 -03:00
}