diff --git a/Build.PL b/Build.PL index 46b1f55..1e4e1e9 100755 --- a/Build.PL +++ b/Build.PL @@ -16,6 +16,7 @@ my $build = Module::Build->new( 'DBI' => 0, 'File::HomeDir' => 0, 'Moo' => 0, + 'namespace::clean' => 0, }, ); $build->create_build_script; diff --git a/lib/GEmeTool/Config.pm b/lib/GEmeTool/Config.pm index db3fb24..5450a9d 100644 --- a/lib/GEmeTool/Config.pm +++ b/lib/GEmeTool/Config.pm @@ -13,10 +13,14 @@ my $dist = 'GEmeTool'; sub config_dir { my $class = shift; - return path(File::HomeDir->my_dist_config($dist, { create => 1 })); + my $config_dir = path(File::HomeDir->my_home)->child('.gemetool/config'); + $config_dir->mkpath; + return $config_dir; } sub data_dir { my $class = shift; - return path(File::HomeDir->my_dist_data($dist, { create => 1 })); + my $data_dir = path(File::HomeDir->my_home)->child('.gemetool/data'); + $data_dir->mkpath; + return $data_dir; } 1; diff --git a/lib/GEmeTool/DB.pm b/lib/GEmeTool/DB.pm index de95401..3f0abcc 100644 --- a/lib/GEmeTool/DB.pm +++ b/lib/GEmeTool/DB.pm @@ -21,7 +21,6 @@ sub connect { my $class = shift; my $app = shift; my $data_dir = GEmeTool::Config->new->data_dir; - say $data_dir; $dbh = DBI->connect( "dbi:SQLite:dbname=@{[$data_dir->child('gemetool.sqlite')]}", undef, undef, diff --git a/lib/GEmeTool/Save.pm b/lib/GEmeTool/Save.pm new file mode 100644 index 0000000..d4b3659 --- /dev/null +++ b/lib/GEmeTool/Save.pm @@ -0,0 +1,166 @@ +package GEmeTool::Save; + +use v5.16.3; + +use strict; +use warnings; + +use Rsaves + qw/read_save check_correct_size get_saves find_current_save_index check_correct_size find_pokemon_substruct change_gender read_pc_storage save_pc_changes enable_eon_ticket save_changes pokemon_set_shiny read_pkm_file_box parse_version_name check_flag_id get_first_super_data check_rematch/; +use Rsaves::Constants::Global + qw/$SAPPHIRE_VERSION $RUBY_VERSION $EMERALD_VERSION $FIRERED_VERSION $LEAFGREEN_VERSION $COLOSSEUM_VERSION/; +use Rsaves::Constants::Emerald::Flags; +use Rsaves::Constants::Emerald::Rematches; + +use Moo; + +use namespace::clean; + +has __saves => ( is => 'rw' ); + +has __extra => ( is => 'rw' ); + +has input_file => ( is => 'rw', required => 1 ); + +sub instance { + my $class = shift; + my $input_file = shift; + my $save = $class->new( input_file => $input_file ); + return $save; +} + +sub get_flags_save { + my $self = shift; + my %flags_by_id = $self->_get_flags_hash_by_id; + my @return; + for my $i ( sort { $a <=> $b } keys %flags_by_id ) { + push @return, + { value => $self->has_flag($i), name => $flags_by_id{$i}, id => $i }; + } + return \@return; +} + +sub get_rematches_save { + my $self = shift; + my %rematch_by_id = $self->_get_rematches_hash_by_id; + my @return; + for my $i ( sort { $a <=> $b } keys %rematch_by_id ) { + push @return, + { + value => $self->has_rematch($i), + name => $rematch_by_id{$i}, + id => $i + }; + } + return \@return; +} + +sub save { + my $self = shift; + my $file = shift; + my $extra = $self->__extra; + my $saves = $self->__saves; + Rsaves::save_changes( @$saves, $extra, $file ); +} + +sub has_rematch { + my $self = shift; + my $flag_id = shift; + my $superdata = $self->_get_superdata; + my $save = $self->_get_current_save; + return Rsaves::check_rematch( $save, $superdata, $flag_id ); +} + +sub has_flag { + my $self = shift; + my $flag_id = shift; + my $superdata = $self->_get_superdata; + my $save = $self->_get_current_save; + return Rsaves::check_flag_id( $save, $superdata, $flag_id ); +} + +sub set_rematch { + my $self = shift; + my $flag_id = shift; + my $value = shift; + my $superdata = $self->_get_superdata; + my $save = $self->_get_current_save; + Rsaves::set_rematch( $save, $superdata, $flag_id, $value ); + $self->_set_superdata($superdata); +} + +sub _set_superdata { + my $self = shift; + my $superdata = shift; + my $save = $self->_get_current_save; + Rsaves::set_first_super_data( $save, $superdata ); +} + +sub _saves { + my $self = shift; + my $saves = $self->__saves; + if ( !defined $saves ) { + $self->_build_saves_and_extra; + } + return $self->__saves; +} + +sub _extra { + my $self = shift; + my $extra = $self->__extra; + if ( !defined $extra ) { + $self->_build_saves_and_extra; + } + return $self->__extra; +} + +sub _get_superdata { + my $self = shift; + return Rsaves::get_first_super_data( $self->_get_current_save ); +} + +sub _get_rematches_hash_by_id { + my $self = shift; + my @rematches = $self->_get_rematches_constant_list; + my $rematches = {@rematches}; + my @final_hash = ( + map { $_ => $rematches->{$_} } + grep { $_ =~ /^\d+$/ } keys %$rematches + ); + return @final_hash; +} + +sub _build_saves_and_extra { + my $self = shift; + my ( @saves_raw, $extra ); + ( @saves_raw[ 0, 1 ], $extra ) = read_save( $self->input_file ); + my @saves = get_saves( @saves_raw, $EMERALD_VERSION ); + $self->__saves( \@saves ); + $self->__extra($extra); +} + +sub _get_rematches_constant_list { + return @Rsaves::Constants::Emerald::Rematches::REMATCHES; +} + +sub _get_flags_constant_list { + return @Rsaves::Constants::Emerald::Flags::FLAGS; +} + +sub _get_current_save { + my $self = shift; + return + $self->_saves->[ Rsaves::find_current_save_index( @{ $self->_saves } ) ]; +} + +sub _get_flags_hash_by_id { + my $self = shift; + my @flags = $self->_get_flags_constant_list; + my $flags = {@flags}; + my @final_hash = ( + map { $_ => $flags->{$_} } + grep { $_ =~ /^\d+$/ } keys %$flags + ); + return @final_hash; +} +1; diff --git a/lib/GEmeTool/View/MainWindow.pm b/lib/GEmeTool/View/MainWindow.pm new file mode 100644 index 0000000..e39dedd --- /dev/null +++ b/lib/GEmeTool/View/MainWindow.pm @@ -0,0 +1,285 @@ +package GEmeTool::View::MainWindow; + +use v5.16.3; + +use strict; +use warnings; +use utf8; + +use Glib; +use Glib::IO; +use Glib::Object::Introspection; +use Data::Dumper; +use Path::Tiny; +use GEmeTool::Options; +use GEmeTool::Save; + +use Moo; + +Glib::Object::Introspection->setup( + basename => 'Gtk', + version => '4.0', + package => 'Gtk4', +); + +Glib::Object::Introspection->setup( + basename => 'Gdk', + version => '4.0', + package => 'Gdk', +); + +use namespace::clean; + +has _win => ( + is => 'rw', +); + +has _options => ( + is => 'rw', +); + +has _app => ( + is => 'rw', +); + +has _save => ( + is => 'rw', +); + +has _save_as => ( + is => 'rw', +); + +has _file => ( + is => 'rw', +); + +sub start { + my $self = shift; + my $options = GEmeTool::Options->new; + $self->_options($options); + my $app = Gtk4::Application->new( 'tech.owlcode.GEmeTool', 'default-flags' ); + $self->_app($app); + $app->signal_connect( activate => sub { + $self->activate; + } ); + $app->run; +} + +sub activate { + my $self = shift; + my $display = Gdk::Display::get_default(); + my $icon_theme = Gtk4::IconTheme::get_for_display($display); + $icon_theme->set_search_path( + path(__FILE__)->parent->parent->child('resources/icons')->absolute ); + Gtk4::Window::set_default_icon_name('gemetool'); + + my $win; + + my $menu = Glib::IO::Menu->new; + my $about = Glib::IO::SimpleAction->new( 'about', undef ); + my $open = Glib::IO::SimpleAction->new( 'open', undef ); + my $save_as = Glib::IO::SimpleAction->new( 'save_as', undef ); + $save_as->set_enabled(0); + my $app = $self->_app; + $app->add_action($about); + $app->add_action($open); + $app->add_action($save_as); + $self->_save_as($save_as); + my $save; + my $extra; + my $save_menu_item = Glib::IO::MenuItem->new( 'Save as', 'app.save_as' ); + $open->signal_connect( + activate => sub { + $self->activate_open; + } + ); + $save_as->signal_connect( + activate => sub { + $self->activate_save; + } + ); + $about->signal_connect( activate => \&activate_about, ); + + my $about_menu_item = Glib::IO::MenuItem->new( 'About', 'app.about' ); + my $open_menu_item = Glib::IO::MenuItem->new( 'Open', 'app.open' ); + my $submenu_file = Glib::IO::Menu->new; + my $submenu_help = Glib::IO::Menu->new; + $submenu_help->append_item($about_menu_item); + $submenu_file->append_item($open_menu_item); + $submenu_file->append_item($save_menu_item); + $menu->append_submenu( 'File', $submenu_file ); + $menu->append_submenu( 'Help', $submenu_help ); + $app->set_menubar($menu); + my $box = Gtk4::Box->new( 'vertical', 0 ); + $box->append( + Gtk4::Label->new('You have to load a Pokemon Emerald Save.') ); + + $win = Gtk4::ApplicationWindow->new($app); + $self->_win($win); + $win->set_title('GEmeTool'); + $win->set_show_menubar(1); + $win->set_default_size( 600, 600 ); + $win->set_icon_name('gemetool'); + $win->set_child($box); + $win->present; +} + +sub activate_open { + my $self = shift; + my $cancellable = Glib::IO::Cancellable->new; + my $dialog = Gtk4::FileDialog->new; + my $options = $self->_options; + my $win = $self->_win; + my $last_dir = $options->get_last_dir_open; + if (defined $last_dir && -d $last_dir) { + my $curdir = Glib::IO::File::new_for_path($last_dir); + $dialog->set_initial_folder($curdir); + } + $dialog->open( + $win, $cancellable, + sub { + my ( $self_dialog, $res ) = @_; + if ($res->had_error) { + return; + } + my $file = $dialog->open_finish($res); + return if !defined $file; + $file = path( $file->get_path ); + $self->_file($file); + $options->set_last_dir_open($file->parent.''); + $self->start_editing_file; + } + ); +} + +sub activate_save { + my $self = shift; + my $win = $self->_win; + my $dialog = Gtk4::FileDialog->new; + my $options = $self->_options; + my $last_dir = $options->get_last_dir_save; + if (defined $last_dir && -d $last_dir) { + my $curdir = Glib::IO::File::new_for_path($last_dir); + $dialog->set_initial_folder($curdir); + } + $dialog->save( + $win, undef, + sub { + my ( $self_dialog, $res ) = @_; + if ($res->had_error) { + return; + } + my $file = $dialog->save_finish($res); + return if !defined $file; + $file = path( $file->get_path ); + $options->set_last_dir_save($file->parent.''); + $self->_save->save( $file); + } + ); +} + +sub save { + my $self = shift; + my $file = shift; + $self->_save->save($file); +} + +sub start_editing_file { + my $self = shift; + my $file = $self->_file; + my $save_as = $self->_save_as; + my (@saves_raw); + my $box_app = Gtk4::Box->new( 'vertical', 0 ); + $self->_save(GEmeTool::Save->instance($file)); + my $save_obj = $self->_save; + + my $search = Gtk4::SearchBar->new; + my $entry = Gtk4::Entry->new; + $search->set_child($entry); + $search->set_search_mode(1); + $box_app->append($search); + $box_app->set_property( 'vexpand', 1 ); + my $scroll = Gtk4::ScrolledWindow->new; + $scroll->set_property( 'vexpand', 1 ); + + my $populate_func = sub { + my $box_flags = Gtk4::Box->new( 'vertical', 0 ); + for my $rematch (@{$self->_save->get_rematches_save}) + { + my $name = $rematch->{name}; + my $value = $rematch->{value}; + my $id = $rematch->{id}; + my $buffer = $entry->get_buffer; + my $search_text = uc( $buffer->get_text ); + next unless index( $name, $search_text ) >= 0; + my $toggle = Gtk4::ToggleButton->new_with_label($name); + $toggle->set_active($value); + $toggle->signal_connect( + toggled => sub { + my $active = $toggle->get_active; + $self->_save->set_rematch($id, $active); + } + ); + $box_flags->append($toggle); + } + for my $flag (@{$self->_save->get_flags_save}) { + my $name = $flag->{name}; + my $value = $flag->{value}; + my $id = $flag->{id}; + my $buffer = $entry->get_buffer; + my $search_text = uc( $buffer->get_text ); + next unless index( $name, $search_text ) >= 0; + my $toggle = Gtk4::ToggleButton->new_with_label($name); + $toggle->set_active( $value ); + $toggle->signal_connect( + toggled => sub { + my $active = $toggle->get_active; + $self->_save->set_flag($active); + } + ); + + $box_flags->append($toggle); + } + $scroll->set_child($box_flags); + }; + $populate_func->(); + $entry->signal_connect( + activate => sub { + $populate_func->(); + } + ); + $box_app->append($scroll); + my $win = $self->_win; + $win->set_child($box_app); + $save_as->set_enabled(1); + +} + +sub activate_about { + my $about = Gtk4::AboutDialog->new; + $about->set_program_name('GEmeTool'); + $about->set_copyright("© Sergio Iglesias (2024)"); + $about->set_license_type('gpl-3-0'); + $about->add_credit_section( 'Ideas:', ['SpectreSpecs'] ); + $about->set_authors( ['Sergio Iglesias'] ); + eval { + my $texture = Gdk::Texture->new_from_filename( + path(__FILE__)->parent->parent->parent->parent->child( + 'resources/icons/hicolor/scalable/apps/gemetool.svg'), + ); + $about->set_logo($texture); + }; + if ($@) { + warn $@; + } + $about->set_website('https://git.owlcode.tech/sergiotarxz/GEmeTool'); + $about->set_website_label('https://git.owlcode.tech/sergiotarxz/GEmeTool'); + + # my $header_bar = Gtk4::HeaderBar->new; + # $header_bar->set_property( + # 'title-widget' => Gtk4::Label->new('About GEmeTool') ); + # $about->set_titlebar($header_bar); + $about->present; +} +1; diff --git a/lib/Rsaves.pm b/lib/Rsaves.pm index 08cfe8d..1afa5c8 100644 --- a/lib/Rsaves.pm +++ b/lib/Rsaves.pm @@ -761,7 +761,6 @@ sub _save_section { sub save_changes { my ( @saves, $extra, $filename ); ( @saves[ 0, 1 ], $extra, $filename ) = @_; - say $filename; my $content = ''; for my $save (@saves) { for my $section (@$save) { diff --git a/scripts/start.pl b/scripts/start.pl index d348eef..9dcdabd 100644 --- a/scripts/start.pl +++ b/scripts/start.pl @@ -5,259 +5,6 @@ use v5.16.3; use strict; use warnings; -use Glib; -use Glib::IO; -use Glib::Object::Introspection; -use Data::Dumper; -use Path::Tiny; -use GEmeTool::Options; -use utf8; +use GEmeTool::View::MainWindow; -use Rsaves - qw/read_save check_correct_size get_saves find_current_save_index check_correct_size find_pokemon_substruct change_gender read_pc_storage save_pc_changes enable_eon_ticket save_changes pokemon_set_shiny read_pkm_file_box parse_version_name check_flag_id get_first_super_data check_rematch/; -use Rsaves::Constants::Global - qw/$SAPPHIRE_VERSION $RUBY_VERSION $EMERALD_VERSION $FIRERED_VERSION $LEAFGREEN_VERSION $COLOSSEUM_VERSION/; -use Rsaves::Constants::Emerald::Flags; -use Rsaves::Constants::Emerald::Rematches; - -Glib::Object::Introspection->setup( - basename => 'Gtk', - version => '4.0', - package => 'Gtk4', -); - -Glib::Object::Introspection->setup( - basename => 'Gdk', - version => '4.0', - package => 'Gdk', -); - -my %rematches = @Rsaves::Constants::Emerald::Rematches::REMATCHES; - -my $app = Gtk4::Application->new( 'tech.owlcode.GEmeTool', 'default-flags' ); -my $options = GEmeTool::Options->new; -$app->signal_connect( activate => \&activate ); -$app->run; - -sub activate { - my $display = Gdk::Display::get_default(); - my $icon_theme = Gtk4::IconTheme::get_for_display($display); - $icon_theme->set_search_path( - path(__FILE__)->parent->parent->child('resources/icons')->absolute ); - Gtk4::Window::set_default_icon_name('gemetool'); - - my $win; - - my $menu = Glib::IO::Menu->new; - my $about = Glib::IO::SimpleAction->new( 'about', undef ); - my $open = Glib::IO::SimpleAction->new( 'open', undef ); - my $save_as = Glib::IO::SimpleAction->new( 'save_as', undef ); - $save_as->set_enabled(0); - $app->add_action($about); - $app->add_action($open); - $app->add_action($save_as); - my @saves; - my $extra; - my $save_menu_item = Glib::IO::MenuItem->new( 'Save as', 'app.save_as' ); - $open->signal_connect( - activate => sub { - activate_open( $win, \@saves, \$extra, $save_as ); - } - ); - $save_as->signal_connect( - activate => sub { - activate_save( $win, \@saves, $extra ); - } - ); - $about->signal_connect( activate => \&activate_about, ); - - my $about_menu_item = Glib::IO::MenuItem->new( 'About', 'app.about' ); - my $open_menu_item = Glib::IO::MenuItem->new( 'Open', 'app.open' ); - my $submenu_file = Glib::IO::Menu->new; - my $submenu_help = Glib::IO::Menu->new; - $submenu_help->append_item($about_menu_item); - $submenu_file->append_item($open_menu_item); - $submenu_file->append_item($save_menu_item); - $menu->append_submenu( 'File', $submenu_file ); - $menu->append_submenu( 'Help', $submenu_help ); - $app->set_menubar($menu); - my $box = Gtk4::Box->new( 'vertical', 0 ); - $box->append( - Gtk4::Label->new('You have to load a Pokemon Emerald Save.') ); - - $win = Gtk4::ApplicationWindow->new($app); - $win->set_title('GEmeTool'); - $win->set_show_menubar(1); - $win->set_default_size( 600, 600 ); - $win->set_icon_name('gemetool'); - $win->set_child($box); - $win->present; -} - -sub activate_open { - my $win = shift; - my $saves = shift; - my $extra = shift; - my $save_as = shift; - my $cancellable = Glib::IO::Cancellable->new; - my $dialog = Gtk4::FileDialog->new; - my $last_dir = $options->get_last_dir_open; - if (defined $last_dir && -d $last_dir) { - my $curdir = Glib::IO::File::new_for_path($last_dir); - $dialog->set_initial_folder($curdir); - } - $dialog->open( - $win, $cancellable, - sub { - my ( $self, $res ) = @_; - if ($res->had_error) { - return; - } - my $file = $dialog->open_finish($res); - return if !defined $file; - $file = path( $file->get_path ); - $options->set_last_dir_open($file->parent.''); - start_editing_file( $win, $file, $saves, $extra, $save_as ); - } - ); -} - -sub activate_save { - my $win = shift; - my $saves = shift; - my $extra = shift; - my $dialog = Gtk4::FileDialog->new; - my $last_dir = $options->get_last_dir_save; - if (defined $last_dir && -d $last_dir) { - my $curdir = Glib::IO::File::new_for_path($last_dir); - $dialog->set_initial_folder($curdir); - } - $dialog->save( - $win, undef, - sub { - my ( $self, $res ) = @_; - if ($res->had_error) { - return; - } - my $file = $dialog->save_finish($res); - return if !defined $file; - $file = path( $file->get_path ); - $options->set_last_dir_save($file->parent.''); - save( $file, $saves, $extra ); - } - ); -} - -sub save { - my ( $file, $saves, $extra ) = @_; - - save_changes( @$saves, $extra, $file ); -} - -sub start_editing_file { - my $win = shift; - my $file = shift; - my $saves = shift; - my $extra = shift; - my $save_as = shift; - my (@saves_raw); - ( @saves_raw[ 0, 1 ], ${$extra} ) = read_save($file); - @$saves = get_saves( @saves_raw, $EMERALD_VERSION ); - my $current_save_index = find_current_save_index(@$saves); - my $save = $saves->[$current_save_index]; - my %flags = @Rsaves::Constants::Emerald::Flags::FLAGS; - my $box_app = Gtk4::Box->new( 'vertical', 0 ); - my $superdata = get_first_super_data($save); - - my $search = Gtk4::SearchBar->new; - my $entry = Gtk4::Entry->new; - $search->set_child($entry); - $search->set_search_mode(1); - $box_app->append($search); - $box_app->set_property( 'vexpand', 1 ); - my $scroll = Gtk4::ScrolledWindow->new; - $scroll->set_property( 'vexpand', 1 ); - - my $populate_func = sub { - my $box_flags = Gtk4::Box->new( 'vertical', 0 ); - for my $rematch_id ( - sort { $a <=> $b } - grep { $_ =~ /^\d+$/ } keys %rematches - ) - { - my $buffer = $entry->get_buffer; - my $search_text = uc( $buffer->get_text ); - my $text = $rematches{$rematch_id}; - next unless index( $text, $search_text ) >= 0; - my $toggle = Gtk4::ToggleButton->new_with_label($text); - $toggle->set_active( - check_rematch( $save, $superdata, $rematch_id ) ); - $toggle->signal_connect( - toggled => sub { - my $active = $toggle->get_active; - Rsaves::set_rematch( $save, $superdata, $rematch_id, - $active ); - Rsaves::set_first_super_data( $save, $superdata ); - } - ); - $box_flags->append($toggle); - } - for - my $flag_id ( sort { $a <=> $b } grep { $_ =~ /^\d+$/ } keys %flags ) - { - my $buffer = $entry->get_buffer; - my $search_text = uc( $buffer->get_text ); - my $text = $flags{$flag_id}; - next unless index( $text, $search_text ) >= 0; - my $toggle = Gtk4::ToggleButton->new_with_label($text); - $toggle->set_active( check_flag_id( $save, $superdata, $flag_id ) ); - $toggle->signal_connect( - toggled => sub { - my $active = $toggle->get_active; - Rsaves::set_flag_id( $save, $superdata, $flag_id, $active ); - Rsaves::set_first_super_data( $save, $superdata ); - } - ); - - $box_flags->append($toggle); - } - $scroll->set_child($box_flags); - }; - $populate_func->(); - $entry->signal_connect( - activate => sub { - $populate_func->(); - } - ); - $box_app->append($scroll); - $win->set_child($box_app); - $save_as->set_enabled(1); - -} - -sub activate_about { - my $about = Gtk4::AboutDialog->new; - $about->set_program_name('GEmeTool'); - $about->set_copyright("© Sergio Iglesias (2024)"); - $about->set_license_type('gpl-3-0'); - $about->add_credit_section( 'Ideas:', ['SpectreSpecs'] ); - $about->set_authors( ['Sergio Iglesias'] ); - eval { - my $texture = Gdk::Texture->new_from_filename( - path(__FILE__)->parent->parent->child( - 'resources/icons/hicolor/scalable/apps/gemetool.svg'), - ); - $about->set_logo($texture); - }; - if ($@) { - warn $@; - } - $about->set_website('https://git.owlcode.tech/sergiotarxz/GEmeTool'); - $about->set_website_label('https://git.owlcode.tech/sergiotarxz/GEmeTool'); - - # my $header_bar = Gtk4::HeaderBar->new; - # $header_bar->set_property( - # 'title-widget' => Gtk4::Label->new('About GEmeTool') ); - # $about->set_titlebar($header_bar); - $about->present; -} +GEmeTool::View::MainWindow->new->start;