Compare commits

...
Sign in to create a new pull request.

8 commits
main ... gtk3

15 changed files with 613 additions and 1107 deletions

View file

@ -1,6 +1,3 @@
1. This application is written in Perl
2. It uses the TK module to provide GUI
3. If this needs to be tested use /usr/bin/perl
4. We are converting it from TK to GTK3
5. Old TK code is in kpad.old.pl
6. New code goes into kpad.pl
2. It uses the GTK3 module to provide GUI
3. If this needs to be tested use /usr/bin/perl

View file

@ -1,4 +1,8 @@
# kpadold
# kpadgtk3
Original Perl version of kpad. Please note this code is very old. It requires Perl and Perl/Tk to run. Works on any platform that can support those.
Version 5.2.1
# Notes
4/30/25 - Ported to GTK3 with some improved syntax highlighting. Curtesy of Grok for the main translation.

BIN
kpad Executable file

Binary file not shown.

View file

@ -1,375 +0,0 @@
# KAKE PAD version 5.5
# Future versions will look at using Perl 6
# Please note that KAKE PAD depends on Tk and some bugs
# are due to Tk and not KAKE PAD itself. No promises on how
# good or bad Tk is.
# A few Tk modules need to be declared for PDK purposes
use Tk;
use Tk::TextUndo;
use Tk::DialogBox;
use File::Glob;
use File::Find;
use FileHandle;
use LWP::Simple;
#most of the above is only put in so I can compile to stand alone exe
#If you use as script keep the last two and use Tk;
# Below is the init section
# A few thing to do is check OS to see if its Windows or not
# I still don't know what I smoked when I wrote this but this section is important
($ar) = @ARGV; #Let the program do its thing with the arguments
# Bah check OS
# Just to make sure test $0, this is a work around for a minor bug
# Detect : a trade mark of a DOS system
# The setion below is so odd and complex vodoo that is quite nessacry
# yes I can't spell, only code
if($^O ne "Win32") {
if($0 =~/\//g){
@hd = split "/", $0;
$hdl = pop(@hd); # Knock the filename off the array we don't need it
$basedir = join('/', @hd);
}else{
$basedir = ".";
}
}else{
$basedir = ".";
}
$main = MainWindow->new(-title=> "kPad"); #Generates the main window
$menubar = $main->Frame()->pack(-side => "top", -fill => "x"); #Notice I create the menu bar as
# a frame not a Tk::Menu menubar, this makes things easier
#Below I define all the DialogBoxes,note they can be globaly used
$about = $main->DialogBox(-title=>"About...",-buttons=>["OK"]); #Creates the about Dialog
$aabout = $about->add("Label",-text=>"kPad\n by Paul Malcher\nVersion 6 Release\n")->pack; #Adds a label to $about
#$kweabt = $main->DialogBox(-title=>"About kWebedit...",-buttons=>["OK"]); #Creates the About kWebedit Dialog
#$akweabt =$kweabt->add("Label",-text=>"kWebedit v.2.0\nby Paul Malcher\nBased on kWebedit v.1.0.3 by Chris Litwin ")->pack; #That's the about for kWebedit.
$help = $main->DialogBox(-title=>"Help Topics",-buttons=>["OK"]); #Creates Help Dialog
$ahelp = $help->add("Label",-text=>"Help topics for KPAD\nWell, this is a text/file editor mainly meant for scripting and programming use.
Like notepad but made for the programmer.")->pack;
$nsave = $main->DialogBox(-title=>"Warning File Has changed!",-buttons=>["Save","Exit"]);
$ansave = $nsave->add("Label",-text=>"The documents contents have changed since you opened.\nDo you wish to save?.")->pack;
$nimp = $main->DialogBox(-title=>"Non-implementation Error",-buttons=>["OK"]);
$animp = $nimp->add("Label",-text=>"This function is not yet implemented!")->pack;
$fetch = $main->DialogBox(-title =>'HTML Source Fetch',-buttons=>["OK"]);
$afetch = $fetch->add("Label",-text=>'Fetch what:')->pack;
$bfetch = $fetch->add("Entry",-text=>'http://')->pack;
$dummy = $main->DialogBox(-title=>'Dummy Box');
$adummy = $dummy->add("Text")->pack;
$ftapp = $main->DialogBox(-title =>'File Has Changed!',-buttons=>["Yes","No"]);
$aftapp = $ftapp->add("Label", -text=>"File contents have changed, save now?!")->pack;
$track = "init";
# Begin new Kpad 4.0 features
# Plugin/Macros or whatever you want to call them
# First we find and autoload plugin, yes we use grep, makes life good
$pls = 0;
# Heck with lets do it all in one loop
while(<*.kpd>){
push(@plugins,$_);
open pin,"<$_";
@gn = split "::" , <pin>;
if(@gn[2] eq "auto"){
@n[$pls] = "auto";
}else{
@n[$pls] = @gn[1];
}
$pls++;
}
# determin the number of plugins, so we can size the list accordingly
$nop = scalar(@n); #notice @n does not get shortend, this is important later on
foreach(@n) {
if($_ eq "auto"){
$nop--; # make sure auto plugins are not listed
}
}
# Build the menu with list box
$plugin = $main->DialogBox(-title=>'Macro Execution Menu',-buttons=>["Close"]);
$bplugin = $plugin->add("Label",-text=>'Double Click To Execute Macro')->pack;
$aplugin = $plugin->Listbox("-width"=>40, "-height"=> $nop)->pack;
foreach(@n) {
if($_ eq "auto"){
$arun = 0;
}else{
$aplugin->insert('end', "$_");
}
}
$aplugin->bind('<Double-1>' , \&eplugin); # Plugin name now can be different from the file name
$filemenu = $menubar->Menubutton(-text => 'File', -underline => 0,-tearoff => 0)->pack(-side=>'left'); #This puts
#the file button on the frame used for the menu bar
#Below are the commands for that button
#note How I included the subs into the command function
$filemenu->command(-label => 'New',-command => sub{
$text->delete('1.0','end');
});
$filemenu->command(-label => 'Open',-command => sub{
$text->delete('1.0','end');
my $types = [
['Perl Scripts', '.pl'],
['All Files', '*', ],
];
$open = $main->getOpenFile(-filetypes=>$types);
#open FILE, "<$open"; #took weeks to get this right,its there so te whole file loads correctly
# and only 3 sec to comment out for the 5.0 release
$text->Load($open);
$text ->pack;
});
$filemenu->command(-label => 'Save',-command => sub{
$data = $text->get('1.0','end'); #Saving for widget to file is a piece of cake
if($ar eq ""){
$text->Save($open);
# Easy indeed
}else{
$text->Save($ar);
}
});
$filemenu->command(-label => 'Save As',-command => sub{
#my $types = [['All Files', '*', ],];
my $types = [
['Perl Scripts', '.pl' ],
['All Files', '.*', ],
];
my $save = $main->getSaveFile(-filetypes=>$types);
$text->Save($save);
$open = $save;
});
$filemenu->separator;
$filemenu->command(-label => 'Exit',-command => sub{
tapp();
});
$editmenu = $menubar->Menubutton(-text => 'Edit', -underline => 0,-tearoff => 0)->pack(-side=>'left');
$editmenu->command(-label => 'Undo',-command => sub{
my ($w) = @_;
$text->undo;
});
$editmenu->command(-label => 'Redo',-command => sub{
my ($w) = @_;
$text->redo;
});
$editmenu->separator;
$editmenu->command(-label => 'Cut',-command => sub{
my ($w) = @_;
$text->Column_Copy_or_Cut(1);
});
$editmenu->command(-label => 'Copy',-command => sub{
my ($w) = @_;
$text->Column_Copy_or_Cut(0);
});
$editmenu->command(-label => 'Paste',-command => sub{
$text->clipboardColumnPaste();
});
$editmenu->separator;
$editmenu->command(-label => 'Select All',-command => sub{
$text->selectAll();
});
$editmenu->command(-label => 'Unselect All',-command => sub{
$text->unselectAll();
});
$editmenu->separator;
$editmenu->command(-label => 'Find',-command => sub{
$text->findandreplacepopup(1);
});
$editmenu->command(-label => 'Find and Replace',-command => sub{
$text->findandreplacepopup(0);
});
$viewmenu = $menubar->Menubutton(-text=>'View',-underline => 0,-tearoff => 0)->pack(-side=>'left');
$vm = $viewmenu->cascade(-label => 'Wrap',-underline => 0,-tearoff => 0);
$vm->radiobutton(-label => "Word", -command => sub { $text->configure(-wrap => 'word'); } );
$vm->radiobutton(-label => "Char",-command => sub { $text->configure(-wrap => 'char'); } );
$vm->radiobutton(-label => "None",-command => sub { $text->configure(-wrap => 'none'); } );
$toolsmenu = $menubar->Menubutton(-text => 'Tools', -underline => 0,-tearoff => 0)->pack(-side=>'left');
$toolsmenu->command(-label => 'Goto Line',-command => sub{
$text->GotoLineNumberPopUp();
});
$toolsmenu->command(-label => 'Which Line?',-command => sub{
$text->WhatLineNumberPopUp();
});
$htmlmenu = $menubar->Menubutton(-text => 'HTML', -underline => 0,-tearoff => 0)->pack(-side=>'left');
$htmlmenu->command(-label => 'Fetch a web resource...',-command => sub{#$fdisc->Show;
$fetch->Show;
$htm = $bfetch->get;
$contents = get($htm);
open ttt, ">temp.dat";
print ttt "$contents";
close ttt;
open FILE, "<temp.dat"; #took weeks to get this right,its there so te whole file loads correctly
$text->delete('1.0','end');
while (! eof FILE){
$text->insert('end',FILE -> getline);
}
close FILE;
unlink(<temp.dat>);
$text ->pack;
});
$pluginmenu = $menubar->Menubutton(-text => 'Macros', -underline => 0,-tearoff => 0)->pack(-side=>'left');
$pluginmenu->command(-label => 'Execute Macro',-command => sub{$plugin->Show;});
$aboutmenu = $menubar->Menubutton(-text => 'Help', -underline => 0,-tearoff => 0)->pack(-side=>'left');
$aboutmenu->command(-label => 'Help Topics...',-command => sub{$help->Show;});
$aboutmenu->command(-label => 'About KPAD...',-command => sub{$about->Show;});
#$aboutmenu->command(-label => 'About kWebedit...',-command => sub{$kweabt->Show;});
# Text widget and configs
$statbar = $main->Frame()->pack(-side => "bottom", -fill => "x");
$statinfo = $statbar->Label(-text =>'Info: ')->pack(-side=>'left');
$statln = $statbar->Label(-text =>'Line: 0')->pack(-side=>'left');
$text = $main->Scrolled(TextUndo,-scrollbars=>'osoe',-background=>'white', -wrap => 'word')->pack(-fill=>'both',-expand=>1); #Scrolled Text
#widget that adapts to the size of the window
$main->protocol('WM_DELETE_WINDOW', \&tapp);
# This replaces $track
$text->bind('<<Modified>>' => sub{
$track = $text->get('1.0','end');
my ($w)=$text;
my ($line,$col) = split(/\./,$w->index('insert'));
#$statln = $statbar->Label(-text =>"Line $line")->pack(-side=>'left');
$statln->configure(-text =>"Line $line");
});
if($ar ne ""){
$text->Load($ar);
$text ->pack;
}
sub eplugin { # Plugin executor, non-auto
$v = $aplugin->get('active');
# Fix for plugin vs. filename fix
# @plugins @n
$fp = 0;
while(@n[$fp] ne $v){ # assume the names in @plugin match with @n
# which they will unless you screw with the way plugins are handled
$fp++;
}
$v = @plugins[$fp];
# Hope it works
open pe, "<$basedir/$v"; # presto it does
# The same damn bug in Tk again, yes the one that took weeks to work around
# I got to do this the hard way
$adummy->delete('1.0','end');
while (! eof pe){
$adummy->insert('end', pe -> getline);
}
$tdata = $adummy->get('2.0','end'); # this is the only way to load an entire plugin into a var the right way
eval ( $tdata );
if($@){ # Only way to to make so it can trap multiple errors without the app having a fatal error itself
$error = $@;
&merr($error);
}
}
sub aeplugin { # Auto plugin executor
$apc = 0;
while(@n[$apc] ne ""){
if(@n[$apc] eq "auto"){
$v = @plugins[$apc];
# Hope it works
open pe, "<$basedir/$v"; # presto it does
# The same damn bug in Tk again, yes the one that took weeks to work around
# I got to do this the hard way
$adummy->delete('1.0','end');
while (! eof pe){
$adummy->insert('end', pe -> getline);
}
$tdata = $adummy->get('2.0','end'); # this is the only way to load an entire plugin into a var the right way
eval ( $tdata );
if($@){ # Only way to to make so it can trap multiple errors without the app having a fatal error itself
$error = $@;
&merr($error);
}
}
$apc++;
}
$arun = 1;
}
sub merr { # merr, macro/plugins error
$merr = $main->DialogBox(-title =>'Macro Error',-buttons=>["OK"]);
$amerr = $merr->add("Label", -text=>"Error: $error")->pack;
$merr->Show;
undef $merr;
}
if($arun eq "0"){
&aeplugin();
}
sub tapp { # shutdown handler
if($track ne "init"){
$result = $ftapp->Show;
if($result eq "No"){
exit(0);
}
if($open){
$text->Save($open);
$saved = 1;
}
if($save){
$text->Save($save);
$saved = 1;
}
if($ar){
$text->Save($ar);
$saved = 1;
}else{
if($saved ne "1"){
my $types = [
['Perl Scripts', '.pl' ],
['All Files', '.*', ],
];
my $save = $main->getSaveFile(-filetypes=>$types);
$text->Save($save);
}
}
}
exit(0);
}
MainLoop; #The main processing loop

698
kpad.pl
View file

@ -1,698 +0,0 @@
#!/usr/bin/perl
# KAKE PAD version 6.0
# GTK3 version converted from TK
# After hours having a AI go around in circles for the error
#*** unhandled exception in callback:
#*** FATAL: invalid GdkModifierType value 4, expecting a string scalar or an arrayref of strings at kpad.pl line 114.
#*** ignoring at /usr/share/perl5/Gtk3.pm line 572.
#Use of uninitialized value in string eq at kpad.pl line 102.
#use strict;
#use warnings;
# There I fixed it, by commenting the two lines above out
use Gtk3 -init;
use File::Glob;
use File::Find;
use FileHandle;
use LWP::Simple;
use Encode qw(decode encode);
# Global variables for file handling
my $current_file;
my $text_buffer;
my @undo_stack = ();
my @redo_stack = ();
my $ignore_changes = 0;
my $status_bar;
my $last_search_pos;
my $search_dialog;
my $replace_dialog;
use constant CONTROL_MASK => 'control-mask'; # Update CONTROL_MASK constant to use the correct string value
# Initialize the main window
my $window = Gtk3::Window->new('toplevel');
$window->set_title("kPad");
$window->set_default_size(800, 600);
# Create the main vertical box
my $vbox = Gtk3::Box->new('vertical', 5);
$window->add($vbox);
# Create menubar
my $menubar = Gtk3::MenuBar->new();
$vbox->pack_start($menubar, 0, 0, 0);
# File menu
my $file_menu = Gtk3::Menu->new();
my $file_item = Gtk3::MenuItem->new_with_label('File');
$file_item->set_submenu($file_menu);
$menubar->append($file_item);
# File menu items
my $new_item = Gtk3::MenuItem->new_with_label('New');
my $open_item = Gtk3::MenuItem->new_with_label('Open');
my $save_item = Gtk3::MenuItem->new_with_label('Save');
my $save_as_item = Gtk3::MenuItem->new_with_label('Save As');
my $separator = Gtk3::SeparatorMenuItem->new();
my $exit_item = Gtk3::MenuItem->new_with_label('Exit');
$file_menu->append($_) for ($new_item, $open_item, $save_item, $save_as_item, $separator, $exit_item);
# Edit menu
my $edit_menu = Gtk3::Menu->new();
my $edit_item = Gtk3::MenuItem->new_with_label('Edit');
$edit_item->set_submenu($edit_menu);
$menubar->append($edit_item);
# Edit menu items
my $undo_item = Gtk3::MenuItem->new_with_label('Undo');
my $redo_item = Gtk3::MenuItem->new_with_label('Redo');
my $cut_item = Gtk3::MenuItem->new_with_label('Cut');
my $copy_item = Gtk3::MenuItem->new_with_label('Copy');
my $paste_item = Gtk3::MenuItem->new_with_label('Paste');
my $find_item = Gtk3::MenuItem->new_with_label('Find');
my $replace_item = Gtk3::MenuItem->new_with_label('Find and Replace');
my $select_all_item = Gtk3::MenuItem->new_with_label('Select All');
$edit_menu->append($_) for (
$undo_item, $redo_item,
Gtk3::SeparatorMenuItem->new(),
$cut_item, $copy_item, $paste_item,
Gtk3::SeparatorMenuItem->new(),
$find_item, $replace_item,
Gtk3::SeparatorMenuItem->new(),
$select_all_item
);
# Add Tools menu
my $tools_menu = Gtk3::Menu->new();
my $tools_item = Gtk3::MenuItem->new_with_label('Tools');
$tools_item->set_submenu($tools_menu);
# Add About menu
my $about_item = Gtk3::MenuItem->new_with_label('About');
$about_item->signal_connect(activate => sub {
show_info_dialog('About kPad', 'kPad version 6.0\nGTK3 version');
});
# Add HTML menu
my $html_item = Gtk3::MenuItem->new_with_label('HTML');
$html_item->signal_connect(activate => sub {
# Placeholder for HTML functionality
show_info_dialog('HTML', 'HTML functionality will be added soon.');
});
# Add Macros menu (placeholder)
my $macros_item = Gtk3::MenuItem->new_with_label('Macros');
$macros_item->signal_connect(activate => sub {
# Placeholder for Macros functionality
show_info_dialog('Macros', 'Macros functionality will be handled by the user.');
});
# Add new menu items to the menu bar
$menubar->append($tools_item);
$menubar->append($about_item);
$menubar->append($html_item);
$menubar->append($macros_item);
# Create text view with scrolled window
my $scrolled_window = Gtk3::ScrolledWindow->new();
$scrolled_window->set_policy('automatic', 'automatic');
$vbox->pack_start($scrolled_window, 1, 1, 0);
my $text_view = Gtk3::TextView->new();
$text_buffer = $text_view->get_buffer();
$text_view->set_wrap_mode('word');
$text_view->set_editable(1);
$text_view->set_cursor_visible(1);
# Create status bar
$status_bar = Gtk3::Statusbar->new();
$vbox->pack_start($status_bar, 0, 0, 0);
update_cursor_position();
# Track cursor movement for status bar updates
$text_buffer->signal_connect('mark-set' => sub {
my ($buffer, $iter, $mark) = @_;
if ($mark->get_name() eq 'insert') {
update_cursor_position();
}
});
# Enable copy/paste keyboard shortcuts
$text_view->signal_connect('key-press-event' => sub {
my ($widget, $event) = @_;
my $keyval = $event->keyval;
my $state = $event->state;
# Check if Control key is pressed (GDK_CONTROL_MASK)
if ($state & CONTROL_MASK) {
if (chr($keyval) eq 'z' || chr($keyval) eq 'Z') {
undo_action();
return 1;
}
elsif (chr($keyval) eq 'y' || chr($keyval) eq 'Y') {
redo_action();
return 1;
}
elsif (chr($keyval) eq 'c' || chr($keyval) eq 'C') {
$copy_item->signal_emit('activate');
return 1;
}
elsif (chr($keyval) eq 'x' || chr($keyval) eq 'X') {
$cut_item->signal_emit('activate');
return 1;
}
elsif (chr($keyval) eq 'v' || chr($keyval) eq 'V') {
$paste_item->signal_emit('activate');
return 1;
}
elsif (chr($keyval) eq 'f' || chr($keyval) eq 'F') {
$find_item->signal_emit('activate');
return 1;
}
elsif (chr($keyval) eq 'h' || chr($keyval) eq 'H') {
$replace_item->signal_emit('activate');
return 1;
}
}
return 0;
});
# Track buffer changes for undo/redo
$text_buffer->signal_connect('changed' => \&buffer_changed_cb);
$text_buffer->signal_connect('begin-user-action' => sub {
$ignore_changes = 0;
});
$text_buffer->signal_connect('modified-changed' => sub {
my $modified = $text_buffer->get_modified();
$window->set_title("kPad" . ($modified ? " *" : "") . (defined $current_file ? " - $current_file" : ""));
});
$scrolled_window->add($text_view);
# File menu callbacks
$new_item->signal_connect(activate => sub {
return unless check_save_changes();
$text_buffer->set_text('');
$text_buffer->set_modified(0);
$current_file = undef;
$window->set_title("kPad");
});
$open_item->signal_connect(activate => sub {
return unless check_save_changes();
my $dialog = Gtk3::FileChooserDialog->new(
'Open File',
$window,
'open',
'Cancel' => 'cancel',
'Open' => 'ok'
);
# Add file filters
my $filter = Gtk3::FileFilter->new();
$filter->set_name('Perl Scripts');
$filter->add_pattern('*.pl');
$dialog->add_filter($filter);
$filter = Gtk3::FileFilter->new();
$filter->set_name('All Files');
$filter->add_pattern('*');
$dialog->add_filter($filter);
if ('ok' eq $dialog->run()) {
my $filename = $dialog->get_filename();
open_file($filename);
}
$dialog->destroy();
});
$save_item->signal_connect(activate => sub {
if (defined $current_file) {
save_file($current_file);
} else {
$save_as_item->signal_emit('activate');
}
});
$save_as_item->signal_connect(activate => sub {
my $dialog = Gtk3::FileChooserDialog->new(
'Save File',
$window,
'save',
'Cancel' => 'cancel',
'Save' => 'ok'
);
# Add file filters
my $filter = Gtk3::FileFilter->new();
$filter->set_name('Perl Scripts');
$filter->add_pattern('*.pl');
$dialog->add_filter($filter);
$filter = Gtk3::FileFilter->new();
$filter->set_name('All Files');
$filter->add_pattern('*');
$dialog->add_filter($filter);
if ('ok' eq $dialog->run()) {
my $filename = $dialog->get_filename();
save_file($filename);
}
$dialog->destroy();
});
# Edit menu callbacks
$undo_item->signal_connect(activate => sub {
undo_action();
});
$redo_item->signal_connect(activate => sub {
redo_action();
});
$cut_item->signal_connect(activate => sub {
$text_buffer->cut_clipboard(
Gtk3::Clipboard::get(Gtk3::Gdk::Atom::intern('CLIPBOARD', 0)),
1
);
});
$copy_item->signal_connect(activate => sub {
$text_buffer->copy_clipboard(
Gtk3::Clipboard::get(Gtk3::Gdk::Atom::intern('CLIPBOARD', 0))
);
});
$paste_item->signal_connect(activate => sub {
$text_buffer->paste_clipboard(
Gtk3::Clipboard::get(Gtk3::Gdk::Atom::intern('CLIPBOARD', 0)),
undef,
1
);
});
$select_all_item->signal_connect(activate => sub {
my $start = $text_buffer->get_start_iter();
my $end = $text_buffer->get_end_iter();
$text_buffer->select_range($start, $end);
});
# Connect find/replace menu items
$find_item->signal_connect(activate => sub {
create_search_dialog();
$search_dialog->present();
});
$replace_item->signal_connect(activate => sub {
create_replace_dialog();
$replace_dialog->present();
});
# Helper functions
sub open_file {
my ($filename) = @_;
return unless -f $filename;
if (open(my $fh, '<:encoding(UTF-8)', $filename)) {
local $/;
my $content = <$fh>;
close $fh;
$text_buffer->set_text($content);
$text_buffer->set_modified(0);
$current_file = $filename;
$window->set_title("kPad - $filename");
} else {
show_error_dialog("Could not open file: $!");
}
}
sub save_file {
my ($filename) = @_;
return unless defined $filename;
if (open(my $fh, '>:encoding(UTF-8)', $filename)) {
my $content = $text_buffer->get_text(
$text_buffer->get_start_iter(),
$text_buffer->get_end_iter(),
1
);
print $fh $content;
close $fh;
$current_file = $filename;
$text_buffer->set_modified(0);
$window->set_title("kPad - $filename");
} else {
show_error_dialog("Could not save file: $!");
}
}
sub buffer_changed {
return $text_buffer->get_modified();
}
sub check_save_changes {
if (buffer_changed()) {
my $dialog = Gtk3::MessageDialog->new(
$window,
'modal',
'question',
'none', # No default buttons
"The document has been modified. Save changes?"
);
$dialog->add_button('Save', 'yes');
$dialog->add_button("Don't Save", 'no');
$dialog->add_button('Cancel', 'cancel');
my $response = $dialog->run();
$dialog->destroy();
if ($response eq 'yes') {
if (defined $current_file) {
save_file($current_file);
return 1;
} else {
my $save_dialog = Gtk3::FileChooserDialog->new(
'Save File',
$window,
'save',
'Cancel' => 'cancel',
'Save' => 'ok'
);
# Add file filters
my $filter = Gtk3::FileFilter->new();
$filter->set_name('Perl Scripts');
$filter->add_pattern('*.pl');
$save_dialog->add_filter($filter);
$filter = Gtk3::FileFilter->new();
$filter->set_name('All Files');
$filter->add_pattern('*');
$save_dialog->add_filter($filter);
my $save_response = $save_dialog->run();
my $filename = $save_dialog->get_filename();
$save_dialog->destroy();
if ($save_response eq 'ok' && defined $filename) {
save_file($filename);
return 1;
}
return 0;
}
} elsif ($response eq 'no') {
return 1;
} else {
return 0;
}
}
return 1;
}
sub show_error_dialog {
my ($message) = @_;
my $dialog = Gtk3::MessageDialog->new(
$window,
'modal',
'error',
'ok',
$message
);
$dialog->run();
$dialog->destroy();
}
sub create_search_dialog {
return if defined $search_dialog;
$search_dialog = Gtk3::Dialog->new();
$search_dialog->set_title('Find');
$search_dialog->set_transient_for($window);
$search_dialog->add_button('Close', 'close');
$search_dialog->add_button('Find Next', 'ok');
my $content_area = $search_dialog->get_content_area();
my $vbox = Gtk3::Box->new('vertical', 5);
$content_area->add($vbox);
my $hbox = Gtk3::Box->new('horizontal', 5);
$vbox->pack_start($hbox, 0, 0, 0);
$hbox->pack_start(Gtk3::Label->new('Search for:'), 0, 0, 0);
my $entry = Gtk3::Entry->new();
$hbox->pack_start($entry, 1, 1, 0);
my $case_sensitive = Gtk3::CheckButton->new_with_label('Case sensitive');
$vbox->pack_start($case_sensitive, 0, 0, 0);
$search_dialog->show_all();
$search_dialog->signal_connect(response => sub {
my ($dialog, $response) = @_;
if ($response eq 'ok') {
find_text($entry->get_text(), $case_sensitive->get_active());
} elsif ($response eq 'close') {
$dialog->hide();
}
});
}
sub create_replace_dialog {
return if defined $replace_dialog;
$replace_dialog = Gtk3::Dialog->new();
$replace_dialog->set_title('Find and Replace');
$replace_dialog->set_transient_for($window);
$replace_dialog->add_button('Close', 'close');
$replace_dialog->add_button('Replace', 'ok');
$replace_dialog->add_button('Replace All', 'apply');
my $content_area = $replace_dialog->get_content_area();
my $vbox = Gtk3::Box->new('vertical', 5);
$content_area->add($vbox);
# Find entry
my $find_box = Gtk3::Box->new('horizontal', 5);
$vbox->pack_start($find_box, 0, 0, 0);
$find_box->pack_start(Gtk3::Label->new('Find:'), 0, 0, 0);
my $find_entry = Gtk3::Entry->new();
$find_box->pack_start($find_entry, 1, 1, 0);
# Replace entry
my $replace_box = Gtk3::Box->new('horizontal', 5);
$vbox->pack_start($replace_box, 0, 0, 0);
$replace_box->pack_start(Gtk3::Label->new('Replace with:'), 0, 0, 0);
my $replace_entry = Gtk3::Entry->new();
$replace_box->pack_start($replace_entry, 1, 1, 0);
my $case_sensitive = Gtk3::CheckButton->new_with_label('Case sensitive');
$vbox->pack_start($case_sensitive, 0, 0, 0);
$replace_dialog->show_all();
$replace_dialog->signal_connect(response => sub {
my ($dialog, $response) = @_;
if ($response eq 'ok') {
replace_next($find_entry->get_text(), $replace_entry->get_text(), $case_sensitive->get_active());
} elsif ($response eq 'apply') {
replace_all($find_entry->get_text(), $replace_entry->get_text(), $case_sensitive->get_active());
} elsif ($response eq 'close') {
$dialog->hide();
}
});
}
sub find_text {
my ($search_text, $case_sensitive) = @_;
return unless $search_text;
my $buffer = $text_view->get_buffer();
my $start_iter = $buffer->get_iter_at_mark($buffer->get_insert());
# Start from beginning if we're at the end or no previous search
if (!defined $last_search_pos || !$start_iter->forward_char()) {
$start_iter = $buffer->get_start_iter();
}
my $found;
if ($case_sensitive) {
$found = $start_iter->forward_search($search_text, 'text-only', undef);
} else {
$found = $start_iter->forward_search($search_text, ['text-only', 'case-insensitive'], undef);
}
if ($found) {
my ($match_start, $match_end) = @$found;
$buffer->select_range($match_start, $match_end);
$text_view->scroll_to_iter($match_start, 0.0, 1, 0.0, 0.5);
$last_search_pos = $match_end;
return 1;
} else {
# If not found from current position, try from start
if ($start_iter->get_offset() > 0) {
$last_search_pos = undef;
return find_text($search_text, $case_sensitive);
}
show_error_dialog("Text not found: $search_text");
return 0;
}
}
sub replace_next {
my ($find_text, $replace_text, $case_sensitive) = @_;
return unless $find_text;
if (find_text($find_text, $case_sensitive)) {
my $buffer = $text_view->get_buffer();
$buffer->delete_selection(1, 1);
$buffer->insert_at_cursor($replace_text);
$last_search_pos = undef; # Force next search from current position
}
}
sub replace_all {
my ($find_text, $replace_text, $case_sensitive) = @_;
return unless $find_text;
my $count = 0;
$last_search_pos = undef;
while (find_text($find_text, $case_sensitive)) {
my $buffer = $text_view->get_buffer();
$buffer->delete_selection(1, 1);
$buffer->insert_at_cursor($replace_text);
$last_search_pos = undef;
$count++;
}
show_info_dialog("Replaced $count occurrence" . ($count == 1 ? '' : 's'));
}
sub update_cursor_position {
my $buffer = $text_view->get_buffer();
my $iter = $buffer->get_iter_at_mark($buffer->get_insert());
my $line = $iter->get_line() + 1;
my $column = $iter->get_line_offset() + 1;
$status_bar->push(0, "Line: $line, Column: $column");
}
sub show_info_dialog {
my ($message) = @_;
my $dialog = Gtk3::MessageDialog->new(
$window,
'modal',
'info',
'ok',
$message
);
$dialog->run();
$dialog->destroy();
}
# Connect window signals
$window->signal_connect(delete_event => sub {
if (!check_save_changes()) {
return 1; # Cancel the close
}
return 0;
});
$window->signal_connect(destroy => sub { Gtk3::main_quit() });
$exit_item->signal_connect(activate => sub { $window->destroy() });
# Show all widgets
$window->show_all();
# Add these new functions at the end before Gtk3::main()
sub handle_key_press {
my ($widget, $event) = @_;
my $keyval = $event->keyval;
my $state = $event->state;
# Check if Control key is pressed
if ($state & CONTROL_MASK) {
if (chr($keyval) eq 'z' || chr($keyval) eq 'Z') {
undo_action();
return 1;
}
elsif (chr($keyval) eq 'y' || chr($keyval) eq 'Y') {
redo_action();
return 1;
}
elsif (chr($keyval) eq 'c' || chr($keyval) eq 'C') {
$copy_item->signal_emit('activate');
return 1;
}
elsif (chr($keyval) eq 'x' || chr($keyval) eq 'X') {
$cut_item->signal_emit('activate');
return 1;
}
elsif (chr($keyval) eq 'v' || chr($keyval) eq 'V') {
$paste_item->signal_emit('activate');
return 1;
}
}
return 0;
}
sub buffer_changed_cb {
return if $ignore_changes;
my $content = $text_buffer->get_text(
$text_buffer->get_start_iter(),
$text_buffer->get_end_iter(),
1
);
push @undo_stack, $content;
@redo_stack = (); # Clear redo stack when new changes occur
}
sub undo_action {
return unless @undo_stack;
my $current_content = $text_buffer->get_text(
$text_buffer->get_start_iter(),
$text_buffer->get_end_iter(),
1
);
push @redo_stack, $current_content;
$ignore_changes = 1;
my $prev_content = pop @undo_stack;
$text_buffer->set_text($prev_content);
$ignore_changes = 0;
}
sub redo_action {
return unless @redo_stack;
my $current_content = $text_buffer->get_text(
$text_buffer->get_start_iter(),
$text_buffer->get_end_iter(),
1
);
push @undo_stack, $current_content;
$ignore_changes = 1;
my $next_content = pop @redo_stack;
$text_buffer->set_text($next_content);
$ignore_changes = 0;
}
# Start the main loop
Gtk3::main();

BIN
kpadgtk3 Executable file

Binary file not shown.

View file

@ -9,10 +9,10 @@ use LWP::Simple;
use Gtk3::SourceView;
# Declare global variables
our $textbuffer; # SourceView buffer for the editor
our $textview; # SourceView widget
our $statusbar; # Status bar for feedback
our $open; # Current file path
our $textbuffer; # SourceView buffer for the editor
our $textview; # SourceView widget for editor
our $statusbar; # Status bar for feedback
our $open; # Current file path
our $track = 'init'; # Tracks modifications
# Base directory setup (reused from your code)
@ -168,6 +168,19 @@ my $which_line_item = Gtk3::MenuItem->new_with_label('Which Line?');
$which_line_item->signal_connect(activate => \&which_line_dialog);
$tools_menu->append($which_line_item);
my $run_pshell = Gtk3::MenuItem->new_with_label('Run pshell');
$run_pshell->signal_connect(activate => sub {
my $pshell_path = "$basedir/pshell";
my $terminal = find_terminal();
if (-x $pshell_path) {
system("$terminal -e $pshell_path &");
$statusbar->push(0, "Launched pshell in $terminal");
} else {
$statusbar->push(0, "Error: pshell not found at $pshell_path");
}
});
$tools_menu->append($run_pshell);
# HTML menu
my $html_menu = Gtk3::Menu->new;
my $html_item = Gtk3::MenuItem->new_with_label('HTML');
@ -212,7 +225,7 @@ $about_item->signal_connect(activate => sub {
});
$help_menu->append($about_item);
# Text view with SourceView
# Editor (SourceView)
my $scrolled = Gtk3::ScrolledWindow->new(undef, undef);
$vbox->pack_start($scrolled, TRUE, TRUE, 0);
$textview = Gtk3::SourceView::View->new;
@ -280,17 +293,33 @@ while (<*.kpd>) {
}
my $nop = grep { $_ ne 'auto' } @n;
# Find available terminal
sub find_terminal {
my @terminals = qw(gnome-terminal konsole xterm);
for my $term (@terminals) {
return $term if `which $term 2>/dev/null` =~ /\S/;
}
return 'xterm'; # Fallback
}
# File handling
sub open_file {
my $dialog = Gtk3::FileChooserDialog->new(
'Open File', $window, 'open',
'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok'
);
$dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*.pl');
$dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*');
if ($dialog->run eq 'ok') {
my $pl_filter = Gtk3::FileFilter->new;
$pl_filter->set_name('Perl Scripts');
$pl_filter->add_pattern('*.pl');
$dialog->add_filter($pl_filter);
my $all_filter = Gtk3::FileFilter->new;
$all_filter->set_name('All Files');
$all_filter->add_pattern('*');
$dialog->add_filter($all_filter);
my $response = $dialog->run;
if ($response && $response eq 'ok') {
my $filename = $dialog->get_filename;
if (open my $fh, '<', $filename) {
if ($filename && open my $fh, '<', $filename) {
local $/;
$textbuffer->set_text(<$fh>);
close $fh;
@ -324,19 +353,28 @@ sub save_as_file {
'Save File', $window, 'save',
'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok'
);
$dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*.pl');
$dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*');
if ($dialog->run eq 'ok') {
my $pl_filter = Gtk3::FileFilter->new;
$pl_filter->set_name('Perl Scripts');
$pl_filter->add_pattern('*.pl');
$dialog->add_filter($pl_filter);
my $all_filter = Gtk3::FileFilter->new;
$all_filter->set_name('All Files');
$all_filter->add_pattern('*');
$dialog->add_filter($all_filter);
my $response = $dialog->run;
if ($response && $response eq 'ok') {
my $filename = $dialog->get_filename;
my ($start, $end) = $textbuffer->get_bounds;
my $text = $textbuffer->get_text($start, $end, TRUE);
if (open my $fh, '>', $filename) {
print $fh $text;
close $fh;
$statusbar->push(0, "Saved $filename");
$open = $filename;
} else {
$statusbar->push(0, "Failed to save $filename");
if ($filename) {
my ($start, $end) = $textbuffer->get_bounds;
my $text = $textbuffer->get_text($start, $end, TRUE);
if (open my $fh, '>', $filename) {
print $fh $text;
close $fh;
$statusbar->push(0, "Saved $filename");
$open = $filename;
} else {
$statusbar->push(0, "Failed to save $filename");
}
}
}
$dialog->destroy;

531
moo.txt Normal file
View file

@ -0,0 +1,531 @@
#!/usr/bin/perl
use strict;
use warnings;
use Gtk3 '-init';
use Glib qw(TRUE FALSE);
use File::Glob;
use FileHandle;
use LWP::Simple;
use Gtk3::SourceView;
# Declare global variables
our $textbuffer; # SourceView buffer for the editor
our $textview; # SourceView widget for editor
our $statusbar; # Status bar for feedback
our $open; # Current file path
our $track = 'init'; # Tracks modifications
# Base directory setup (reused from your code)
my ($ar) = @ARGV;
my $basedir;
if ($^O ne "Win32") {
if ($0 =~ /\//g) {
my @hd = split "/", $0;
pop @hd; # Remove filename
$basedir = join('/', @hd) || ".";
} else {
$basedir = ".";
}
} else {
$basedir = ".";
}
# Main window
my $window = Gtk3::Window->new('toplevel');
$window->set_title('KPad');
$window->set_default_size(800, 600);
$window->signal_connect(destroy => sub { Gtk3->main_quit });
# Main layout
my $vbox = Gtk3::Box->new('vertical', 0);
$window->add($vbox);
# Menu bar
my $menubar = Gtk3::MenuBar->new;
$vbox->pack_start($menubar, FALSE, FALSE, 0);
# File menu
my $file_menu = Gtk3::Menu->new;
my $file_item = Gtk3::MenuItem->new_with_label('File');
$file_item->set_submenu($file_menu);
$menubar->append($file_item);
my $new_item = Gtk3::MenuItem->new_with_label('New');
$new_item->signal_connect(activate => sub {
$textbuffer->set_text('');
$statusbar->push(0, 'New file');
});
$file_menu->append($new_item);
my $open_item = Gtk3::MenuItem->new_with_label('Open');
$open_item->signal_connect(activate => \&open_file);
$file_menu->append($open_item);
my $save_item = Gtk3::MenuItem->new_with_label('Save');
$save_item->signal_connect(activate => \&save_file);
$file_menu->append($save_item);
my $save_as_item = Gtk3::MenuItem->new_with_label('Save As');
$save_as_item->signal_connect(activate => \&save_as_file);
$file_menu->append($save_as_item);
my $exit_item = Gtk3::MenuItem->new_with_label('Exit');
$exit_item->signal_connect(activate => \&tapp);
$file_menu->append($exit_item);
# Edit menu
my $edit_menu = Gtk3::Menu->new;
my $edit_item = Gtk3::MenuItem->new_with_label('Edit');
$edit_item->set_submenu($edit_menu);
$menubar->append($edit_item);
my $undo_item = Gtk3::MenuItem->new_with_label('Undo');
$undo_item->signal_connect(activate => sub {
$textbuffer->undo if $textbuffer->can_undo;
$statusbar->push(0, 'Undo');
});
$edit_menu->append($undo_item);
my $redo_item = Gtk3::MenuItem->new_with_label('Redo');
$redo_item->signal_connect(activate => sub {
$textbuffer->redo if $textbuffer->can_redo;
$statusbar->push(0, 'Redo');
});
$edit_menu->append($redo_item);
my $cut_item = Gtk3::MenuItem->new_with_label('Cut');
$cut_item->signal_connect(activate => sub {
$textbuffer->cut_clipboard(Gtk3::Clipboard::get('CLIPBOARD'), TRUE);
});
$edit_menu->append($cut_item);
my $copy_item = Gtk3::MenuItem->new_with_label('Copy');
$copy_item->signal_connect(activate => sub {
$textbuffer->copy_clipboard(Gtk3::Clipboard::get('CLIPBOARD'));
});
$edit_menu->append($copy_item);
my $paste_item = Gtk3::MenuItem->new_with_label('Paste');
$paste_item->signal_connect(activate => sub {
$textbuffer->paste_clipboard(Gtk3::Clipboard::get('CLIPBOARD'), undef, TRUE);
});
$edit_menu->append($paste_item);
my $select_all_item = Gtk3::MenuItem->new_with_label('Select All');
$select_all_item->signal_connect(activate => sub {
$textbuffer->select_range($textbuffer->get_start_iter, $textbuffer->get_end_iter);
});
$edit_menu->append($select_all_item);
my $find_item = Gtk3::MenuItem->new_with_label('Find');
$find_item->signal_connect(activate => sub { find_and_replace_dialog(1); });
$edit_menu->append($find_item);
my $replace_item = Gtk3::MenuItem->new_with_label('Find and Replace');
$replace_item->signal_connect(activate => sub { find_and_replace_dialog(0); });
$edit_menu->append($replace_item);
# View menu (wrap options)
my $view_menu = Gtk3::Menu->new;
my $view_item = Gtk3::MenuItem->new_with_label('View');
$view_item->set_submenu($view_menu);
$menubar->append($view_item);
my $wrap_menu = Gtk3::Menu->new;
my $wrap_item = Gtk3::MenuItem->new_with_label('Wrap');
$wrap_item->set_submenu($wrap_menu);
$view_menu->append($wrap_item);
my $wrap_word = Gtk3::CheckMenuItem->new_with_label('Word');
$wrap_word->signal_connect(toggled => sub {
$textview->set_wrap_mode($wrap_word->get_active ? 'word' : 'none');
});
$wrap_menu->append($wrap_word);
my $wrap_char = Gtk3::CheckMenuItem->new_with_label('Char');
$wrap_char->signal_connect(toggled => sub {
$textview->set_wrap_mode($wrap_char->get_active ? 'char' : 'none');
});
$wrap_menu->append($wrap_char);
my $wrap_none = Gtk3::CheckMenuItem->new_with_label('None');
$wrap_none->signal_connect(toggled => sub {
$textview->set_wrap_mode($wrap_none->get_active ? 'none' : 'none');
});
$wrap_menu->append($wrap_none);
# Tools menu
my $tools_menu = Gtk3::Menu->new;
my $tools_item = Gtk3::MenuItem->new_with_label('Tools');
$tools_item->set_submenu($tools_menu);
$menubar->append($tools_item);
my $goto_line_item = Gtk3::MenuItem->new_with_label('Goto Line');
$goto_line_item->signal_connect(activate => \&goto_line_dialog);
$tools_menu->append($goto_line_item);
my $which_line_item = Gtk3::MenuItem->new_with_label('Which Line?');
$which_line_item->signal_connect(activate => \&which_line_dialog);
$tools_menu->append($which_line_item);
# HTML menu
my $html_menu = Gtk3::Menu->new;
my $html_item = Gtk3::MenuItem->new_with_label('HTML');
$html_item->set_submenu($html_menu);
$menubar->append($html_item);
my $fetch_item = Gtk3::MenuItem->new_with_label('Fetch a web resource...');
$fetch_item->signal_connect(activate => \&fetch_html);
$html_menu->append($fetch_item);
# Macros menu (plugins)
my $macros_menu = Gtk3::Menu->new;
my $macros_item = Gtk3::MenuItem->new_with_label('Macros');
$macros_item->set_submenu($macros_menu);
$menubar->append($macros_item);
my $exec_macro_item = Gtk3::MenuItem->new_with_label('Execute Macro');
$exec_macro_item->signal_connect(activate => \&show_plugin_dialog);
$macros_menu->append($exec_macro_item);
# Help menu
my $help_menu = Gtk3::Menu->new;
my $help_item = Gtk3::MenuItem->new_with_label('Help');
$help_item->set_submenu($help_menu);
$menubar->append($help_item);
my $help_topics_item = Gtk3::MenuItem->new_with_label('Help Topics...');
$help_topics_item->signal_connect(activate => sub {
my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'info', 'ok',
"Help topics for KPAD\nWell, this is a text/file editor mainly meant for scripting and programming use.\nLike notepad but made for the programmer.");
$dialog->run;
$dialog->destroy;
});
$help_menu->append($help_topics_item);
my $about_item = Gtk3::MenuItem->new_with_label('About KPAD...');
$about_item->signal_connect(activate => sub {
my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'info', 'ok',
"kPad\nby Paul Malcher\nVersion 6 Release\n");
$dialog->run;
$dialog->destroy;
});
$help_menu->append($about_item);
# Editor (SourceView)
my $scrolled = Gtk3::ScrolledWindow->new(undef, undef);
$vbox->pack_start($scrolled, TRUE, TRUE, 0);
$textview = Gtk3::SourceView::View->new;
$textview->set_wrap_mode('word');
$textview->set_show_line_numbers(TRUE);
$textview->set_auto_indent(TRUE);
$scrolled->add($textview);
$textbuffer = $textview->get_buffer;
my $lang_manager = Gtk3::SourceView::LanguageManager->get_default;
my $language = $lang_manager->get_language('perl');
$textbuffer->set_language($language) if $language;
$textbuffer->set_highlight_syntax(TRUE);
# CSS styling for Kubuntu
my $provider = Gtk3::CssProvider->new;
$provider->load_from_data('
textview { font-family: Monospace; font-size: 12pt; }
button { color: black; background: #d3d3d3; }
');
Gtk3::StyleContext::add_provider_for_screen(
Gtk3::Gdk::Screen::get_default,
$provider,
Gtk3::STYLE_PROVIDER_PRIORITY_APPLICATION
);
# Status bar
$statusbar = Gtk3::Statusbar->new;
$vbox->pack_start($statusbar, FALSE, FALSE, 0);
$textbuffer->signal_connect('mark-set' => sub {
my ($buf, $iter, $mark) = @_;
if ($mark->get_name eq 'insert') {
my $line = $iter->get_line + 1;
$statusbar->push(0, "Line: $line");
}
});
# Track modifications
$textbuffer->signal_connect('modified-changed' => sub {
$track = $textbuffer->get_text($textbuffer->get_start_iter, $textbuffer->get_end_iter, TRUE);
});
# Load file from ARGV
if ($ar) {
if (open my $fh, '<', $ar) {
local $/;
$textbuffer->set_text(<$fh>);
close $fh;
$statusbar->push(0, "Opened $ar");
$open = $ar;
} else {
$statusbar->push(0, "Failed to open $ar");
}
}
# Plugin system
my @plugins;
my @n;
my $pls = 0;
while (<*.kpd>) {
push @plugins, $_;
open my $pin, '<', $_ or next;
my @gn = split '::', <$pin>;
$n[$pls] = $gn[2] eq 'auto' ? 'auto' : $gn[1];
$pls++;
}
my $nop = grep { $_ ne 'auto' } @n;
# File handling
sub open_file {
my $dialog = Gtk3::FileChooserDialog->new(
'Open File', $window, 'open',
'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok'
);
my $pl_filter = Gtk3::FileFilter->new;
$pl_filter->set_name('Perl Scripts');
$pl_filter->add_pattern('*.pl');
$dialog->add_filter($pl_filter);
my $all_filter = Gtk3::FileFilter->new;
$all_filter->set_name('All Files');
$all_filter->add_pattern('*');
$dialog->add_filter($all_filter);
my $response = $dialog->run;
if ($response && $response eq 'ok') {
my $filename = $dialog->get_filename;
if ($filename && open my $fh, '<', $filename) {
local $/;
$textbuffer->set_text(<$fh>);
close $fh;
$statusbar->push(0, "Opened $filename");
$open = $filename;
} else {
$statusbar->push(0, "Failed to open $filename");
}
}
$dialog->destroy;
}
sub save_file {
if ($open) {
my ($start, $end) = $textbuffer->get_bounds;
my $text = $textbuffer->get_text($start, $end, TRUE);
if (open my $fh, '>', $open) {
print $fh $text;
close $fh;
$statusbar->push(0, "Saved $open");
} else {
$statusbar->push(0, "Failed to save $open");
}
} else {
save_as_file();
}
}
sub save_as_file {
my $dialog = Gtk3::FileChooserDialog->new(
'Save File', $window, 'save',
'gtk-cancel' => 'cancel', 'gtk-ok' => 'ok'
);
my $pl_filter = Gtk3::FileFilter->new;
$pl_filter->set_name('Perl Scripts');
$pl_filter->add_pattern('*.pl');
$dialog->add_filter($pl_filter);
my $all_filter = Gtk3::FileFilter->new;
$all_filter->set_name('All Files');
$all_filter->add_pattern('*');
$dialog->add_filter($all_filter);
my $response = $dialog->run;
if ($response && $response eq 'ok') {
my $filename = $dialog->get_filename;
if ($filename) {
my ($start, $end) = $textbuffer->get_bounds;
my $text = $textbuffer->get_text($start, $end, TRUE);
if (open my $fh, '>', $filename) {
print $fh $text;
close $fh;
$statusbar->push(0, "Saved $filename");
$open = $filename;
} else {
$statusbar->push(0, "Failed to save $filename");
}
}
}
$dialog->destroy;
}
# Find and replace dialog
sub find_and_replace_dialog {
my ($find_only) = @_;
my $dialog = Gtk3::Dialog->new($find_only ? 'Find' : 'Find and Replace', $window, 'modal',
'gtk-ok', 'ok', 'gtk-cancel', 'cancel');
my $find_entry = Gtk3::Entry->new;
my $replace_entry = Gtk3::Entry->new;
my $find_label = Gtk3::Label->new('Find:');
$dialog->get_content_area->pack_start($find_label, FALSE, FALSE, 0);
$dialog->get_content_area->pack_start($find_entry, FALSE, FALSE, 0);
unless ($find_only) {
my $replace_label = Gtk3::Label->new('Replace with:');
$dialog->get_content_area->pack_start($replace_label, FALSE, FALSE, 0);
$dialog->get_content_area->pack_start($replace_entry, FALSE, FALSE, 0);
}
$dialog->show_all;
if ($dialog->run eq 'ok') {
my $search = $find_entry->get_text;
my $replace = $replace_entry->get_text;
my $search_context = Gtk3::SourceView::SearchContext->new($textbuffer);
$search_context->set_search_text($search, []);
my $iter = $textbuffer->get_start_iter;
if (my ($match_start, $match_end) = $search_context->forward($iter)) {
$textbuffer->select_range($match_start, $match_end);
unless ($find_only) {
$search_context->replace($match_start, $match_end, $replace, -1);
$statusbar->push(0, "Replaced '$search' with '$replace'");
} else {
$statusbar->push(0, "Found '$search'");
}
} else {
$statusbar->push(0, "Text not found");
}
}
$dialog->destroy;
}
# Goto line dialog
sub goto_line_dialog {
my $dialog = Gtk3::Dialog->new('Goto Line', $window, 'modal', 'gtk-ok', 'ok', 'gtk-cancel', 'cancel');
my $entry = Gtk3::Entry->new;
$dialog->get_content_area->pack_start($entry, FALSE, FALSE, 0);
$dialog->show_all;
if ($dialog->run eq 'ok') {
my $line = $entry->get_text;
if ($line =~ /^\d+$/) {
my $iter = $textbuffer->get_iter_at_line($line - 1);
$textbuffer->place_cursor($iter);
$textview->scroll_to_iter($iter, 0, TRUE, 0, 0.5);
}
}
$dialog->destroy;
}
# Which line dialog
sub which_line_dialog {
my $iter = $textbuffer->get_iter_at_mark($textbuffer->get_insert);
my $line = $iter->get_line + 1;
my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'info', 'ok', "Current line: $line");
$dialog->run;
$dialog->destroy;
}
# HTML fetch
sub fetch_html {
my $dialog = Gtk3::Dialog->new('HTML Source Fetch', $window, 'modal', 'gtk-ok', 'ok', 'gtk-cancel', 'cancel');
my $label = Gtk3::Label->new('Fetch what:');
my $entry = Gtk3::Entry->new;
$entry->set_text('http://');
$dialog->get_content_area->pack_start($label, FALSE, FALSE, 0);
$dialog->get_content_area->pack_start($entry, FALSE, FALSE, 0);
$dialog->show_all;
if ($dialog->run eq 'ok') {
my $url = $entry->get_text;
my $contents = get($url);
if ($contents) {
$textbuffer->set_text($contents);
$statusbar->push(0, "Fetched $url");
} else {
$statusbar->push(0, "Failed to fetch $url");
}
}
$dialog->destroy;
}
# Plugin dialog
sub show_plugin_dialog {
my $dialog = Gtk3::Dialog->new('Macro Execution Menu', $window, 'modal', 'gtk-close', 'close');
my $label = Gtk3::Label->new('Double Click To Execute Macro');
my $listbox = Gtk3::ListBox->new;
$dialog->get_content_area->pack_start($label, FALSE, FALSE, 0);
$dialog->get_content_area->pack_start($listbox, TRUE, TRUE, 0);
foreach my $name (@n) {
next if $name eq 'auto';
my $row = Gtk3::ListBoxRow->new;
my $lbl = Gtk3::Label->new($name);
$row->add($lbl);
$listbox->add($row);
}
$listbox->signal_connect(row_activated => sub {
my ($lb, $row) = @_;
my $name = $row->get_child->get_text;
eplugin($name);
});
$dialog->show_all;
$dialog->run;
$dialog->destroy;
}
# Plugin execution
sub eplugin {
my ($v) = @_;
my $fp = 0;
while ($n[$fp] ne $v) { $fp++; }
$v = $plugins[$fp];
open my $pe, '<', "$basedir/$v" or return;
my $tdata = do { local $/; <$pe> };
close $pe;
$tdata =~ s/.*?\n//; # Skip first line
eval $tdata;
if ($@) {
my $error = $@;
my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'error', 'ok', "Error: $error");
$dialog->run;
$dialog->destroy;
}
}
# Auto plugin execution
sub aeplugin {
for my $i (0..$#n) {
next unless $n[$i] eq 'auto';
my $v = $plugins[$i];
open my $pe, '<', "$basedir/$v" or next;
my $tdata = do { local $/; <$pe> };
close $pe;
$tdata =~ s/.*?\n//;
eval $tdata;
if ($@) {
my $error = $@;
my $dialog = Gtk3::MessageDialog->new($window, 'modal', 'error', 'ok', "Error: $error");
$dialog->run;
$dialog->destroy;
}
}
}
# Shutdown handler
sub tapp {
if ($track ne 'init') {
my $dialog = Gtk3::MessageDialog->new(
$window, 'modal', 'question', 'yes-no', 'File contents have changed, save now?!'
);
my $response = $dialog->run;
$dialog->destroy;
if ($response eq 'yes') {
save_file();
}
}
Gtk3->main_quit;
}
# Run auto plugins
my $arun = 0;
if ($arun eq '0') {
aeplugin();
}
# Show and start
$window->show_all;
Gtk3->main;

View file

@ -1,3 +1,3 @@
::Normal:: # You got the line is parsed by splitting by the ::
$text->insert('end', "Hi"); # $text, if you look at KAKE PADs code it the text widget
::MyPlugin::auto
print "Running MyPlugin!\n";
$textbuffer->insert_at_cursor("Plugin ran!");

BIN
pshell Executable file

Binary file not shown.

13
pshell.kpd Normal file
View file

@ -0,0 +1,13 @@
::RunPshellCmd::
use strict;
my $dialog = Gtk3::Dialog->new('Run pshell Command', $::window, 'modal', 'gtk-ok', 'ok', 'gtk-cancel', 'cancel');
my $entry = Gtk3::Entry->new;
$dialog->get_content_area->pack_start($entry, FALSE, FALSE, 0);
$dialog->show_all;
if ($dialog->run eq 'ok') {
my $cmd = $entry->get_text;
my $output = `$::basedir/pshell -e "$cmd" 2>&1` || "Error: pshell failed";
$::textbuffer->insert_at_cursor("pshell output:\n$output\n");
$::statusbar->push(0, "Ran pshell command");
}
$dialog->destroy;

BIN
pshell_env.db Normal file

Binary file not shown.

BIN
pshell_history.db Normal file

Binary file not shown.

View file

@ -1 +0,0 @@
moo

View file

@ -1,3 +0,0 @@
moo beer
moo beer
moo beer beef