Adding kanji support, not to be released yet.
This commit is contained in:
parent
ac9447cff2
commit
9a89f97910
@ -54,6 +54,11 @@ has _window => ( is => 'rw' );
|
||||
has _on_resize_triggers => ( is => 'ro', default => sub { {}; } );
|
||||
has accessibility => ( is => 'lazy' );
|
||||
has characters => ( is => 'lazy' );
|
||||
has kanji => ( is => 'lazy' );
|
||||
|
||||
sub _build_kanji($self) {
|
||||
return JapaChar::Kanji->new(app => $self);
|
||||
}
|
||||
|
||||
sub _build_characters($self) {
|
||||
require JapaChar::Characters;
|
||||
@ -124,7 +129,7 @@ sub window_set_child( $self, $child ) {
|
||||
sub _application_start( $self, $app ) {
|
||||
my $main_window = Adw::ApplicationWindow->new($app);
|
||||
$self->_window($main_window);
|
||||
$main_window->set_default_size( 1200, 600 );
|
||||
$main_window->set_default_size( 1200, 800 );
|
||||
$main_window->signal_connect(
|
||||
notify => sub( $object, $param ) {
|
||||
if ( $param->{name} eq 'default-width' ) {
|
||||
|
@ -99,7 +99,12 @@ sub _apply_migration {
|
||||
$current_migration->($dbh);
|
||||
next;
|
||||
}
|
||||
eval {
|
||||
$dbh->do($current_migration);
|
||||
};
|
||||
if ($@) {
|
||||
die "$current_migration\n$@"
|
||||
}
|
||||
}
|
||||
$dbh->do( <<'EOF', undef, 'current_migration', $migration_number );
|
||||
INSERT INTO options
|
||||
|
@ -25,6 +25,47 @@ sub MIGRATIONS {
|
||||
);',
|
||||
'INSERT INTO options (name, value) VALUES (\'user_score\', \'0\');',
|
||||
'ALTER TABLE basic_characters ADD consecutive_failures INTEGER NOT NULL DEFAULT 0',
|
||||
'CREATE TABLE kanji (
|
||||
id INTEGER PRIMARY KEY,
|
||||
kanji TEXT NOT NULL UNIQUE,
|
||||
grade INTEGER NOT NULL,
|
||||
started BOOLEAN NOT NULL DEFAULT 0,
|
||||
score INTEGER NOT NULL DEFAULT 0,
|
||||
consecutive_success INTEGER NOT NULL DEFAULT 0
|
||||
);',
|
||||
'CREATE TABLE kanji_meanings (
|
||||
id INTEGER PRIMARY KEY,
|
||||
id_kanji INTEGER NOT NULL,
|
||||
meaning TEXT NOT NULL,
|
||||
FOREIGN KEY (id_kanji) REFERENCES kanji(id)
|
||||
);',
|
||||
'CREATE TABLE kanji_on_readings (
|
||||
id INTEGER PRIMARY KEY,
|
||||
id_kanji INTEGER NOT NULL,
|
||||
reading TEXT NOT NULL,
|
||||
FOREIGN KEY (id_kanji) REFERENCES kanji(id)
|
||||
);',
|
||||
'CREATE TABLE kanji_kun_readings (
|
||||
id INTEGER PRIMARY KEY,
|
||||
id_kanji INTEGER NOT NULL,
|
||||
reading TEXT NOT NULL,
|
||||
FOREIGN KEY (id_kanji) REFERENCES kanji(id)
|
||||
);',
|
||||
'INSERT INTO options (name, value) VALUES (\'kanji_version\', \'0\');',
|
||||
'INSERT INTO options (name, value) VALUES (\'want_kanji_version\', \'1\');',
|
||||
'ALTER TABLE kanji ADD consecutive_failures INTEGER NOT NULL DEFAULT 0;',
|
||||
'CREATE TABLE kanji2 (
|
||||
id INTEGER PRIMARY KEY,
|
||||
kanji TEXT NOT NULL UNIQUE,
|
||||
grade INTEGER,
|
||||
started BOOLEAN NOT NULL DEFAULT 0,
|
||||
score INTEGER NOT NULL DEFAULT 0,
|
||||
consecutive_success INTEGER NOT NULL DEFAULT 0,
|
||||
consecutive_failures INTEGER NOT NULL DEFAULT 0
|
||||
);',
|
||||
'INSERT INTO kanji2 SELECT * FROM kanji;',
|
||||
'DROP TABLE kanji;',
|
||||
'ALTER TABLE kanji2 RENAME TO kanji;',
|
||||
);
|
||||
}
|
||||
1;
|
||||
|
253
lib/JapaChar/Kanji.pm
Normal file
253
lib/JapaChar/Kanji.pm
Normal file
@ -0,0 +1,253 @@
|
||||
package JapaChar::Kanji;
|
||||
|
||||
use v5.40.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Data::Dumper;
|
||||
|
||||
use Mojo::DOM;
|
||||
|
||||
use JapaChar::Schema;
|
||||
|
||||
use Moo;
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
has app => ( is => 'ro', required => 1 );
|
||||
has _kanji_schema => ( is => 'lazy' );
|
||||
has _options_schema => ( is => 'lazy' );
|
||||
has _schema => ( is => 'lazy' );
|
||||
|
||||
sub _build__schema($self) {
|
||||
return JapaChar::Schema->Schema;
|
||||
}
|
||||
|
||||
sub _build__kanji_schema($self) {
|
||||
return $self->_schema->resultset('Kanji');
|
||||
}
|
||||
|
||||
sub _build__options_schema($self) {
|
||||
return $self->_schema->resultset('Option');
|
||||
}
|
||||
|
||||
sub grades($self) {
|
||||
my @grades =
|
||||
grep { defined $_ }
|
||||
map { $_->grade }
|
||||
$self->_kanji_schema->search( {},
|
||||
{ columns => ['grade'], distinct => 1 } );
|
||||
return \@grades;
|
||||
}
|
||||
|
||||
sub get_4_incorrect_answers( $self, $char, $guess) {
|
||||
if ($guess->isa('JapaChar::Schema::Result::KanjiMeanings')) {
|
||||
my %already_present_guesses;
|
||||
my $invalid_results = [map { $_->meaning } $char->meanings];
|
||||
my $meanings_resultset = $self->_schema->resultset('KanjiMeanings');
|
||||
my @possible_meanings = map { $_->meaning } $meanings_resultset->search({
|
||||
-bool => 'kanji.started',
|
||||
meaning => { -not_in => $invalid_results },
|
||||
},
|
||||
{
|
||||
order_by => { -asc => \'RANDOM()' },
|
||||
rows => 4,
|
||||
join => 'kanji',
|
||||
}
|
||||
);
|
||||
return \@possible_meanings;
|
||||
}
|
||||
if ($guess->isa('JapaChar::Schema::Result::KanjiOnReadings')) {
|
||||
my %already_present_guesses;
|
||||
my $invalid_results = [map { $_->reading } $char->on_readings];
|
||||
my $readings_resultset = $self->_schema->resultset('KanjiOnReadings');
|
||||
my @possible_readings = map { decode 'utf-8', $_->reading } $readings_resultset->search({
|
||||
-bool => 'kanji.started',
|
||||
reading => { -not_in => $invalid_results },
|
||||
},
|
||||
{
|
||||
order_by => { -asc => \'RANDOM()' },
|
||||
rows => 4,
|
||||
join => 'kanji',
|
||||
}
|
||||
);
|
||||
return \@possible_readings;
|
||||
}
|
||||
if ($guess->isa('JapaChar::Schema::Result::KanjiKunReadings')) {
|
||||
my %already_present_guesses;
|
||||
my $invalid_results = [map { $_->reading } $char->kun_readings];
|
||||
my $readings_resultset = $self->_schema->resultset('KanjiOnReadings');
|
||||
my @possible_readings = map { decode 'utf-8', $_->reading } $readings_resultset->search({
|
||||
-bool => 'kanji.started',
|
||||
reading => { -not_in => $invalid_results },
|
||||
},
|
||||
{
|
||||
order_by => { -asc => \'RANDOM()' },
|
||||
rows => 4,
|
||||
join => 'kanji',
|
||||
}
|
||||
);
|
||||
return \@possible_readings;
|
||||
}
|
||||
}
|
||||
|
||||
sub migrated($self) {
|
||||
my ($option_want_kanji_version) =
|
||||
$self->_options_schema->search( { name => 'want_kanji_version' } );
|
||||
my ($option_kanji_version) =
|
||||
$self->_options_schema->search( { name => 'kanji_version' } );
|
||||
if ( $option_kanji_version->value >= $option_want_kanji_version->value ) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub populate_kanji( $self, $parent_pid, $write ) {
|
||||
$self->_schema->txn_do(
|
||||
sub {
|
||||
my ($option_want_kanji_version) =
|
||||
$self->_options_schema->search(
|
||||
{ name => 'want_kanji_version' } );
|
||||
my ($option_kanji_version) =
|
||||
$self->_options_schema->search( { name => 'kanji_version' } );
|
||||
if ( $self->migrated ) {
|
||||
say 'You already have the kanji database';
|
||||
return;
|
||||
}
|
||||
say 'Populating Kanji database, please wait...';
|
||||
my $schema = $self->_kanji_schema;
|
||||
my $root = $self->app->root;
|
||||
my $dom =
|
||||
Mojo::DOM->new( $root->child('kanjidic2.xml')->slurp_raw );
|
||||
$dom->xml(1);
|
||||
my @characters;
|
||||
my $i = 0;
|
||||
|
||||
my @characters_dom =
|
||||
grep { $_->type eq 'tag' && $_->tag eq 'character' }
|
||||
$dom->at('kanjidic2')->child_nodes->each;
|
||||
$write->syswrite( ( scalar @characters_dom ) . "\n" );
|
||||
$write->flush;
|
||||
for my $character_dom (@characters_dom) {
|
||||
if ( !kill 0, $parent_pid ) {
|
||||
die 'Parent died';
|
||||
}
|
||||
my $literal = $character_dom->at('literal')->text;
|
||||
my $grade;
|
||||
my $grade_dom = $character_dom->at('grade');
|
||||
if ( defined $grade_dom ) {
|
||||
$grade = $grade_dom->text;
|
||||
}
|
||||
my @meanings;
|
||||
for my $meaning_dom ( $character_dom->find('meaning')->each ) {
|
||||
next if scalar %{ $meaning_dom->attr };
|
||||
push @meanings, { meaning => $meaning_dom->text, };
|
||||
}
|
||||
my @readings_on;
|
||||
my @readings_kun;
|
||||
for my $reading_dom ( $character_dom->find('reading')->each ) {
|
||||
if ( $reading_dom->attr('r_type') eq 'ja_on' ) {
|
||||
push @readings_on, { reading => $reading_dom->text, };
|
||||
}
|
||||
if ( $reading_dom->attr('r_type') eq 'ja_kun' ) {
|
||||
push @readings_on, { reading => $reading_dom->text, };
|
||||
}
|
||||
}
|
||||
push @characters,
|
||||
{
|
||||
id => $i++,
|
||||
kanji => $literal,
|
||||
grade => $grade,
|
||||
meanings => \@meanings,
|
||||
on_readings => \@readings_on,
|
||||
kun_readings => \@readings_kun,
|
||||
};
|
||||
if ( $i % 300 == 0 ) {
|
||||
$self->_kanji_schema->populate( \@characters );
|
||||
$write->syswrite( $i . "\n" );
|
||||
$write->flush;
|
||||
@characters = ();
|
||||
}
|
||||
}
|
||||
$self->_kanji_schema->populate( \@characters );
|
||||
$write->syswrite( scalar @characters_dom . "\n" );
|
||||
$write->flush;
|
||||
$option_kanji_version->update(
|
||||
{ value => $option_want_kanji_version->value } );
|
||||
say 'Populated kanji database';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub next_char( $self, $accesibility, $type = undef ) {
|
||||
my $next_review = $self->_next_review_char($type);
|
||||
my $next_learning = $self->_next_learning_char($type);
|
||||
if ( !defined $next_review ) {
|
||||
return $next_learning;
|
||||
}
|
||||
if ( !defined $next_learning ) {
|
||||
return $next_review;
|
||||
}
|
||||
my $rng = JapaChar::Random->new->get( 1, 100 );
|
||||
if ( $rng > 20 ) {
|
||||
return $next_learning;
|
||||
}
|
||||
return $next_review;
|
||||
}
|
||||
|
||||
sub _next_review_char( $self, $type = undef ) {
|
||||
my $kanji_resultset =
|
||||
JapaChar::Schema->Schema->resultset('Kanji');
|
||||
my @chars = $kanji_resultset->search(
|
||||
{
|
||||
score => { '>=' => 300 },
|
||||
( ( $type ne 'all' ) ? ( grade => { is => $type} ) : () )
|
||||
},
|
||||
{
|
||||
order_by => { -asc => \'RANDOM()' },
|
||||
rows => 1
|
||||
}
|
||||
);
|
||||
if ( !@chars ) {
|
||||
return;
|
||||
}
|
||||
return $chars[0];
|
||||
}
|
||||
|
||||
sub _next_learning_char( $self, $type = undef ) {
|
||||
my $kanji_resultset =
|
||||
JapaChar::Schema->Schema->resultset('Kanji');
|
||||
my @candidate_chars = $self->_retrieve_started_chars_not_finished($type);
|
||||
if ( @candidate_chars < 5 ) {
|
||||
my @new_chars = $kanji_resultset->search(
|
||||
{
|
||||
-not_bool => 'started',
|
||||
( ( $type ne 'all' ) ? ( grade => { is => $type } ) : () )
|
||||
},
|
||||
{
|
||||
order_by => { -asc => ['grade', 'id'] },
|
||||
rows => 5 - scalar @candidate_chars,
|
||||
}
|
||||
);
|
||||
for my $char (@new_chars) {
|
||||
$char->update( { started => 1 } );
|
||||
}
|
||||
@candidate_chars = $self->_retrieve_started_chars_not_finished($type);
|
||||
}
|
||||
my $char = $candidate_chars[ int( rand( scalar @candidate_chars ) ) ];
|
||||
return $char;
|
||||
}
|
||||
|
||||
sub _retrieve_started_chars_not_finished( $self, $type ) {
|
||||
my $kanji_resultset =
|
||||
JapaChar::Schema->Schema->resultset('Kanji');
|
||||
return $kanji_resultset->search(
|
||||
{
|
||||
( ( $type ne 'all' ) ? ( grade => { is => $type } ) : () ),
|
||||
score => { '<' => 100 },
|
||||
-bool => 'started',
|
||||
}
|
||||
);
|
||||
}
|
||||
1;
|
99
lib/JapaChar/Schema/Result/Kanji.pm
Normal file
99
lib/JapaChar/Schema/Result/Kanji.pm
Normal file
@ -0,0 +1,99 @@
|
||||
package JapaChar::Schema::Result::Kanji;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
__PACKAGE__->table('kanji');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'INTEGER',
|
||||
is_auto_increment => 1,
|
||||
},
|
||||
kanji => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
accessor => '_kanji',
|
||||
},
|
||||
grade => {
|
||||
data_type => 'INTEGER',
|
||||
},
|
||||
started => {
|
||||
data_type => 'BOOLEAN',
|
||||
},
|
||||
score => {
|
||||
data_type => 'INTEGER',
|
||||
},
|
||||
consecutive_success => {
|
||||
data_type => 'INTEGER',
|
||||
},
|
||||
consecutive_failures => {
|
||||
data_type => 'INTEGER',
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
|
||||
__PACKAGE__->has_many(meanings => 'JapaChar::Schema::Result::KanjiMeanings', 'id_kanji');
|
||||
__PACKAGE__->has_many(on_readings => 'JapaChar::Schema::Result::KanjiOnReadings', 'id_kanji');
|
||||
__PACKAGE__->has_many(kun_readings => 'JapaChar::Schema::Result::KanjiKunReadings', 'id_kanji');
|
||||
|
||||
sub kanji( $self, $kanji = undef ) {
|
||||
if ( defined $kanji ) {
|
||||
$self->_kanji($kanji);
|
||||
}
|
||||
return decode 'utf-8', $self->_kanji;
|
||||
}
|
||||
|
||||
sub fail($self) {
|
||||
my $score = $self->score;
|
||||
my $consecutive_success = 0;
|
||||
my $consecutive_failures = $self->consecutive_failures + 1;
|
||||
$score -= 25;
|
||||
if ( $score < 0 ) {
|
||||
$score = 0;
|
||||
}
|
||||
$self->update(
|
||||
{
|
||||
score => $score,
|
||||
consecutive_failures => $consecutive_failures,
|
||||
consecutive_success => 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sub get( $self, $what ) {
|
||||
if ( $what eq 'kana' ) {
|
||||
return $self->value;
|
||||
}
|
||||
if ( $what eq 'romanji' ) {
|
||||
return $self->romanji;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub success($self) {
|
||||
my $score = $self->score;
|
||||
my $consecutive_success = $self->consecutive_success + 1;
|
||||
my $consecutive_failures = 0;
|
||||
$score += 5 + 10 * $consecutive_success;
|
||||
if ( $score > 300 ) {
|
||||
$score = 300;
|
||||
}
|
||||
$self->update(
|
||||
{
|
||||
score => $score,
|
||||
consecutive_success => $consecutive_success,
|
||||
consecutive_failures => $consecutive_failures,
|
||||
}
|
||||
);
|
||||
}
|
||||
1;
|
33
lib/JapaChar/Schema/Result/KanjiKunReadings.pm
Normal file
33
lib/JapaChar/Schema/Result/KanjiKunReadings.pm
Normal file
@ -0,0 +1,33 @@
|
||||
package JapaChar::Schema::Result::KanjiKunReadings;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
__PACKAGE__->table('kanji_kun_readings');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'INTEGER',
|
||||
is_auto_increment => 1,
|
||||
},
|
||||
id_kanji => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 0,
|
||||
},
|
||||
reading => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
__PACKAGE__->belongs_to(kanji => 'JapaChar::Schema::Result::Kanji', 'id_kanji');
|
||||
1;
|
33
lib/JapaChar/Schema/Result/KanjiMeanings.pm
Normal file
33
lib/JapaChar/Schema/Result/KanjiMeanings.pm
Normal file
@ -0,0 +1,33 @@
|
||||
package JapaChar::Schema::Result::KanjiMeanings;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
__PACKAGE__->table('kanji_meanings');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'INTEGER',
|
||||
is_auto_increment => 1,
|
||||
},
|
||||
id_kanji => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 0,
|
||||
},
|
||||
meaning => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
__PACKAGE__->belongs_to(kanji => 'JapaChar::Schema::Result::Kanji', 'id_kanji');
|
||||
1;
|
33
lib/JapaChar/Schema/Result/KanjiOnReadings.pm
Normal file
33
lib/JapaChar/Schema/Result/KanjiOnReadings.pm
Normal file
@ -0,0 +1,33 @@
|
||||
package JapaChar::Schema::Result::KanjiOnReadings;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
__PACKAGE__->table('kanji_on_readings');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
id => {
|
||||
data_type => 'INTEGER',
|
||||
is_auto_increment => 1,
|
||||
},
|
||||
id_kanji => {
|
||||
data_type => 'INTEGER',
|
||||
is_nullable => 0,
|
||||
},
|
||||
reading => {
|
||||
data_type => 'TEXT',
|
||||
is_nullable => 0,
|
||||
},
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key('id');
|
||||
__PACKAGE__->belongs_to(kanji => 'JapaChar::Schema::Result::Kanji', 'id_kanji');
|
||||
1;
|
221
lib/JapaChar/View/KanjiLesson.pm
Normal file
221
lib/JapaChar/View/KanjiLesson.pm
Normal file
@ -0,0 +1,221 @@
|
||||
package JapaChar::View::KanjiLesson;
|
||||
|
||||
use v5.40.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Moo;
|
||||
use Path::Tiny;
|
||||
use Glib::Object::Introspection;
|
||||
use YAML::PP;
|
||||
use JapaChar::DB;
|
||||
use JapaChar::Characters;
|
||||
use Pango;
|
||||
use JapaChar::Random;
|
||||
use JapaChar::Score;
|
||||
|
||||
use Glib::IO;
|
||||
|
||||
use constant PANGO_SCALE => 1024;
|
||||
my $exit_the_lesson_id = 'exit-the-lesson';
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gtk',
|
||||
version => '4.0',
|
||||
package => 'Gtk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gdk',
|
||||
version => '4.0',
|
||||
package => 'Gtk::Gdk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gsk',
|
||||
version => '4.0',
|
||||
package => 'Gtk::Gsk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Adw',
|
||||
version => '1',
|
||||
package => 'Adw',
|
||||
);
|
||||
|
||||
has app => ( is => 'ro', required => 1 );
|
||||
has type => ( is => 'ro', default => sub { 'all' } );
|
||||
has counter => ( is => 'rw' );
|
||||
has _successes => ( is => 'rw' );
|
||||
|
||||
sub run($self) {
|
||||
$self->counter(31);
|
||||
$self->_show_start_lesson;
|
||||
}
|
||||
|
||||
sub _show_start_lesson($self) {
|
||||
my $type = $self->type;
|
||||
my $box = Gtk::Box->new( 'vertical', 0 );
|
||||
my $back_button = Gtk::Button->new_from_icon_name('go-previous-symbolic');
|
||||
my $intro = Gtk::Label->new('This lesson has 30 exercises.');
|
||||
my $intro2 = Gtk::Label->new('30 points on completion.');
|
||||
my $intro3 = Gtk::Label->new('30 extra points if you do it very well');
|
||||
$intro->set_margin_top(50);
|
||||
$box->append($intro);
|
||||
$box->append($intro2);
|
||||
$box->append($intro3);
|
||||
my $continue_button = Gtk::Button->new_with_label('Continue');
|
||||
$continue_button->add_css_class('accent');
|
||||
my $resize = sub {
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size_number = 30 * $self->app->get_width;
|
||||
my $size_pango_number = PANGO_SCALE * 60;
|
||||
my $size = Pango::AttrSize->new($size_number);
|
||||
|
||||
if ( $size_pango_number < $size_number ) {
|
||||
$size = Pango::AttrSize->new($size_pango_number);
|
||||
}
|
||||
$attr_list->insert($size);
|
||||
$intro->set_attributes($attr_list);
|
||||
$intro2->set_attributes($attr_list);
|
||||
$intro3->set_attributes($attr_list);
|
||||
$continue_button->get_child->set_attributes($attr_list);
|
||||
};
|
||||
$resize->();
|
||||
$self->app->on_resize($resize);
|
||||
$continue_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->app->delete_on_resize($resize);
|
||||
$continue_button->set_sensitive(0);
|
||||
$self->_successes(0);
|
||||
require JapaChar::View::KanjiTestExercise;
|
||||
JapaChar::View::KanjiTestExercise->new( lesson => $self )->run;
|
||||
}
|
||||
);
|
||||
$box->append($continue_button);
|
||||
$continue_button->set_halign('end');
|
||||
$continue_button->set_valign('end');
|
||||
$continue_button->set_vexpand(1);
|
||||
$back_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->app->delete_on_resize($resize);
|
||||
require JapaChar::View::MainMenu;
|
||||
JapaChar::View::MainMenu->new( app => $self->app )->run;
|
||||
}
|
||||
);
|
||||
$continue_button->set_margin_end(50);
|
||||
$continue_button->set_margin_bottom(50);
|
||||
$self->app->window_set_child($box);
|
||||
$self->app->headerbar->pack_start($back_button);
|
||||
}
|
||||
|
||||
sub create_continue_lesson_button( $self, $on_click ) {
|
||||
my $type = $self->type;
|
||||
my $continue_button = Gtk::Button->new_with_label('Continue');
|
||||
$continue_button->set_valign('center');
|
||||
$continue_button->set_halign('end');
|
||||
$continue_button->set_sensitive(0);
|
||||
$continue_button->add_css_class('accent');
|
||||
$continue_button->signal_connect( 'clicked', $on_click, );
|
||||
return $continue_button;
|
||||
}
|
||||
|
||||
sub add_one_success($self) {
|
||||
$self->_successes( $self->_successes + 1 );
|
||||
}
|
||||
|
||||
sub create_exit_lesson_back_button( $self, $on_exit ) {
|
||||
my $back_button = Gtk::Button->new_from_icon_name('go-previous-symbolic');
|
||||
$back_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$back_button->set_sensitive(0);
|
||||
$self->_create_dialog_exit_lesson($on_exit);
|
||||
}
|
||||
);
|
||||
return $back_button;
|
||||
}
|
||||
|
||||
sub _create_dialog_exit_lesson( $self, $on_exit ) {
|
||||
my $dialog = Adw::AlertDialog->new( 'Exit the lessson',
|
||||
'On exit you will lose your progress' );
|
||||
$dialog->add_response( 'close', 'Continue' );
|
||||
$dialog->add_response( $exit_the_lesson_id, 'Exit' );
|
||||
$dialog->set_response_appearance( $exit_the_lesson_id, 'destructive' );
|
||||
$dialog->signal_connect(
|
||||
'response',
|
||||
sub( $obj, $response ) {
|
||||
$self->_on_dialog_exit_lesson_response( $response, $on_exit );
|
||||
}
|
||||
);
|
||||
$self->app->present_dialog($dialog);
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
sub _on_dialog_exit_lesson_response( $self, $response, $on_exit ) {
|
||||
if ( $response eq $exit_the_lesson_id ) {
|
||||
$on_exit->();
|
||||
require JapaChar::View::MainMenu;
|
||||
JapaChar::View::MainMenu->new( app => $self->app )->run;
|
||||
}
|
||||
}
|
||||
|
||||
sub finish_lesson_screen($self) {
|
||||
my $notable_lesson = $self->_successes >= 7;
|
||||
my $feedback_label;
|
||||
my $box = Gtk::Box->new( 'vertical', 10 );
|
||||
if ($notable_lesson) {
|
||||
$feedback_label =
|
||||
Gtk::Label->new('You did it great, here you have your 60 points.');
|
||||
}
|
||||
else {
|
||||
$feedback_label = Gtk::Label->new(
|
||||
'You need to continue improving, we have 30 points for you');
|
||||
}
|
||||
|
||||
my $continue_button = Gtk::Button->new_with_label('Continue');
|
||||
$continue_button->add_css_class('accent');
|
||||
$continue_button->set_halign('end');
|
||||
$continue_button->set_valign('end');
|
||||
$continue_button->set_vexpand(1);
|
||||
$feedback_label->set_valign('center');
|
||||
$feedback_label->set_halign('center');
|
||||
$feedback_label->set_vexpand(1);
|
||||
my $resize = sub {
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size_number = 20 * $self->app->get_width;
|
||||
my $size_pango_number = PANGO_SCALE * 35;
|
||||
my $size = Pango::AttrSize->new($size_number);
|
||||
|
||||
if ( $size_pango_number < $size_number ) {
|
||||
$size = Pango::AttrSize->new($size_pango_number);
|
||||
}
|
||||
$attr_list->insert($size);
|
||||
$feedback_label->set_attributes($attr_list);
|
||||
$continue_button->get_child->set_attributes($attr_list);
|
||||
};
|
||||
$self->app->on_resize($resize);
|
||||
$continue_button->set_margin_end(50);
|
||||
$continue_button->set_margin_bottom(50);
|
||||
$resize->();
|
||||
$box->append($feedback_label);
|
||||
$box->append($continue_button);
|
||||
$continue_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->app->delete_on_resize($resize);
|
||||
JapaChar::Score->sum( $notable_lesson ? 60 : 30 );
|
||||
$continue_button->set_sensitive(0);
|
||||
require JapaChar::View::MainMenu;
|
||||
JapaChar::View::MainMenu->new( app => $self->app )->run;
|
||||
}
|
||||
);
|
||||
$self->app->window_set_child($box);
|
||||
}
|
||||
|
||||
1;
|
266
lib/JapaChar/View/KanjiTestExercise.pm
Normal file
266
lib/JapaChar/View/KanjiTestExercise.pm
Normal file
@ -0,0 +1,266 @@
|
||||
package JapaChar::View::KanjiTestExercise;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
use Moo;
|
||||
use Path::Tiny;
|
||||
use Glib::Object::Introspection;
|
||||
use YAML::PP;
|
||||
use JapaChar::DB;
|
||||
use JapaChar::Characters;
|
||||
use Pango;
|
||||
use JapaChar::Random;
|
||||
use JapaChar::Score;
|
||||
|
||||
use Glib;
|
||||
use Glib::IO;
|
||||
|
||||
use constant PANGO_SCALE => 1024;
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gtk',
|
||||
version => '4.0',
|
||||
package => 'Gtk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gdk',
|
||||
version => '4.0',
|
||||
package => 'Gtk::Gdk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gsk',
|
||||
version => '4.0',
|
||||
package => 'Gtk::Gsk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Adw',
|
||||
version => '1',
|
||||
package => 'Adw',
|
||||
);
|
||||
|
||||
has lesson => ( is => 'rw' );
|
||||
has _type => ( is => 'lazy' );
|
||||
has _app => ( is => 'lazy' );
|
||||
has _buttons => ( is => 'rw' );
|
||||
has _buttons_box => ( is => 'rw' );
|
||||
has _first_press_continue => ( is => 'rw', default => sub { 1 } );
|
||||
has _continue_button => ( is => 'rw' );
|
||||
has _on_resize_continue_button => ( is => 'lazy' );
|
||||
has _final_answer => ( is => 'rw' );
|
||||
has _on_resize_buttons => ( is => 'lazy' );
|
||||
|
||||
sub _counter($self) {
|
||||
return $self->lesson->counter;
|
||||
}
|
||||
|
||||
sub _build__type($self) {
|
||||
return $self->lesson->type;
|
||||
}
|
||||
|
||||
sub _build__app($self) {
|
||||
return $self->lesson->app;
|
||||
}
|
||||
|
||||
sub run($self) {
|
||||
$self->lesson->counter( $self->_counter - 1 );
|
||||
if ( $self->_counter < 1 ) {
|
||||
$self->lesson->finish_lesson_screen();
|
||||
return;
|
||||
}
|
||||
my $rng = JapaChar::Random->new->get( 1, 100 );
|
||||
my $char =
|
||||
$self->_app->kanji->next_char( $self->_app->accessibility, $self->_type );
|
||||
my @available_guessses = ($char->meanings, $char->on_readings, $char->kun_readings);
|
||||
$rng = JapaChar::Random->new->get( 0, scalar(@available_guessses) - 1 );
|
||||
$self->_create_challenge($char, $available_guessses[$rng]);
|
||||
}
|
||||
|
||||
sub guess_to_string($self, $guess) {
|
||||
return $guess->meaning if $guess->isa('JapaChar::Schema::Result::KanjiMeanings');
|
||||
return decode 'utf-8', $guess->reading;
|
||||
}
|
||||
|
||||
sub _create_challenge($self, $char, $guess) {
|
||||
my $grid = $self->_create_grid_challenge;
|
||||
my $kanji_label = $self->_get_label_featured_character( $char->kanji );
|
||||
$kanji_label->set_halign('center');
|
||||
$kanji_label->set_valign('center');
|
||||
my $box_kanji = Gtk::Box->new( 'vertical', 10 );
|
||||
$box_kanji->append( $self->_new_exercise_number_label );
|
||||
$box_kanji->append($kanji_label);
|
||||
$grid->attach( $box_kanji, 0, 0, 12, 1 );
|
||||
$self->_app->window_set_child($grid);
|
||||
my $back_button = $self->lesson->create_exit_lesson_back_button(
|
||||
sub {
|
||||
$self->_app->delete_on_resize( $self->_on_resize_continue_button );
|
||||
}
|
||||
);
|
||||
$self->_app->headerbar->pack_start($back_button);
|
||||
my $incorrect_answers =
|
||||
$self->_app->kanji->get_4_incorrect_answers($char, $guess);
|
||||
$self->_app->on_resize( $self->_on_resize_continue_button );
|
||||
my @buttons;
|
||||
my $continue_button = $self->lesson->create_continue_lesson_button(
|
||||
sub {
|
||||
$self->_on_click_continue_button( $grid, $char, $guess );
|
||||
}
|
||||
);
|
||||
$self->_continue_button($continue_button);
|
||||
$self->_on_resize_continue_button->();
|
||||
my $on_answer = sub ($correct) {
|
||||
$continue_button->set_sensitive(1);
|
||||
};
|
||||
my $correct_answer_button =
|
||||
Gtk::ToggleButton->new_with_label( $self->guess_to_string($guess) );
|
||||
$correct_answer_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_final_answer( $self->guess_to_string($guess) );
|
||||
$on_answer->(1);
|
||||
}
|
||||
);
|
||||
push @buttons, $correct_answer_button;
|
||||
$self->_buttons( \@buttons );
|
||||
for my $bad_answer (@$incorrect_answers) {
|
||||
my $incorrect_button =
|
||||
Gtk::ToggleButton->new_with_label( $bad_answer );
|
||||
$incorrect_button->set_group($correct_answer_button);
|
||||
$incorrect_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_final_answer( $bad_answer );
|
||||
$on_answer->(0);
|
||||
}
|
||||
);
|
||||
push @buttons, $incorrect_button;
|
||||
}
|
||||
@buttons = sort { rand() <=> rand() } @buttons;
|
||||
my $box = Gtk::Box->new( 'horizontal', 10 );
|
||||
$box->set_valign('center');
|
||||
$box->set_halign('center');
|
||||
|
||||
for my $button (@buttons) {
|
||||
$box->append($button);
|
||||
}
|
||||
$self->_buttons_box($box);
|
||||
$self->_on_resize_buttons->();
|
||||
$self->_app->on_resize($self->_on_resize_buttons);
|
||||
$grid->attach( $box, 0, 2, 12, 1 );
|
||||
$grid->attach( $continue_button, 6, 3, 5, 1 );
|
||||
}
|
||||
|
||||
sub _build__on_resize_buttons($self) {
|
||||
return sub {
|
||||
return if !defined $self->_buttons_box;
|
||||
my @buttons = $self->_buttons->@*;
|
||||
my $window_size = $self->_app->get_width;
|
||||
for my $button (@buttons) {
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size_number = 14 * $window_size;
|
||||
my $size_pango_number = PANGO_SCALE * 60;
|
||||
my $size = Pango::AttrSize->new($size_number);
|
||||
if ( $size_pango_number < $size_number ) {
|
||||
$size = Pango::AttrSize->new($size_pango_number);
|
||||
}
|
||||
$attr_list->insert($size);
|
||||
$button->get_child->set_attributes($attr_list);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sub _create_grid_challenge($self) {
|
||||
my $grid = Gtk::Grid->new;
|
||||
$grid->set_column_homogeneous(1);
|
||||
$grid->set_row_homogeneous(1);
|
||||
return $grid;
|
||||
}
|
||||
|
||||
sub _get_label_featured_character( $self, $text ) {
|
||||
my $label = Gtk::Label->new($text);
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size = Pango::AttrSize->new( 72 * PANGO_SCALE );
|
||||
my $color = Pango::Color->new;
|
||||
|
||||
$attr_list->insert($size);
|
||||
my $fore_attr = $self->_app->characters->get_color_attr($text);
|
||||
|
||||
$label->set_attributes($attr_list);
|
||||
$label->set_halign('center');
|
||||
return $label;
|
||||
}
|
||||
|
||||
sub _new_exercise_number_label($self) {
|
||||
my $exercise_number = abs( $self->_counter - 31 );
|
||||
my $return = Gtk::Label->new( 'Exercise: ' . $exercise_number );
|
||||
$return->set_halign('start');
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub _build__on_resize_continue_button($self) {
|
||||
return sub {
|
||||
my $continue_button = $self->_continue_button;
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size = Pango::AttrSize->new( 40 * $self->_app->get_width );
|
||||
|
||||
$attr_list->insert($size);
|
||||
$continue_button->get_child->set_attributes($attr_list);
|
||||
};
|
||||
}
|
||||
|
||||
sub _on_exit($self) {
|
||||
$self->_app->delete_on_resize( $self->_on_resize_buttons );
|
||||
$self->_app->delete_on_resize( $self->_on_resize_continue_button );
|
||||
}
|
||||
|
||||
sub _on_click_continue_button( $self, $grid, $char, $guess ) {
|
||||
my $continue_button = $self->_continue_button;
|
||||
if ( defined $self->_buttons ) {
|
||||
for my $button ( $self->_buttons->@* ) {
|
||||
$button->set_sensitive(0);
|
||||
}
|
||||
}
|
||||
if ( !$self->_first_press_continue ) {
|
||||
$self->_on_exit;
|
||||
$continue_button->set_sensitive(0);
|
||||
$self->new( lesson => $self->lesson )->run;
|
||||
return;
|
||||
}
|
||||
$self->_first_press_continue(0);
|
||||
my $label_feedback;
|
||||
{
|
||||
if ( $self->_final_answer eq $self->guess_to_string($guess) ) {
|
||||
$label_feedback = Gtk::Label->new('You are doing it great.');
|
||||
$label_feedback->add_css_class('success');
|
||||
$self->lesson->add_one_success;
|
||||
$char->success;
|
||||
next;
|
||||
}
|
||||
$label_feedback = Gtk::Label->new(
|
||||
'Meck!! The correct answer is ' . $self->guess_to_string($guess) );
|
||||
$label_feedback->add_css_class('error');
|
||||
$char->fail;
|
||||
$continue_button->set_sensitive(0);
|
||||
Glib::Timeout->add_seconds(1, sub {
|
||||
$continue_button->set_sensitive(1);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size = Pango::AttrSize->new( 15 * $self->_app->get_width );
|
||||
$attr_list->insert($size);
|
||||
$label_feedback->set_halign('center');
|
||||
$label_feedback->set_attributes($attr_list);
|
||||
$grid->attach( $label_feedback, 0, 3, 7, 1 );
|
||||
}
|
||||
1;
|
@ -17,6 +17,7 @@ use Pango;
|
||||
use JapaChar::Random;
|
||||
use JapaChar::Score;
|
||||
use JapaChar::View::HiraganaKatakanaLesson;
|
||||
use JapaChar::View::SelectKanjiLesson;
|
||||
|
||||
use Glib::IO;
|
||||
|
||||
@ -74,6 +75,7 @@ sub run($self) {
|
||||
}
|
||||
);
|
||||
my $button_start_katakana_lesson = Gtk::Button->new_with_label('Katakana');
|
||||
my $button_start_kanji_lesson = Gtk::Button->new_with_label('Kanji (BETA)');
|
||||
$button_start_katakana_lesson->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
@ -84,14 +86,21 @@ sub run($self) {
|
||||
$lesson->run;
|
||||
}
|
||||
);
|
||||
for my $button ( $button_start_basic_lesson, $button_start_hiragana_lesson,
|
||||
$button_start_katakana_lesson )
|
||||
for my $button (
|
||||
$button_start_basic_lesson, $button_start_hiragana_lesson,
|
||||
$button_start_katakana_lesson, $button_start_kanji_lesson
|
||||
)
|
||||
{
|
||||
my $attr_list = Pango::AttrList->new;
|
||||
my $size = Pango::AttrSize->new( 25 * PANGO_SCALE );
|
||||
$attr_list->insert($size);
|
||||
$button->get_child->set_attributes($attr_list);
|
||||
}
|
||||
$button_start_kanji_lesson->signal_connect(
|
||||
clicked => sub {
|
||||
JapaChar::View::SelectKanjiLesson->new( app => $self->app, )->run;
|
||||
}
|
||||
);
|
||||
my $box = Gtk::Box->new( 'horizontal', 10 );
|
||||
my $box_score_basic_lesson = Gtk::Box->new( 'vertical', 10 );
|
||||
my $score_label =
|
||||
@ -110,18 +119,25 @@ sub run($self) {
|
||||
$box->set_margin_top(40);
|
||||
$box->append($button_start_hiragana_lesson);
|
||||
$box->append($button_start_katakana_lesson);
|
||||
$button_start_kanji_lesson->set_halign('center');
|
||||
$button_start_kanji_lesson->set_valign('center');
|
||||
$box->set_valign('start');
|
||||
$box->set_halign('center');
|
||||
$grid->attach( $box, 0, 1, 5, 1 );
|
||||
$grid->attach( $button_assisted_mode, 0, 2, 5, 1 );
|
||||
$button_assisted_mode->signal_connect('clicked', sub {
|
||||
$grid->attach( $button_start_kanji_lesson, 0, 2, 5, 1 );
|
||||
$grid->attach( $button_assisted_mode, 0, 3, 5, 1 );
|
||||
$button_assisted_mode->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->app->accessibility->show_assisted_mode_selection;
|
||||
});
|
||||
}
|
||||
);
|
||||
$button_assisted_mode->set_vexpand(1);
|
||||
$button_assisted_mode->set_hexpand(1);
|
||||
$button_assisted_mode->set_valign('center');
|
||||
$button_assisted_mode->set_halign('center');
|
||||
my $button_discord_community = Gtk::Button->new_with_label('Join the discord community');
|
||||
my $button_discord_community =
|
||||
Gtk::Button->new_with_label('Join the discord community');
|
||||
$button_discord_community->set_vexpand(1);
|
||||
$button_discord_community->set_hexpand(1);
|
||||
$button_discord_community->set_valign('center');
|
||||
@ -131,7 +147,7 @@ sub run($self) {
|
||||
$self->app->launch_discord;
|
||||
}
|
||||
);
|
||||
$grid->attach( $button_discord_community, 0, 3, 5, 1 );
|
||||
$grid->attach( $button_discord_community, 0, 4, 5, 1 );
|
||||
$self->app->window_set_child($grid);
|
||||
}
|
||||
1;
|
||||
|
146
lib/JapaChar/View/SelectKanjiLesson.pm
Normal file
146
lib/JapaChar/View/SelectKanjiLesson.pm
Normal file
@ -0,0 +1,146 @@
|
||||
package JapaChar::View::SelectKanjiLesson;
|
||||
|
||||
use v5.38.2;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Moo;
|
||||
|
||||
use JapaChar;
|
||||
use JapaChar::Kanji;
|
||||
use JapaChar::View::KanjiLesson;
|
||||
|
||||
use Glib::Object::Introspection;
|
||||
use Glib::IO;
|
||||
use POSIX qw/:sys_wait_h/;
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gtk',
|
||||
version => '4.0',
|
||||
package => 'Gtk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gdk',
|
||||
version => '4.0',
|
||||
package => 'Gtk::Gdk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Gsk',
|
||||
version => '4.0',
|
||||
package => 'Gtk::Gsk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'Adw',
|
||||
version => '1',
|
||||
package => 'Adw',
|
||||
);
|
||||
|
||||
has app => ( is => 'ro' );
|
||||
has _kanji => ( is => 'lazy' );
|
||||
|
||||
sub _build__kanji($self) {
|
||||
return JapaChar::Kanji->new(app => JapaChar->new);
|
||||
}
|
||||
|
||||
sub run($self) {
|
||||
if (!$self->_kanji->migrated) {
|
||||
$self->_migrate_kanji;
|
||||
return;
|
||||
}
|
||||
$self->_select_kanji;
|
||||
}
|
||||
|
||||
sub _migrate_kanji($self) {
|
||||
my $box = Gtk::Box->new( 'vertical', 10 );
|
||||
my $label = Gtk::Label->new('Populating Kanji database...');
|
||||
my $progress_bar = Gtk::ProgressBar->new;
|
||||
$progress_bar->set_halign('center');
|
||||
$box->set_vexpand(1);
|
||||
$box->set_valign('center');
|
||||
$label->set_valign('center');
|
||||
$box->append($label);
|
||||
$box->append($progress_bar);
|
||||
$self->app->window_set_child($box);
|
||||
my ($read, $write);
|
||||
pipe $read, $write;
|
||||
my $parent_pid = $$;
|
||||
my $pid = fork;
|
||||
if (!$pid) {
|
||||
$self->_kanji->populate_kanji($parent_pid, $write);
|
||||
exit;
|
||||
}
|
||||
my $n_characters;
|
||||
Glib::Timeout->add(1_000, sub {
|
||||
$n_characters = <$read>;
|
||||
chomp $n_characters;
|
||||
say 'Copying ' . $n_characters . ' kanji';
|
||||
Glib::Timeout->add(
|
||||
100,
|
||||
sub {
|
||||
$read->blocking(0);
|
||||
my $last_number;
|
||||
my $line;
|
||||
while ($line = <$read>) {
|
||||
$last_number = $line;
|
||||
}
|
||||
if ($last_number) {
|
||||
$progress_bar->set_fraction($last_number / $n_characters);
|
||||
}
|
||||
if (0 == waitpid $pid, WNOHANG) {
|
||||
return 1;
|
||||
}
|
||||
$self->_select_kanji;
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
sub _select_kanji($self) {
|
||||
my $back_button = Gtk::Button->new_from_icon_name('go-previous-symbolic');
|
||||
$back_button->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
require JapaChar::View::MainMenu;
|
||||
JapaChar::View::MainMenu->new( app => $self->app )->run;
|
||||
}
|
||||
);
|
||||
my $grades = $self->_kanji->grades;
|
||||
my $box = Gtk::Box->new( 'vertical', 10 );
|
||||
|
||||
my $button = Gtk::Button->new_with_label("Study everything ordered by grade");
|
||||
$button->signal_connect(clicked => sub {
|
||||
JapaChar::View::KanjiLesson->new(app => $self->app)->run;
|
||||
});
|
||||
$button->set_margin_top(20);
|
||||
$button->add_css_class('accent');
|
||||
$button->set_halign('center');
|
||||
$button->set_property('width-request', 330);
|
||||
$box->append($button);
|
||||
for my $grade (@$grades) {
|
||||
my $button = Gtk::Button->new_with_label("Study kanji grade $grade");
|
||||
$button->signal_connect(clicked => sub {
|
||||
JapaChar::View::KanjiLesson->new(app => $self->app, type => $grade)->run;
|
||||
});
|
||||
$button->set_halign('center');
|
||||
$button->set_property('width-request', 330);
|
||||
$box->append($button);
|
||||
}
|
||||
$button = Gtk::Button->new_with_label("Study unclassified kanjis");
|
||||
$button->signal_connect(clicked => sub {
|
||||
JapaChar::View::KanjiLesson->new(app => $self->app, type => undef)->run;
|
||||
});
|
||||
$button->set_property('width-request', 330);
|
||||
$button->set_halign('center');
|
||||
$box->append($button);
|
||||
$self->app->window_set_child($box);
|
||||
$self->app->headerbar->pack_start($back_button);
|
||||
}
|
||||
1;
|
@ -19,7 +19,9 @@
|
||||
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="language-humor">mild</content_attribute>
|
||||
<content_attribute id="money-advertising">mild</content_attribute>
|
||||
<content_attribute id="social-contacts">intense</content_attribute>
|
||||
<content_attribute id="money-purchasing">intense</content_attribute>
|
||||
<content_attribute id="money-advertising">intense</content_attribute>
|
||||
</content_rating>
|
||||
|
||||
<branding>
|
||||
|
Loading…
Reference in New Issue
Block a user