diff --git a/.windsurfrules b/.windsurfrules index 99352f1..f9578bf 100755 --- a/.windsurfrules +++ b/.windsurfrules @@ -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 \ No newline at end of file +2. It uses the GTK3 module to provide GUI +3. If this needs to be tested use /usr/bin/perl \ No newline at end of file diff --git a/README.md b/README.md index 31bf7b8..3069155 100755 --- a/README.md +++ b/README.md @@ -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. diff --git a/kpad b/kpad new file mode 100755 index 0000000..ffcb431 Binary files /dev/null and b/kpad differ diff --git a/kpad.old.pl b/kpad.old.pl deleted file mode 100755 index 16445d6..0000000 --- a/kpad.old.pl +++ /dev/null @@ -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 "::" , ; -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 deleted file mode 100755 index 0520769..0000000 --- a/kpad.pl +++ /dev/null @@ -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(); diff --git a/kpadgtk3 b/kpadgtk3 new file mode 100755 index 0000000..092893b Binary files /dev/null and b/kpadgtk3 differ diff --git a/kpadgtk3.pl b/kpadgtk3.pl index cc20793..58d952b 100644 --- a/kpadgtk3.pl +++ b/kpadgtk3.pl @@ -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; diff --git a/moo.txt b/moo.txt new file mode 100644 index 0000000..9f4537c --- /dev/null +++ b/moo.txt @@ -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; \ No newline at end of file diff --git a/normal.kpd b/normal.kpd index 840a797..1250a5d 100755 --- a/normal.kpd +++ b/normal.kpd @@ -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!"); \ No newline at end of file diff --git a/pshell b/pshell new file mode 100755 index 0000000..916a622 Binary files /dev/null and b/pshell differ diff --git a/pshell.kpd b/pshell.kpd new file mode 100644 index 0000000..c135388 --- /dev/null +++ b/pshell.kpd @@ -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; \ No newline at end of file diff --git a/pshell_env.db b/pshell_env.db new file mode 100644 index 0000000..9d8e80f Binary files /dev/null and b/pshell_env.db differ diff --git a/pshell_history.db b/pshell_history.db new file mode 100644 index 0000000..f712c8d Binary files /dev/null and b/pshell_history.db differ diff --git a/test.txt b/test.txt deleted file mode 100755 index a7fbfc0..0000000 --- a/test.txt +++ /dev/null @@ -1 +0,0 @@ -moo \ No newline at end of file diff --git a/test2.txt b/test2.txt deleted file mode 100755 index 5e0cedc..0000000 --- a/test2.txt +++ /dev/null @@ -1,3 +0,0 @@ -moo beer -moo beer -moo beer beef \ No newline at end of file