Perl Tk: help me fix highlighting in my text editor - perl

So i'm writing a text editor in Perl Tk and I have a subroutine for highlighting, it inserts two highlighted spaces for you to type in.
I'm having 3 problems.
I want the cursor to move to the left 1 space when the highlight subroutine is triggered. So that when one inserts a highlighting or underline style the cursor is automatically in between the two spaces and the user can type in that style instantly rather than having to use the left arrowkey. I guess my question is how do I insert a style at the cursor instead of the end of the file.
The subroutine's formatting of the text isn't saving properly to the filetype. I'm using .rtf currently.
I want to implement a drop down font selection menu that draws from a system font list but i'm unsure how.
#!/usr/local/bin/perl
#!/C:/Perl/site/lib
use Tk;
use utf8;
use vars qw/$TOP/;
# Main Window
my $mw = new MainWindow;
#Making a text area
my $txt = $mw -> Scrolled('Text', -width => 50,-scrollbars=>'e') -> pack (), -setgrid => true;
#Declare that there is a menu
my $mbar = $mw -> Menu();
$mw -> configure(-menu => $mbar);
#The Main Buttons
my $file = $mbar -> cascade(-label=>"File", -underline=>0, -tearoff => 0);
my $others = $mbar -> cascade(-label =>"Others", -underline=>0, -tearoff => 0);
my $help = $mbar -> cascade(-label =>"Help", -underline=>0, -tearoff => 0);
## File Menu ##
$file -> command(-label => "New", -underline=>0,
-command=>sub { $txt -> delete('1.0','end');} );
$file -> checkbutton(-label =>"Open", -underline => 0,
-command => [\&openfunction, "Open"]);
$file -> command(-label =>"Save", -underline => 0,
-command => [\&savefunction, "Save"]);
$file -> separator();
$file -> command(-label =>"Exit", -underline => 1,
-command => sub { exit } );
## Others Menu ##
my $insert = $others -> cascade(-label =>"Insert", -underline => 0, -tearoff => 0);
$insert -> command(-label =>"Highlight",
-command => [\&highlight, "Highlight"]);
$insert -> command(-label =>"Underline",
-command => [\&underline, "Underline"]);
$insert -> command(-label =>"Title",
-command => [\&bold, "Title"]);
$insert -> command(-label =>"Stippling",
-command => [\&stippling, "Stippling"]);
$insert -> command(-label =>"Find & Replace",
-command => [\&find_replace, "Find & Replace"]);
$insert -> command(-label =>"Name",
-command => sub { $txt->insert('end',"Name : Thaddeus Roebuck Badgercock\n");});
$insert -> command(-label =>"Bullet Point", -command=>sub {
$txt->insert('end',"⚫\t");});
$insert -> command(-label =>"Email",
-command=> sub {$txt->insert('end',"E-Mail :\n");});
$others -> command(-label =>"Insert All", -underline => 7,
-command => sub { $txt->insert('end',"Name : Thaddeus Roebuck Badgercock
Website :
E-Mail :");
});
## Help ##
$help -> command(-label =>"About", -command => sub {
$txt->delete('1.0','end');
$txt->insert('end',
"About
----------
This is a simple text editor written in Perl Tk. This program is licensed under the GNU Public License and is Free Software.
"); });
## Tags ##
$txt->tag(qw/configure bgstipple -background black -borderwidth 0
-bgstipple gray12/);
$txt->tag(qw/configure bold -font C_bold/);
$txt->tag(qw/configure color1 -background/ => '#a0b7ce');
$txt->tag(qw/configure raised -background white -relief raised/);
$txt->tag(qw/configure sunken -background white -relief sunken/);
$txt->tag(qw/configure underline -underline on/);
MainLoop;
sub find_replace {
$txt->FindAndReplacePopUp;
}
sub stippling {
$txt->insert('end', ' ', 'bgstipple');
} # end style
sub bold {
$txt->insert('end', ' ', 'bold');
}
sub highlight {
$txt->insert('end', ' ', 'color1');
}
sub raised {
$txt->insert('end', ' ', 'raised');
}
sub underline {
$txt->insert('end', ' ', 'underline'); #how do
}
sub savefunction {
my $fileDataToSave=$txt->get("1.0","end");
# Trigger dialog
$filename = $mw->getSaveFile( -title => "Selecting file to Save",
-defaultextension => '.rtf', -initialdir => '.' );
# save the file
open(my $fh, '>', $filename) or die $!;
print $fh $fileDataToSave;
close $fh;
}
sub openfunction {
# function to get file dialog box
$filename = $mw->getOpenFile( -title => "Selecting file to Load",
-defaultextension => '.txt', -initialdir => '.' );
# function to load file into string e.g. if you have use File::Slurp
open($fh, '<', $filename) or die $!;
my $file_content = do { local $/; <$fh> };
close $fh;
$txt->Contents($file_content)
}
sub menuClicked {
my ($opt) = #_;
$mw->messageBox(-message=>"You have clicked $opt.
This function is not implemented yet.");
}
#todo:
#figure out how to package as monolithic executables for various platforms

You can insert the highlight formatting at the current cursor position using:
sub highlight {
$txt->insert('insert', ' ', 'color1');
$txt->SetCursor( 'insert - 1 chars' ); # <-- moves the cursor back into the
# hightlight region
}

Related

Perl Tk Formatting Text in a button

The code below creates Perl/Tk buttons filled with each event's details from the hash %events. Is there a way to format the text inside the buttons so that it appears in aligned columns in the manner printf prints it out on line 16.
use strict;
use Tk;
my %events;
$events{"Jazz Fest"}{city} = "New Orleans";
$events{"Jazz Fest"}{state} = "LA";
$events{"Jazz Fest"}{date} = "June 6th, 2023";
$events{"Lollapalooza"}{city} = "Chicago";
$events{"Lollapalooza"}{state} = "IL";
$events{"Lollapalooza"}{date} = "July 25th, 2023";
$events{"Blues Fest"}{city} = "Chicago";
$events{"Blues Fest"}{state} = "IL";
$events{"Blues Fest"}{date} = "Augsut 4th, 2023";
my %data;
foreach my $event (sort keys %events) {
printf("%-15.15s %-20.20s %-2.2s %18.18s\n", $event, $events{$event}{city}, $events{$event}{state}, $events{$event}{date});
$data{$event} = sprintf("%-15.15s %-20.20s %-2.2s %18.18s", $event, $events{$event}{city}, $events{$event}{state}, $events{$event}{date});
}
my $data;
my $mw = MainWindow->new;
$mw->title("Event Clipboard Copy");
$mw->label("Pick Event Details to Copy.");
for(sort keys %data){
my $text = $data{$_};
$mw->Button( -text => $text, -anchor => 'w', -command => sub {
Clipboard->copy( $text );
}
)->pack( -fill => 'x' );
}
MainLoop;
To align the columns, set font of each button to "Courier" (or another monospaced font). There are at least two ways to do this:
when creating the button
assign the button to a variable and then call configure on it.
#Setting font when creating the button
$mw->Button( -text => $text, -anchor => 'w', -font => [-family => 'Courier'], -command => sub {
#Calling configure on the button
my $button = $mw->Button(-text => $text, -anchor => 'w', -command => sub {
...
}->pack( -fill => 'x' );
$button->configure(-font => [-family => 'Courier']);
Here's how it looks with font set to Courier
And without setting font:

Perl Log::Dispatch: Change logging location mid-run?

I'm using Log::Dispatch in a large Mooseified app, via MooseX::LogDispatch. My setup is more or less:
use Moose;
with 'MooseX::LogDispatch';
has log_dispatch_conf => (
is => 'ro',
isa => 'HashRef',
lazy => 1,
required => 1,
default => sub {
my $self = shift;
return {
class => 'Log::Dispatch::File',
min_level => 'debug',
filename => $self->config->{logfile},
mode => '>>',
newline => 1
};
},
);
Is there any way I can change the location of the log file in the middle of a running process? My specific use case is that I'm processing a number of different large [things], which are passed in at runtime, and I'd like each [thing] to log to its own file. Something like this:
foreach my $thing (#things) {
my $logfile = $self->config->{log_base} . $thing->{name} . time() . ".log";
# do something here to set log location
$self->logger->info("Start processing " . $thing->{name} . " at " . scalar localtime());
# process $thing
}
Right. I abandoned MooseX::LogDispatch and did it myself.
When we have a new [thing], I just call a trigger to fire a _set_logger method:
sub _set_logger {
my ($self, $thing) = #_;
my $log_dir = $self->config->{log_dir}; # /path/to/log_dir
my $log_file_base = $self->config->{log_file_base}; # e.g. process-thing-log
my $t = localtime;
my $logfile = $log_dir . "/" . $log_file_base . $thing->{name} . "-" . $t->ymd . ".log";
my $logger = Log::Dispatch->new(
outputs => [
[ 'File',
min_level => 'debug',
filename => $logfile,
mode => '>>',
newline => 1,
],
],
);
$self->logger($logger);
$self->logger->info("Started run at " . scalar localtime);
}
Don't know if it's "right", but it's working smoothly.

perl tk gui to show script stdout and stderr in text widget

I have a GUI that runs a script from a command button, but how can I get it to display output in the text widget?
If I wanted to display the output via a logfile insert, could I have the command on the same button/sub as the run button?
use warnings;
use strict;
use Tk;
use Tk::Text ;
use POSIX 'strftime';
my $DATE = strftime("Report.pl for %dth %b %Y" , localtime());
my $mw = MainWindow->new;
my $filename = "c:\\Temp\\perl.txt";
$mw->geometry("720x500");
$mw->title(" backupadmin ");
my $main_frame = $mw->Frame()->pack(-side => 'top', -fill => 'x');
my $left_frame = $main_frame->Frame(-background => "snow2")->pack(-side => 'left', -fill => 'y');
my $right_frame = $main_frame->Scrolled("Text", -scrollbars => 'se',-background => "black",-foreground => "yellow",-height => '44')->pack(-expand => 1, -fill => 'both');
my $failures_button = $left_frame->Button(-text => " $DATE ",
-command => [\&runscript])->pack;
my $Close_button = $left_frame->Button(-text => ' Close ',
-command => [$mw => 'destroy'])->pack;
my $Help_button = $left_frame->Button(-text => " Help Guide ",
-command => [\&load_file])->pack(-side => "bottom");
my $Close_help = $left_frame->Button(-text => ' Close Help ',
-command => [$right_frame => \&clear_file])->pack(-side => "bottom");
MainLoop;
sub runscript {
system("report.pl");
}
sub load_file {
open (FH, "$filename");
while (<FH>) { $right_frame->insert("end", $_); }
close (FH);
}
sub clear_file {
$right_frame->('quit');
}
If your report.pl script outputs to STDOUT, then you could try something like this in your runscript callback:
sub runscript {
right_frame->delete('1.0','end');
my $text = `report.pl`;
$right_frame->insert('end', $text);
}
Alternatively, if report.pl outputs to c:\temp\perl.txt then you could try the following:
sub runscript {
right_frame->delete('1.0','end');
system("report.pl");
load_file();
}

How to print selected value with checkbutton in Tkx

I'm using the Tkx module to create a window populated with text values from a #list. Then I select one or more then one with a checkbutton. I want to print the already selected ones after pressing the 'OK' button but don't know how to pass the variables to the 'OK' -command => sub {}. Thanks.
use autodie;
use strict;
use warnings;
use Tkx;
my $mw = Tkx::widget->new( "." );
$mw->g_wm_title( "Listbox" );
$mw->m_configure( -background => "#191919" );
my $width = '700';
my $height = '500';
Tkx::update('idletasks');
$width ||= Tkx::winfo('reqwidth', $mw);
$height ||= Tkx::winfo('reqheight', $mw);
my $x = int((Tkx::winfo('screenwidth', $mw) / 2) - ($width / 2));
my $y = int((Tkx::winfo('screenheight', $mw) / 2) - ($height / 2));
$mw->g_wm_geometry($width . "x" . $height . "+" . $x . "+" . $y);
my #list = ('TEXT1', 'TEXT2', 'TEXT3', 'TEXT4', 'TEXT5');
for my $list (#list) {
my $cb = $mw->new_ttk__checkbutton(
-text => $list,
-onvalue => 1,
-offvalue => 0,
);
$cb->g_pack(
-anchor=>'w',
-side=>'top',
-fill => 'x'
);
}
my $ok = $mw->new_button(
-text => "OK",
-command => sub {
print "Selected Values";
Tkx::after(500, sub { $mw->g_destroy });
},
);
$ok->g_pack(
-anchor=>'c',
-side=>'bottom',
);
Tkx::MainLoop();
If you just want to find what checkboxes are selected:
my $settings;
for my $list (#list) {
my $cb = $mw->new_ttk__checkbutton(
-text => $list,
-onvalue => 1,
-offvalue => 0,
-variable => \$settings->{checkbuttons}->{$list},
);
$cb->g_pack(
-anchor=>'w',
-side=>'top',
-fill => 'x'
);
}
my $ok = $mw->new_button(
-text => "OK",
-command => sub {
print "Selected Values: [";
print join( ", ", grep { $settings->{checkbuttons}->{$_} } #list ), "]\n";
Tkx::after(500, sub { $mw->g_destroy });
},
);

Delete the subwidgets/entries from a tk::menu widget

I want to implement a history/recent-files functionality for my Perl/Tk program.
Here is a working code excerpt from my program to demonstrate my problem.
#!/usr/bin/perl
use strict;
use warnings;
use English qw( -no_match_vars );
use Tk;
my #history_entries = qw(Back To The History);
my $mw = MainWindow->new();
my $menubar = $mw->Menu();
$mw->configure( -menu => $menubar );
my $file = $menubar->cascade( -label => '~File' );
my $history = $file->cascade( -label => '~History', );
build_history();
MainLoop();
#=== FUNCTION ================================================================
# NAME: build_history
# PURPOSE: Polulate the history
#===============================================================================
sub build_history {
foreach (#history_entries) {
$history->command(
-label => $ARG,
-command => \&some_function,
-compound => 'left',
);
}
return;
} # ---------- end of subroutine build_history ----------
As you can see, the entries are created with $history->command but how can I delete them every time I call build_history?
The reason I want them deleted is that everytime a user opens a file, the last item (in this case 'History') should be deleted and a new item should be put on top of the list. So that I have a maximum of (in this example) four entries.
Any other ways of implementing this functionality with Tk are welcome.
Thanks for your time.
UPDATE:
I followed the advice of "mu" and tried to get the subwidgets with the children() function just after the function is called, like this:
my #child = $history->children();
foreach my $c ( #child ) {
if ($c->Exists()){
$c->destroy;
}
}
The program exits with the error:
Can't locate object method "children" via package "Tk::Menu::Cascade" at /home/alex/Desktop/stack_history.pl line 28.
you can create a submenu from the menubar first:
my $sub_menu = $menubar->Menu(-tearoff => 0);
Then you can pass this submenu to the cascade menuitem:
$file->cascade( -label => '~History', -menu => $sub_menu);
Then you can add/delete menuitems to the submenu:
foreach (#history_entries) {
$sub_menu->command(
-label => $ARG,
-compound => 'left',
);
}
...
$sub_menu->delete(0); # Remove first element
With this solution you can avoid rebulding the whole menu.
I ended up rebuilding the whole menu. That's how my code looks like atm. I am not proud of it but it works ... I am open to any form of advice.
#!/usr/bin/perl
use strict;
use warnings;
use English qw( -no_match_vars );
use Tk;
# History entries are stored in array
my #history_entries = qw(Back To The History);
my $mw = MainWindow->new();
$mw->geometry('200x200');
my $menubar = $mw->Menu();
#Build menus
$mw->configure( -menu => $menubar );
build_menu();
$mw->Button(
-text => 'Update History',
-command => \sub {
#when a user opens a file insert_history is called.
insert_history();
}
)->pack( -side => 'bottom', -anchor => 'sw' );
MainLoop();
#=== FUNCTION ================================================================
# NAME: build_menu
# PURPOSE: Update/Build the menu
#===============================================================================
sub build_menu {
#delete the whole menu
$menubar->delete(1);
#built it again
my $file = $menubar->cascade( -label => '~File' );
my $history = $file->cascade( -label => '~History', );
foreach (#history_entries) {
$history->command(
-label => $ARG,
-compound => 'left',
);
}
return;
} # ---------- end of subroutine build_menu ----------
#=== FUNCTION ================================================================
# NAME: insert_history
# PURPOSE: Do something with the array containing the history entries.
# Then rebuild the menu.
#===============================================================================
sub insert_history {
#make something with the array
my $last_element = pop #history_entries;
unshift #history_entries, $last_element;
#update menu
build_menu();
return;
} # ---------- end of subroutine insert_history ----------