File Coverage

blib/lib/Gtk2/Ex/TextView/FollowAppend.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # Copyright 2009, 2010, 2011, 2013 Kevin Ryde
2              
3             # This file is part of Gtk2-Ex-ErrorTextDialog.
4             #
5             # Gtk2-Ex-ErrorTextDialog is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU General Public License as published
7             # by the Free Software Foundation; either version 3, or (at your option) any
8             # later version.
9             #
10             # Gtk2-Ex-ErrorTextDialog is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13             # Public License for more details.
14             #
15             # You should have received a copy of the GNU General Public License along
16             # with Gtk2-Ex-ErrorTextDialog. If not, see .
17              
18             package Gtk2::Ex::TextView::FollowAppend;
19 1     1   25172 use 5.008;
  1         3  
  1         32  
20 1     1   4 use strict;
  1         1  
  1         23  
21 1     1   5 use warnings;
  1         1  
  1         27  
22 1     1   1529 use Gtk2;
  0            
  0            
23              
24             # uncomment this to run the ### lines
25             # use Smart::Comments;
26              
27             our $VERSION = 11;
28              
29             use Glib::Object::Subclass
30             'Gtk2::TextView',
31             signals => { size_allocate => \&_do_size_allocate,
32             # notify => \&_do_notify,
33             destroy => \&_do_destroy,
34             };
35              
36             sub new_with_buffer {
37             my ($class, $textbuf) = @_;
38             return $class->Glib::Object::new (buffer => $textbuf);
39             }
40              
41             # "notify" is done as a connect to self rather than a class handler.
42             # A long-standing glib bug causes a $self->signal_chain_from_overridden to
43             # go to the wrong handler, when a notify is invoked from under another
44             # handler, or something like that. Very annoying.
45             sub INIT_INSTANCE {
46             my ($self) = @_;
47             $self->signal_connect (notify => \&_do_notify);
48             }
49              
50             # 'destroy' class closure
51             sub _do_destroy {
52             my ($self) = @_;
53             ### FollowAppend _do_destroy(): "$self"
54              
55             # The GtkObjectFlags GTK_IN_DESTRUCTION might do this, if it was exposed
56             # at the perl level. Except gtk_object_dispose() looks like the flag
57             # might be set solely while 'destroy' runs, but any notifies during
58             # finalize should suppress get_buffer() below too. Except can finalize
59             # emit notify anyway?
60             #
61             $self->{'destroyed'} = 1;
62              
63             $self->signal_chain_from_overridden ();
64             }
65              
66             # 'notify' class closure
67             sub _do_notify {
68             my ($self, $pspec) = @_;
69             ### FollowAppend _do_notify(): $pspec->get_name
70              
71             # my $invocation_hint = $self->signal_get_invocation_hint;
72             # require Data::Dumper;
73             # print Data::Dumper->Indent(1)->Dump([$invocation_hint],
74             # ['invocation_hint']);
75              
76             # $self->signal_chain_from_overridden ($pspec);
77              
78             # After 'destroy' runs it's important not to call ->get_buffer() since
79             # that func creates a new TextBuffer in place of what
80             # gtk_text_view_destroy() just destroyed and set to NULL. If a textbuf is
81             # re-created like that it leads to a fatal error in
82             # gtk_text_view_finalize().
83             #
84             if (! $self->{'destroyed'} && $pspec->get_name eq 'buffer') {
85             require Glib::Ex::SignalIds;
86             require Scalar::Util;
87              
88             my $textbuf = $self->get_buffer;
89             Scalar::Util::weaken (my $weak_self = $self);
90             my $ref_weak_self = \$weak_self;
91              
92             $self->{'textbuf_ids'} = $textbuf && Glib::Ex::SignalIds->new
93             ($textbuf,
94             $textbuf->signal_connect_after ('insert-text',
95             \&_do_textbuf_insert,
96             $ref_weak_self),
97             $textbuf->signal_connect_after ('insert-pixbuf',
98             \&_do_textbuf_insert_pixbuf_or_anchor,
99             $ref_weak_self),
100             $textbuf->signal_connect_after ('insert-child-anchor',
101             \&_do_textbuf_insert_pixbuf_or_anchor,
102             $ref_weak_self));
103             }
104             }
105              
106             # 'size-allocate' class closure
107             sub _do_size_allocate {
108             my ($self, $alloc) = @_;
109             ### FollowAppend size_allocate: $alloc->x.",".$alloc->y." ".$alloc->width."x".$alloc->height
110              
111             my $want_follow = _want_follow ($self);
112             ### $want_follow
113              
114             $self->signal_chain_from_overridden ($alloc);
115              
116             if ($want_follow) {
117             ### _do_size_allocate() scroll_to_mark
118             $self->scroll_to_mark ($self->get_buffer->get_insert, 0, 0, 0,0);
119             }
120             }
121              
122             # 'insert-pixbuf' and 'insert-child-anchor' signal handler on textbuf
123             sub _do_textbuf_insert_pixbuf_or_anchor {
124             my ($textbuf, $iter, $pixbuf_or_anchor, $ref_weak_self) = @_;
125             _do_textbuf_insert ($textbuf, $iter, undef, 1, $ref_weak_self);
126             }
127              
128             # 'insert-text' signal handler on textbuf,
129             # plus fakery from 'insert-pixbuf' and 'insert-child-anchor' above
130             sub _do_textbuf_insert {
131             my ($textbuf, $iter, $text, $textlen, $ref_weak_self) = @_;
132             my $self = $$ref_weak_self || return;
133             ### FollowAppend _do_textbuf_insert() iter: $iter->get_offset
134             ### $textlen
135             ### $text
136              
137             if ($iter->is_end
138             && _want_follow ($self,
139             $textbuf->get_iter_at_offset
140             ($iter->get_offset - $textlen))) {
141             ### _do_textbuf_insert() scroll_to_mark
142             $self->scroll_to_mark ($textbuf->get_insert, 0, 0, 0,0);
143             }
144             }
145              
146             sub _want_follow {
147             my ($self, $insert_iter) = @_;
148             my $textbuf = $self->get_buffer;
149             my $cursor_iter = $textbuf->get_iter_at_mark ($textbuf->get_insert);
150              
151             ### insert at: ($insert_iter||$textbuf->get_end_iter)->get_offset
152             ### end: $textbuf->get_end_iter->get_offset
153             ### cursor: $cursor_iter->get_offset
154             ### charcount: $textbuf->get_char_count
155             ### cursor is_end: $cursor_iter->is_end
156              
157             return ($cursor_iter->is_end
158             && _iter_is_visible ($self, $insert_iter||$textbuf->get_end_iter));
159             }
160              
161             # return true if $iter is visible in $textview
162             # partially visible $iter pos returns true
163             sub _iter_is_visible {
164             my ($textview, $iter) = @_;
165             my $visible_rect = $textview->get_visible_rect;
166              
167             ###
168             ### visible rect: $visible_rect->x.",".$visible_rect->y." ".$visible_rect->width."x".$visible_rect->height." height to ".($visible_rect->y+$visible_rect->height)
169             ### iter: $iter->get_offset
170             ### start_vis: do { my $start_iter = $textview->get_iter_at_location (0, $visible_rect->y); $start_iter->get_offset }
171             ### end vis: do { my $end_iter = $textview->get_iter_at_location (0, $visible_rect->y + $visible_rect->height); $end_iter->get_offset }
172             ### end buf: $textview->get_buffer->get_char_count
173              
174             my $start_iter = $textview->get_iter_at_location(0, $visible_rect->y);
175             if ($iter->compare($start_iter) < 0) {
176             # $iter is before start of visible part
177             return 0;
178             }
179             my $end_iter = $textview->get_iter_at_location
180             (0, $visible_rect->y + $visible_rect->height - 1);
181             $end_iter->forward_visible_line;
182             # true if iter is at or before end of visible part
183             return ($iter->compare($end_iter) <= 0);
184             }
185              
186             # This version based on get_iter_location() for the rectangle of the target
187             # iter, where the above only asks for the iter extents of the visible
188             # window. Will the above help with lazy calculation of lines, or does
189             # everything have to be calculated anyway for the total size for the
190             # scrollbar etc?
191             #
192             # # return true if $iter is visible in $textview
193             # sub _iter_is_visible {
194             # my ($textview, $iter) = @_;
195             # my $visible_rect = $textview->get_visible_rect;
196             # my $iter_rect = $textview->get_iter_location ($iter);
197             # if (DEBUG) { print " visible rect ",
198             # $visible_rect->x,",",$visible_rect->y," ",
199             # $visible_rect->width,"x",$visible_rect->height,
200             # " height to ",$visible_rect->y+$visible_rect->height,"\n";
201             # print " iter rect ",
202             # $iter_rect->x,",",$iter_rect->y," ",
203             # $iter_rect->width,"x",$iter_rect->height,
204             # " height to ",$iter_rect->y+$iter_rect->height,"\n";
205             # }
206             #
207             # # if y1+h1 < y2 then rect1 is entirely above rect2, or if y2+h2 < y1 then
208             # # rect2 is entirely above rect1; if neither then there's overlap
209             # # rect_overlaps_rect() ?
210             # return ! ($visible_rect->y + $visible_rect->height < $iter_rect->y
211             # || $iter_rect->y + $iter_rect->height < $visible_rect->y);
212             # }
213              
214             1;
215             __END__