root/branches/gajim_0.10/src/chat_control.py

Revision 6407, 58.7 kB (checked in by dkirov, 2 years ago)

r6265, r6266, r6267, r6269, r6350, r6366

Line 
1##      chat_control.py
2##
3## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>
4## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
5## Copyright (C) 2006 Travis Shirk <travis@pobox.com>
6## Copyright (C) 2006 Dimitur Kirov <dkirov@gmail.com>
7##
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published
10## by the Free Software Foundation; version 2 only.
11##
12## This program is distributed in the hope that it will be useful,
13## but WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15## GNU General Public License for more details.
16##
17
18import os
19import time
20import gtk
21import gtk.glade
22import pango
23import gobject
24import gtkgui_helpers
25import message_control
26import dialogs
27import history_window
28
29from common import gajim
30from common import helpers
31from message_control import MessageControl
32from conversation_textview import ConversationTextview
33from message_textview import MessageTextView
34from common.contacts import GC_Contact
35from common.logger import Constants
36constants = Constants()
37
38try:
39        import gtkspell
40        HAS_GTK_SPELL = True
41except:
42        HAS_GTK_SPELL = False
43
44####################
45# FIXME: Can't this stuff happen once?
46from common import i18n
47_ = i18n._
48APP = i18n.APP
49
50################################################################################
51class ChatControlBase(MessageControl):
52        '''A base class containing a banner, ConversationTextview, MessageTextView
53        '''
54        def get_font_attrs(self):
55                ''' get pango font  attributes for banner from theme settings '''
56                theme = gajim.config.get('roster_theme')
57                bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
58                bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
59               
60                if bannerfont:
61                        font = pango.FontDescription(bannerfont)
62                else:
63                        font = pango.FontDescription('Normal')
64                if bannerfontattrs:
65                        # B attribute is set by default
66                        if 'B' in bannerfontattrs:
67                                font.set_weight(pango.WEIGHT_HEAVY)
68                        if 'I' in bannerfontattrs:
69                                font.set_style(pango.STYLE_ITALIC)
70               
71                font_attrs = 'font_desc="%s"' % font.to_string()
72               
73                # in case there is no font specified we use x-large font size
74                if font.get_size() == 0:
75                        font_attrs = '%s size="x-large"' % font_attrs
76                font.set_weight(pango.WEIGHT_NORMAL)
77                font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
78                return (font_attrs, font_attrs_small)
79                       
80        def draw_banner(self):
81                self._paint_banner()
82                self._update_banner_state_image()
83                # Derived types SHOULD implement this
84
85        def update_ui(self):
86                self.draw_banner()
87                # Derived types SHOULD implement this
88
89        def repaint_themed_widgets(self):
90                self.draw_banner()
91                # Derived classes MAY implement this
92
93        def _update_banner_state_image(self):
94                pass # Derived types MAY implement this
95
96        def handle_message_textview_mykey_press(self, widget, event_keyval,
97        event_keymod):
98                pass # Derived should implement this rather than connecting to the event itself.
99
100        def __init__(self, type_id, parent_win, widget_name, display_names, contact, acct, resource = None):
101                MessageControl.__init__(self, type_id, parent_win, widget_name,
102                        display_names,  contact, acct, resource = resource);
103                # when/if we do XHTML we will but formatting buttons back
104                widget = self.xml.get_widget('emoticons_button')
105                id = widget.connect('clicked', self.on_emoticons_button_clicked)
106                self.handlers[id] = widget
107               
108                id = self.widget.connect('key_press_event', self._on_keypress_event)
109                self.handlers[id] = self.widget
110
111                widget = self.xml.get_widget('banner_eventbox')
112                id = widget.connect('button-press-event',
113                        self._on_banner_eventbox_button_press_event)
114                self.handlers[id] = widget
115       
116                # Create textviews and connect signals
117                self.conv_textview = ConversationTextview(self.account)
118               
119                self.conv_scrolledwindow = self.xml.get_widget(
120                        'conversation_scrolledwindow')
121                self.conv_scrolledwindow.add(self.conv_textview.tv)
122                widget = self.conv_scrolledwindow.get_vadjustment()
123                id = widget.connect('value-changed',
124                        self.on_conversation_vadjustment_value_changed)
125                self.handlers[id] = widget
126                # add MessageTextView to UI and connect signals
127                self.msg_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
128                self.msg_textview = MessageTextView()
129                id = self.msg_textview.connect('mykeypress',
130                                        self._on_message_textview_mykeypress_event)
131                self.handlers[id] = self.msg_textview
132                self.msg_scrolledwindow.add(self.msg_textview)
133                id = self.msg_textview.connect('key_press_event',
134                                        self._on_message_textview_key_press_event)
135                self.handlers[id] = self.msg_textview
136                id = self.msg_textview.connect('size-request', self.size_request)
137                self.handlers[id] = self.msg_textview
138                self.update_font()
139
140                # Hook up send button
141                widget = self.xml.get_widget('send_button')
142                id = widget.connect('clicked',
143                                                        self._on_send_button_clicked)
144                self.handlers[id] = widget
145
146                # the following vars are used to keep history of user's messages
147                self.sent_history = []
148                self.sent_history_pos = 0
149                self.typing_new = False
150                self.orig_msg = ''
151
152                self.nb_unread = 0
153
154                # Emoticons menu
155                # set image no matter if user wants at this time emoticons or not
156                # (so toggle works ok)
157                img = self.xml.get_widget('emoticons_button_image')
158                img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
159                        'smile.png'))
160                self.toggle_emoticons()
161
162                # Attach speller
163                if gajim.config.get('use_speller') and HAS_GTK_SPELL:
164                        try:
165                                gtkspell.Spell(self.msg_textview)
166                        except gobject.GError, msg:
167                                #FIXME: add a ui for this use spell.set_language()
168                                dialogs.ErrorDialog(unicode(msg), _('If that is not your language '
169                                        'for which you want to highlight misspelled words, then please '
170                                        'set your $LANG as appropriate. Eg. for French do export '
171                                        'LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to '
172                                        'make it global in /etc/profile.\n\nHighlighting misspelled '
173                                        'words feature will not be used'))
174                                gajim.config.set('use_speller', False)
175
176                self.style_event_id = 0
177                self.conv_textview.tv.show()
178        # moved from ChatControl
179        def _on_banner_eventbox_button_press_event(self, widget, event):
180                '''If right-clicked, show popup'''
181                if event.button == 3: # right click
182                        self.parent_win.popup_menu(event)
183
184        def _on_send_button_clicked(self, widget):
185                '''When send button is pressed: send the current message'''
186                if gajim.connections[self.account].connected < 2: # we are not connected
187                        dialog = dialogs.ErrorDialog(_('A connection is not available'),
188                                _('Your message can not be sent until you are connected.'))
189                        return
190                message_buffer = self.msg_textview.get_buffer()
191                start_iter = message_buffer.get_start_iter()
192                end_iter = message_buffer.get_end_iter()
193                message = message_buffer.get_text(start_iter, end_iter, 0).decode('utf-8')
194
195                # send the message
196                self.send_message(message)
197
198        def _paint_banner(self):
199                '''Repaint banner with theme color'''
200                theme = gajim.config.get('roster_theme')
201                bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
202                textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
203                # the backgrounds are colored by using an eventbox by
204                # setting the bg color of the eventbox and the fg of the name_label
205                banner_eventbox = self.xml.get_widget('banner_eventbox')
206                banner_name_label = self.xml.get_widget('banner_name_label')
207                self.disconnect_style_event(banner_name_label)
208                if bgcolor:
209                        banner_eventbox.modify_bg(gtk.STATE_NORMAL, 
210                                gtk.gdk.color_parse(bgcolor))
211                        default_bg = False
212                else:
213                        default_bg = True
214                if textcolor:
215                        banner_name_label.modify_fg(gtk.STATE_NORMAL,
216                                gtk.gdk.color_parse(textcolor))
217                        default_fg = False
218                else:
219                        default_fg = True
220                if default_bg or default_fg:
221                        self._on_style_set_event(banner_name_label, None, default_fg,
222                                default_bg)
223       
224        def disconnect_style_event(self, widget):
225                if self.style_event_id:
226                        widget.disconnect(self.style_event_id)
227                        del self.handlers[self.style_event_id]
228                        self.style_event_id = 0 
229       
230        def connect_style_event(self, widget, set_fg = False, set_bg = False):
231                self.disconnect_style_event(widget)
232                self.style_event_id = widget.connect('style-set',
233                        self._on_style_set_event, set_fg, set_bg)
234                self.handlers[self.style_event_id] = widget
235       
236        def _on_style_set_event(self, widget, style, *opts):
237                '''set style of widget from style class *.Frame.Eventbox
238                        opts[0] == True -> set fg color
239                        opts[1] == True -> set bg color'''
240                banner_eventbox = self.xml.get_widget('banner_eventbox')
241                self.disconnect_style_event(widget)
242                if opts[1]:
243                        bg_color = widget.style.bg[gtk.STATE_SELECTED]
244                        banner_eventbox.modify_bg(gtk.STATE_NORMAL, bg_color)
245                if opts[0]:
246                        fg_color = widget.style.fg[gtk.STATE_SELECTED]
247                        widget.modify_fg(gtk.STATE_NORMAL, fg_color)
248                self.connect_style_event(widget, opts[0], opts[1])
249       
250        def _on_keypress_event(self, widget, event):
251                if event.state & gtk.gdk.CONTROL_MASK:
252                        # CTRL + l|L: clear conv_textview
253                        if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
254                                self.conv_textview.tv.get_buffer().set_text('')
255                                return True
256                        # CTRL + v: Paste into msg_textview
257                        elif event.keyval == gtk.keysyms.v:
258                                if not self.msg_textview.is_focus():
259                                        self.msg_textview.grab_focus()
260                                # Paste into the msg textview
261                                self.msg_textview.emit('key_press_event', event)
262                        # CTRL + u: emacs style clear line
263                        elif event.keyval == gtk.keysyms.u:
264                                self.clear(self.msg_textview) # clear message textview too
265                        elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
266                                self.parent_win.move_to_next_unread_tab(False)
267                                return True
268                        elif event.keyval == gtk.keysyms.Tab: # CTRL + TAB
269                                self.parent_win.move_to_next_unread_tab(True)
270                                return True
271                        # CTRL + PAGE_[UP|DOWN]: send to parent notebook
272                        elif event.keyval == gtk.keysyms.Page_Down or \
273                                        event.keyval == gtk.keysyms.Page_Up:
274                                self.parent_win.notebook.emit('key_press_event', event)
275                                return True
276                elif event.keyval == gtk.keysyms.m and \
277                        (event.state & gtk.gdk.MOD1_MASK): # alt + m opens emoticons menu
278                        if gajim.config.get('emoticons_theme'):
279                                msg_tv = self.msg_textview
280                                def set_emoticons_menu_position(w, msg_tv = self.msg_textview):
281                                        window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET)
282                                        # get the window position
283                                        origin = window.get_origin()
284                                        size = window.get_size()
285                                        buf = msg_tv.get_buffer()
286                                        # get the cursor position
287                                        cursor = msg_tv.get_iter_location(buf.get_iter_at_mark(
288                                                buf.get_insert()))
289                                        cursor =  msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
290                                                cursor.x, cursor.y)
291                                        x = origin[0] + cursor[0]
292                                        y = origin[1] + size[1]
293                                        menu_width, menu_height = \
294                                                gajim.interface.emoticons_menu.size_request()
295                                        #FIXME: get_line_count is not so good
296                                        #get the iter of cursor, then tv.get_line_yrange
297                                        # so we know in which y we are typing (not how many lines we have
298                                        # then go show just above the current cursor line for up
299                                        # or just below the current cursor line for down
300                                        #TEST with having 3 lines and writing in the 2nd
301                                        if y + menu_height > gtk.gdk.screen_height():
302                                                # move menu just above cursor
303                                                y -= menu_height +\
304                                                        (msg_tv.allocation.height / buf.get_line_count())
305                                        #else: # move menu just below cursor
306                                        #       y -= (msg_tv.allocation.height / buf.get_line_count())
307                                        return (x, y, True) # push_in True
308                                gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
309                                gajim.interface.emoticons_menu.popup(None, None,
310                                        set_emoticons_menu_position, 1, 0)
311                return False
312
313        def _on_message_textview_key_press_event(self, widget, event):
314                if self.widget_name == 'muc_child_vbox':
315                        if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab):
316                                self.last_key_tabs = False
317                if event.state & gtk.gdk.SHIFT_MASK:
318                        # CTRL + SHIFT + TAB
319                        if event.state & gtk.gdk.CONTROL_MASK and \
320                                        event.keyval == gtk.keysyms.ISO_Left_Tab:
321                                self.parent_win.move_to_next_unread_tab(False)
322                                return True
323                        # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
324                        elif event.keyval == gtk.keysyms.Page_Down or \
325                                        event.keyval == gtk.keysyms.Page_Up:
326                                self.conv_textview.tv.emit('key_press_event', event)
327                                return True
328                elif event.state & gtk.gdk.CONTROL_MASK:
329                        if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
330                                self.parent_win.move_to_next_unread_tab(True)
331                                return True
332                        # CTRL + PAGE_[UP|DOWN]: send to parent notebook
333                        elif event.keyval == gtk.keysyms.Page_Down or \
334                                        event.keyval == gtk.keysyms.Page_Up:
335                                self.parent_win.notebook.emit('key_press_event', event)
336                                return True
337                        # we pressed a control key or ctrl+sth: we don't block
338                        # the event in order to let ctrl+c (copy text) and
339                        # others do their default work
340                        self.conv_textview.tv.emit('key_press_event', event)
341                return False
342
343        def _on_message_textview_mykeypress_event(self, widget, event_keyval,
344                event_keymod):
345                '''When a key is pressed:
346                if enter is pressed without the shift key, message (if not empty) is sent
347                and printed in the conversation'''
348
349                # NOTE: handles mykeypress which is custom signal connected to this
350                # CB in new_tab(). for this singal see message_textview.py
351                message_textview = widget
352                message_buffer = message_textview.get_buffer()
353                start_iter, end_iter = message_buffer.get_bounds()
354                message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
355
356                # construct event instance from binding
357                event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
358                event.keyval = event_keyval
359                event.state = event_keymod
360                event.time = 0 # assign current time
361
362                if event.keyval == gtk.keysyms.Up:
363                        if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
364                                self.sent_messages_scroll('up', widget.get_buffer())
365                elif event.keyval == gtk.keysyms.Down:
366                        if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
367                                self.sent_messages_scroll('down', widget.get_buffer())
368                elif event.keyval == gtk.keysyms.Return or \
369                        event.keyval == gtk.keysyms.KP_Enter: # ENTER
370                        # NOTE: SHIFT + ENTER is not needed to be emulated as it is not
371                        # binding at all (textview's default action is newline)
372
373                        if gajim.config.get('send_on_ctrl_enter'):
374                                # here, we emulate GTK default action on ENTER (add new line)
375                                # normally I would add in keypress but it gets way to complex
376                                # to get instant result on changing this advanced setting
377                                if event.state == 0: # no ctrl, no shift just ENTER add newline
378                                        end_iter = message_buffer.get_end_iter()
379                                        message_buffer.insert_at_cursor('\n')
380                                        send_message = False
381                                elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
382                                        send_message = True
383                        else: # send on Enter, do newline on Ctrl Enter
384                                if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
385                                        end_iter = message_buffer.get_end_iter()
386                                        message_buffer.insert_at_cursor('\n')
387                                        send_message = False
388                                else: # ENTER
389                                        send_message = True
390                               
391                        if gajim.connections[self.account].connected < 2: # we are not connected
392                                dialog = dialogs.ErrorDialog(_('A connection is not available'),
393                                        _('Your message can not be sent until you are connected.'))
394                                send_message = False
395
396                        if send_message:
397                                self.send_message(message) # send the message
398                else:
399                        # Give the control itself a chance to process
400                        self.handle_message_textview_mykey_press(widget, event_keyval, event_keymod)
401
402        def _process_command(self, message):
403                if not message:
404                        return False
405
406                message = message[1:]
407                message_array = message.split(' ', 1)
408                command = message_array.pop(0).lower()
409                if message_array == ['']:
410                        message_array = []
411
412                if command == 'clear' and not len(message_array):
413                        self.conv_textview.clear() # clear conversation
414                        self.clear(self.msg_textview) # clear message textview too
415                        return True
416                elif message == 'compact' and not len(message_array):
417                        self.chat_buttons_set_visible(not self.hide_chat_buttons_current)
418                        self.clear(self.msg_textview)
419                        return True
420                return False
421
422        def send_message(self, message, keyID = '', type = 'chat', chatstate = None,
423        msg_id = None, composing_jep = None, resource = None):
424                '''Send the given message to the active tab'''
425                if not message or message == '\n':
426                        return
427
428                if not self._process_command(message):
429                        MessageControl.send_message(self, message, keyID, type = type,
430                                chatstate = chatstate, msg_id = msg_id,
431                                composing_jep = composing_jep, resource = resource)
432                        # Record message history
433                        self.save_sent_message(message)
434
435                # Clear msg input
436                message_buffer = self.msg_textview.get_buffer()
437                message_buffer.set_text('') # clear message buffer (and tv of course)
438
439        def save_sent_message(self, message):
440                #save the message, so user can scroll though the list with key up/down
441                size = len(self.sent_history)
442                #we don't want size of the buffer to grow indefinately
443                max_size = gajim.config.get('key_up_lines')
444                if size >= max_size:
445                        for i in xrange(0, size - 1): 
446                                self.sent_history[i] = self.sent_history[i + 1]
447                        self.sent_history[max_size - 1] = message
448                else:
449                        self.sent_history.append(message)
450                        self.sent_history_pos = size + 1
451
452                self.typing_new = True
453                self.orig_msg = ''
454
455        def print_conversation_line(self, text, kind, name, tim,
456                other_tags_for_name = [], other_tags_for_time = [], 
457                other_tags_for_text = [], count_as_new = True, subject = None):
458                '''prints 'chat' type messages'''
459                jid = self.contact.jid
460                full_jid = self.get_full_jid()
461                textview = self.conv_textview
462                end = False
463                if textview.at_the_end() or kind == 'outgoing':
464                        end = True
465                textview.print_conversation_line(text, jid, kind, name, tim,
466                        other_tags_for_name, other_tags_for_time, other_tags_for_text, subject)
467
468                if not count_as_new:
469                        return
470                if kind == 'incoming':
471                        gajim.last_message_time[self.account][full_jid] = time.time()
472                urgent = True
473                if (not self.parent_win.get_active_jid() or \
474                                full_jid != self.parent_win.get_active_jid() or \
475                                not self.parent_win.is_active() or not end) and \
476                                kind in ('incoming', 'incoming_queue'):
477                        self.nb_unread += 1
478                        if gajim.interface.systray_enabled and self.notify_on_new_messages():
479                                gajim.interface.systray.add_jid(full_jid, self.account, self.type_id)
480                        self.parent_win.redraw_tab(self)
481                        if not self.parent_win.is_active():
482                                ctrl = gajim.interface.msg_win_mgr.get_control(full_jid,
483                                        self.account)
484                                self.parent_win.show_title(urgent, ctrl)
485
486        def toggle_emoticons(self):
487                '''hide show emoticons_button and make sure emoticons_menu is always there
488                when needed'''
489                emoticons_button = self.xml.get_widget('emoticons_button')
490                if gajim.config.get('emoticons_theme'):
491                        emoticons_button.show()
492                        emoticons_button.set_no_show_all(False)
493                else:
494                        emoticons_button.hide()
495                        emoticons_button.set_no_show_all(True)
496
497        def append_emoticon(self, str_):
498                buffer = self.msg_textview.get_buffer()
499                if buffer.get_char_count():
500                        buffer.insert_at_cursor(' %s ' % str_)
501                else: # we are the beginning of buffer
502                        buffer.insert_at_cursor('%s ' % str_)
503                self.msg_textview.grab_focus()
504        def on_emoticons_button_clicked(self, widget):
505                '''popup emoticons menu'''
506                gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
507                gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
508
509        def on_actions_button_clicked(self, widget):
510                '''popup action menu'''
511                #FIXME: BUG http://bugs.gnome.org/show_bug.cgi?id=316786
512                self.button_clicked = widget
513               
514                menu = self.prepare_context_menu()
515                menu.show_all()
516                gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.parent_win)
517
518        def update_font(self):
519                font = pango.FontDescription(gajim.config.get('conversation_font'))
520                self.conv_textview.tv.modify_font(font)
521                self.msg_textview.modify_font(font)
522
523        def update_tags(self):
524                self.conv_textview.update_tags()
525
526        def clear(self, tv):
527                buffer = tv.get_buffer()
528                start, end = buffer.get_bounds()
529                buffer.delete(start, end)
530
531        def _on_history_menuitem_activate(self, widget = None, jid = None):
532                '''When history menuitem is pressed: call history window'''
533                if not jid:
534                        jid = self.contact.jid
535
536                if gajim.interface.instances['logs'].has_key(jid):
537                        gajim.interface.instances['logs'][jid].window.present()
538                else:
539                        gajim.interface.instances['logs'][jid] = \
540                                history_window.HistoryWindow(jid, self.account)
541
542        def _on_compact_view_menuitem_activate(self, widget):
543                isactive = widget.get_active()
544                self.chat_buttons_set_visible(isactive)
545
546        def set_control_active(self, state):
547                if state:
548                        jid = self.contact.jid
549                        if self.conv_textview.at_the_end():
550                                #we are at the end
551                                if self.nb_unread > 0:
552                                        self.nb_unread = self.get_specific_unread()
553                                        self.parent_win.redraw_tab(self)
554                                        self.parent_win.show_title()
555                                        if gajim.interface.systray_enabled:
556                                                gajim.interface.systray.remove_jid(self.get_full_jid(),
557                                                                                self.account,
558                                                                                self.type_id)
559                        self.msg_textview.grab_focus()
560                        # Note, we send None chatstate to preserve current
561                        self.parent_win.redraw_tab(self)
562
563        def bring_scroll_to_end(self, textview, diff_y = 0):
564                ''' scrolls to the end of textview if end is not visible '''
565                buffer = textview.get_buffer()
566                end_iter = buffer.get_end_iter()
567                end_rect = textview.get_iter_location(end_iter)
568                visible_rect = textview.get_visible_rect()
569                # scroll only if expected end is not visible
570                if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
571                        gobject.idle_add(self.scroll_to_end_iter, textview)
572
573        def scroll_to_end_iter(self, textview):
574                buffer = textview.get_buffer()
575                end_iter = buffer.get_end_iter()
576                textview.scroll_to_iter(end_iter, 0, False, 1, 1)
577                return False</