From 868aabae0adb2efa1e4faa29c2e6fab7ba0b548a Mon Sep 17 00:00:00 2001 From: kake26 Date: Wed, 30 Apr 2025 11:42:00 -0500 Subject: [PATCH] Convert KPAD from TK to GTK3 and add new features --- .windsurfrules | 6 + LICENSE | 0 README.md | 0 The Offical KAKE PAD Macro Tutorial.doc | Bin kpad.old.pl | 375 ++++++++ kpad.pl | 1073 +++++++++++++++-------- kpadgtk3.pl | 515 +++++++++++ normal.kpd | 0 test.txt | 1 + test2.txt | 3 + 10 files changed, 1598 insertions(+), 375 deletions(-) create mode 100755 .windsurfrules mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 The Offical KAKE PAD Macro Tutorial.doc create mode 100755 kpad.old.pl mode change 100644 => 100755 kpad.pl create mode 100644 kpadgtk3.pl mode change 100644 => 100755 normal.kpd create mode 100755 test.txt create mode 100755 test2.txt diff --git a/.windsurfrules b/.windsurfrules new file mode 100755 index 0000000..99352f1 --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,6 @@ +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 \ No newline at end of file diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/The Offical KAKE PAD Macro Tutorial.doc b/The Offical KAKE PAD Macro Tutorial.doc old mode 100644 new mode 100755 diff --git a/kpad.old.pl b/kpad.old.pl new file mode 100755 index 0000000..16445d6 --- /dev/null +++ b/kpad.old.pl @@ -0,0 +1,375 @@ +# 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 "::" , ; +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('' , \&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, "delete('1.0','end'); +while (! eof FILE){ +$text->insert('end',FILE -> getline); +} +close FILE; +unlink(); +$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('<>' => 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 \ No newline at end of file diff --git a/kpad.pl b/kpad.pl old mode 100644 new mode 100755 index 16445d6..0520769 --- a/kpad.pl +++ b/kpad.pl @@ -1,375 +1,698 @@ -# 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 "::" , ; -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('' , \&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, "delete('1.0','end'); -while (! eof FILE){ -$text->insert('end',FILE -> getline); -} -close FILE; -unlink(); -$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('<>' => 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 \ No newline at end of file +#!/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(); diff --git a/kpadgtk3.pl b/kpadgtk3.pl new file mode 100644 index 0000000..cc20793 --- /dev/null +++ b/kpadgtk3.pl @@ -0,0 +1,515 @@ +#!/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 +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); + +# Text view with 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' + ); + $dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*.pl'); + $dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*'); + if ($dialog->run eq 'ok') { + my $filename = $dialog->get_filename; + if (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' + ); + $dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*.pl'); + $dialog->add_filter(Gtk3::FileFilter->new)->add_pattern('*'); + if ($dialog->run 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"); + } + } + $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; \ No newline at end of file diff --git a/normal.kpd b/normal.kpd old mode 100644 new mode 100755 diff --git a/test.txt b/test.txt new file mode 100755 index 0000000..a7fbfc0 --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +moo \ No newline at end of file diff --git a/test2.txt b/test2.txt new file mode 100755 index 0000000..5e0cedc --- /dev/null +++ b/test2.txt @@ -0,0 +1,3 @@ +moo beer +moo beer +moo beer beef \ No newline at end of file