2020-01-18 09:38:21 +01:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
2019-10-12 18:08:12 -05:00
# include "HexEditorWidget.h"
2021-01-22 23:22:00 +03:00
# include "FindDialog.h"
2019-10-12 18:08:12 -05:00
# include <AK/Optional.h>
# include <AK/StringBuilder.h>
2020-02-06 15:04:03 +01:00
# include <LibCore/File.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/Action.h>
# include <LibGUI/BoxLayout.h>
# include <LibGUI/Button.h>
# include <LibGUI/FilePicker.h>
# include <LibGUI/InputBox.h>
2020-02-15 01:56:30 +01:00
# include <LibGUI/Menu.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Menubar.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/MessageBox.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Statusbar.h>
2020-02-06 20:33:02 +01:00
# include <LibGUI/TextBox.h>
# include <LibGUI/TextEditor.h>
2021-04-13 16:18:20 +02:00
# include <LibGUI/Toolbar.h>
2019-10-12 18:08:12 -05:00
# include <stdio.h>
2020-03-08 12:05:14 +01:00
# include <string.h>
2019-10-12 18:08:12 -05:00
HexEditorWidget : : HexEditorWidget ( )
{
2020-04-23 18:51:32 +02:00
set_fill_with_background_color ( true ) ;
2020-03-04 09:43:54 +01:00
set_layout < GUI : : VerticalBoxLayout > ( ) ;
2020-04-23 18:21:23 +02:00
layout ( ) - > set_spacing ( 2 ) ;
2019-10-12 18:08:12 -05:00
2020-02-23 12:07:13 +01:00
m_editor = add < HexEditor > ( ) ;
2019-10-12 18:08:12 -05:00
m_editor - > on_status_change = [ this ] ( int position , HexEditor : : EditMode edit_mode , int selection_start , int selection_end ) {
2020-10-06 13:32:00 +02:00
m_statusbar - > set_text ( 0 , String : : formatted ( " Offset: {:#08X} " , position ) ) ;
2020-10-05 18:26:31 +02:00
m_statusbar - > set_text ( 1 , String : : formatted ( " Edit Mode: {} " , edit_mode = = HexEditor : : EditMode : : Hex ? " Hex " : " Text " ) ) ;
m_statusbar - > set_text ( 2 , String : : formatted ( " Selection Start: {} " , selection_start ) ) ;
m_statusbar - > set_text ( 3 , String : : formatted ( " Selection End: {} " , selection_end ) ) ;
m_statusbar - > set_text ( 4 , String : : formatted ( " Selected Bytes: {} " , abs ( selection_end - selection_start ) + 1 ) ) ;
2019-10-12 18:08:12 -05:00
} ;
m_editor - > on_change = [ this ] {
bool was_dirty = m_document_dirty ;
m_document_dirty = true ;
if ( ! was_dirty )
update_title ( ) ;
} ;
2021-04-13 16:18:20 +02:00
m_statusbar = add < GUI : : Statusbar > ( 5 ) ;
2019-10-12 18:08:12 -05:00
2020-02-06 11:56:38 +01:00
m_new_action = GUI : : Action : : create ( " New " , { Mod_Ctrl , Key_N } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/new.png " ) , [ this ] ( const GUI : : Action & ) {
2019-10-26 15:31:43 -05:00
if ( m_document_dirty ) {
2020-12-26 00:47:49 +01:00
if ( GUI : : MessageBox : : show ( window ( ) , " Save changes to current file first? " , " Warning " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : OKCancel ) ! = GUI : : Dialog : : ExecResult : : ExecOK )
2019-10-26 15:31:43 -05:00
return ;
m_save_action - > activate ( ) ;
}
2020-07-16 07:54:42 -06:00
String value ;
2021-02-20 12:03:28 +01:00
if ( GUI : : InputBox : : show ( window ( ) , value , " Enter new file size: " , " New file size " ) = = GUI : : InputBox : : ExecOK & & ! value . is_empty ( ) ) {
2020-07-16 07:54:42 -06:00
auto file_size = value . to_int ( ) ;
2020-06-12 21:07:52 +02:00
if ( file_size . has_value ( ) & & file_size . value ( ) > 0 ) {
2019-10-26 15:31:43 -05:00
m_document_dirty = false ;
2020-06-12 21:07:52 +02:00
m_editor - > set_buffer ( ByteBuffer : : create_zeroed ( file_size . value ( ) ) ) ;
2020-05-26 14:52:44 +03:00
set_path ( LexicalPath ( ) ) ;
2019-10-26 15:31:43 -05:00
update_title ( ) ;
} else {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) , " Invalid file size entered. " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2019-10-26 15:31:43 -05:00
}
}
} ) ;
2020-02-02 15:07:41 +01:00
m_open_action = GUI : : CommonActions : : make_open_action ( [ this ] ( auto & ) {
2020-07-15 19:52:02 -06:00
Optional < String > open_path = GUI : : FilePicker : : get_open_filepath ( window ( ) ) ;
2019-10-12 18:08:12 -05:00
if ( ! open_path . has_value ( ) )
return ;
open_file ( open_path . value ( ) ) ;
} ) ;
2020-11-01 21:32:27 +00:00
m_save_action = GUI : : CommonActions : : make_save_action ( [ & ] ( auto & ) {
2019-10-12 18:08:12 -05:00
if ( ! m_path . is_empty ( ) ) {
if ( ! m_editor - > write_to_file ( m_path ) ) {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) , " Unable to save file. \n " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2019-10-12 18:08:12 -05:00
} else {
m_document_dirty = false ;
update_title ( ) ;
}
return ;
}
m_save_as_action - > activate ( ) ;
} ) ;
2020-11-01 21:32:27 +00:00
m_save_as_action = GUI : : CommonActions : : make_save_as_action ( [ & ] ( auto & ) {
2020-07-15 19:52:02 -06:00
Optional < String > save_path = GUI : : FilePicker : : get_save_filepath ( window ( ) , m_name . is_null ( ) ? " Untitled " : m_name , m_extension . is_null ( ) ? " bin " : m_extension ) ;
2019-10-12 18:08:12 -05:00
if ( ! save_path . has_value ( ) )
return ;
if ( ! m_editor - > write_to_file ( save_path . value ( ) ) ) {
2020-07-15 20:45:11 -06:00
GUI : : MessageBox : : show ( window ( ) , " Unable to save file. \n " , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2019-10-12 18:08:12 -05:00
return ;
}
m_document_dirty = false ;
2020-05-26 14:52:44 +03:00
set_path ( LexicalPath ( save_path . value ( ) ) ) ;
2020-10-05 18:26:31 +02:00
dbgln ( " Wrote document to {} " , save_path . value ( ) ) ;
2019-10-12 18:08:12 -05:00
} ) ;
2021-02-26 07:07:13 -05:00
m_editor - > set_focus ( true ) ;
}
HexEditorWidget : : ~ HexEditorWidget ( )
{
}
2021-04-13 16:18:20 +02:00
void HexEditorWidget : : initialize_menubar ( GUI : : Menubar & menubar )
2021-02-26 07:07:13 -05:00
{
2021-03-25 21:41:39 +01:00
auto & app_menu = menubar . add_menu ( " File " ) ;
2020-04-04 12:18:40 +02:00
app_menu . add_action ( * m_new_action ) ;
app_menu . add_action ( * m_open_action ) ;
app_menu . add_action ( * m_save_action ) ;
app_menu . add_action ( * m_save_as_action ) ;
app_menu . add_separator ( ) ;
app_menu . add_action ( GUI : : CommonActions : : make_quit_action ( [ this ] ( auto & ) {
2019-10-12 18:08:12 -05:00
if ( ! request_close ( ) )
return ;
2020-07-04 16:52:01 +02:00
GUI : : Application : : the ( ) - > quit ( ) ;
2019-10-12 18:08:12 -05:00
} ) ) ;
2020-02-06 11:56:38 +01:00
m_goto_decimal_offset_action = GUI : : Action : : create ( " Go To Offset (Decimal)... " , { Mod_Ctrl | Mod_Shift , Key_G } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-forward.png " ) , [ this ] ( const GUI : : Action & ) {
2020-07-16 07:54:42 -06:00
String value ;
2021-02-20 12:03:28 +01:00
if ( GUI : : InputBox : : show ( window ( ) , value , " Enter Decimal offset: " , " Go To " ) = = GUI : : InputBox : : ExecOK & & ! value . is_empty ( ) ) {
2020-07-16 07:54:42 -06:00
auto new_offset = value . to_int ( ) ;
2020-06-12 21:07:52 +02:00
if ( new_offset . has_value ( ) )
m_editor - > set_position ( new_offset . value ( ) ) ;
2019-10-12 18:08:12 -05:00
}
} ) ;
2020-02-06 11:56:38 +01:00
m_goto_hex_offset_action = GUI : : Action : : create ( " Go To Offset (Hex)... " , { Mod_Ctrl , Key_G } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/go-forward.png " ) , [ this ] ( const GUI : : Action & ) {
2020-07-16 07:54:42 -06:00
String value ;
2021-02-20 12:03:28 +01:00
if ( GUI : : InputBox : : show ( window ( ) , value , " Enter Hex offset: " , " Go To " ) = = GUI : : InputBox : : ExecOK & & ! value . is_empty ( ) ) {
2020-07-16 07:54:42 -06:00
auto new_offset = strtol ( value . characters ( ) , nullptr , 16 ) ;
2019-10-21 00:18:51 -05:00
m_editor - > set_position ( new_offset ) ;
}
} ) ;
2021-02-26 07:07:13 -05:00
auto & edit_menu = menubar . add_menu ( " Edit " ) ;
2020-04-04 12:18:40 +02:00
edit_menu . add_action ( GUI : : Action : : create ( " Fill selection... " , { Mod_Ctrl , Key_B } , [ & ] ( const GUI : : Action & ) {
2020-07-16 07:54:42 -06:00
String value ;
2021-02-20 12:03:28 +01:00
if ( GUI : : InputBox : : show ( window ( ) , value , " Fill byte (hex): " , " Fill Selection " ) = = GUI : : InputBox : : ExecOK & & ! value . is_empty ( ) ) {
2020-07-16 07:54:42 -06:00
auto fill_byte = strtol ( value . characters ( ) , nullptr , 16 ) ;
2019-10-26 16:27:26 -05:00
m_editor - > fill_selection ( fill_byte ) ;
}
} ) ) ;
2020-04-04 12:18:40 +02:00
edit_menu . add_separator ( ) ;
edit_menu . add_action ( * m_goto_decimal_offset_action ) ;
edit_menu . add_action ( * m_goto_hex_offset_action ) ;
edit_menu . add_separator ( ) ;
edit_menu . add_action ( GUI : : Action : : create ( " Copy Hex " , { Mod_Ctrl , Key_C } , [ & ] ( const GUI : : Action & ) {
2019-10-12 18:08:12 -05:00
m_editor - > copy_selected_hex_to_clipboard ( ) ;
} ) ) ;
2021-02-26 07:07:13 -05:00
edit_menu . add_action ( GUI : : Action : : create ( " Copy Text " , { Mod_Ctrl | Mod_Shift , Key_C } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/edit-copy.png " ) , [ & ] ( const GUI : : Action & ) {
2019-10-12 18:08:12 -05:00
m_editor - > copy_selected_text_to_clipboard ( ) ;
} ) ) ;
2020-04-04 12:18:40 +02:00
edit_menu . add_action ( GUI : : Action : : create ( " Copy As C Code " , { Mod_Alt | Mod_Shift , Key_C } , [ & ] ( const GUI : : Action & ) {
2019-10-23 17:47:42 -05:00
m_editor - > copy_selected_hex_to_clipboard_as_c_code ( ) ;
} ) ) ;
2021-02-26 07:07:13 -05:00
edit_menu . add_separator ( ) ;
edit_menu . add_action ( GUI : : Action : : create ( " Find " , { Mod_Ctrl , Key_F } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find.png " ) , [ & ] ( const GUI : : Action & ) {
2021-01-22 23:22:00 +03:00
auto old_buffer = m_search_buffer . isolated_copy ( ) ;
if ( FindDialog : : show ( window ( ) , m_search_text , m_search_buffer ) = = GUI : : InputBox : : ExecOK ) {
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 ;
}
auto result = m_editor - > find_and_highlight ( m_search_buffer , same_buffers ? last_found_index ( ) : 0 ) ;
if ( result = = - 1 ) {
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Pattern \" {} \" not found in this file " , m_search_text ) , " Not found " , GUI : : MessageBox : : Type : : Warning ) ;
return ;
}
m_last_found_index = result ;
}
} ) ) ;
2021-02-26 07:07:13 -05:00
edit_menu . add_action ( GUI : : Action : : create ( " Find next " , { Mod_None , Key_F3 } , Gfx : : Bitmap : : load_from_file ( " /res/icons/16x16/find-next.png " ) , [ & ] ( const GUI : : Action & ) {
2021-01-22 23:22:00 +03:00
if ( m_search_text . is_empty ( ) | | m_search_buffer . is_empty ( ) | | m_search_buffer . is_null ( ) ) {
GUI : : MessageBox : : show ( window ( ) , " Nothing to search for " , " Not found " , GUI : : MessageBox : : Type : : Warning ) ;
return ;
}
auto result = m_editor - > find_and_highlight ( m_search_buffer , last_found_index ( ) ) ;
if ( ! result ) {
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " No more matches for \" {} \" found in this file " , m_search_text ) , " Not found " , GUI : : MessageBox : : Type : : Warning ) ;
return ;
}
m_editor - > update ( ) ;
m_last_found_index = result ;
} ) ) ;
2021-02-26 07:07:13 -05:00
auto & view_menu = menubar . add_menu ( " View " ) ;
m_bytes_per_row_actions . set_exclusive ( true ) ;
auto & bytes_per_row_menu = view_menu . add_submenu ( " Bytes per row " ) ;
for ( int i = 8 ; i < = 32 ; i + = 8 ) {
auto action = GUI : : Action : : create_checkable ( String : : number ( i ) , [ this , i ] ( auto & ) {
m_editor - > set_bytes_per_row ( i ) ;
m_editor - > update ( ) ;
} ) ;
m_bytes_per_row_actions . add_action ( action ) ;
bytes_per_row_menu . add_action ( action ) ;
if ( i = = 16 )
action - > set_checked ( true ) ;
}
2019-10-12 18:08:12 -05:00
2021-02-26 07:07:13 -05:00
auto & help_menu = menubar . add_menu ( " Help " ) ;
help_menu . add_action ( GUI : : CommonActions : : make_about_action ( " Hex Editor " , GUI : : Icon : : default_icon ( " app-hex-editor " ) , window ( ) ) ) ;
2019-10-12 18:08:12 -05:00
}
2020-05-26 14:52:44 +03:00
void HexEditorWidget : : set_path ( const LexicalPath & lexical_path )
2019-10-12 18:08:12 -05:00
{
2020-05-26 14:52:44 +03:00
m_path = lexical_path . string ( ) ;
m_name = lexical_path . title ( ) ;
m_extension = lexical_path . extension ( ) ;
2019-10-12 18:08:12 -05:00
update_title ( ) ;
}
void HexEditorWidget : : update_title ( )
{
StringBuilder builder ;
builder . append ( m_path ) ;
if ( m_document_dirty )
builder . append ( " (*) " ) ;
2020-03-13 23:21:35 +01:00
builder . append ( " - Hex Editor " ) ;
2019-10-12 18:08:12 -05:00
window ( ) - > set_title ( builder . to_string ( ) ) ;
}
void HexEditorWidget : : open_file ( const String & path )
{
2020-02-02 12:34:39 +01:00
auto file = Core : : File : : construct ( path ) ;
if ( ! file - > open ( Core : : IODevice : : ReadOnly ) ) {
2020-10-05 18:26:31 +02:00
GUI : : MessageBox : : show ( window ( ) , String : : formatted ( " Opening \" {} \" failed: {} " , path , strerror ( errno ) ) , " Error " , GUI : : MessageBox : : Type : : Error ) ;
2019-10-12 18:08:12 -05:00
return ;
}
m_document_dirty = false ;
m_editor - > set_buffer ( file - > read_all ( ) ) ; // FIXME: On really huge files, this is never going to work. Should really create a framework to fetch data from the file on-demand.
2020-05-26 14:52:44 +03:00
set_path ( LexicalPath ( path ) ) ;
2019-10-12 18:08:12 -05:00
}
bool HexEditorWidget : : request_close ( )
{
if ( ! m_document_dirty )
return true ;
2020-07-15 20:45:11 -06:00
auto result = GUI : : MessageBox : : show ( window ( ) , " The file has been modified. Quit without saving? " , " Quit without saving? " , GUI : : MessageBox : : Type : : Warning , GUI : : MessageBox : : InputType : : OKCancel ) ;
2020-02-02 15:07:41 +01:00
return result = = GUI : : MessageBox : : ExecOK ;
2019-10-12 18:08:12 -05:00
}