Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
1fb189ad48 |
15 changed files with 1088 additions and 1105 deletions
|
@ -1,3 +1,6 @@
|
|||
1. This application is written in Perl
|
||||
2. It uses the GTK3 module to provide GUI
|
||||
3. If this needs to be tested use /usr/bin/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
|
|
@ -1,8 +1,8 @@
|
|||
# kpadgtk3
|
||||
# kpadold
|
||||
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
|
||||
# Notes
|
||||
|
||||
4/30/25 - Ported to GTK3 with some improved syntax highlighting. Curtesy of Grok for the main translation.
|
||||
Ported to GTK3 see the GTK3 branch
|
BIN
kpad
BIN
kpad
Binary file not shown.
375
kpad.old.pl
Executable file
375
kpad.old.pl
Executable file
|
@ -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 "::" , <pin>;
|
||||
if(@gn[2] eq "auto"){
|
||||
@n[$pls] = "auto";
|
||||
}else{
|
||||
@n[$pls] = @gn[1];
|
||||
}
|
||||
$pls++;
|
||||
}
|
||||
# determin the number of plugins, so we can size the list accordingly
|
||||
$nop = scalar(@n); #notice @n does not get shortend, this is important later on
|
||||
foreach(@n) {
|
||||
if($_ eq "auto"){
|
||||
$nop--; # make sure auto plugins are not listed
|
||||
}
|
||||
}
|
||||
|
||||
# Build the menu with list box
|
||||
$plugin = $main->DialogBox(-title=>'Macro Execution Menu',-buttons=>["Close"]);
|
||||
$bplugin = $plugin->add("Label",-text=>'Double Click To Execute Macro')->pack;
|
||||
$aplugin = $plugin->Listbox("-width"=>40, "-height"=> $nop)->pack;
|
||||
foreach(@n) {
|
||||
if($_ eq "auto"){
|
||||
$arun = 0;
|
||||
}else{
|
||||
$aplugin->insert('end', "$_");
|
||||
}
|
||||
}
|
||||
$aplugin->bind('<Double-1>' , \&eplugin); # Plugin name now can be different from the file name
|
||||
|
||||
$filemenu = $menubar->Menubutton(-text => 'File', -underline => 0,-tearoff => 0)->pack(-side=>'left'); #This puts
|
||||
#the file button on the frame used for the menu bar
|
||||
|
||||
#Below are the commands for that button
|
||||
#note How I included the subs into the command function
|
||||
|
||||
$filemenu->command(-label => 'New',-command => sub{
|
||||
$text->delete('1.0','end');
|
||||
});
|
||||
|
||||
$filemenu->command(-label => 'Open',-command => sub{
|
||||
$text->delete('1.0','end');
|
||||
my $types = [
|
||||
['Perl Scripts', '.pl'],
|
||||
['All Files', '*', ],
|
||||
];
|
||||
|
||||
$open = $main->getOpenFile(-filetypes=>$types);
|
||||
#open FILE, "<$open"; #took weeks to get this right,its there so te whole file loads correctly
|
||||
# and only 3 sec to comment out for the 5.0 release
|
||||
$text->Load($open);
|
||||
$text ->pack;
|
||||
});
|
||||
|
||||
$filemenu->command(-label => 'Save',-command => sub{
|
||||
$data = $text->get('1.0','end'); #Saving for widget to file is a piece of cake
|
||||
if($ar eq ""){
|
||||
$text->Save($open);
|
||||
# Easy indeed
|
||||
}else{
|
||||
$text->Save($ar);
|
||||
}
|
||||
});
|
||||
|
||||
$filemenu->command(-label => 'Save As',-command => sub{
|
||||
#my $types = [['All Files', '*', ],];
|
||||
my $types = [
|
||||
['Perl Scripts', '.pl' ],
|
||||
['All Files', '.*', ],
|
||||
];
|
||||
my $save = $main->getSaveFile(-filetypes=>$types);
|
||||
$text->Save($save);
|
||||
$open = $save;
|
||||
});
|
||||
|
||||
$filemenu->separator;
|
||||
|
||||
$filemenu->command(-label => 'Exit',-command => sub{
|
||||
tapp();
|
||||
});
|
||||
|
||||
$editmenu = $menubar->Menubutton(-text => 'Edit', -underline => 0,-tearoff => 0)->pack(-side=>'left');
|
||||
|
||||
$editmenu->command(-label => 'Undo',-command => sub{
|
||||
my ($w) = @_;
|
||||
$text->undo;
|
||||
});
|
||||
|
||||
$editmenu->command(-label => 'Redo',-command => sub{
|
||||
my ($w) = @_;
|
||||
$text->redo;
|
||||
});
|
||||
|
||||
$editmenu->separator;
|
||||
|
||||
$editmenu->command(-label => 'Cut',-command => sub{
|
||||
my ($w) = @_;
|
||||
$text->Column_Copy_or_Cut(1);
|
||||
});
|
||||
|
||||
$editmenu->command(-label => 'Copy',-command => sub{
|
||||
my ($w) = @_;
|
||||
$text->Column_Copy_or_Cut(0);
|
||||
});
|
||||
|
||||
$editmenu->command(-label => 'Paste',-command => sub{
|
||||
$text->clipboardColumnPaste();
|
||||
});
|
||||
|
||||
$editmenu->separator;
|
||||
|
||||
$editmenu->command(-label => 'Select All',-command => sub{
|
||||
$text->selectAll();
|
||||
});
|
||||
|
||||
$editmenu->command(-label => 'Unselect All',-command => sub{
|
||||
$text->unselectAll();
|
||||
});
|
||||
|
||||
$editmenu->separator;
|
||||
|
||||
$editmenu->command(-label => 'Find',-command => sub{
|
||||
$text->findandreplacepopup(1);
|
||||
});
|
||||
|
||||
$editmenu->command(-label => 'Find and Replace',-command => sub{
|
||||
$text->findandreplacepopup(0);
|
||||
});
|
||||
|
||||
$viewmenu = $menubar->Menubutton(-text=>'View',-underline => 0,-tearoff => 0)->pack(-side=>'left');
|
||||
$vm = $viewmenu->cascade(-label => 'Wrap',-underline => 0,-tearoff => 0);
|
||||
$vm->radiobutton(-label => "Word", -command => sub { $text->configure(-wrap => 'word'); } );
|
||||
$vm->radiobutton(-label => "Char",-command => sub { $text->configure(-wrap => 'char'); } );
|
||||
$vm->radiobutton(-label => "None",-command => sub { $text->configure(-wrap => 'none'); } );
|
||||
|
||||
$toolsmenu = $menubar->Menubutton(-text => 'Tools', -underline => 0,-tearoff => 0)->pack(-side=>'left');
|
||||
|
||||
$toolsmenu->command(-label => 'Goto Line',-command => sub{
|
||||
$text->GotoLineNumberPopUp();
|
||||
});
|
||||
|
||||
$toolsmenu->command(-label => 'Which Line?',-command => sub{
|
||||
$text->WhatLineNumberPopUp();
|
||||
});
|
||||
|
||||
$htmlmenu = $menubar->Menubutton(-text => 'HTML', -underline => 0,-tearoff => 0)->pack(-side=>'left');
|
||||
|
||||
$htmlmenu->command(-label => 'Fetch a web resource...',-command => sub{#$fdisc->Show;
|
||||
$fetch->Show;
|
||||
$htm = $bfetch->get;
|
||||
$contents = get($htm);
|
||||
open ttt, ">temp.dat";
|
||||
print ttt "$contents";
|
||||
close ttt;
|
||||
open FILE, "<temp.dat"; #took weeks to get this right,its there so te whole file loads correctly
|
||||
$text->delete('1.0','end');
|
||||
while (! eof FILE){
|
||||
$text->insert('end',FILE -> getline);
|
||||
}
|
||||
close FILE;
|
||||
unlink(<temp.dat>);
|
||||
$text ->pack;
|
||||
});
|
||||
|
||||
$pluginmenu = $menubar->Menubutton(-text => 'Macros', -underline => 0,-tearoff => 0)->pack(-side=>'left');
|
||||
|
||||
$pluginmenu->command(-label => 'Execute Macro',-command => sub{$plugin->Show;});
|
||||
|
||||
$aboutmenu = $menubar->Menubutton(-text => 'Help', -underline => 0,-tearoff => 0)->pack(-side=>'left');
|
||||
|
||||
$aboutmenu->command(-label => 'Help Topics...',-command => sub{$help->Show;});
|
||||
|
||||
$aboutmenu->command(-label => 'About KPAD...',-command => sub{$about->Show;});
|
||||
|
||||
#$aboutmenu->command(-label => 'About kWebedit...',-command => sub{$kweabt->Show;});
|
||||
|
||||
# Text widget and configs
|
||||
$statbar = $main->Frame()->pack(-side => "bottom", -fill => "x");
|
||||
$statinfo = $statbar->Label(-text =>'Info: ')->pack(-side=>'left');
|
||||
$statln = $statbar->Label(-text =>'Line: 0')->pack(-side=>'left');
|
||||
$text = $main->Scrolled(TextUndo,-scrollbars=>'osoe',-background=>'white', -wrap => 'word')->pack(-fill=>'both',-expand=>1); #Scrolled Text
|
||||
|
||||
|
||||
#widget that adapts to the size of the window
|
||||
$main->protocol('WM_DELETE_WINDOW', \&tapp);
|
||||
# This replaces $track
|
||||
$text->bind('<<Modified>>' => sub{
|
||||
$track = $text->get('1.0','end');
|
||||
my ($w)=$text;
|
||||
my ($line,$col) = split(/\./,$w->index('insert'));
|
||||
#$statln = $statbar->Label(-text =>"Line $line")->pack(-side=>'left');
|
||||
$statln->configure(-text =>"Line $line");
|
||||
});
|
||||
|
||||
if($ar ne ""){
|
||||
$text->Load($ar);
|
||||
$text ->pack;
|
||||
}
|
||||
|
||||
sub eplugin { # Plugin executor, non-auto
|
||||
$v = $aplugin->get('active');
|
||||
# Fix for plugin vs. filename fix
|
||||
# @plugins @n
|
||||
$fp = 0;
|
||||
while(@n[$fp] ne $v){ # assume the names in @plugin match with @n
|
||||
# which they will unless you screw with the way plugins are handled
|
||||
$fp++;
|
||||
}
|
||||
$v = @plugins[$fp];
|
||||
# Hope it works
|
||||
open pe, "<$basedir/$v"; # presto it does
|
||||
# The same damn bug in Tk again, yes the one that took weeks to work around
|
||||
# I got to do this the hard way
|
||||
$adummy->delete('1.0','end');
|
||||
while (! eof pe){
|
||||
$adummy->insert('end', pe -> getline);
|
||||
}
|
||||
$tdata = $adummy->get('2.0','end'); # this is the only way to load an entire plugin into a var the right way
|
||||
eval ( $tdata );
|
||||
if($@){ # Only way to to make so it can trap multiple errors without the app having a fatal error itself
|
||||
$error = $@;
|
||||
&merr($error);
|
||||
}
|
||||
}
|
||||
|
||||
sub aeplugin { # Auto plugin executor
|
||||
$apc = 0;
|
||||
while(@n[$apc] ne ""){
|
||||
if(@n[$apc] eq "auto"){
|
||||
$v = @plugins[$apc];
|
||||
# Hope it works
|
||||
open pe, "<$basedir/$v"; # presto it does
|
||||
# The same damn bug in Tk again, yes the one that took weeks to work around
|
||||
# I got to do this the hard way
|
||||
$adummy->delete('1.0','end');
|
||||
while (! eof pe){
|
||||
$adummy->insert('end', pe -> getline);
|
||||
}
|
||||
$tdata = $adummy->get('2.0','end'); # this is the only way to load an entire plugin into a var the right way
|
||||
eval ( $tdata );
|
||||
if($@){ # Only way to to make so it can trap multiple errors without the app having a fatal error itself
|
||||
$error = $@;
|
||||
&merr($error);
|
||||
}
|
||||
}
|
||||
$apc++;
|
||||
}
|
||||
$arun = 1;
|
||||
}
|
||||
|
||||
sub merr { # merr, macro/plugins error
|
||||
$merr = $main->DialogBox(-title =>'Macro Error',-buttons=>["OK"]);
|
||||
$amerr = $merr->add("Label", -text=>"Error: $error")->pack;
|
||||
$merr->Show;
|
||||
undef $merr;
|
||||
}
|
||||
|
||||
if($arun eq "0"){
|
||||
&aeplugin();
|
||||
}
|
||||
|
||||
sub tapp { # shutdown handler
|
||||
if($track ne "init"){
|
||||
$result = $ftapp->Show;
|
||||
if($result eq "No"){
|
||||
exit(0);
|
||||
}
|
||||
if($open){
|
||||
$text->Save($open);
|
||||
$saved = 1;
|
||||
}
|
||||
if($save){
|
||||
$text->Save($save);
|
||||
$saved = 1;
|
||||
}
|
||||
if($ar){
|
||||
$text->Save($ar);
|
||||
$saved = 1;
|
||||
}else{
|
||||
if($saved ne "1"){
|
||||
my $types = [
|
||||
['Perl Scripts', '.pl' ],
|
||||
['All Files', '.*', ],
|
||||
];
|
||||
my $save = $main->getSaveFile(-filetypes=>$types);
|
||||
$text->Save($save);
|
||||
}
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
MainLoop; #The main processing loop
|
698
kpad.pl
Executable file
698
kpad.pl
Executable file
|
@ -0,0 +1,698 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
# KAKE PAD version 6.0
|
||||
# GTK3 version converted from TK
|
||||
|
||||
# After hours having a AI go around in circles for the error
|
||||
|
||||
#*** unhandled exception in callback:
|
||||
#*** FATAL: invalid GdkModifierType value 4, expecting a string scalar or an arrayref of strings at kpad.pl line 114.
|
||||
#*** ignoring at /usr/share/perl5/Gtk3.pm line 572.
|
||||
#Use of uninitialized value in string eq at kpad.pl line 102.
|
||||
|
||||
#use strict;
|
||||
#use warnings;
|
||||
# There I fixed it, by commenting the two lines above out
|
||||
|
||||
use Gtk3 -init;
|
||||
use File::Glob;
|
||||
use File::Find;
|
||||
use FileHandle;
|
||||
use LWP::Simple;
|
||||
use Encode qw(decode encode);
|
||||
|
||||
# Global variables for file handling
|
||||
my $current_file;
|
||||
my $text_buffer;
|
||||
my @undo_stack = ();
|
||||
my @redo_stack = ();
|
||||
my $ignore_changes = 0;
|
||||
my $status_bar;
|
||||
my $last_search_pos;
|
||||
my $search_dialog;
|
||||
my $replace_dialog;
|
||||
|
||||
use constant CONTROL_MASK => 'control-mask'; # Update CONTROL_MASK constant to use the correct string value
|
||||
|
||||
# Initialize the main window
|
||||
my $window = Gtk3::Window->new('toplevel');
|
||||
$window->set_title("kPad");
|
||||
$window->set_default_size(800, 600);
|
||||
|
||||
# Create the main vertical box
|
||||
my $vbox = Gtk3::Box->new('vertical', 5);
|
||||
$window->add($vbox);
|
||||
|
||||
# Create menubar
|
||||
my $menubar = Gtk3::MenuBar->new();
|
||||
$vbox->pack_start($menubar, 0, 0, 0);
|
||||
|
||||
# File menu
|
||||
my $file_menu = Gtk3::Menu->new();
|
||||
my $file_item = Gtk3::MenuItem->new_with_label('File');
|
||||
$file_item->set_submenu($file_menu);
|
||||
$menubar->append($file_item);
|
||||
|
||||
# File menu items
|
||||
my $new_item = Gtk3::MenuItem->new_with_label('New');
|
||||
my $open_item = Gtk3::MenuItem->new_with_label('Open');
|
||||
my $save_item = Gtk3::MenuItem->new_with_label('Save');
|
||||
my $save_as_item = Gtk3::MenuItem->new_with_label('Save As');
|
||||
my $separator = Gtk3::SeparatorMenuItem->new();
|
||||
my $exit_item = Gtk3::MenuItem->new_with_label('Exit');
|
||||
|
||||
$file_menu->append($_) for ($new_item, $open_item, $save_item, $save_as_item, $separator, $exit_item);
|
||||
|
||||
# Edit menu
|
||||
my $edit_menu = Gtk3::Menu->new();
|
||||
my $edit_item = Gtk3::MenuItem->new_with_label('Edit');
|
||||
$edit_item->set_submenu($edit_menu);
|
||||
$menubar->append($edit_item);
|
||||
|
||||
# Edit menu items
|
||||
my $undo_item = Gtk3::MenuItem->new_with_label('Undo');
|
||||
my $redo_item = Gtk3::MenuItem->new_with_label('Redo');
|
||||
my $cut_item = Gtk3::MenuItem->new_with_label('Cut');
|
||||
my $copy_item = Gtk3::MenuItem->new_with_label('Copy');
|
||||
my $paste_item = Gtk3::MenuItem->new_with_label('Paste');
|
||||
my $find_item = Gtk3::MenuItem->new_with_label('Find');
|
||||
my $replace_item = Gtk3::MenuItem->new_with_label('Find and Replace');
|
||||
my $select_all_item = Gtk3::MenuItem->new_with_label('Select All');
|
||||
|
||||
$edit_menu->append($_) for (
|
||||
$undo_item, $redo_item,
|
||||
Gtk3::SeparatorMenuItem->new(),
|
||||
$cut_item, $copy_item, $paste_item,
|
||||
Gtk3::SeparatorMenuItem->new(),
|
||||
$find_item, $replace_item,
|
||||
Gtk3::SeparatorMenuItem->new(),
|
||||
$select_all_item
|
||||
);
|
||||
|
||||
# Add Tools menu
|
||||
my $tools_menu = Gtk3::Menu->new();
|
||||
my $tools_item = Gtk3::MenuItem->new_with_label('Tools');
|
||||
$tools_item->set_submenu($tools_menu);
|
||||
|
||||
# Add About menu
|
||||
my $about_item = Gtk3::MenuItem->new_with_label('About');
|
||||
$about_item->signal_connect(activate => sub {
|
||||
show_info_dialog('About kPad', 'kPad version 6.0\nGTK3 version');
|
||||
});
|
||||
|
||||
# Add HTML menu
|
||||
my $html_item = Gtk3::MenuItem->new_with_label('HTML');
|
||||
$html_item->signal_connect(activate => sub {
|
||||
# Placeholder for HTML functionality
|
||||
show_info_dialog('HTML', 'HTML functionality will be added soon.');
|
||||
});
|
||||
|
||||
# Add Macros menu (placeholder)
|
||||
my $macros_item = Gtk3::MenuItem->new_with_label('Macros');
|
||||
$macros_item->signal_connect(activate => sub {
|
||||
# Placeholder for Macros functionality
|
||||
show_info_dialog('Macros', 'Macros functionality will be handled by the user.');
|
||||
});
|
||||
|
||||
# Add new menu items to the menu bar
|
||||
$menubar->append($tools_item);
|
||||
$menubar->append($about_item);
|
||||
$menubar->append($html_item);
|
||||
$menubar->append($macros_item);
|
||||
|
||||
# Create text view with scrolled window
|
||||
my $scrolled_window = Gtk3::ScrolledWindow->new();
|
||||
$scrolled_window->set_policy('automatic', 'automatic');
|
||||
$vbox->pack_start($scrolled_window, 1, 1, 0);
|
||||
|
||||
my $text_view = Gtk3::TextView->new();
|
||||
$text_buffer = $text_view->get_buffer();
|
||||
$text_view->set_wrap_mode('word');
|
||||
$text_view->set_editable(1);
|
||||
$text_view->set_cursor_visible(1);
|
||||
|
||||
# Create status bar
|
||||
$status_bar = Gtk3::Statusbar->new();
|
||||
$vbox->pack_start($status_bar, 0, 0, 0);
|
||||
update_cursor_position();
|
||||
|
||||
# Track cursor movement for status bar updates
|
||||
$text_buffer->signal_connect('mark-set' => sub {
|
||||
my ($buffer, $iter, $mark) = @_;
|
||||
if ($mark->get_name() eq 'insert') {
|
||||
update_cursor_position();
|
||||
}
|
||||
});
|
||||
|
||||
# Enable copy/paste keyboard shortcuts
|
||||
$text_view->signal_connect('key-press-event' => sub {
|
||||
my ($widget, $event) = @_;
|
||||
my $keyval = $event->keyval;
|
||||
my $state = $event->state;
|
||||
|
||||
# Check if Control key is pressed (GDK_CONTROL_MASK)
|
||||
if ($state & CONTROL_MASK) {
|
||||
if (chr($keyval) eq 'z' || chr($keyval) eq 'Z') {
|
||||
undo_action();
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'y' || chr($keyval) eq 'Y') {
|
||||
redo_action();
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'c' || chr($keyval) eq 'C') {
|
||||
$copy_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'x' || chr($keyval) eq 'X') {
|
||||
$cut_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'v' || chr($keyval) eq 'V') {
|
||||
$paste_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'f' || chr($keyval) eq 'F') {
|
||||
$find_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'h' || chr($keyval) eq 'H') {
|
||||
$replace_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
# Track buffer changes for undo/redo
|
||||
$text_buffer->signal_connect('changed' => \&buffer_changed_cb);
|
||||
$text_buffer->signal_connect('begin-user-action' => sub {
|
||||
$ignore_changes = 0;
|
||||
});
|
||||
$text_buffer->signal_connect('modified-changed' => sub {
|
||||
my $modified = $text_buffer->get_modified();
|
||||
$window->set_title("kPad" . ($modified ? " *" : "") . (defined $current_file ? " - $current_file" : ""));
|
||||
});
|
||||
|
||||
$scrolled_window->add($text_view);
|
||||
|
||||
# File menu callbacks
|
||||
$new_item->signal_connect(activate => sub {
|
||||
return unless check_save_changes();
|
||||
|
||||
$text_buffer->set_text('');
|
||||
$text_buffer->set_modified(0);
|
||||
$current_file = undef;
|
||||
$window->set_title("kPad");
|
||||
});
|
||||
|
||||
$open_item->signal_connect(activate => sub {
|
||||
return unless check_save_changes();
|
||||
|
||||
my $dialog = Gtk3::FileChooserDialog->new(
|
||||
'Open File',
|
||||
$window,
|
||||
'open',
|
||||
'Cancel' => 'cancel',
|
||||
'Open' => 'ok'
|
||||
);
|
||||
|
||||
# Add file filters
|
||||
my $filter = Gtk3::FileFilter->new();
|
||||
$filter->set_name('Perl Scripts');
|
||||
$filter->add_pattern('*.pl');
|
||||
$dialog->add_filter($filter);
|
||||
|
||||
$filter = Gtk3::FileFilter->new();
|
||||
$filter->set_name('All Files');
|
||||
$filter->add_pattern('*');
|
||||
$dialog->add_filter($filter);
|
||||
|
||||
if ('ok' eq $dialog->run()) {
|
||||
my $filename = $dialog->get_filename();
|
||||
open_file($filename);
|
||||
}
|
||||
$dialog->destroy();
|
||||
});
|
||||
|
||||
$save_item->signal_connect(activate => sub {
|
||||
if (defined $current_file) {
|
||||
save_file($current_file);
|
||||
} else {
|
||||
$save_as_item->signal_emit('activate');
|
||||
}
|
||||
});
|
||||
|
||||
$save_as_item->signal_connect(activate => sub {
|
||||
my $dialog = Gtk3::FileChooserDialog->new(
|
||||
'Save File',
|
||||
$window,
|
||||
'save',
|
||||
'Cancel' => 'cancel',
|
||||
'Save' => 'ok'
|
||||
);
|
||||
|
||||
# Add file filters
|
||||
my $filter = Gtk3::FileFilter->new();
|
||||
$filter->set_name('Perl Scripts');
|
||||
$filter->add_pattern('*.pl');
|
||||
$dialog->add_filter($filter);
|
||||
|
||||
$filter = Gtk3::FileFilter->new();
|
||||
$filter->set_name('All Files');
|
||||
$filter->add_pattern('*');
|
||||
$dialog->add_filter($filter);
|
||||
|
||||
if ('ok' eq $dialog->run()) {
|
||||
my $filename = $dialog->get_filename();
|
||||
save_file($filename);
|
||||
}
|
||||
$dialog->destroy();
|
||||
});
|
||||
|
||||
# Edit menu callbacks
|
||||
$undo_item->signal_connect(activate => sub {
|
||||
undo_action();
|
||||
});
|
||||
|
||||
$redo_item->signal_connect(activate => sub {
|
||||
redo_action();
|
||||
});
|
||||
|
||||
$cut_item->signal_connect(activate => sub {
|
||||
$text_buffer->cut_clipboard(
|
||||
Gtk3::Clipboard::get(Gtk3::Gdk::Atom::intern('CLIPBOARD', 0)),
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
$copy_item->signal_connect(activate => sub {
|
||||
$text_buffer->copy_clipboard(
|
||||
Gtk3::Clipboard::get(Gtk3::Gdk::Atom::intern('CLIPBOARD', 0))
|
||||
);
|
||||
});
|
||||
|
||||
$paste_item->signal_connect(activate => sub {
|
||||
$text_buffer->paste_clipboard(
|
||||
Gtk3::Clipboard::get(Gtk3::Gdk::Atom::intern('CLIPBOARD', 0)),
|
||||
undef,
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
$select_all_item->signal_connect(activate => sub {
|
||||
my $start = $text_buffer->get_start_iter();
|
||||
my $end = $text_buffer->get_end_iter();
|
||||
$text_buffer->select_range($start, $end);
|
||||
});
|
||||
|
||||
# Connect find/replace menu items
|
||||
$find_item->signal_connect(activate => sub {
|
||||
create_search_dialog();
|
||||
$search_dialog->present();
|
||||
});
|
||||
|
||||
$replace_item->signal_connect(activate => sub {
|
||||
create_replace_dialog();
|
||||
$replace_dialog->present();
|
||||
});
|
||||
|
||||
# Helper functions
|
||||
sub open_file {
|
||||
my ($filename) = @_;
|
||||
return unless -f $filename;
|
||||
|
||||
if (open(my $fh, '<:encoding(UTF-8)', $filename)) {
|
||||
local $/;
|
||||
my $content = <$fh>;
|
||||
close $fh;
|
||||
|
||||
$text_buffer->set_text($content);
|
||||
$text_buffer->set_modified(0);
|
||||
$current_file = $filename;
|
||||
$window->set_title("kPad - $filename");
|
||||
} else {
|
||||
show_error_dialog("Could not open file: $!");
|
||||
}
|
||||
}
|
||||
|
||||
sub save_file {
|
||||
my ($filename) = @_;
|
||||
return unless defined $filename;
|
||||
|
||||
if (open(my $fh, '>:encoding(UTF-8)', $filename)) {
|
||||
my $content = $text_buffer->get_text(
|
||||
$text_buffer->get_start_iter(),
|
||||
$text_buffer->get_end_iter(),
|
||||
1
|
||||
);
|
||||
print $fh $content;
|
||||
close $fh;
|
||||
|
||||
$current_file = $filename;
|
||||
$text_buffer->set_modified(0);
|
||||
$window->set_title("kPad - $filename");
|
||||
} else {
|
||||
show_error_dialog("Could not save file: $!");
|
||||
}
|
||||
}
|
||||
|
||||
sub buffer_changed {
|
||||
return $text_buffer->get_modified();
|
||||
}
|
||||
|
||||
sub check_save_changes {
|
||||
if (buffer_changed()) {
|
||||
my $dialog = Gtk3::MessageDialog->new(
|
||||
$window,
|
||||
'modal',
|
||||
'question',
|
||||
'none', # No default buttons
|
||||
"The document has been modified. Save changes?"
|
||||
);
|
||||
|
||||
$dialog->add_button('Save', 'yes');
|
||||
$dialog->add_button("Don't Save", 'no');
|
||||
$dialog->add_button('Cancel', 'cancel');
|
||||
|
||||
my $response = $dialog->run();
|
||||
$dialog->destroy();
|
||||
|
||||
if ($response eq 'yes') {
|
||||
if (defined $current_file) {
|
||||
save_file($current_file);
|
||||
return 1;
|
||||
} else {
|
||||
my $save_dialog = Gtk3::FileChooserDialog->new(
|
||||
'Save File',
|
||||
$window,
|
||||
'save',
|
||||
'Cancel' => 'cancel',
|
||||
'Save' => 'ok'
|
||||
);
|
||||
|
||||
# Add file filters
|
||||
my $filter = Gtk3::FileFilter->new();
|
||||
$filter->set_name('Perl Scripts');
|
||||
$filter->add_pattern('*.pl');
|
||||
$save_dialog->add_filter($filter);
|
||||
|
||||
$filter = Gtk3::FileFilter->new();
|
||||
$filter->set_name('All Files');
|
||||
$filter->add_pattern('*');
|
||||
$save_dialog->add_filter($filter);
|
||||
|
||||
my $save_response = $save_dialog->run();
|
||||
my $filename = $save_dialog->get_filename();
|
||||
$save_dialog->destroy();
|
||||
|
||||
if ($save_response eq 'ok' && defined $filename) {
|
||||
save_file($filename);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} elsif ($response eq 'no') {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub show_error_dialog {
|
||||
my ($message) = @_;
|
||||
my $dialog = Gtk3::MessageDialog->new(
|
||||
$window,
|
||||
'modal',
|
||||
'error',
|
||||
'ok',
|
||||
$message
|
||||
);
|
||||
$dialog->run();
|
||||
$dialog->destroy();
|
||||
}
|
||||
|
||||
sub create_search_dialog {
|
||||
return if defined $search_dialog;
|
||||
|
||||
$search_dialog = Gtk3::Dialog->new();
|
||||
$search_dialog->set_title('Find');
|
||||
$search_dialog->set_transient_for($window);
|
||||
$search_dialog->add_button('Close', 'close');
|
||||
$search_dialog->add_button('Find Next', 'ok');
|
||||
|
||||
my $content_area = $search_dialog->get_content_area();
|
||||
my $vbox = Gtk3::Box->new('vertical', 5);
|
||||
$content_area->add($vbox);
|
||||
|
||||
my $hbox = Gtk3::Box->new('horizontal', 5);
|
||||
$vbox->pack_start($hbox, 0, 0, 0);
|
||||
|
||||
$hbox->pack_start(Gtk3::Label->new('Search for:'), 0, 0, 0);
|
||||
my $entry = Gtk3::Entry->new();
|
||||
$hbox->pack_start($entry, 1, 1, 0);
|
||||
|
||||
my $case_sensitive = Gtk3::CheckButton->new_with_label('Case sensitive');
|
||||
$vbox->pack_start($case_sensitive, 0, 0, 0);
|
||||
|
||||
$search_dialog->show_all();
|
||||
|
||||
$search_dialog->signal_connect(response => sub {
|
||||
my ($dialog, $response) = @_;
|
||||
if ($response eq 'ok') {
|
||||
find_text($entry->get_text(), $case_sensitive->get_active());
|
||||
} elsif ($response eq 'close') {
|
||||
$dialog->hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub create_replace_dialog {
|
||||
return if defined $replace_dialog;
|
||||
|
||||
$replace_dialog = Gtk3::Dialog->new();
|
||||
$replace_dialog->set_title('Find and Replace');
|
||||
$replace_dialog->set_transient_for($window);
|
||||
$replace_dialog->add_button('Close', 'close');
|
||||
$replace_dialog->add_button('Replace', 'ok');
|
||||
$replace_dialog->add_button('Replace All', 'apply');
|
||||
|
||||
my $content_area = $replace_dialog->get_content_area();
|
||||
my $vbox = Gtk3::Box->new('vertical', 5);
|
||||
$content_area->add($vbox);
|
||||
|
||||
# Find entry
|
||||
my $find_box = Gtk3::Box->new('horizontal', 5);
|
||||
$vbox->pack_start($find_box, 0, 0, 0);
|
||||
$find_box->pack_start(Gtk3::Label->new('Find:'), 0, 0, 0);
|
||||
my $find_entry = Gtk3::Entry->new();
|
||||
$find_box->pack_start($find_entry, 1, 1, 0);
|
||||
|
||||
# Replace entry
|
||||
my $replace_box = Gtk3::Box->new('horizontal', 5);
|
||||
$vbox->pack_start($replace_box, 0, 0, 0);
|
||||
$replace_box->pack_start(Gtk3::Label->new('Replace with:'), 0, 0, 0);
|
||||
my $replace_entry = Gtk3::Entry->new();
|
||||
$replace_box->pack_start($replace_entry, 1, 1, 0);
|
||||
|
||||
my $case_sensitive = Gtk3::CheckButton->new_with_label('Case sensitive');
|
||||
$vbox->pack_start($case_sensitive, 0, 0, 0);
|
||||
|
||||
$replace_dialog->show_all();
|
||||
|
||||
$replace_dialog->signal_connect(response => sub {
|
||||
my ($dialog, $response) = @_;
|
||||
if ($response eq 'ok') {
|
||||
replace_next($find_entry->get_text(), $replace_entry->get_text(), $case_sensitive->get_active());
|
||||
} elsif ($response eq 'apply') {
|
||||
replace_all($find_entry->get_text(), $replace_entry->get_text(), $case_sensitive->get_active());
|
||||
} elsif ($response eq 'close') {
|
||||
$dialog->hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub find_text {
|
||||
my ($search_text, $case_sensitive) = @_;
|
||||
return unless $search_text;
|
||||
|
||||
my $buffer = $text_view->get_buffer();
|
||||
my $start_iter = $buffer->get_iter_at_mark($buffer->get_insert());
|
||||
|
||||
# Start from beginning if we're at the end or no previous search
|
||||
if (!defined $last_search_pos || !$start_iter->forward_char()) {
|
||||
$start_iter = $buffer->get_start_iter();
|
||||
}
|
||||
|
||||
my $found;
|
||||
if ($case_sensitive) {
|
||||
$found = $start_iter->forward_search($search_text, 'text-only', undef);
|
||||
} else {
|
||||
$found = $start_iter->forward_search($search_text, ['text-only', 'case-insensitive'], undef);
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
my ($match_start, $match_end) = @$found;
|
||||
$buffer->select_range($match_start, $match_end);
|
||||
$text_view->scroll_to_iter($match_start, 0.0, 1, 0.0, 0.5);
|
||||
$last_search_pos = $match_end;
|
||||
return 1;
|
||||
} else {
|
||||
# If not found from current position, try from start
|
||||
if ($start_iter->get_offset() > 0) {
|
||||
$last_search_pos = undef;
|
||||
return find_text($search_text, $case_sensitive);
|
||||
}
|
||||
show_error_dialog("Text not found: $search_text");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub replace_next {
|
||||
my ($find_text, $replace_text, $case_sensitive) = @_;
|
||||
return unless $find_text;
|
||||
|
||||
if (find_text($find_text, $case_sensitive)) {
|
||||
my $buffer = $text_view->get_buffer();
|
||||
$buffer->delete_selection(1, 1);
|
||||
$buffer->insert_at_cursor($replace_text);
|
||||
$last_search_pos = undef; # Force next search from current position
|
||||
}
|
||||
}
|
||||
|
||||
sub replace_all {
|
||||
my ($find_text, $replace_text, $case_sensitive) = @_;
|
||||
return unless $find_text;
|
||||
|
||||
my $count = 0;
|
||||
$last_search_pos = undef;
|
||||
|
||||
while (find_text($find_text, $case_sensitive)) {
|
||||
my $buffer = $text_view->get_buffer();
|
||||
$buffer->delete_selection(1, 1);
|
||||
$buffer->insert_at_cursor($replace_text);
|
||||
$last_search_pos = undef;
|
||||
$count++;
|
||||
}
|
||||
|
||||
show_info_dialog("Replaced $count occurrence" . ($count == 1 ? '' : 's'));
|
||||
}
|
||||
|
||||
sub update_cursor_position {
|
||||
my $buffer = $text_view->get_buffer();
|
||||
my $iter = $buffer->get_iter_at_mark($buffer->get_insert());
|
||||
my $line = $iter->get_line() + 1;
|
||||
my $column = $iter->get_line_offset() + 1;
|
||||
$status_bar->push(0, "Line: $line, Column: $column");
|
||||
}
|
||||
|
||||
sub show_info_dialog {
|
||||
my ($message) = @_;
|
||||
my $dialog = Gtk3::MessageDialog->new(
|
||||
$window,
|
||||
'modal',
|
||||
'info',
|
||||
'ok',
|
||||
$message
|
||||
);
|
||||
$dialog->run();
|
||||
$dialog->destroy();
|
||||
}
|
||||
|
||||
# Connect window signals
|
||||
$window->signal_connect(delete_event => sub {
|
||||
if (!check_save_changes()) {
|
||||
return 1; # Cancel the close
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
$window->signal_connect(destroy => sub { Gtk3::main_quit() });
|
||||
$exit_item->signal_connect(activate => sub { $window->destroy() });
|
||||
|
||||
# Show all widgets
|
||||
$window->show_all();
|
||||
|
||||
# Add these new functions at the end before Gtk3::main()
|
||||
sub handle_key_press {
|
||||
my ($widget, $event) = @_;
|
||||
my $keyval = $event->keyval;
|
||||
my $state = $event->state;
|
||||
|
||||
# Check if Control key is pressed
|
||||
if ($state & CONTROL_MASK) {
|
||||
if (chr($keyval) eq 'z' || chr($keyval) eq 'Z') {
|
||||
undo_action();
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'y' || chr($keyval) eq 'Y') {
|
||||
redo_action();
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'c' || chr($keyval) eq 'C') {
|
||||
$copy_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'x' || chr($keyval) eq 'X') {
|
||||
$cut_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
elsif (chr($keyval) eq 'v' || chr($keyval) eq 'V') {
|
||||
$paste_item->signal_emit('activate');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub buffer_changed_cb {
|
||||
return if $ignore_changes;
|
||||
|
||||
my $content = $text_buffer->get_text(
|
||||
$text_buffer->get_start_iter(),
|
||||
$text_buffer->get_end_iter(),
|
||||
1
|
||||
);
|
||||
|
||||
push @undo_stack, $content;
|
||||
@redo_stack = (); # Clear redo stack when new changes occur
|
||||
}
|
||||
|
||||
sub undo_action {
|
||||
return unless @undo_stack;
|
||||
|
||||
my $current_content = $text_buffer->get_text(
|
||||
$text_buffer->get_start_iter(),
|
||||
$text_buffer->get_end_iter(),
|
||||
1
|
||||
);
|
||||
|
||||
push @redo_stack, $current_content;
|
||||
|
||||
$ignore_changes = 1;
|
||||
my $prev_content = pop @undo_stack;
|
||||
$text_buffer->set_text($prev_content);
|
||||
$ignore_changes = 0;
|
||||
}
|
||||
|
||||
sub redo_action {
|
||||
return unless @redo_stack;
|
||||
|
||||
my $current_content = $text_buffer->get_text(
|
||||
$text_buffer->get_start_iter(),
|
||||
$text_buffer->get_end_iter(),
|
||||
1
|
||||
);
|
||||
|
||||
push @undo_stack, $current_content;
|
||||
|
||||
$ignore_changes = 1;
|
||||
my $next_content = pop @redo_stack;
|
||||
$text_buffer->set_text($next_content);
|
||||
$ignore_changes = 0;
|
||||
}
|
||||
|
||||
# Start the main loop
|
||||
Gtk3::main();
|
BIN
kpadgtk3
BIN
kpadgtk3
Binary file not shown.
553
kpadgtk3.pl
553
kpadgtk3.pl
|
@ -1,553 +0,0 @@
|
|||
#!/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);
|
||||
|
||||
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');
|
||||
$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;
|
||||
|
||||
# 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'
|
||||
);
|
||||
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;
|
531
moo.txt
531
moo.txt
|
@ -1,531 +0,0 @@
|
|||
#!/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;
|
|
@ -1,3 +1,3 @@
|
|||
::MyPlugin::auto
|
||||
print "Running MyPlugin!\n";
|
||||
$textbuffer->insert_at_cursor("Plugin ran!");
|
||||
::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
|
||||
|
||||
|
|
BIN
pshell
BIN
pshell
Binary file not shown.
13
pshell.kpd
13
pshell.kpd
|
@ -1,13 +0,0 @@
|
|||
::RunPshellCmd::
|
||||
use strict;
|
||||
my $dialog = Gtk3::Dialog->new('Run pshell Command', $::window, 'modal', 'gtk-ok', 'ok', 'gtk-cancel', 'cancel');
|
||||
my $entry = Gtk3::Entry->new;
|
||||
$dialog->get_content_area->pack_start($entry, FALSE, FALSE, 0);
|
||||
$dialog->show_all;
|
||||
if ($dialog->run eq 'ok') {
|
||||
my $cmd = $entry->get_text;
|
||||
my $output = `$::basedir/pshell -e "$cmd" 2>&1` || "Error: pshell failed";
|
||||
$::textbuffer->insert_at_cursor("pshell output:\n$output\n");
|
||||
$::statusbar->push(0, "Ran pshell command");
|
||||
}
|
||||
$dialog->destroy;
|
BIN
pshell_env.db
BIN
pshell_env.db
Binary file not shown.
Binary file not shown.
1
test.txt
Executable file
1
test.txt
Executable file
|
@ -0,0 +1 @@
|
|||
moo
|
3
test2.txt
Executable file
3
test2.txt
Executable file
|
@ -0,0 +1,3 @@
|
|||
moo beer
|
||||
moo beer
|
||||
moo beer beef
|
Loading…
Add table
Add a link
Reference in a new issue