package Kephra::App::EditPanel;
our $VERSION = '0.14';
use strict;
use warnings;
#
# internal API to config und app pointer
#
my $ref;
my %mouse_commands;
sub _ref { $ref }
sub _set_ref { $ref = $_[0] if is($_[0]) }
sub _all_ref { Kephra::Document::Data::get_all_ep() }
sub is { 1 if ref $_[0] eq 'Wx::StyledTextCtrl'}
sub _config { Kephra::API::settings()->{editpanel} }
# splitter_pos
sub new {
my $ep = Wx::StyledTextCtrl->new( Kephra::App::Window::_ref() );
$ep->DragAcceptFiles(1) if Wx::wxMSW();
return $ep;
}
sub gets_focus { Wx::Window::SetFocus( _ref() ) if is( _ref() ) }
# general settings
sub apply_settings_here {
my $ep = shift || _ref() || _create();
return unless is($ep);
my $conf = _config();
load_font($ep);
# indicators: caret, selection, whitespaces...
Kephra::App::EditPanel::Indicator::apply_all_here($ep);
# Margins on left side
Kephra::App::EditPanel::Margin::apply_settings_here($ep);
#misc: scroll width, codepage, wordchars
apply_autowrap_settings_here($ep);
$ep->SetScrollWidth($conf->{scroll_width})
unless $conf->{scroll_width} eq 'auto';
#wxSTC_CP_UTF8 Wx::wxUNICODE()
$ep->SetCodePage(65001);#
set_word_chars_here($ep);
# internal
$ep->SetLayoutCache(&Wx::wxSTC_CACHE_PAGE);
$ep->SetBufferedDraw(1);
$conf->{contextmenu}{visible} eq 'default' ? $ep->UsePopUp(1) : $ep->UsePopUp(0);
Kephra::Edit::eval_newline_sub();
Kephra::Edit::Marker::define_marker($ep);
connect_events($ep);
Kephra::EventTable::add_call ( 'editpanel.focus', 'editpanel', sub {
Wx::Window::SetFocus( _ref() ) unless $Kephra::temp{dialog}{active};
}, __PACKAGE__ ) if $conf->{auto}{focus};
Kephra::EventTable::add_call( 'document.text.change', 'update_edit_pos', sub {
Kephra::Document::Data::attr('edit_pos', _ref()->GetCurrentPos());
}, __PACKAGE__);
}
sub connect_events {
my $ep = shift || _ref();
my $trigger = \&Kephra::EventTable::trigger;
my $config = _config();
my $selection;
my $rectangular_mode;
my ($dragpos,$droppos);
# override sci presets
Wx::Event::EVT_DROP_FILES ($ep, \&Kephra::File::add_dropped);
Wx::Event::EVT_STC_START_DRAG ($ep, -1, sub {
my ( $ep, $event) = @_;
$dragpos = $ep->GetCurrentPos();
$selection = $ep->GetSelectedText();
$rectangular_mode = $ep->SelectionIsRectangle();
$event->Skip;
});
Wx::Event::EVT_STC_DRAG_OVER ($ep, -1, sub { $droppos = $_[1]->GetPosition });
Wx::Event::EVT_STC_DO_DROP ($ep, -1, sub {
my ( $ep, $event) = @_;
$rectangular_mode
? Kephra::Edit::paste_rectangular($selection, $ep, $dragpos, $droppos)
: $event->Skip;
});
Wx::Event::EVT_ENTER_WINDOW ($ep, sub { &$trigger('editpanel.focus')} );
Wx::Event::EVT_LEFT_DOWN ($ep, sub {
my ($ep, $event) = @_;
my $nr = Kephra::App::EditPanel::Margin::in_nr( $event->GetX, $ep );
if ($nr == -1) { Kephra::Edit::copy() if clicked_on_selection($event) }
else { Kephra::App::EditPanel::Margin::on_left_click($ep, $event, $nr) }
$event->Skip;
});
Wx::Event::EVT_MIDDLE_DOWN ($ep, sub {
my ($ep, $event) = @_;
my $nr = Kephra::App::EditPanel::Margin::in_nr( $event->GetX, $ep );
# click is above text area
if ($nr == -1) {
if ($event->LeftIsDown){
Kephra::Edit::paste();
set_caret_on_cursor($event);
}
# just middle clicked
else {
if ($ep->GetSelectedText){
if (clicked_on_selection($event, $ep)) {
Kephra::Edit::Search::set_selection_as_find_item();
Kephra::Edit::Search::find_next();
}
else { insert_selection_at_cursor($event, $ep) }
}
else { Kephra::Edit::Goto::last_edit() }
}
}
else { Kephra::App::EditPanel::Margin::on_middle_click($ep, $event, $nr) }
});
Wx::Event::EVT_RIGHT_DOWN ($ep, sub {
my ($ep, $event) = @_;
my $nr = Kephra::App::EditPanel::Margin::in_nr( $event->GetX, $ep );
if ($nr == -1) {
if ($event->LeftIsDown){
Kephra::Edit::_selection_left_to_right($ep)
? Kephra::Edit::cut()
: Kephra::Edit::clear();
set_caret_on_cursor($event);
} else {
my $mconf = $config->{contextmenu};
if ($mconf->{visible} eq 'custom'){
my $menu_id = $ep->GetSelectedText
? $mconf->{ID_selection} : $mconf->{ID_normal};
my $menu = Kephra::App::ContextMenu::get($menu_id);
$ep->PopupMenu($menu, $event->GetX, $event->GetY) if $menu;
}
}
} else {Kephra::App::EditPanel::Margin::on_right_click($ep, $event, $nr)}
});
#Wx::EVT_SET_FOCUS ($ep, sub {});
Wx::Event::EVT_STC_SAVEPOINTREACHED($ep, -1, \&Kephra::File::savepoint_reached);
Wx::Event::EVT_STC_SAVEPOINTLEFT($ep, -1, \&Kephra::File::savepoint_left);
# -DEP
#Wx::Event::EVT_STC_MARGINCLICK ($ep, -1, \&Kephra::App::EditPanel::Margin::on_left_click);
Wx::Event::EVT_STC_CHANGE ($ep, -1, sub {&$trigger('document.text.change')} );
Wx::Event::EVT_STC_UPDATEUI ($ep, -1, sub {
my ( $ep, $event) = @_;
my ( $sel_beg, $sel_end ) = $ep->GetSelection;
my $is_sel = $sel_beg != $sel_end;
my $was_sel = Kephra::Document::Data::attr('text_selected');
Kephra::Document::Data::attr('text_selected', $is_sel);
&$trigger('document.text.select') if $is_sel xor $was_sel;
&$trigger('caret.move');
});
Wx::Event::EVT_KEY_DOWN ($ep, sub {
my ($ep, $event) = @_;
#$ep = _ref();
my $key = $event->GetKeyCode +
1000 * ($event->ShiftDown + $event->ControlDown*2 + $event->AltDown*4);
# reacting on shortkeys that are defined in the Commanlist
#print "$key\n";
return if Kephra::CommandList::run_cmd_by_keycode($key);
# reacting on Enter
if ($key == &Wx::WXK_RETURN) {
if ($config->{auto}{brace}{indention}) {
my $pos = $ep->GetCurrentPos - 1;
my $char = $ep->GetCharAt($pos);
if ($char == 123) {
return Kephra::Edit::Format::blockindent_open($pos);
} elsif ($char == 125) {
return Kephra::Edit::Format::blockindent_close($pos);
}
}
$config->{auto}{indention}
? Kephra::Edit::Format::autoindent()
: $ep->CmdKeyExecute(&Wx::wxSTC_CMD_NEWLINE) ;
}
# scintilla handles the rest of the shortkeys
else { $event->Skip }
#SCI_SETSELECTIONMODE
#($key == 350){use Kephra::Ext::Perl::Syntax; Kephra::Ext::Perl::Syntax::check()};
});
}
sub create_mouse_binding {
my @cmd = qw(left-middle left-right left-selection
middle middle-selected middle-selection
);
if (_config()->{control}{use_mouse_function}) {
my $config = _config()->{control}{mouse_function};
$mouse_commands{$_} = Kephra::Macro::create_from_cmd_list($config->{$_})
for @cmd
}
else { $mouse_commands{$_} = sub {} for @cmd }
}
sub set_caret_on_cursor {
my $event = shift;
my $ep = shift || _ref();
return unless ref $event eq 'Wx::MouseEvent' and is($ep);
my $pos = cursor_2_caret_pos($event, $ep);
$pos = $ep->GetCurrentPos() if $pos == -1;
$ep->SetSelection( $pos, $pos );
}
sub clicked_on_selection {
my $event = shift;
my $ep = shift || _ref();
return unless ref $event eq 'Wx::MouseEvent' and is($ep);
my ($start, $end) = $ep->GetSelection();
my $pos = cursor_2_caret_pos($event, $ep);
return 1 if $start != $end and $pos >= $start and $pos <= $end;
}
sub insert_selection_at_cursor {
my $event = shift;
my $ep = shift || _ref();
my $pos = cursor_2_caret_pos($event, $ep);
Kephra::Edit::insert_text($ep->GetSelectedText(), $pos) if $pos > -1;
}
sub cursor_2_caret_pos {
my $event = shift;
my $ep = shift || _ref();
return -1 unless ref $event eq 'Wx::MouseEvent' and is($ep);
my $pos = $ep->PositionFromPointClose($event->GetX, $event->GetY);
if ($pos == -1) {
my $width = Kephra::App::EditPanel::Margin::width($ep)
+ Kephra::App::EditPanel::Margin::get_text_width();
my $y = $event->GetY;
my $line = $ep->LineFromPosition( $ep->PositionFromPointClose($width, $y) );
$pos = $ep->GetLineEndPosition ($line);
my $font_size = _config()->{font}{size};
if ($line == 0 and $y > $font_size + 12) {
my $lcc = 0;
$pos = $ep->PositionFromPointClose($width-1, $y);
while ($pos == -1 and $lcc < $ep->GetLineCount() ){
$lcc++; # line counter
$y += $font_size;
$pos = $ep->PositionFromPointClose($width, $y);
}
return -1 if $pos == -1;
return $ep->PositionFromLine( $ep->LineFromPosition($pos) - $lcc );
}
}
$pos;
}
sub disconnect_events {
my $ep = shift || _ref();
Wx::Event::EVT_STC_CHANGE ($ep, -1, sub {});
Wx::Event::EVT_STC_UPDATEUI($ep, -1, sub {});
}
sub set_contextmenu_custom { set_contextmenu('custom') }
sub set_contextmenu_default { set_contextmenu('default')}
sub set_contextmenu_none { set_contextmenu('none') }
sub set_contextmenu {
my $mode = shift;
$mode = 'custom' unless $mode;
my $ep = _ref();
$mode eq 'default' ? $ep->UsePopUp(1) : $ep->UsePopUp(0);
_config()->{contextmenu}{visible} = $mode;
}
sub get_contextmenu { _config()->{contextmenu}{visible} }
#
sub set_word_chars { set_word_chars_here($_) for @{_all_ref()} }
sub set_word_chars_here {
my $ep = shift || _ref();
my $conf = _config();
if ( $conf->{word_chars} ) {
$ep->SetWordChars( $conf->{word_chars} );
} else {
$ep->SetWordChars( '$%&-@_abcdefghijklmnopqrstuvwxyzäöüßABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ0123456789' );
}
}
# line wrap
sub apply_autowrap_settings { apply_autowrap_settings_here($_) for @{_all_ref()} }
sub apply_autowrap_settings_here {
my $ep = shift || _ref();
$ep->SetWrapMode( _config()->{line_wrap} );
Kephra::EventTable::trigger('editpanel.autowrap');
}
sub get_autowrap_mode { _config()->{line_wrap} == &Wx::wxSTC_WRAP_WORD}
sub switch_autowrap_mode {
_config()->{line_wrap} = get_autowrap_mode()
? &Wx::wxSTC_WRAP_NONE
: &Wx::wxSTC_WRAP_WORD;
apply_autowrap_settings();
}
# font settings
sub load_font {
my $ep = shift || _ref();
my ( $fontweight, $fontstyle ) = ( &Wx::wxNORMAL, &Wx::wxNORMAL );
my $font = _config()->{font};
$fontweight = &Wx::wxLIGHT if $font->{weight} eq 'light';
$fontweight = &Wx::wxBOLD if $font->{weight} eq 'bold';
$fontstyle = &Wx::wxSLANT if $font->{style} eq 'slant';
$fontstyle = &Wx::wxITALIC if $font->{style} eq 'italic';
my $wx_font = Wx::Font->new( $font->{size}, &Wx::wxDEFAULT,
$fontstyle, $fontweight, 0, $font->{family} );
$ep->StyleSetFont( &Wx::wxSTC_STYLE_DEFAULT, $wx_font ) if $wx_font->Ok > 0;
}
sub change_font {
my ( $fontweight, $fontstyle ) = ( &Wx::wxNORMAL, &Wx::wxNORMAL );
my $font_config = _config()->{font};
$fontweight = &Wx::wxLIGHT if ( $$font_config{weight} eq 'light' );
$fontweight = &Wx::wxBOLD if ( $$font_config{weight} eq 'bold' );
$fontstyle = &Wx::wxSLANT if ( $$font_config{style} eq 'slant' );
$fontstyle = &Wx::wxITALIC if ( $$font_config{style} eq 'italic' );
my $oldfont = Wx::Font->new( $$font_config{size}, &Wx::wxDEFAULT, $fontstyle,
$fontweight, 0, $$font_config{family} );
my $newfont = Kephra::Dialog::get_font( $oldfont );
if ( $newfont->Ok > 0 ) {
($fontweight, $fontstyle) = ($newfont->GetWeight, $newfont->GetStyle);
$$font_config{size} = $newfont->GetPointSize;
$$font_config{family} = $newfont->GetFaceName;
$$font_config{weight} = 'normal';
$$font_config{weight} = 'light' if $fontweight == &Wx::wxLIGHT;
$$font_config{weight} = 'bold' if $fontweight == &Wx::wxBOLD;
$$font_config{style} = 'normal';
$$font_config{style} = 'slant' if $fontstyle == &Wx::wxSLANT;
$$font_config{style} = 'italic' if $fontstyle == &Wx::wxITALIC;
Kephra::Document::SyntaxMode::reload($_) for @{Kephra::Document::Data::all_nr()};
Kephra::App::EditPanel::Margin::apply_line_number_width();
}
}
#
sub zoom_in {
my $ep = shift || _ref();
$ep->SetZoom( $ep->GetZoom()+1 ) if $ep->GetZoom() < 45;
}
sub zoom_out {
my $ep = shift || _ref();
$ep->SetZoom( $ep->GetZoom()-1 ) if $ep->GetZoom() > -10;
}
sub zoom_normal {
my $ep = shift || _ref();
$ep->SetZoom( 0 ) ;
}
#
# auto indention
sub get_autoindention { Kephra::App::EditPanel::_config()->{auto}{indention} }
sub set_autoindention {
Kephra::App::EditPanel::_config()->{auto}{indention} = shift;
Kephra::Edit::eval_newline_sub();
}
sub switch_autoindention { set_autoindention( get_autoindention() ^ 1 ) }
sub set_autoindent_on { set_autoindention( 1 ) }
sub set_autoindent_off { set_autoindention( 0 ) }
#
# brace indention
sub get_braceindention { Kephra::App::EditPanel::_config()->{auto}{brace}{indention}}
sub set_braceindention {
Kephra::App::EditPanel::_config()->{auto}{brace}{indention} = shift;
Kephra::Edit::eval_newline_sub();
}
sub switch_braceindention { set_braceindention( get_braceindention() ^ 1 ) }
sub set_braceindent_on { set_braceindention( 1 ) }
sub set_braceindent_off { set_braceindention( 0 ) }
#
#
sub get_bracecompletion { Kephra::App::EditPanel::_config()->{auto}{brace}{make} }
sub set_bracecompletion {
Kephra::App::EditPanel::_config()->{auto}{brace}{make} = shift;
}
sub switch_bracecompletion{ set_bracecompletion( get_bracecompletion() ^ 1 ) }
1;
#EVT_STC_CHARADDED EVT_STC_MODIFIED
#wxSTC_CP_UTF8 wxSTC_CP_UTF16 Wx::wxUNICODE()
#wxSTC_WS_INVISIBLE wxSTC_WS_VISIBLEALWAYS
#$ep->StyleSetForeground (wxSTC_STYLE_CONTROLCHAR, Wx::Colour->new(0x55, 0x55, 0x55));
#$ep->CallTipShow(3,"testtooltip\n next line"); #tips
#SetSelectionMode(wxSTC_SEL_RECTANGLE);