Finishing image storage in .exd and adding save and open capability.
This commit is contained in:
parent
3285ac3987
commit
de0bc9a655
@ -10,8 +10,12 @@ use Moo;
|
||||
use Path::Tiny;
|
||||
use Archive::Zip;
|
||||
|
||||
use Exd::Utils;
|
||||
|
||||
use Digest::SHA qw/sha256_hex/;
|
||||
|
||||
use Exd::FileFormat::DB;
|
||||
|
||||
has dir => ( is => 'rw' );
|
||||
|
||||
sub new_tmp($class) {
|
||||
@ -22,21 +26,85 @@ sub new_tmp($class) {
|
||||
has _images_dir => ( is => 'lazy' );
|
||||
|
||||
sub _build__images_dir($self) {
|
||||
my $dir = $self->dir;
|
||||
my $dir = $self->dir;
|
||||
my $images = $dir->child('images');
|
||||
$images->mkpath;
|
||||
return $images;
|
||||
}
|
||||
|
||||
sub add_png_image( $self, $file_contents ) {
|
||||
sub add_png_image( $self, $file_contents, $label = '' ) {
|
||||
my $sha_image = sha256_hex($file_contents);
|
||||
$self->_images_dir->child("$sha_image.png")->spew_raw($file_contents);
|
||||
my $file = $self->_images_dir->child("$sha_image.png");
|
||||
$file->spew_raw($file_contents);
|
||||
if ( defined $label && '' . $label ) {
|
||||
$self->_register_label( $label, $sha_image );
|
||||
}
|
||||
}
|
||||
|
||||
sub _image_hashes($self) {
|
||||
my $glob = ( '' . $self->_images_dir ) . '/*.png';
|
||||
my @images = glob( $glob );
|
||||
@images = map {
|
||||
my $image = path($_);
|
||||
$image = $image->basename;
|
||||
$image =~ s/\.png$//r;
|
||||
} @images;
|
||||
return \@images;
|
||||
}
|
||||
|
||||
sub image_hashes_to_label($self) {
|
||||
my %images;
|
||||
for my $image_hash ( $self->_image_hashes->@* ) {
|
||||
my $dbh = $self->_dbh;
|
||||
my $hashes = $dbh->selectall_arrayref(
|
||||
'SELECT label FROM label_to_image_hash WHERE hash = ?',
|
||||
{Slice => {}}, $image_hash );
|
||||
$images{$image_hash} = [ sort { $a cmp $b } map { $_->{label} } @$hashes ];
|
||||
}
|
||||
return \%images;
|
||||
}
|
||||
|
||||
sub _dbh($self) {
|
||||
return Exd::FileFormat::DB->connect( '' . $self->dir->child('db.sqlite3') );
|
||||
}
|
||||
|
||||
sub _register_label( $self, $label, $sha_image ) {
|
||||
my $dbh = $self->_dbh;
|
||||
eval {
|
||||
$dbh->do(
|
||||
'INSERT INTO label_to_image_hash (label, hash) VALUES (?, ?);',
|
||||
{}, $label, $sha_image );
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_image_gd_from_label ( $self, $label ) {
|
||||
return $self->get_image_gd( $self->_get_hash_from_label($label) );
|
||||
}
|
||||
|
||||
sub _get_hash_from_label($self, $label) {
|
||||
my $dbh = $self->_dbh;
|
||||
my $row = $dbh->selectrow_hashref(
|
||||
'SELECT hash FROM label_to_image_hash WHERE label = ?',
|
||||
{}, $label );
|
||||
die 'No such image in file' if !defined $row || !defined $row->{hash};
|
||||
return $row->{hash};
|
||||
}
|
||||
|
||||
sub get_image_from_label ( $self, $label ) {
|
||||
return $self->get_image( $self->_get_hash_from_label($label) );
|
||||
}
|
||||
|
||||
sub get_image( $self, $sha ) {
|
||||
return $self->_images_dir->child("$sha.png");
|
||||
}
|
||||
|
||||
sub get_image_gd ( $self, $sha ) {
|
||||
return Exd::Utils::get_gd_image( $self->get_image($sha) );
|
||||
}
|
||||
|
||||
sub get_script($self) {
|
||||
return $self->dir->child('script.pl')->slurp_raw;
|
||||
}
|
||||
@ -51,7 +119,7 @@ sub to_zip($self) {
|
||||
sub( $path, $state ) {
|
||||
return if $path->is_dir;
|
||||
my $path_relative = $path->relative( $self->dir );
|
||||
$zip->addFile( $path, $path_relative );
|
||||
$zip->addFile( ''.$path, ''.$path_relative );
|
||||
},
|
||||
{ recurse => 1 }
|
||||
);
|
||||
@ -61,7 +129,7 @@ sub to_zip($self) {
|
||||
sub from_zip_file( $class, $zip_file ) {
|
||||
my $tempdir = Path::Tiny->tempdir();
|
||||
my $zip = Archive::Zip->new($zip_file);
|
||||
$zip->extractTree( '.', $tempdir );
|
||||
$zip->extractTree( '', $tempdir );
|
||||
return $class->new( dir => $tempdir );
|
||||
}
|
||||
|
||||
@ -74,7 +142,7 @@ sub execute( $self, $printer ) {
|
||||
if ($@) {
|
||||
die $@;
|
||||
}
|
||||
$sub->($self, $printer);
|
||||
$sub->( $self, $printer );
|
||||
}
|
||||
|
||||
sub save( $self, $output_file ) {
|
||||
|
95
lib/Exd/FileFormat/DB.pm
Normal file
95
lib/Exd/FileFormat/DB.pm
Normal file
@ -0,0 +1,95 @@
|
||||
package Exd::FileFormat::DB;
|
||||
|
||||
use v5.40.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use DBI;
|
||||
use Path::Tiny;
|
||||
|
||||
use Exd::FileFormat::DB::Migrations;
|
||||
|
||||
my $dbh;
|
||||
|
||||
sub connect {
|
||||
if ( defined $dbh ) {
|
||||
return $dbh;
|
||||
}
|
||||
my $class = shift or die 'Missing arg class';
|
||||
my $database = shift or die 'Missing arg database file';
|
||||
$dbh = DBI->connect(
|
||||
"dbi:SQLite:dbname=$database",
|
||||
,
|
||||
undef, undef,
|
||||
{
|
||||
RaiseError => 1,
|
||||
},
|
||||
);
|
||||
$class->_migrate($dbh);
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
sub _migrate {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
local $dbh->{RaiseError} = 0;
|
||||
local $dbh->{PrintError} = 0;
|
||||
my @migrations = Exd::FileFormat::DB::Migrations::MIGRATIONS();
|
||||
if ( $class->get_current_migration($dbh) > @migrations ) {
|
||||
warn "Something happened there, wrong migration number.";
|
||||
}
|
||||
if ( $class->get_current_migration($dbh) >= @migrations ) {
|
||||
say STDERR "Migrations already applied.";
|
||||
return;
|
||||
}
|
||||
$class->_apply_migrations( $dbh, \@migrations );
|
||||
}
|
||||
|
||||
sub _apply_migrations {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
my $migrations = shift;
|
||||
for (
|
||||
my $i = $class->get_current_migration($dbh) ;
|
||||
$i < @$migrations ;
|
||||
$i++
|
||||
)
|
||||
{
|
||||
local $dbh->{RaiseError} = 1;
|
||||
my $current_migration = $migrations->[$i];
|
||||
my $migration_number = $i + 1;
|
||||
$class->_apply_migration( $dbh, $current_migration, $migration_number );
|
||||
}
|
||||
}
|
||||
|
||||
sub _apply_migration {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
my $current_migration = shift;
|
||||
my $migration_number = shift;
|
||||
{
|
||||
if (ref $current_migration eq 'CODE') {
|
||||
$current_migration->($dbh);
|
||||
next;
|
||||
}
|
||||
$dbh->do($current_migration);
|
||||
}
|
||||
$dbh->do( <<'EOF', undef, 'current_migration', $migration_number );
|
||||
INSERT INTO options
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (name) DO
|
||||
UPDATE SET value = $2;
|
||||
EOF
|
||||
}
|
||||
|
||||
sub get_current_migration {
|
||||
my $class = shift;
|
||||
my $dbh = shift;
|
||||
my $result = $dbh->selectrow_hashref( <<'EOF', undef, 'current_migration' );
|
||||
select value from options where name = ?;
|
||||
EOF
|
||||
return int( $result->{value} // 0 );
|
||||
}
|
||||
1;
|
23
lib/Exd/FileFormat/DB/Migrations.pm
Normal file
23
lib/Exd/FileFormat/DB/Migrations.pm
Normal file
@ -0,0 +1,23 @@
|
||||
package Exd::FileFormat::DB::Migrations;
|
||||
|
||||
use v5.40.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
sub MIGRATIONS {
|
||||
return (
|
||||
'CREATE TABLE options (
|
||||
name TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
);',
|
||||
'CREATE TABLE label_to_image_hash (
|
||||
label TEXT NOT NULL PRIMARY KEY,
|
||||
hash TEXT NOT NULL
|
||||
);',
|
||||
);
|
||||
}
|
||||
1;
|
249
lib/Exd/Gui.pm
249
lib/Exd/Gui.pm
@ -38,6 +38,12 @@ Glib::Object::Introspection->setup(
|
||||
package => 'Gdk',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'GdkWayland',
|
||||
version => '4.0',
|
||||
package => 'GdkWayland',
|
||||
);
|
||||
|
||||
Glib::Object::Introspection->setup(
|
||||
basename => 'GtkSource',
|
||||
version => '5',
|
||||
@ -66,7 +72,6 @@ has _file_format => (
|
||||
is => 'rw',
|
||||
default => sub {
|
||||
my $tmp = Exd::FileFormat->new_tmp;
|
||||
say $tmp->dir;
|
||||
$tmp->set_script(<<'EOF');
|
||||
sub ($exd, $printer) {
|
||||
$printer->print_text(
|
||||
@ -106,6 +111,11 @@ sub start($self) {
|
||||
$self->_daemon_script_runner;
|
||||
my $app = Gtk4::Application->new( 'me.sergiotarxz.Exd', 'default-flags' );
|
||||
$self->_app($app);
|
||||
$app->signal_connect(
|
||||
startup => sub {
|
||||
$self->_startup;
|
||||
}
|
||||
);
|
||||
$app->signal_connect(
|
||||
activate => sub {
|
||||
$self->_activate;
|
||||
@ -114,6 +124,54 @@ sub start($self) {
|
||||
$app->run;
|
||||
}
|
||||
|
||||
sub _save_action($self) {
|
||||
my $file_format = $self->_file_format;
|
||||
my $dialog = Gtk4::FileDialog->new;
|
||||
$dialog->set_initial_name('project.exd');
|
||||
$dialog->save($self->window, undef, sub ($source, $res, $data) {
|
||||
eval {
|
||||
my $file = $dialog->save_finish($res);
|
||||
$file = $file->get_path;
|
||||
my $zip = $file_format->to_zip;
|
||||
$zip->writeToFileNamed(''.$file);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
sub _open_action($self) {
|
||||
my $file_format = $self->_file_format;
|
||||
my $dialog = Gtk4::FileDialog->new;
|
||||
$dialog->set_initial_name('project.exd');
|
||||
$dialog->open($self->window, undef, sub ($source, $res, $data) {
|
||||
eval {
|
||||
my $file = $dialog->open_finish($res);
|
||||
$file = $file->get_path;
|
||||
$self->_file_format(Exd::FileFormat->from_zip_file($file));
|
||||
$self->_update_editor_buffer;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
sub _startup($self) {
|
||||
my $app = $self->_app;
|
||||
my $menu_model = Glib::IO::Menu->new;
|
||||
my $file_menu = Glib::IO::Menu->new;
|
||||
my $open_action = Glib::IO::SimpleAction->new('open', undef);
|
||||
my $save_action = Glib::IO::SimpleAction->new('save', undef);
|
||||
$open_action->signal_connect('activate', sub {
|
||||
$self->_open_action;
|
||||
});
|
||||
$save_action->signal_connect('activate', sub {
|
||||
$self->_save_action;
|
||||
});
|
||||
$app->add_action($open_action);
|
||||
$app->add_action($save_action);
|
||||
$file_menu->append('Open', 'app.open');
|
||||
$file_menu->append('Save', 'app.save');
|
||||
$menu_model->append_submenu('File', $file_menu);
|
||||
$app->set_menubar($menu_model);
|
||||
}
|
||||
|
||||
sub _generate_preview_file( $self, $verbose = 1 ) {
|
||||
$self->_preview_file(1) if -f $self->_preview_file;
|
||||
my $device =
|
||||
@ -137,7 +195,7 @@ sub _run_script( $self, $device = undef, $verbose = 1 ) {
|
||||
my $fh = $self->_write_to_script;
|
||||
print $fh JSON::to_json(
|
||||
{
|
||||
exd_dir => ''.$self->_file_format->dir,
|
||||
exd_dir => '' . $self->_file_format->dir,
|
||||
device => $device->serialize,
|
||||
verbose => $verbose,
|
||||
}
|
||||
@ -179,8 +237,8 @@ sub _daemon_script_runner($self) {
|
||||
my $new_pid = fork;
|
||||
if ( !$new_pid ) {
|
||||
eval {
|
||||
$self->_on_run_script( path($exd_dir), $device, $write_to_parent,
|
||||
$verbose );
|
||||
$self->_on_run_script( path($exd_dir), $device,
|
||||
$write_to_parent, $verbose );
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
@ -219,12 +277,10 @@ sub _on_run_script( $self, $exd_dir, $device, $write_to_parent, $verbose = 1 ) {
|
||||
my $printer = Exd::Printer->new( device => $device );
|
||||
local $| = 1;
|
||||
|
||||
my $exd = Exd::FileFormat->new(dir => $exd_dir);
|
||||
my $exd = Exd::FileFormat->new( dir => $exd_dir );
|
||||
|
||||
my ( $stdout, $stderr, $exit ) = capture {
|
||||
eval {
|
||||
$exd->execute($printer);
|
||||
};
|
||||
eval { $exd->execute($printer); };
|
||||
if ($@) {
|
||||
if ($verbose) {
|
||||
print $write_to_parent $@ . "\n";
|
||||
@ -279,6 +335,12 @@ sub _on_preview($self) {
|
||||
}
|
||||
}
|
||||
|
||||
sub _update_editor_buffer($self) {
|
||||
my $editor = $self->_editor;
|
||||
my $buffer = $editor->get_buffer();
|
||||
$buffer->set_text( $self->_file_format->get_script, -1 );
|
||||
}
|
||||
|
||||
sub _populate_editor( $self, $box_editor_preview ) {
|
||||
my $editor = Gtk4::Source::View->new;
|
||||
$self->_editor($editor);
|
||||
@ -291,7 +353,7 @@ sub _populate_editor( $self, $box_editor_preview ) {
|
||||
$editor->set_smart_backspace(1);
|
||||
$editor->set_show_line_numbers(1);
|
||||
my $buffer = $editor->get_buffer();
|
||||
$buffer->set_text( $self->_file_format->get_script, -1 );
|
||||
$self->_update_editor_buffer;
|
||||
$buffer->set_language(
|
||||
Gtk4::Source::LanguageManager::get_default()->get_language('perl') );
|
||||
$buffer->set_highlight_syntax(1);
|
||||
@ -337,6 +399,166 @@ sub _populate_preview( $self, $box_editor_preview ) {
|
||||
$self->_generate_preview_file;
|
||||
}
|
||||
|
||||
sub _open_gallery($self) {
|
||||
my $window = Gtk4::Window->new;
|
||||
$window->set_transient_for( $self->window );
|
||||
$window->set_default_size( 600, 600 );
|
||||
$window->set_title('Image gallery');
|
||||
my $box = Gtk4::Box->new( 'vertical', 1 );
|
||||
my $box_upper_menu = Gtk4::Box->new( 'horizontal', 10 );
|
||||
my $add_image = Gtk4::Button->new_with_label('Add PNG image');
|
||||
$add_image->set_halign('start');
|
||||
my $label_entry_label =
|
||||
Gtk4::Label->new('(Recommended) Put a label to the new image:');
|
||||
my $entry_label = Gtk4::Entry->new;
|
||||
$box_upper_menu->append($label_entry_label);
|
||||
$box_upper_menu->append($entry_label);
|
||||
my $images_scroll = Gtk4::ScrolledWindow->new;
|
||||
$add_image->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
my $dialog = Gtk4::FileDialog->new;
|
||||
$dialog->open(
|
||||
$window, undef,
|
||||
sub( $source, $res, $data ) {
|
||||
eval {
|
||||
my $file = $dialog->open_finish($res);
|
||||
my $buffer = $entry_label->get_buffer;
|
||||
my $label = $buffer->get_text;
|
||||
$file = $file->get_path;
|
||||
if ( $file !~ /\.png$/ ) {
|
||||
$self->write_to_parent->say("$file is not png.");
|
||||
$self->write_to_parent->flush;
|
||||
return;
|
||||
}
|
||||
eval {
|
||||
$self->_file_format->add_png_image(
|
||||
path($file)->slurp_raw, $label );
|
||||
$buffer->set_text( '', -1 );
|
||||
$self->_update_gallery_images( $window,
|
||||
$images_scroll );
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
$self->write_to_parent->print($@);
|
||||
$self->write_to_parent->flush;
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
warn $@;
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
$self->_update_gallery_images( $window, $images_scroll );
|
||||
$box->append($add_image);
|
||||
$box->append($box_upper_menu);
|
||||
$box->append($images_scroll);
|
||||
$images_scroll->set_vexpand(1);
|
||||
$window->set_child($box);
|
||||
$window->present;
|
||||
}
|
||||
|
||||
sub _update_gallery_images( $self, $window, $images_scroll ) {
|
||||
my %image_hashes_to_label = $self->_file_format->image_hashes_to_label->%*;
|
||||
my $gallery_box = Gtk4::Box->new( 'vertical', 10 );
|
||||
my $i = 0;
|
||||
my $row_image;
|
||||
for my $hash ( sort { $a cmp $b } keys %image_hashes_to_label ) {
|
||||
my @labels = $image_hashes_to_label{$hash}->@*;
|
||||
if ( scalar @labels == 0 ) {
|
||||
$row_image = $self->_get_row_image( $i, $gallery_box, $row_image );
|
||||
my $picture = $self->_create_gallery_image($hash);
|
||||
my $box_image = Gtk4::Box->new( 'vertical', 1 );
|
||||
$box_image->set_property( 'width-request', 180 );
|
||||
my $copy = Gtk4::Button->new_with_label('Copy to clipboard');
|
||||
my $copy_gd = Gtk4::Button->new_with_label('Copy GD to clipboard');
|
||||
$copy->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_set_clipboard("\$exd->get_image('$hash')");
|
||||
$window->close;
|
||||
}
|
||||
);
|
||||
$copy_gd->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_set_clipboard("\$exd->get_image_gd('$hash')");
|
||||
$window->close;
|
||||
}
|
||||
);
|
||||
$box_image->append($picture);
|
||||
my $scroll = Gtk4::ScrolledWindow->new;
|
||||
$scroll->set_child( Gtk4::Label->new($hash) );
|
||||
$box_image->append($scroll);
|
||||
$box_image->append($copy);
|
||||
$box_image->append($copy_gd);
|
||||
$row_image->append($box_image);
|
||||
$i++;
|
||||
next;
|
||||
}
|
||||
for my $label (@labels) {
|
||||
$row_image = $self->_get_row_image( $i, $gallery_box, $row_image );
|
||||
my $picture = $self->_create_gallery_image($hash);
|
||||
my $copy = Gtk4::Button->new_with_label('Copy to clipboard');
|
||||
my $copy_gd = Gtk4::Button->new_with_label('Copy GD to clipboard');
|
||||
my $box_image = Gtk4::Box->new( 'vertical', 1 );
|
||||
$box_image->set_property( 'width-request', 180 );
|
||||
$copy->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_set_clipboard("\$exd->get_image_from_label('$label')");
|
||||
$window->close;
|
||||
}
|
||||
);
|
||||
$copy_gd->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_set_clipboard("\$exd->get_image_gd_from_label('$label')");
|
||||
$window->close;
|
||||
}
|
||||
);
|
||||
$box_image->append($picture);
|
||||
my $scroll = Gtk4::ScrolledWindow->new;
|
||||
$scroll->set_child( Gtk4::Label->new($label) );
|
||||
$box_image->append($scroll);
|
||||
$box_image->append($copy);
|
||||
$box_image->append($copy_gd);
|
||||
$row_image->append($box_image);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$images_scroll->set_child($gallery_box);
|
||||
}
|
||||
|
||||
sub _set_clipboard($self, $text) {
|
||||
my $display = Gdk::Display::get_default();
|
||||
my $clipboard = $display->get_clipboard;
|
||||
my $wrapper =
|
||||
Glib::Object::Introspection::GValueWrapper->new( 'Glib::String', $text );
|
||||
$clipboard->set($wrapper);
|
||||
}
|
||||
|
||||
sub _get_row_image( $self, $i, $gallery_box, $row_image ) {
|
||||
if ( $i % 3 == 0 ) {
|
||||
$row_image = Gtk4::Box->new( 'horizontal', 10 );
|
||||
$gallery_box->append($row_image);
|
||||
}
|
||||
return $row_image;
|
||||
}
|
||||
|
||||
sub _create_gallery_image( $self, $hash ) {
|
||||
my $picture = Gtk4::Picture->new;
|
||||
my $image_file = $self->_file_format->get_image($hash);
|
||||
$picture->set_property( 'width-request', 180 );
|
||||
$picture->set_property( 'height-request', 180 );
|
||||
$picture->set_content_fit('fill');
|
||||
$picture->set_filename($image_file);
|
||||
return $picture;
|
||||
}
|
||||
|
||||
sub _activate($self) {
|
||||
Glib::Timeout->add(
|
||||
1000,
|
||||
@ -347,12 +569,20 @@ sub _activate($self) {
|
||||
);
|
||||
my $app = $self->_app;
|
||||
my $win = Gtk4::ApplicationWindow->new($app);
|
||||
$win->set_show_menubar(1);
|
||||
$self->window($win);
|
||||
my $box_vertical = Gtk4::Box->new( 'vertical', 10 );
|
||||
my $execute_log = Gtk4::TextView->new;
|
||||
my $run_button = Gtk4::Button->new_with_label('Run');
|
||||
my $preview_button = Gtk4::Button->new_with_label('Preview');
|
||||
my $select_printer = Gtk4::Button->new_with_label('Select Printer');
|
||||
my $open_gallery = Gtk4::Button->new_with_label('Open image gallery');
|
||||
$open_gallery->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
$self->_open_gallery;
|
||||
}
|
||||
);
|
||||
$select_printer->signal_connect(
|
||||
'clicked',
|
||||
sub {
|
||||
@ -388,6 +618,7 @@ sub _activate($self) {
|
||||
$box_buttons->append($run_button);
|
||||
$box_buttons->append($preview_button);
|
||||
$box_buttons->append($select_printer);
|
||||
$box_buttons->append($open_gallery);
|
||||
$self->_populate_editor_and_preview($box_vertical);
|
||||
$box_vertical->append($box_buttons);
|
||||
my $execute_log_window = Gtk4::ScrolledWindow->new;
|
||||
|
Loading…
Reference in New Issue
Block a user