2020-01-18 08:38:21 +00:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2021-09-13 20:48:22 +00:00
* Copyright ( c ) 2021 , Mustafa Quraish < mustafa @ serenityos . org >
2022-02-10 19:28:48 +00:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2022-03-03 14:26:38 +00:00
* Copyright ( c ) 2022 , Timothy Slater < tslater2006 @ gmail . com >
2024-01-29 19:57:27 +00:00
* Copyright ( c ) 2024 , Sam Atkins < atkinssj @ serenityos . org >
2020-01-18 08:38:21 +00:00
*
2021-04-22 08:24:48 +00:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 08:38:21 +00:00
*/
2019-10-12 23:08:12 +00:00
# include "HexEditorWidget.h"
2021-01-22 20:22:00 +00:00
# include "FindDialog.h"
2021-05-23 08:36:30 +00:00
# include "GoToOffsetDialog.h"
2021-05-27 15:51:21 +00:00
# include "SearchResultsModel.h"
2022-03-03 14:26:38 +00:00
# include "ValueInspectorModel.h"
2022-04-12 17:12:03 +00:00
# include <AK/Forward.h>
2019-10-12 23:08:12 +00:00
# include <AK/Optional.h>
# include <AK/StringBuilder.h>
2021-08-27 00:07:43 +00:00
# include <LibConfig/Client.h>
2022-08-28 19:36:15 +00:00
# include <LibDesktop/Launcher.h>
2021-09-02 11:03:44 +00:00
# include <LibFileSystemAccessClient/Client.h>
2020-02-06 19:33:02 +00:00
# include <LibGUI/Action.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
# include <LibGUI/FilePicker.h>
# include <LibGUI/InputBox.h>
2024-05-04 16:12:16 +00:00
# include <LibGUI/ItemListModel.h>
2020-02-15 00:56:30 +00:00
# include <LibGUI/Menu.h>
2021-04-13 14:18:20 +00:00
# include <LibGUI/Menubar.h>
2020-02-06 19:33:02 +00:00
# include <LibGUI/MessageBox.h>
2021-05-27 15:51:21 +00:00
# include <LibGUI/Model.h>
2024-01-29 19:57:27 +00:00
# include <LibGUI/SortingProxyModel.h>
2021-04-13 14:18:20 +00:00
# include <LibGUI/Statusbar.h>
2021-05-27 15:51:21 +00:00
# include <LibGUI/TableView.h>
2020-02-06 19:33:02 +00:00
# include <LibGUI/TextBox.h>
2021-04-13 14:18:20 +00:00
# include <LibGUI/Toolbar.h>
2021-05-22 14:08:05 +00:00
# include <LibGUI/ToolbarContainer.h>
2022-04-26 13:14:05 +00:00
# include <LibTextCodec/Decoder.h>
2020-03-08 11:05:14 +00:00
# include <string.h>
2019-10-12 23:08:12 +00:00
2023-09-28 15:06:49 +00:00
namespace HexEditor {
2021-05-22 14:08:05 +00:00
2023-09-28 15:06:49 +00:00
ErrorOr < NonnullRefPtr < HexEditorWidget > > HexEditorWidget : : create ( )
2019-10-12 23:08:12 +00:00
{
2023-09-28 15:06:49 +00:00
auto widget = TRY ( try_create ( ) ) ;
TRY ( widget - > setup ( ) ) ;
return widget ;
}
2021-05-24 14:56:27 +00:00
2023-09-28 15:06:49 +00:00
ErrorOr < void > HexEditorWidget : : setup ( )
{
2021-05-22 14:08:05 +00:00
m_toolbar = * find_descendant_of_type_named < GUI : : Toolbar > ( " toolbar " ) ;
m_toolbar_container = * find_descendant_of_type_named < GUI : : ToolbarContainer > ( " toolbar_container " ) ;
2023-09-28 15:06:49 +00:00
m_editor = * find_descendant_of_type_named < : : HexEditor : : HexEditor > ( " editor " ) ;
2021-05-22 14:08:05 +00:00
m_statusbar = * find_descendant_of_type_named < GUI : : Statusbar > ( " statusbar " ) ;
2024-01-29 19:57:27 +00:00
m_annotations = * find_descendant_of_type_named < GUI : : TableView > ( " annotations " ) ;
m_annotations_container = * find_descendant_of_type_named < GUI : : Widget > ( " annotations_container " ) ;
2021-05-27 15:51:21 +00:00
m_search_results = * find_descendant_of_type_named < GUI : : TableView > ( " search_results " ) ;
m_search_results_container = * find_descendant_of_type_named < GUI : : Widget > ( " search_results_container " ) ;
2024-02-06 16:08:14 +00:00
m_side_panel_container = * find_descendant_of_type_named < GUI : : DynamicWidgetContainer > ( " side_panel_container " ) ;
2022-03-03 14:26:38 +00:00
m_value_inspector_container = * find_descendant_of_type_named < GUI : : Widget > ( " value_inspector_container " ) ;
m_value_inspector = * find_descendant_of_type_named < GUI : : TableView > ( " value_inspector " ) ;
2024-05-04 16:12:16 +00:00
m_value_inspector_endianness = * find_descendant_of_type_named < GUI : : ComboBox > ( " value_inspector_endianness " ) ;
2022-03-03 14:26:38 +00:00
2024-02-06 16:08:14 +00:00
// FIXME: Set this in GML once the compiler supports it.
m_side_panel_container - > set_container_with_individual_order ( true ) ;
2024-05-04 16:12:16 +00:00
// TODO: Switch to regular strings once ComboBox::on_change passes a String
m_endianness_options . append ( " Little Endian " ) ;
m_endianness_options . append ( " Big Endian " ) ;
m_value_inspector_endianness - > set_model ( GUI : : ItemListModel < ByteString > : : create ( m_endianness_options ) ) ;
m_value_inspector_endianness - > set_only_allow_values_from_model ( true ) ;
m_value_inspector_endianness - > on_change = [ this ] ( ByteString const & value , GUI : : ModelIndex const & ) {
set_inspector_little_endian ( value = = " Little Endian " ) ;
} ;
2022-03-03 14:26:38 +00:00
m_value_inspector - > on_activation = [ this ] ( GUI : : ModelIndex const & index ) {
if ( ! index . is_valid ( ) )
return ;
m_selecting_from_inspector = true ;
2022-04-12 16:55:27 +00:00
m_editor - > set_selection ( m_editor - > selection_start_offset ( ) , index . data ( GUI : : ModelRole : : Custom ) . to_integer < size_t > ( ) ) ;
2022-03-03 14:26:38 +00:00
m_editor - > update ( ) ;
} ;
2019-10-12 23:08:12 +00:00
2024-01-09 20:19:45 +00:00
m_editor - > on_status_change = [ this ] ( int position , HexEditor : : HexEditor : : EditMode edit_mode , auto selection ) {
2023-06-04 08:24:38 +00:00
m_statusbar - > set_text ( 0 , String : : formatted ( " Offset: {:#08X} " , position ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2023-09-28 15:06:49 +00:00
m_statusbar - > set_text ( 1 , String : : formatted ( " Edit Mode: {} " , edit_mode = = HexEditor : : HexEditor : : EditMode : : Hex ? " Hex " : " Text " ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2024-01-09 20:19:45 +00:00
m_statusbar - > set_text ( 2 , String : : formatted ( " Selection Start: {} " , selection . start ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
m_statusbar - > set_text ( 3 , String : : formatted ( " Selection End: {} " , selection . end ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
m_statusbar - > set_text ( 4 , String : : formatted ( " Selected Bytes: {} " , selection . size ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-03-13 17:47:33 +00:00
bool has_selection = m_editor - > has_selection ( ) ;
m_copy_hex_action - > set_enabled ( has_selection ) ;
m_copy_text_action - > set_enabled ( has_selection ) ;
m_copy_as_c_code_action - > set_enabled ( has_selection ) ;
m_fill_selection_action - > set_enabled ( has_selection ) ;
2022-03-03 14:26:38 +00:00
if ( m_value_inspector_container - > is_visible ( ) & & ! m_selecting_from_inspector ) {
2024-01-09 20:19:45 +00:00
update_inspector_values ( selection . start ) ;
2022-03-03 14:26:38 +00:00
}
m_selecting_from_inspector = false ;
2019-10-12 23:08:12 +00:00
} ;
2022-09-05 21:49:35 +00:00
m_editor - > on_change = [ this ] ( bool is_document_dirty ) {
window ( ) - > set_modified ( is_document_dirty ) ;
2019-10-12 23:08:12 +00:00
} ;
2022-09-07 21:41:37 +00:00
m_editor - > undo_stack ( ) . on_state_change = [ this ] {
m_undo_action - > set_enabled ( m_editor - > undo_stack ( ) . can_undo ( ) ) ;
m_redo_action - > set_enabled ( m_editor - > undo_stack ( ) . can_redo ( ) ) ;
} ;
2024-01-29 19:57:27 +00:00
initialize_annotations_model ( ) ;
m_annotations - > set_activates_on_selection ( true ) ;
m_annotations - > on_activation = [ this ] ( GUI : : ModelIndex const & index ) {
if ( ! index . is_valid ( ) )
return ;
auto start_offset = m_annotations - > model ( ) - > data ( index , ( GUI : : ModelRole ) AnnotationsModel : : CustomRole : : StartOffset ) . to_integer < size_t > ( ) ;
m_editor - > set_position ( start_offset ) ;
m_editor - > update ( ) ;
} ;
2024-02-06 16:57:02 +00:00
m_annotations_context_menu = GUI : : Menu : : construct ( ) ;
m_edit_annotation_action = GUI : : Action : : create (
" &Edit Annotation " ,
TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/annotation.png " sv ) ) ,
[ this ] ( GUI : : Action & ) {
if ( m_annotations - > selection ( ) . is_empty ( ) )
return ;
auto index = m_annotations_sorting_model - > map_to_source ( m_annotations - > selection ( ) . first ( ) ) ;
auto & annotation = m_editor - > document ( ) . annotations ( ) . get_annotation ( index ) . value ( ) ;
m_editor - > show_edit_annotation_dialog ( annotation ) ;
} ,
this ) ;
m_annotations_context_menu - > add_action ( * m_edit_annotation_action ) ;
m_delete_annotation_action = GUI : : Action : : create (
" &Delete Annotation " ,
TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/annotation-remove.png " sv ) ) ,
[ this ] ( GUI : : Action & ) {
if ( m_annotations - > selection ( ) . is_empty ( ) )
return ;
auto index = m_annotations_sorting_model - > map_to_source ( m_annotations - > selection ( ) . first ( ) ) ;
auto & annotation = m_editor - > document ( ) . annotations ( ) . get_annotation ( index ) . value ( ) ;
m_editor - > show_delete_annotation_dialog ( annotation ) ;
} ,
this ) ;
m_annotations_context_menu - > add_action ( * m_delete_annotation_action ) ;
m_annotations - > on_context_menu_request = [ this ] ( GUI : : ModelIndex const & index , GUI : : ContextMenuEvent const & event ) {
if ( index . is_valid ( ) )
m_annotations_context_menu - > popup ( event . screen_position ( ) ) ;
} ;
2021-05-27 15:51:21 +00:00
m_search_results - > set_activates_on_selection ( true ) ;
m_search_results - > on_activation = [ this ] ( const GUI : : ModelIndex & index ) {
if ( ! index . is_valid ( ) )
return ;
auto offset = index . data ( GUI : : ModelRole : : Custom ) . to_i32 ( ) ;
m_last_found_index = offset ;
m_editor - > set_position ( offset ) ;
m_editor - > update ( ) ;
} ;
2024-02-07 20:43:43 +00:00
m_new_action = GUI : : Action : : create ( " New... " , { Mod_Ctrl , Key_N } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/new.png " sv ) ) , [ this ] ( const GUI : : Action & ) {
2023-04-16 20:02:07 +00:00
String value ;
2023-05-22 17:07:09 +00:00
if ( request_close ( ) & & GUI : : InputBox : : show ( window ( ) , value , " Enter a size: " sv , " New File " sv , GUI : : InputType : : NonemptyText ) = = GUI : : InputBox : : ExecResult : : OK ) {
2023-04-16 20:02:07 +00:00
auto file_size = AK : : StringUtils : : convert_to_uint ( value ) ;
2022-07-28 07:34:15 +00:00
if ( ! file_size . has_value ( ) ) {
2022-07-11 17:32:29 +00:00
GUI : : MessageBox : : show ( window ( ) , " Invalid file size entered. " sv , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2022-07-28 07:34:15 +00:00
return ;
2019-10-26 20:31:43 +00:00
}
2022-07-28 07:34:15 +00:00
if ( auto error = m_editor - > open_new_file ( file_size . value ( ) ) ; error . is_error ( ) ) {
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to open new file: {} " sv , error . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2022-07-28 07:34:15 +00:00
return ;
}
set_path ( { } ) ;
2024-01-29 19:57:27 +00:00
initialize_annotations_model ( ) ;
2022-07-28 07:34:15 +00:00
window ( ) - > set_modified ( false ) ;
2019-10-26 20:31:43 +00:00
}
} ) ;
2020-02-02 14:07:41 +00:00
m_open_action = GUI : : CommonActions : : make_open_action ( [ this ] ( auto & ) {
2022-02-14 12:03:42 +00:00
if ( ! request_close ( ) )
2019-10-12 23:08:12 +00:00
return ;
2023-05-28 11:38:15 +00:00
auto response = FileSystemAccessClient : : Client : : the ( ) . open_file ( window ( ) , { . requested_access = Core : : File : : OpenMode : : ReadWrite } ) ;
2022-02-14 12:03:42 +00:00
if ( response . is_error ( ) )
2022-02-14 14:29:24 +00:00
return ;
2021-06-23 07:19:58 +00:00
2023-01-15 02:47:50 +00:00
open_file ( response . value ( ) . filename ( ) , response . value ( ) . release_stream ( ) ) ;
2019-10-12 23:08:12 +00:00
} ) ;
2020-11-01 21:32:27 +00:00
m_save_action = GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
2021-09-02 11:03:44 +00:00
if ( m_path . is_empty ( ) )
return m_save_as_action - > activate ( ) ;
2023-01-15 03:09:43 +00:00
if ( auto result = m_editor - > save ( ) ; result . is_error ( ) ) {
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to save file: {} \n " sv , result . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2021-09-02 11:03:44 +00:00
} else {
2022-02-14 15:20:13 +00:00
window ( ) - > set_modified ( false ) ;
2021-11-10 00:37:10 +00:00
m_editor - > update ( ) ;
2021-09-02 11:03:44 +00:00
}
return ;
2019-10-12 23:08:12 +00:00
} ) ;
2020-11-01 21:32:27 +00:00
m_save_as_action = GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
2023-02-09 02:02:46 +00:00
auto response = FileSystemAccessClient : : Client : : the ( ) . save_file ( window ( ) , m_name , m_extension , Core : : File : : OpenMode : : ReadWrite | Core : : File : : OpenMode : : Truncate ) ;
2022-01-16 08:41:34 +00:00
if ( response . is_error ( ) )
2019-10-12 23:08:12 +00:00
return ;
2022-01-16 08:41:34 +00:00
auto file = response . release_value ( ) ;
2023-01-15 03:04:20 +00:00
if ( auto result = m_editor - > save_as ( file . release_stream ( ) ) ; result . is_error ( ) ) {
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to save file: {} \n " sv , result . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
2019-10-12 23:08:12 +00:00
return ;
}
2022-02-14 15:20:13 +00:00
window ( ) - > set_modified ( false ) ;
2023-01-15 02:47:50 +00:00
set_path ( file . filename ( ) ) ;
dbgln ( " Wrote document to {} " , file . filename ( ) ) ;
2019-10-12 23:08:12 +00:00
} ) ;
2024-02-07 20:43:43 +00:00
m_open_annotations_action = GUI : : Action : : create ( " Load Annotations... " , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/open.png " sv ) ) , [ this ] ( auto & ) {
2024-01-30 12:33:36 +00:00
auto response = FileSystemAccessClient : : Client : : the ( ) . open_file ( window ( ) ,
{ . window_title = " Load annotations file " sv ,
. requested_access = Core : : File : : OpenMode : : Read ,
. allowed_file_types = { { GUI : : FileTypeFilter { " Annotations files " , { { " annotations " } } } , GUI : : FileTypeFilter : : all_files ( ) } } } ) ;
if ( response . is_error ( ) )
return ;
auto result = m_editor - > document ( ) . annotations ( ) . load_from_file ( response . value ( ) . stream ( ) ) ;
if ( result . is_error ( ) ) {
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to load annotations: {} \n " sv , result . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
m_annotations_path = response . value ( ) . filename ( ) ;
} ) ;
m_open_annotations_action - > set_status_tip ( " Load annotations from a file " _string ) ;
2024-02-07 20:43:43 +00:00
m_save_annotations_action = GUI : : Action : : create ( " Save Annotations " , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/save.png " sv ) ) , [ & ] ( auto & ) {
2024-01-30 12:33:36 +00:00
if ( m_annotations_path . is_empty ( ) )
return m_save_annotations_as_action - > activate ( ) ;
auto response = FileSystemAccessClient : : Client : : the ( ) . request_file ( window ( ) , m_annotations_path , Core : : File : : OpenMode : : Write | Core : : File : : OpenMode : : Truncate ) ;
if ( response . is_error ( ) )
return ;
auto file = response . release_value ( ) ;
if ( auto result = m_editor - > document ( ) . annotations ( ) . save_to_file ( file . stream ( ) ) ; result . is_error ( ) ) {
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to save annotations file: {} \n " sv , result . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
}
} ) ;
m_save_annotations_action - > set_status_tip ( " Save annotations to a file " _string ) ;
2024-02-07 20:43:43 +00:00
m_save_annotations_as_action = GUI : : Action : : create ( " Save Annotations As... " , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/save-as.png " sv ) ) , [ & ] ( auto & ) {
2024-01-30 12:33:36 +00:00
auto response = FileSystemAccessClient : : Client : : the ( ) . save_file ( window ( ) , m_name , " annotations " sv , Core : : File : : OpenMode : : Write | Core : : File : : OpenMode : : Truncate ) ;
if ( response . is_error ( ) )
return ;
auto file = response . release_value ( ) ;
if ( auto result = m_editor - > document ( ) . annotations ( ) . save_to_file ( file . stream ( ) ) ; result . is_error ( ) ) {
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to save annotations file: {} \n " sv , result . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
}
} ) ;
m_save_annotations_as_action - > set_status_tip ( " Save annotations to a file with a new name " _string ) ;
2022-09-07 21:41:37 +00:00
m_undo_action = GUI : : CommonActions : : make_undo_action ( [ & ] ( auto & ) {
m_editor - > undo ( ) ;
} ) ;
m_undo_action - > set_enabled ( false ) ;
m_redo_action = GUI : : CommonActions : : make_redo_action ( [ & ] ( auto & ) {
m_editor - > redo ( ) ;
} ) ;
m_redo_action - > set_enabled ( false ) ;
2024-02-07 20:43:43 +00:00
m_find_action = GUI : : Action : : create ( " &Find... " , { Mod_Ctrl , Key_F } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " sv ) ) , [ & ] ( const GUI : : Action & ) {
2021-05-22 14:08:05 +00:00
auto old_buffer = m_search_buffer ;
2021-05-27 15:51:21 +00:00
bool find_all = false ;
2022-05-13 12:10:27 +00:00
if ( FindDialog : : show ( window ( ) , m_search_text , m_search_buffer , find_all ) = = GUI : : InputBox : : ExecResult : : OK ) {
2021-05-27 15:51:21 +00:00
if ( find_all ) {
auto matches = m_editor - > find_all ( m_search_buffer , 0 ) ;
m_search_results - > set_model ( * new SearchResultsModel ( move ( matches ) ) ) ;
m_search_results - > update ( ) ;
if ( matches . is_empty ( ) ) {
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Pattern \" {} \" not found in this file " , m_search_text ) , " Not Found " sv , GUI : : MessageBox : : Type : : Warning ) ;
2021-05-27 15:51:21 +00:00
return ;
}
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Found {} matches for \" {} \" in this file " , matches . size ( ) , m_search_text ) , ByteString : : formatted ( " {} Matches " , matches . size ( ) ) , GUI : : MessageBox : : Type : : Warning ) ;
2021-05-27 15:51:21 +00:00
set_search_results_visible ( true ) ;
} else {
bool same_buffers = false ;
if ( old_buffer . size ( ) = = m_search_buffer . size ( ) ) {
if ( memcmp ( old_buffer . data ( ) , m_search_buffer . data ( ) , old_buffer . size ( ) ) = = 0 )
same_buffers = true ;
}
2021-05-22 14:08:05 +00:00
2021-05-27 15:51:21 +00:00
auto result = m_editor - > find_and_highlight ( m_search_buffer , same_buffers ? last_found_index ( ) : 0 ) ;
2021-05-22 14:08:05 +00:00
2021-09-27 11:14:08 +00:00
if ( ! result . has_value ( ) ) {
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Pattern \" {} \" not found in this file " , m_search_text ) , " Not Found " sv , GUI : : MessageBox : : Type : : Warning ) ;
2021-05-27 15:51:21 +00:00
return ;
}
2021-05-22 14:08:05 +00:00
2021-09-27 11:14:08 +00:00
m_last_found_index = result . value ( ) ;
2021-05-22 14:08:05 +00:00
}
2021-05-27 15:51:21 +00:00
2021-05-22 14:08:05 +00:00
m_editor - > update ( ) ;
}
} ) ;
2024-02-07 20:43:43 +00:00
m_goto_offset_action = GUI : : Action : : create ( " &Go to Offset... " , { Mod_Ctrl , Key_G } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-to.png " sv ) ) , [ this ] ( const GUI : : Action & ) {
2021-05-23 08:36:30 +00:00
int new_offset ;
auto result = GoToOffsetDialog : : show (
window ( ) ,
m_goto_history ,
new_offset ,
m_editor - > selection_start_offset ( ) ,
m_editor - > buffer_size ( ) ) ;
2022-05-13 12:10:27 +00:00
if ( result = = GUI : : InputBox : : ExecResult : : OK ) {
2021-05-23 08:36:30 +00:00
m_editor - > highlight ( new_offset , new_offset ) ;
m_editor - > update ( ) ;
}
} ) ;
2021-05-22 14:08:05 +00:00
m_layout_toolbar_action = GUI : : Action : : create_checkable ( " &Toolbar " , [ & ] ( auto & action ) {
m_toolbar_container - > set_visible ( action . is_checked ( ) ) ;
2022-07-11 17:32:29 +00:00
Config : : write_bool ( " HexEditor " sv , " Layout " sv , " ShowToolbar " sv , action . is_checked ( ) ) ;
2021-05-22 14:08:05 +00:00
} ) ;
2024-01-29 19:57:27 +00:00
m_layout_annotations_action = GUI : : Action : : create_checkable ( " &Annotations " , [ & ] ( auto & action ) {
set_annotations_visible ( action . is_checked ( ) ) ;
Config : : write_bool ( " HexEditor " sv , " Layout " sv , " ShowAnnotations " sv , action . is_checked ( ) ) ;
} ) ;
2021-05-27 15:51:21 +00:00
m_layout_search_results_action = GUI : : Action : : create_checkable ( " &Search Results " , [ & ] ( auto & action ) {
set_search_results_visible ( action . is_checked ( ) ) ;
2023-05-01 17:38:04 +00:00
Config : : write_bool ( " HexEditor " sv , " Layout " sv , " ShowSearchResults " sv , action . is_checked ( ) ) ;
2021-05-27 15:51:21 +00:00
} ) ;
2024-04-07 11:01:37 +00:00
m_show_offsets_column_action = GUI : : Action : : create_checkable ( " Show &Offsets Column " , [ & ] ( auto & action ) {
m_editor - > set_show_offsets_column ( action . is_checked ( ) ) ;
Config : : write_bool ( " HexEditor " sv , " Layout " sv , " ShowOffsetsColumn " sv , action . is_checked ( ) ) ;
} ) ;
2024-02-07 20:43:43 +00:00
m_copy_hex_action = GUI : : Action : : create ( " Copy &Hex " , { Mod_Ctrl , Key_C } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/hex.png " sv ) ) , [ & ] ( const GUI : : Action & ) {
2022-03-13 17:47:33 +00:00
m_editor - > copy_selected_hex_to_clipboard ( ) ;
} ) ;
m_copy_hex_action - > set_enabled ( false ) ;
2024-02-07 20:43:43 +00:00
m_copy_text_action = GUI : : Action : : create ( " Copy &Text " , { Mod_Ctrl | Mod_Shift , Key_C } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/edit-copy.png " sv ) ) , [ & ] ( const GUI : : Action & ) {
2022-03-13 17:47:33 +00:00
m_editor - > copy_selected_text_to_clipboard ( ) ;
} ) ;
m_copy_text_action - > set_enabled ( false ) ;
2024-02-07 20:43:43 +00:00
m_copy_as_c_code_action = GUI : : Action : : create ( " Copy as &C Code " , { Mod_Alt | Mod_Shift , Key_C } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/c.png " sv ) ) , [ & ] ( const GUI : : Action & ) {
2022-03-13 17:47:33 +00:00
m_editor - > copy_selected_hex_to_clipboard_as_c_code ( ) ;
} ) ;
m_copy_as_c_code_action - > set_enabled ( false ) ;
m_fill_selection_action = GUI : : Action : : create ( " Fill &Selection... " , { Mod_Ctrl , Key_B } , [ & ] ( const GUI : : Action & ) {
2023-04-16 20:02:07 +00:00
String value ;
2022-12-30 20:13:54 +00:00
if ( GUI : : InputBox : : show ( window ( ) , value , " Fill byte (hex): " sv , " Fill Selection " sv , GUI : : InputType : : NonemptyText ) = = GUI : : InputBox : : ExecResult : : OK ) {
2023-04-16 20:02:07 +00:00
auto fill_byte = strtol ( value . bytes_as_string_view ( ) . characters_without_null_termination ( ) , nullptr , 16 ) ;
2022-12-07 18:32:07 +00:00
auto result = m_editor - > fill_selection ( fill_byte ) ;
if ( result . is_error ( ) )
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show_error ( window ( ) , ByteString : : formatted ( " {} " , result . error ( ) ) ) ;
2022-03-13 17:47:33 +00:00
}
} ) ;
m_fill_selection_action - > set_enabled ( false ) ;
2022-03-03 14:26:38 +00:00
m_layout_value_inspector_action = GUI : : Action : : create_checkable ( " &Value Inspector " , [ & ] ( auto & action ) {
set_value_inspector_visible ( action . is_checked ( ) ) ;
2023-05-01 17:38:04 +00:00
Config : : write_bool ( " HexEditor " sv , " Layout " sv , " ShowValueInspector " sv , action . is_checked ( ) ) ;
2022-03-03 14:26:38 +00:00
} ) ;
2024-02-06 16:57:02 +00:00
auto & annotations_toolbar = * find_descendant_of_type_named < GUI : : Toolbar > ( " annotations_toolbar " ) ;
annotations_toolbar . add_action ( * m_open_annotations_action ) ;
annotations_toolbar . add_action ( * m_save_annotations_action ) ;
annotations_toolbar . add_action ( * m_save_annotations_as_action ) ;
annotations_toolbar . add_separator ( ) ;
annotations_toolbar . add_action ( GUI : : Action : : create (
" &Add Annotation " ,
TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/annotation-add.png " sv ) ) ,
[ this ] ( GUI : : Action & ) { m_editor - > show_create_annotation_dialog ( ) ; } ,
this ) ) ;
2021-05-22 14:08:05 +00:00
m_toolbar - > add_action ( * m_new_action ) ;
m_toolbar - > add_action ( * m_open_action ) ;
m_toolbar - > add_action ( * m_save_action ) ;
m_toolbar - > add_separator ( ) ;
2022-09-07 21:41:37 +00:00
m_toolbar - > add_action ( * m_undo_action ) ;
m_toolbar - > add_action ( * m_redo_action ) ;
m_toolbar - > add_separator ( ) ;
2021-05-22 14:08:05 +00:00
m_toolbar - > add_action ( * m_find_action ) ;
2021-05-23 08:36:30 +00:00
m_toolbar - > add_action ( * m_goto_offset_action ) ;
2021-05-22 14:08:05 +00:00
2022-03-13 17:41:47 +00:00
m_statusbar - > segment ( 0 ) . set_clickable ( true ) ;
m_statusbar - > segment ( 0 ) . set_action ( * m_goto_offset_action ) ;
2021-02-26 12:07:13 +00:00
m_editor - > set_focus ( true ) ;
2023-06-17 15:54:33 +00:00
GUI : : Application : : the ( ) - > on_action_enter = [ this ] ( GUI : : Action & action ) {
m_statusbar - > set_override_text ( action . status_tip ( ) ) ;
} ;
GUI : : Application : : the ( ) - > on_action_leave = [ this ] ( GUI : : Action & ) {
m_statusbar - > set_override_text ( { } ) ;
} ;
2023-09-28 15:06:49 +00:00
return { } ;
2021-02-26 12:07:13 +00:00
}
2022-03-03 14:26:38 +00:00
void HexEditorWidget : : update_inspector_values ( size_t position )
{
// build out primitive types like u8, i8, u16, etc
size_t byte_read_count = 0 ;
u64 unsigned_64_bit_int = 0 ;
for ( int i = 0 ; i < 8 ; + + i ) {
Optional < u8 > read_result = m_editor - > get_byte ( position + i ) ;
u8 current_byte = 0 ;
if ( ! read_result . has_value ( ) )
break ;
current_byte = read_result . release_value ( ) ;
if ( m_value_inspector_little_endian )
unsigned_64_bit_int = ( ( u64 ) current_byte < < ( 8 * byte_read_count ) ) + unsigned_64_bit_int ;
else
unsigned_64_bit_int = ( unsigned_64_bit_int < < 8 ) + current_byte ;
+ + byte_read_count ;
}
if ( ! m_value_inspector_little_endian ) {
// if we didn't read far enough, lets finish shifting the bytes so the code below works
size_t bytes_left_to_read = 8 - byte_read_count ;
unsigned_64_bit_int = ( unsigned_64_bit_int < < ( 8 * bytes_left_to_read ) ) ;
}
// Populate the model
2022-04-16 22:00:27 +00:00
NonnullRefPtr < ValueInspectorModel > value_inspector_model = make_ref_counted < ValueInspectorModel > ( m_value_inspector_little_endian ) ;
2022-03-03 14:26:38 +00:00
if ( byte_read_count > = 1 ) {
u8 unsigned_byte_value = 0 ;
if ( m_value_inspector_little_endian )
unsigned_byte_value = ( unsigned_64_bit_int & 0xFF ) ;
else
unsigned_byte_value = ( unsigned_64_bit_int > > ( 64 - 8 ) ) & 0xFF ;
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedByte , String : : number ( static_cast < i8 > ( unsigned_byte_value ) ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedByte , String : : number ( unsigned_byte_value ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : ASCII , String : : formatted ( " {:c} " , static_cast < char > ( unsigned_byte_value ) ) ) ;
2022-03-03 14:26:38 +00:00
} else {
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedByte , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedByte , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : ASCII , " " _string ) ;
2022-03-03 14:26:38 +00:00
}
if ( byte_read_count > = 2 ) {
u16 unsigned_short_value = 0 ;
if ( m_value_inspector_little_endian )
unsigned_short_value = ( unsigned_64_bit_int & 0xFFFF ) ;
else
unsigned_short_value = ( unsigned_64_bit_int > > ( 64 - 16 ) ) & 0xFFFF ;
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedShort , String : : number ( static_cast < i16 > ( unsigned_short_value ) ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedShort , String : : number ( unsigned_short_value ) ) ;
2022-03-03 14:26:38 +00:00
} else {
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedShort , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedShort , " " _string ) ;
2022-03-03 14:26:38 +00:00
}
if ( byte_read_count > = 4 ) {
u32 unsigned_int_value = 0 ;
if ( m_value_inspector_little_endian )
unsigned_int_value = ( unsigned_64_bit_int & 0xFFFFFFFF ) ;
else
unsigned_int_value = ( unsigned_64_bit_int > > 32 ) & 0xFFFFFFFF ;
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedInt , String : : number ( static_cast < i32 > ( unsigned_int_value ) ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedInt , String : : number ( unsigned_int_value ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : Float , String : : number ( bit_cast < float > ( unsigned_int_value ) ) ) ;
2022-03-03 14:26:38 +00:00
} else {
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedInt , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedInt , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : Float , " " _string ) ;
2022-03-03 14:26:38 +00:00
}
if ( byte_read_count > = 8 ) {
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedLong , String : : number ( static_cast < i64 > ( unsigned_64_bit_int ) ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedLong , String : : number ( unsigned_64_bit_int ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : Double , String : : number ( bit_cast < double > ( unsigned_64_bit_int ) ) ) ;
2022-03-03 14:26:38 +00:00
} else {
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : SignedLong , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UnsignedLong , " " _string ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : Double , " " _string ) ;
2022-03-03 14:26:38 +00:00
}
2022-04-12 17:00:36 +00:00
// FIXME: This probably doesn't honour endianness correctly.
Utf8View utf8_view { ReadonlyBytes { reinterpret_cast < u8 const * > ( & unsigned_64_bit_int ) , 4 } } ;
size_t valid_bytes ;
utf8_view . validate ( valid_bytes ) ;
if ( valid_bytes = = 0 )
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF8 , " " _string ) ;
else {
auto utf8 = String : : from_utf8 ( utf8_view . unicode_substring_view ( 0 , 1 ) . as_string ( ) ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF8 , move ( utf8 ) ) ;
}
2022-04-12 17:00:36 +00:00
2022-04-12 17:12:03 +00:00
if ( byte_read_count % 2 = = 0 ) {
2023-02-05 19:02:54 +00:00
Utf16View utf16_view { ReadonlySpan < u16 > { reinterpret_cast < u16 const * > ( & unsigned_64_bit_int ) , 4 } } ;
2022-04-12 17:12:03 +00:00
size_t valid_code_units ;
2022-08-19 13:25:48 +00:00
utf16_view . validate ( valid_code_units ) ;
2022-04-12 17:12:03 +00:00
if ( valid_code_units = = 0 )
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF16 , " " _string ) ;
2022-04-12 17:12:03 +00:00
else
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF16 , utf16_view . unicode_substring_view ( 0 , 1 ) . to_utf8 ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-04-12 17:12:03 +00:00
} else {
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF16 , " " _string ) ;
2022-04-12 17:12:03 +00:00
}
2022-04-26 13:14:05 +00:00
auto selected_bytes = m_editor - > get_selected_bytes ( ) ;
2023-08-16 16:47:06 +00:00
auto ascii_string = String : : from_utf8 ( ReadonlyBytes { selected_bytes } ) ;
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : ASCIIString , move ( ascii_string ) ) ;
2022-04-26 13:14:05 +00:00
Utf8View utf8_string_view { ReadonlyBytes { selected_bytes } } ;
utf8_string_view . validate ( valid_bytes ) ;
if ( valid_bytes = = 0 )
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF8String , " " _string ) ;
2022-04-26 13:14:05 +00:00
else
// FIXME: replace control chars with something else - we don't want line breaks here ;)
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF8String , String : : from_utf8 ( utf8_string_view . as_string ( ) ) ) ;
2022-04-26 13:14:05 +00:00
2022-04-12 17:12:03 +00:00
// FIXME: Parse as other values like Timestamp etc
2022-03-03 14:26:38 +00:00
2023-02-26 18:45:53 +00:00
auto decoder = TextCodec : : decoder_for ( m_value_inspector_little_endian ? " utf-16le " sv : " utf-16be " sv ) ;
2023-08-16 16:47:06 +00:00
ErrorOr < String > utf16_string = decoder - > to_utf8 ( StringView ( selected_bytes . span ( ) ) ) ;
2023-02-26 18:45:53 +00:00
2023-08-16 16:47:06 +00:00
value_inspector_model - > set_parsed_value ( ValueInspectorModel : : ValueType : : UTF16String , move ( utf16_string ) ) ;
2022-04-26 13:14:05 +00:00
2022-03-03 14:26:38 +00:00
m_value_inspector - > set_model ( value_inspector_model ) ;
m_value_inspector - > update ( ) ;
}
2024-05-04 16:12:16 +00:00
void HexEditorWidget : : set_inspector_little_endian ( bool little_endian , bool force )
{
if ( m_value_inspector_little_endian = = little_endian & & ! force )
return ;
m_value_inspector_little_endian = little_endian ;
update_inspector_values ( m_editor - > selection_start_offset ( ) ) ;
m_value_inspector_endianness - > set_selected_index ( little_endian ? 0 : 1 ) ;
Config : : write_bool ( " HexEditor " sv , " Layout " sv , " UseLittleEndianInValueInspector " sv , m_value_inspector_little_endian ) ;
}
2023-04-16 12:56:27 +00:00
ErrorOr < void > HexEditorWidget : : initialize_menubar ( GUI : : Window & window )
2021-02-26 12:07:13 +00:00
{
2023-08-14 08:44:42 +00:00
auto file_menu = window . add_menu ( " &File " _string ) ;
2023-08-14 08:14:27 +00: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 ) ;
2023-08-14 05:19:40 +00:00
file_menu - > add_separator ( ) ;
2024-01-30 12:33:36 +00:00
file_menu - > add_action ( * m_open_annotations_action ) ;
file_menu - > add_action ( * m_save_annotations_action ) ;
file_menu - > add_action ( * m_save_annotations_as_action ) ;
file_menu - > add_separator ( ) ;
2023-08-14 08:16:18 +00:00
file_menu - > add_recent_files_list ( [ & ] ( auto & action ) {
2023-05-11 15:07:27 +00:00
auto path = action . text ( ) ;
auto response = FileSystemAccessClient : : Client : : the ( ) . request_file_read_only_approved ( & window , path ) ;
if ( response . is_error ( ) )
return ;
auto file = response . release_value ( ) ;
open_file ( file . filename ( ) , file . release_stream ( ) ) ;
2023-08-14 08:16:18 +00:00
} ) ;
2023-08-14 08:14:27 +00:00
file_menu - > add_action ( GUI : : CommonActions : : make_quit_action ( [ this ] ( auto & ) {
2019-10-12 23:08:12 +00:00
if ( ! request_close ( ) )
return ;
2020-07-04 14:52:01 +00:00
GUI : : Application : : the ( ) - > quit ( ) ;
2023-08-14 08:14:27 +00:00
} ) ) ;
2019-10-12 23:08:12 +00:00
2023-08-14 08:44:42 +00:00
auto edit_menu = window . add_menu ( " &Edit " _string ) ;
2023-08-14 08:14:27 +00:00
edit_menu - > add_action ( * m_undo_action ) ;
edit_menu - > add_action ( * m_redo_action ) ;
2023-08-14 05:19:40 +00:00
edit_menu - > add_separator ( ) ;
2023-08-14 08:14:27 +00:00
edit_menu - > add_action ( GUI : : CommonActions : : make_select_all_action ( [ this ] ( auto & ) {
2021-05-22 07:44:10 +00:00
m_editor - > select_all ( ) ;
m_editor - > update ( ) ;
2023-08-14 08:14:27 +00:00
} ) ) ;
edit_menu - > add_action ( * m_fill_selection_action ) ;
2023-08-14 05:19:40 +00:00
edit_menu - > add_separator ( ) ;
2024-01-10 19:49:40 +00:00
edit_menu - > add_action ( GUI : : Action : : create (
" Add Annotation " ,
2024-02-07 20:43:43 +00:00
TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/annotation-add.png " sv ) ) ,
2024-01-10 19:49:40 +00:00
[ this ] ( GUI : : Action & ) { m_editor - > show_create_annotation_dialog ( ) ; } ,
this ) ) ;
edit_menu - > add_separator ( ) ;
2023-08-14 08:14:27 +00:00
edit_menu - > add_action ( * m_copy_hex_action ) ;
edit_menu - > add_action ( * m_copy_text_action ) ;
edit_menu - > add_action ( * m_copy_as_c_code_action ) ;
2023-08-14 05:19:40 +00:00
edit_menu - > add_separator ( ) ;
2023-08-14 08:14:27 +00:00
edit_menu - > add_action ( * m_find_action ) ;
edit_menu - > add_action ( GUI : : Action : : create ( " Find &Next " , { Mod_None , Key_F3 } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find-next.png " sv ) ) , [ & ] ( const GUI : : Action & ) {
2021-05-16 06:47:46 +00:00
if ( m_search_text . is_empty ( ) | | m_search_buffer . is_empty ( ) ) {
2023-05-22 17:07:09 +00:00
GUI : : MessageBox : : show ( & window , " Nothing to search for " sv , " Not Found " sv , GUI : : MessageBox : : Type : : Warning ) ;
2021-01-22 20:22:00 +00:00
return ;
}
auto result = m_editor - > find_and_highlight ( m_search_buffer , last_found_index ( ) ) ;
2021-09-27 11:14:08 +00:00
if ( ! result . has_value ( ) ) {
2023-12-16 14:19:34 +00:00
GUI : : MessageBox : : show ( & window , ByteString : : formatted ( " No more matches for \" {} \" found in this file " , m_search_text ) , " Not Found " sv , GUI : : MessageBox : : Type : : Warning ) ;
2021-01-22 20:22:00 +00:00
return ;
}
m_editor - > update ( ) ;
2021-09-27 11:14:08 +00:00
m_last_found_index = result . value ( ) ;
2023-08-14 08:14:27 +00:00
} ) ) ;
2021-05-29 08:49:35 +00:00
2023-08-14 08:14:27 +00:00
edit_menu - > add_action ( GUI : : Action : : create ( " Find All &Strings " , { Mod_Ctrl | Mod_Shift , Key_F } , TRY ( Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " sv ) ) , [ & ] ( const GUI : : Action & ) {
2021-05-29 08:49:35 +00:00
int min_length = 4 ;
auto matches = m_editor - > find_all_strings ( min_length ) ;
m_search_results - > set_model ( * new SearchResultsModel ( move ( matches ) ) ) ;
m_search_results - > update ( ) ;
if ( matches . is_empty ( ) ) {
2023-05-22 17:07:09 +00:00
GUI : : MessageBox : : show ( & window , " No strings found in this file " sv , " Not Found " sv , GUI : : MessageBox : : Type : : Warning ) ;
2021-05-29 08:49:35 +00:00
return ;
}
set_search_results_visible ( true ) ;
m_editor - > update ( ) ;
2023-08-14 08:14:27 +00:00
} ) ) ;
2023-08-14 05:19:40 +00:00
edit_menu - > add_separator ( ) ;
2023-08-14 08:14:27 +00:00
edit_menu - > add_action ( * m_goto_offset_action ) ;
2021-01-22 20:22:00 +00:00
2023-08-14 08:44:42 +00:00
auto view_menu = window . add_menu ( " &View " _string ) ;
2021-05-24 14:56:27 +00:00
2022-07-11 17:32:29 +00:00
auto show_toolbar = Config : : read_bool ( " HexEditor " sv , " Layout " sv , " ShowToolbar " sv , true ) ;
2021-05-24 14:56:27 +00:00
m_layout_toolbar_action - > set_checked ( show_toolbar ) ;
m_toolbar_container - > set_visible ( show_toolbar ) ;
2023-05-01 17:38:04 +00:00
2024-01-29 19:57:27 +00:00
auto show_annotations = Config : : read_bool ( " HexEditor " sv , " Layout " sv , " ShowAnnotations " sv , false ) ;
set_annotations_visible ( show_annotations ) ;
2023-05-01 17:38:04 +00:00
auto show_search_results = Config : : read_bool ( " HexEditor " sv , " Layout " sv , " ShowSearchResults " sv , false ) ;
set_search_results_visible ( show_search_results ) ;
auto show_value_inspector = Config : : read_bool ( " HexEditor " sv , " Layout " sv , " ShowValueInspector " sv , false ) ;
set_value_inspector_visible ( show_value_inspector ) ;
2023-08-14 08:14:27 +00:00
view_menu - > add_action ( * m_layout_toolbar_action ) ;
2024-01-29 19:57:27 +00:00
view_menu - > add_action ( * m_layout_annotations_action ) ;
2023-08-14 08:14:27 +00:00
view_menu - > add_action ( * m_layout_search_results_action ) ;
view_menu - > add_action ( * m_layout_value_inspector_action ) ;
2023-08-14 05:19:40 +00:00
view_menu - > add_separator ( ) ;
2021-05-24 14:56:27 +00:00
2024-04-07 11:01:37 +00:00
auto show_offsets_column = Config : : read_bool ( " HexEditor " sv , " Layout " sv , " ShowOffsetsColumn " sv , true ) ;
m_show_offsets_column_action - > set_checked ( show_offsets_column ) ;
m_editor - > set_show_offsets_column ( show_offsets_column ) ;
view_menu - > add_action ( * m_show_offsets_column_action ) ;
2024-04-07 11:22:49 +00:00
auto offset_format = HexEditor : : offset_format_from_string ( Config : : read_string ( " HexEditor " sv , " Layout " sv , " OffsetFormat " sv ) ) ;
m_editor - > set_offset_format ( offset_format ) ;
m_offset_format_actions . set_exclusive ( true ) ;
auto offset_format_menu = view_menu - > add_submenu ( " Offset Format " _string ) ;
auto offset_format_decimal_action = GUI : : Action : : create_checkable ( " &Decimal " , [ this ] ( auto & ) {
m_editor - > set_offset_format ( HexEditor : : OffsetFormat : : Decimal ) ;
Config : : write_string ( " HexEditor " sv , " Layout " sv , " OffsetFormat " sv , " decimal " sv ) ;
} ) ;
auto offset_format_hexadecimal_action = GUI : : Action : : create_checkable ( " &Hexadecimal " , [ this ] ( auto & ) {
m_editor - > set_offset_format ( HexEditor : : OffsetFormat : : Hexadecimal ) ;
Config : : write_string ( " HexEditor " sv , " Layout " sv , " OffsetFormat " sv , " hexadecimal " sv ) ;
} ) ;
switch ( offset_format ) {
case HexEditor : : OffsetFormat : : Decimal :
offset_format_decimal_action - > set_checked ( true ) ;
break ;
case HexEditor : : OffsetFormat : : Hexadecimal :
offset_format_hexadecimal_action - > set_checked ( true ) ;
break ;
}
m_offset_format_actions . add_action ( offset_format_decimal_action ) ;
m_offset_format_actions . add_action ( offset_format_hexadecimal_action ) ;
offset_format_menu - > add_action ( offset_format_decimal_action ) ;
offset_format_menu - > add_action ( offset_format_hexadecimal_action ) ;
2024-04-07 11:01:37 +00:00
view_menu - > add_separator ( ) ;
2022-07-11 17:32:29 +00:00
auto bytes_per_row = Config : : read_i32 ( " HexEditor " sv , " Layout " sv , " BytesPerRow " sv , 16 ) ;
2021-05-24 14:56:27 +00:00
m_editor - > set_bytes_per_row ( bytes_per_row ) ;
m_editor - > update ( ) ;
2021-02-26 12:07:13 +00:00
m_bytes_per_row_actions . set_exclusive ( true ) ;
2023-08-14 07:04:41 +00:00
auto bytes_per_row_menu = view_menu - > add_submenu ( " Bytes per &Row " _string ) ;
2021-02-26 12:07:13 +00:00
for ( int i = 8 ; i < = 32 ; i + = 8 ) {
2023-12-16 14:19:34 +00:00
auto action = GUI : : Action : : create_checkable ( ByteString : : number ( i ) , [ this , i ] ( auto & ) {
2021-02-26 12:07:13 +00:00
m_editor - > set_bytes_per_row ( i ) ;
m_editor - > update ( ) ;
2022-07-11 17:32:29 +00:00
Config : : write_i32 ( " HexEditor " sv , " Layout " sv , " BytesPerRow " sv , i ) ;
2021-02-26 12:07:13 +00:00
} ) ;
m_bytes_per_row_actions . add_action ( action ) ;
2023-08-14 08:14:27 +00:00
bytes_per_row_menu - > add_action ( action ) ;
2021-05-24 14:56:27 +00:00
if ( i = = bytes_per_row )
2021-02-26 12:07:13 +00:00
action - > set_checked ( true ) ;
}
2019-10-12 23:08:12 +00:00
2023-05-01 17:38:04 +00:00
auto use_little_endian = Config : : read_bool ( " HexEditor " sv , " Layout " sv , " UseLittleEndianInValueInspector " sv , true ) ;
2024-05-04 16:12:16 +00:00
set_inspector_little_endian ( use_little_endian , true ) ;
2022-03-03 14:26:38 +00:00
2024-01-04 19:57:23 +00:00
view_menu - > add_separator ( ) ;
view_menu - > add_action ( GUI : : CommonActions : : make_fullscreen_action ( [ & ] ( auto & ) {
window . set_fullscreen ( ! window . is_fullscreen ( ) ) ;
} ) ) ;
2023-08-14 08:44:42 +00:00
auto help_menu = window . add_menu ( " &Help " _string ) ;
2023-08-14 08:14:27 +00:00
help_menu - > add_action ( GUI : : CommonActions : : make_command_palette_action ( & window ) ) ;
help_menu - > add_action ( GUI : : CommonActions : : make_help_action ( [ ] ( auto & ) {
2023-04-08 09:48:22 +00:00
Desktop : : Launcher : : open ( URL : : create_with_file_scheme ( " /usr/share/man/man1/Applications/HexEditor.md " ) , " /bin/Help " ) ;
2023-08-14 08:14:27 +00:00
} ) ) ;
2023-09-13 19:34:46 +00:00
help_menu - > add_action ( GUI : : CommonActions : : make_about_action ( " Hex Editor " _string , GUI : : Icon : : default_icon ( " app-hex-editor " sv ) , & window ) ) ;
2023-04-16 12:56:27 +00:00
return { } ;
2019-10-12 23:08:12 +00:00
}
2021-11-10 23:55:02 +00:00
void HexEditorWidget : : set_path ( StringView path )
2019-10-12 23:08:12 +00:00
{
2021-06-29 18:12:53 +00:00
if ( path . is_empty ( ) ) {
m_path = { } ;
m_name = { } ;
m_extension = { } ;
} else {
auto lexical_path = LexicalPath ( path ) ;
m_path = lexical_path . string ( ) ;
m_name = lexical_path . title ( ) ;
m_extension = lexical_path . extension ( ) ;
}
2019-10-12 23:08:12 +00:00
update_title ( ) ;
}
void HexEditorWidget : : update_title ( )
{
StringBuilder builder ;
2021-06-23 07:20:23 +00:00
if ( m_path . is_empty ( ) )
2022-07-11 17:32:29 +00:00
builder . append ( " Untitled " sv ) ;
2021-06-23 07:20:23 +00:00
else
builder . append ( m_path ) ;
2022-07-11 17:32:29 +00:00
builder . append ( " [*] - Hex Editor " sv ) ;
2023-12-16 14:19:34 +00:00
window ( ) - > set_title ( builder . to_byte_string ( ) ) ;
2019-10-12 23:08:12 +00:00
}
2024-01-23 16:05:59 +00:00
void HexEditorWidget : : open_file ( ByteString const & filename , NonnullOwnPtr < Core : : File > file )
2019-10-12 23:08:12 +00:00
{
2022-02-14 15:20:13 +00:00
window ( ) - > set_modified ( false ) ;
2023-01-15 02:47:50 +00:00
m_editor - > open_file ( move ( file ) ) ;
2024-01-23 16:05:59 +00:00
set_path ( filename ) ;
2024-01-29 19:57:27 +00:00
initialize_annotations_model ( ) ;
2024-01-30 12:33:36 +00:00
m_annotations_path = " " ;
2024-01-23 16:05:59 +00:00
GUI : : Application : : the ( ) - > set_most_recently_open_file ( filename ) ;
2019-10-12 23:08:12 +00:00
}
2024-01-30 12:53:36 +00:00
void HexEditorWidget : : open_annotations_file ( StringView filename )
{
auto response = FileSystemAccessClient : : Client : : the ( ) . request_file_read_only_approved ( window ( ) , filename ) ;
if ( response . is_error ( ) )
return ;
auto result = m_editor - > document ( ) . annotations ( ) . load_from_file ( response . value ( ) . stream ( ) ) ;
if ( result . is_error ( ) ) {
GUI : : MessageBox : : show ( window ( ) , ByteString : : formatted ( " Unable to load annotations: {} \n " sv , result . error ( ) ) , " Error " sv , GUI : : MessageBox : : Type : : Error ) ;
return ;
}
m_annotations_path = filename ;
}
2019-10-12 23:08:12 +00:00
bool HexEditorWidget : : request_close ( )
{
2022-02-14 15:20:13 +00:00
if ( ! window ( ) - > is_modified ( ) )
2019-10-12 23:08:12 +00:00
return true ;
2021-06-15 16:15:28 +00:00
2022-02-14 14:29:24 +00:00
auto result = GUI : : MessageBox : : ask_about_unsaved_changes ( window ( ) , m_path ) ;
2022-05-13 12:10:27 +00:00
if ( result = = GUI : : MessageBox : : ExecResult : : Yes ) {
2021-06-15 16:15:28 +00:00
m_save_action - > activate ( ) ;
2022-02-14 15:20:13 +00:00
return ! window ( ) - > is_modified ( ) ;
2021-06-15 16:15:28 +00:00
}
2022-05-13 12:10:27 +00:00
return result = = GUI : : MessageBox : : ExecResult : : No ;
2019-10-12 23:08:12 +00:00
}
2021-05-27 15:51:21 +00:00
2024-01-29 19:57:27 +00:00
void HexEditorWidget : : update_side_panel_visibility ( )
{
m_side_panel_container - > set_visible (
m_annotations_container - > is_visible ( )
| | m_search_results_container - > is_visible ( )
| | m_value_inspector_container - > is_visible ( ) ) ;
}
void HexEditorWidget : : set_annotations_visible ( bool visible )
{
m_layout_annotations_action - > set_checked ( visible ) ;
m_annotations_container - > set_visible ( visible ) ;
update_side_panel_visibility ( ) ;
}
void HexEditorWidget : : initialize_annotations_model ( )
{
2024-02-06 16:57:02 +00:00
m_annotations_sorting_model = MUST ( GUI : : SortingProxyModel : : create ( m_editor - > document ( ) . annotations ( ) ) ) ;
m_annotations_sorting_model - > set_sort_role ( ( GUI : : ModelRole ) AnnotationsModel : : CustomRole : : StartOffset ) ;
m_annotations_sorting_model - > sort ( AnnotationsModel : : Column : : Start , GUI : : SortOrder : : Ascending ) ;
m_annotations - > set_model ( m_annotations_sorting_model ) ;
2024-01-29 19:57:27 +00:00
}
2021-05-27 15:51:21 +00:00
void HexEditorWidget : : set_search_results_visible ( bool visible )
{
m_layout_search_results_action - > set_checked ( visible ) ;
m_search_results_container - > set_visible ( visible ) ;
2024-01-29 19:57:27 +00:00
update_side_panel_visibility ( ) ;
2022-03-03 14:26:38 +00:00
}
void HexEditorWidget : : set_value_inspector_visible ( bool visible )
{
if ( visible )
update_inspector_values ( m_editor - > selection_start_offset ( ) ) ;
m_layout_value_inspector_action - > set_checked ( visible ) ;
m_value_inspector_container - > set_visible ( visible ) ;
2024-01-29 19:57:27 +00:00
update_side_panel_visibility ( ) ;
2021-05-27 15:51:21 +00:00
}
2021-07-12 13:42:00 +00:00
2022-11-04 21:32:17 +00:00
void HexEditorWidget : : drag_enter_event ( GUI : : DragEvent & event )
{
auto const & mime_types = event . mime_types ( ) ;
2023-09-21 17:02:52 +00:00
if ( mime_types . contains_slow ( " text/uri-list " sv ) )
2022-11-04 21:32:17 +00:00
event . accept ( ) ;
}
2021-07-12 13:42:00 +00:00
void HexEditorWidget : : 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 ( ) ;
2022-11-05 15:13:51 +00:00
if ( ! request_close ( ) )
return ;
2021-09-02 11:03:44 +00:00
// TODO: A drop event should be considered user consent for opening a file
2023-04-14 19:12:03 +00:00
auto response = FileSystemAccessClient : : Client : : the ( ) . request_file ( window ( ) , urls . first ( ) . serialize_path ( ) , Core : : File : : OpenMode : : Read ) ;
2022-01-16 08:41:34 +00:00
if ( response . is_error ( ) )
2021-09-02 11:03:44 +00:00
return ;
2023-01-15 02:47:50 +00:00
open_file ( response . value ( ) . filename ( ) , response . value ( ) . release_stream ( ) ) ;
2021-07-12 13:42:00 +00:00
}
}
2023-09-28 15:06:49 +00:00
}