Philipp Hörist pushed to branch master at gajim / gajim

Commits:

6 changed files:

Changes:

  • src/chat_control.py
    ... ... @@ -33,25 +33,17 @@ from gi.repository import Gtk
    33 33
     from gi.repository import Gdk
    
    34 34
     from gi.repository import GdkPixbuf
    
    35 35
     from gi.repository import Pango
    
    36
    -from gi.repository import GObject
    
    37 36
     from gi.repository import GLib
    
    38 37
     import gtkgui_helpers
    
    39 38
     import gui_menu_builder
    
    40 39
     import message_control
    
    41 40
     import dialogs
    
    42
    -import history_window
    
    43
    -import notify
    
    44
    -import re
    
    45 41
     
    
    46
    -from common import events
    
    47 42
     from common import gajim
    
    48 43
     from common import helpers
    
    49 44
     from common import exceptions
    
    50 45
     from common import ged
    
    51 46
     from common import i18n
    
    52
    -from message_control import MessageControl
    
    53
    -from conversation_textview import ConversationTextview
    
    54
    -from message_textview import MessageTextView
    
    55 47
     from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
    
    56 48
     from common.contacts import GC_Contact
    
    57 49
     from common.logger import constants
    
    ... ... @@ -63,1263 +55,15 @@ from nbxmpp.protocol import NS_CHATSTATES
    63 55
     from common.connection_handlers_events import MessageOutgoingEvent
    
    64 56
     from common.exceptions import GajimGeneralException
    
    65 57
     
    
    66
    -from command_system.implementation.middleware import ChatCommandProcessor
    
    67
    -from command_system.implementation.middleware import CommandTools
    
    68 58
     from command_system.implementation.hosts import ChatCommands
    
    69 59
     
    
    70
    -# Here we load the module with the standard commands, so they are being detected
    
    71
    -# and dispatched.
    
    72
    -from command_system.implementation.standard import StandardChatCommands
    
    73
    -from command_system.implementation.execute import Execute, Show
    
    74
    -
    
    75 60
     try:
    
    76 61
         import gtkspell
    
    77 62
         HAS_GTK_SPELL = True
    
    78 63
     except (ImportError, ValueError):
    
    79 64
         HAS_GTK_SPELL = False
    
    80 65
     
    
    81
    -from common import dbus_support
    
    82
    -if dbus_support.supported:
    
    83
    -    import dbus
    
    84
    -    import remote_control
    
    85
    -
    
    86
    -################################################################################
    
    87
    -class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
    
    88
    -    """
    
    89
    -    A base class containing a banner, ConversationTextview, MessageTextView
    
    90
    -    """
    
    91
    -
    
    92
    -    keymap = Gdk.Keymap.get_default()
    
    93
    -    try:
    
    94
    -        keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
    
    95
    -    except TypeError:
    
    96
    -        keycode_c = 54
    
    97
    -    try:
    
    98
    -        keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
    
    99
    -    except TypeError:
    
    100
    -        keycode_ins = 118
    
    101
    -
    
    102
    -    def make_href(self, match):
    
    103
    -        url_color = gajim.config.get('urlmsgcolor')
    
    104
    -        url = match.group()
    
    105
    -        if not '://' in url:
    
    106
    -            url = 'http://' + url
    
    107
    -        return '<a href="%s"><span color="%s">%s</span></a>' % (url,
    
    108
    -                url_color, match.group())
    
    109
    -
    
    110
    -    def get_font_attrs(self):
    
    111
    -        """
    
    112
    -        Get pango font attributes for banner from theme settings
    
    113
    -        """
    
    114
    -        theme = gajim.config.get('roster_theme')
    
    115
    -        bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
    
    116
    -        bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
    
    117
    -
    
    118
    -        if bannerfont:
    
    119
    -            font = Pango.FontDescription(bannerfont)
    
    120
    -        else:
    
    121
    -            font = Pango.FontDescription('Normal')
    
    122
    -        if bannerfontattrs:
    
    123
    -            # B attribute is set by default
    
    124
    -            if 'B' in bannerfontattrs:
    
    125
    -                font.set_weight(Pango.Weight.HEAVY)
    
    126
    -            if 'I' in bannerfontattrs:
    
    127
    -                font.set_style(Pango.Style.ITALIC)
    
    128
    -
    
    129
    -        font_attrs = 'font_desc="%s"' % font.to_string()
    
    130
    -
    
    131
    -        # in case there is no font specified we use x-large font size
    
    132
    -        if font.get_size() == 0:
    
    133
    -            font_attrs = '%s size="x-large"' % font_attrs
    
    134
    -        font.set_weight(Pango.Weight.NORMAL)
    
    135
    -        font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
    
    136
    -        return (font_attrs, font_attrs_small)
    
    137
    -
    
    138
    -    def get_nb_unread(self):
    
    139
    -        jid = self.contact.jid
    
    140
    -        if self.resource:
    
    141
    -            jid += '/' + self.resource
    
    142
    -        type_ = self.type_id
    
    143
    -        return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
    
    144
    -                type_]))
    
    145
    -
    
    146
    -    def draw_banner(self):
    
    147
    -        """
    
    148
    -        Draw the fat line at the top of the window that houses the icon, jid, etc
    
    149
    -
    
    150
    -        Derived types MAY implement this.
    
    151
    -        """
    
    152
    -        self.draw_banner_text()
    
    153
    -        self._update_banner_state_image()
    
    154
    -        gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
    
    155
    -            self)
    
    156
    -
    
    157
    -    def update_toolbar(self):
    
    158
    -        """
    
    159
    -        update state of buttons in toolbar
    
    160
    -        """
    
    161
    -        self._update_toolbar()
    
    162
    -        gajim.plugin_manager.gui_extension_point(
    
    163
    -            'chat_control_base_update_toolbar', self)
    
    164
    -
    
    165
    -    def draw_banner_text(self):
    
    166
    -        """
    
    167
    -        Derived types SHOULD implement this
    
    168
    -        """
    
    169
    -        pass
    
    170
    -
    
    171
    -    def update_ui(self):
    
    172
    -        """
    
    173
    -        Derived types SHOULD implement this
    
    174
    -        """
    
    175
    -        self.draw_banner()
    
    176
    -
    
    177
    -    def repaint_themed_widgets(self):
    
    178
    -        """
    
    179
    -        Derived types MAY implement this
    
    180
    -        """
    
    181
    -        self._paint_banner()
    
    182
    -        self.draw_banner()
    
    183
    -
    
    184
    -    def _update_banner_state_image(self):
    
    185
    -        """
    
    186
    -        Derived types MAY implement this
    
    187
    -        """
    
    188
    -        pass
    
    189
    -
    
    190
    -    def _update_toolbar(self):
    
    191
    -        """
    
    192
    -        Derived types MAY implement this
    
    193
    -        """
    
    194
    -        pass
    
    195
    -
    
    196
    -    def _nec_our_status(self, obj):
    
    197
    -        if self.account != obj.conn.name:
    
    198
    -            return
    
    199
    -        if obj.show == 'offline' or (obj.show == 'invisible' and \
    
    200
    -        obj.conn.is_zeroconf):
    
    201
    -            self.got_disconnected()
    
    202
    -        else:
    
    203
    -            # Other code rejoins all GCs, so we don't do it here
    
    204
    -            if not self.type_id == message_control.TYPE_GC:
    
    205
    -                self.got_connected()
    
    206
    -        if self.parent_win:
    
    207
    -            self.parent_win.redraw_tab(self)
    
    208
    -
    
    209
    -    def _nec_ping_sent(self, obj):
    
    210
    -        if self.contact != obj.contact:
    
    211
    -            return
    
    212
    -        self.print_conversation(_('Ping?'), 'status')
    
    213
    -
    
    214
    -    def _nec_ping_error(self, obj):
    
    215
    -        if self.contact != obj.contact:
    
    216
    -            return
    
    217
    -        self.print_conversation(_('Error.'), 'status')
    
    218
    -
    
    219
    -    def status_url_clicked(self, widget, url):
    
    220
    -        helpers.launch_browser_mailer('url', url)
    
    221
    -
    
    222
    -    def setup_seclabel(self, combo):
    
    223
    -        self.seclabel_combo = combo
    
    224
    -        self.seclabel_combo.hide()
    
    225
    -        self.seclabel_combo.set_no_show_all(True)
    
    226
    -        lb = Gtk.ListStore(str)
    
    227
    -        self.seclabel_combo.set_model(lb)
    
    228
    -        cell = Gtk.CellRendererText()
    
    229
    -        cell.set_property('xpad', 5)  # padding for status text
    
    230
    -        self.seclabel_combo.pack_start(cell, True)
    
    231
    -        # text to show is in in first column of liststore
    
    232
    -        self.seclabel_combo.add_attribute(cell, 'text', 0)
    
    233
    -        if gajim.connections[self.account].seclabel_supported:
    
    234
    -            gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
    
    235
    -
    
    236
    -    def on_seclabels_ready(self):
    
    237
    -        lb = self.seclabel_combo.get_model()
    
    238
    -        lb.clear()
    
    239
    -        i = 0
    
    240
    -        sel = 0
    
    241
    -        catalogue = gajim.connections[self.account].seclabel_catalogues[
    
    242
    -            self.contact.jid]
    
    243
    -        for label in catalogue[2]:
    
    244
    -            lb.append([label])
    
    245
    -            if label == catalogue[3]:
    
    246
    -                sel = i
    
    247
    -            i += 1
    
    248
    -        self.seclabel_combo.set_active(sel)
    
    249
    -        self.seclabel_combo.set_no_show_all(False)
    
    250
    -        self.seclabel_combo.show_all()
    
    251
    -
    
    252
    -    def __init__(self, type_id, parent_win, widget_name, contact, acct,
    
    253
    -    resource=None):
    
    254
    -        # Undo needs this variable to know if space has been pressed.
    
    255
    -        # Initialize it to True so empty textview is saved in undo list
    
    256
    -        self.space_pressed = True
    
    257
    -
    
    258
    -        if resource is None:
    
    259
    -            # We very likely got a contact with a random resource.
    
    260
    -            # This is bad, we need the highest for caps etc.
    
    261
    -            c = gajim.contacts.get_contact_with_highest_priority(acct,
    
    262
    -                contact.jid)
    
    263
    -            if c and not isinstance(c, GC_Contact):
    
    264
    -                contact = c
    
    265
    -
    
    266
    -        MessageControl.__init__(self, type_id, parent_win, widget_name,
    
    267
    -            contact, acct, resource=resource)
    
    268
    -
    
    269
    -        widget = self.xml.get_object('history_button')
    
    270
    -        # set document-open-recent icon for history button
    
    271
    -        if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
    
    272
    -            img = self.xml.get_object('history_image')
    
    273
    -            img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
    
    274
    -
    
    275
    -        id_ = widget.connect('clicked', self._on_history_menuitem_activate)
    
    276
    -        self.handlers[id_] = widget
    
    277
    -
    
    278
    -        # when/if we do XHTML we will put formatting buttons back
    
    279
    -        widget = self.xml.get_object('emoticons_button')
    
    280
    -        widget.set_sensitive(False)
    
    281
    -        id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
    
    282
    -        self.handlers[id_] = widget
    
    283
    -
    
    284
    -        # Create banner and connect signals
    
    285
    -        widget = self.xml.get_object('banner_eventbox')
    
    286
    -        id_ = widget.connect('button-press-event',
    
    287
    -            self._on_banner_eventbox_button_press_event)
    
    288
    -        self.handlers[id_] = widget
    
    289
    -
    
    290
    -        self.urlfinder = re.compile(
    
    291
    -            r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
    
    292
    -
    
    293
    -        self.banner_status_label = self.xml.get_object('banner_label')
    
    294
    -        id_ = self.banner_status_label.connect('populate_popup',
    
    295
    -            self.on_banner_label_populate_popup)
    
    296
    -        self.handlers[id_] = self.banner_status_label
    
    297
    -
    
    298
    -        # Init DND
    
    299
    -        self.TARGET_TYPE_URI_LIST = 80
    
    300
    -        self.dnd_list = [Gtk.TargetEntry.new('text/uri-list', 0,
    
    301
    -            self.TARGET_TYPE_URI_LIST), Gtk.TargetEntry.new('MY_TREE_MODEL_ROW',
    
    302
    -            Gtk.TargetFlags.SAME_APP, 0)]
    
    303
    -        id_ = self.widget.connect('drag_data_received',
    
    304
    -            self._on_drag_data_received)
    
    305
    -        self.handlers[id_] = self.widget
    
    306
    -        self.widget.drag_dest_set(Gtk.DestDefaults.MOTION |
    
    307
    -            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
    
    308
    -            self.dnd_list, Gdk.DragAction.COPY)
    
    309
    -
    
    310
    -        # Create textviews and connect signals
    
    311
    -        self.conv_textview = ConversationTextview(self.account)
    
    312
    -        id_ = self.conv_textview.connect('quote', self.on_quote)
    
    313
    -        self.handlers[id_] = self.conv_textview.tv
    
    314
    -        id_ = self.conv_textview.tv.connect('key_press_event',
    
    315
    -            self._conv_textview_key_press_event)
    
    316
    -        self.handlers[id_] = self.conv_textview.tv
    
    317
    -        # FIXME: DND on non editable TextView, find a better way
    
    318
    -        self.drag_entered = False
    
    319
    -        id_ = self.conv_textview.tv.connect('drag_data_received',
    
    320
    -            self._on_drag_data_received)
    
    321
    -        self.handlers[id_] = self.conv_textview.tv
    
    322
    -        id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
    
    323
    -        self.handlers[id_] = self.conv_textview.tv
    
    324
    -        id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
    
    325
    -        self.handlers[id_] = self.conv_textview.tv
    
    326
    -        self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
    
    327
    -            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
    
    328
    -            self.dnd_list, Gdk.DragAction.COPY)
    
    329
    -
    
    330
    -        self.conv_scrolledwindow = self.xml.get_object(
    
    331
    -            'conversation_scrolledwindow')
    
    332
    -        self.conv_scrolledwindow.add(self.conv_textview.tv)
    
    333
    -        widget = self.conv_scrolledwindow.get_vadjustment()
    
    334
    -        id_ = widget.connect('value-changed',
    
    335
    -            self.on_conversation_vadjustment_value_changed)
    
    336
    -        self.handlers[id_] = widget
    
    337
    -        id_ = widget.connect('changed',
    
    338
    -            self.on_conversation_vadjustment_changed)
    
    339
    -        self.handlers[id_] = widget
    
    340
    -        self.scroll_to_end_id = None
    
    341
    -        self.was_at_the_end = True
    
    342
    -        self.correcting = False
    
    343
    -        self.last_sent_msg = None
    
    344
    -        self.last_received_txt = {} # one per name
    
    345
    -        self.last_received_id = {} # one per name
    
    346
    -
    
    347
    -        # add MessageTextView to UI and connect signals
    
    348
    -        self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
    
    349
    -        self.msg_textview = MessageTextView()
    
    350
    -        self.msg_scrolledwindow.add(self.msg_textview)
    
    351
    -        id_ = self.msg_textview.connect('key_press_event',
    
    352
    -            self._on_message_textview_key_press_event)
    
    353
    -        self.handlers[id_] = self.msg_textview
    
    354
    -        id_ = self.msg_textview.connect('configure-event',
    
    355
    -            self.on_configure_event)
    
    356
    -        self.handlers[id_] = self.msg_textview
    
    357
    -        id_ = self.msg_textview.connect('populate_popup',
    
    358
    -            self.on_msg_textview_populate_popup)
    
    359
    -        self.handlers[id_] = self.msg_textview
    
    360
    -        # Setup DND
    
    361
    -        id_ = self.msg_textview.connect('drag_data_received',
    
    362
    -            self._on_drag_data_received)
    
    363
    -        self.handlers[id_] = self.msg_textview
    
    364
    -        self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
    
    365
    -            Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
    
    366
    -
    
    367
    -        self.update_font()
    
    368
    -
    
    369
    -        # Hook up send button
    
    370
    -        widget = self.xml.get_object('send_button')
    
    371
    -        id_ = widget.connect('clicked', self._on_send_button_clicked)
    
    372
    -        widget.set_sensitive(False)
    
    373
    -        self.handlers[id_] = widget
    
    374
    -
    
    375
    -        # the following vars are used to keep history of user's messages
    
    376
    -        self.sent_history = []
    
    377
    -        self.sent_history_pos = 0
    
    378
    -        self.received_history = []
    
    379
    -        self.received_history_pos = 0
    
    380
    -        self.orig_msg = None
    
    381
    -
    
    382
    -        # Emoticons menu
    
    383
    -        # set image no matter if user wants at this time emoticons or not
    
    384
    -        # (so toggle works ok)
    
    385
    -        img = self.xml.get_object('emoticons_button_image')
    
    386
    -        img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
    
    387
    -            'smile.png'))
    
    388
    -        self.toggle_emoticons()
    
    389
    -
    
    390
    -        # Attach speller
    
    391
    -        if gajim.config.get('use_speller') and HAS_GTK_SPELL:
    
    392
    -            self.set_speller()
    
    393
    -        self.conv_textview.tv.show()
    
    394
    -        self._paint_banner()
    
    395
    -
    
    396
    -        # For XEP-0172
    
    397
    -        self.user_nick = None
    
    398
    -
    
    399
    -        self.smooth = True
    
    400
    -
    
    401
    -        self.command_hits = []
    
    402
    -        self.last_key_tabs = False
    
    403
    -
    
    404
    -        # PluginSystem: adding GUI extension point for ChatControlBase
    
    405
    -        # instance object (also subclasses, eg. ChatControl or GroupchatControl)
    
    406
    -        gajim.plugin_manager.gui_extension_point('chat_control_base', self)
    
    407
    -
    
    408
    -        gajim.ged.register_event_handler('our-show', ged.GUI1,
    
    409
    -            self._nec_our_status)
    
    410
    -        gajim.ged.register_event_handler('ping-sent', ged.GUI1,
    
    411
    -            self._nec_ping_sent)
    
    412
    -        gajim.ged.register_event_handler('ping-reply', ged.GUI1,
    
    413
    -            self._nec_ping_reply)
    
    414
    -        gajim.ged.register_event_handler('ping-error', ged.GUI1,
    
    415
    -            self._nec_ping_error)
    
    416
    -
    
    417
    -        # This is bascially a very nasty hack to surpass the inability
    
    418
    -        # to properly use the super, because of the old code.
    
    419
    -        CommandTools.__init__(self)
    
    420
    -
    
    421
    -    def set_speller(self):
    
    422
    -        # now set the one the user selected
    
    423
    -        per_type = 'contacts'
    
    424
    -        if self.type_id == message_control.TYPE_GC:
    
    425
    -            per_type = 'rooms'
    
    426
    -        lang = gajim.config.get_per(per_type, self.contact.jid,
    
    427
    -                'speller_language')
    
    428
    -        if not lang:
    
    429
    -            # use the default one
    
    430
    -            lang = gajim.config.get('speller_language')
    
    431
    -            if not lang:
    
    432
    -                lang = gajim.LANG
    
    433
    -        if lang:
    
    434
    -            try:
    
    435
    -                self.spell = gtkspell.Spell(self.msg_textview, lang)
    
    436
    -                self.msg_textview.lang = lang
    
    437
    -                self.spell.connect('language_changed', self.on_language_changed)
    
    438
    -            except (GObject.GError, RuntimeError, TypeError, OSError):
    
    439
    -                dialogs.AspellDictError(lang)
    
    440
    -
    
    441
    -    def on_language_changed(self, spell, lang):
    
    442
    -        per_type = 'contacts'
    
    443
    -        if self.type_id == message_control.TYPE_GC:
    
    444
    -            per_type = 'rooms'
    
    445
    -        if not gajim.config.get_per(per_type, self.contact.jid):
    
    446
    -            gajim.config.add_per(per_type, self.contact.jid)
    
    447
    -        gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
    
    448
    -            lang)
    
    449
    -        self.msg_textview.lang = lang
    
    450
    -
    
    451
    -    def on_banner_label_populate_popup(self, label, menu):
    
    452
    -        """
    
    453
    -        Override the default context menu and add our own menutiems
    
    454
    -        """
    
    455
    -        item = Gtk.SeparatorMenuItem.new()
    
    456
    -        menu.prepend(item)
    
    457
    -
    
    458
    -        menu2 = self.prepare_context_menu()
    
    459
    -        i = 0
    
    460
    -        for item in menu2:
    
    461
    -            menu2.remove(item)
    
    462
    -            menu.prepend(item)
    
    463
    -            menu.reorder_child(item, i)
    
    464
    -            i += 1
    
    465
    -        menu.show_all()
    
    466
    -
    
    467
    -    def shutdown(self):
    
    468
    -        super(ChatControlBase, self).shutdown()
    
    469
    -        # PluginSystem: removing GUI extension points connected with ChatControlBase
    
    470
    -        # instance object
    
    471
    -        gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
    
    472
    -            self)
    
    473
    -        gajim.plugin_manager.remove_gui_extension_point(
    
    474
    -            'chat_control_base_draw_banner', self)
    
    475
    -        gajim.plugin_manager.remove_gui_extension_point('print_special_text',
    
    476
    -            self)
    
    477
    -        gajim.ged.remove_event_handler('our-show', ged.GUI1,
    
    478
    -            self._nec_our_status)
    
    479
    -
    
    480
    -    def on_msg_textview_populate_popup(self, textview, menu):
    
    481
    -        """
    
    482
    -        Override the default context menu and we prepend an option to switch
    
    483
    -        languages
    
    484
    -        """
    
    485
    -        item = Gtk.MenuItem.new_with_mnemonic(_('_Undo'))
    
    486
    -        menu.prepend(item)
    
    487
    -        id_ = item.connect('activate', self.msg_textview.undo)
    
    488
    -        self.handlers[id_] = item
    
    489
    -
    
    490
    -        item = Gtk.SeparatorMenuItem.new()
    
    491
    -        menu.prepend(item)
    
    492
    -
    
    493
    -        item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
    
    494
    -        menu.prepend(item)
    
    495
    -        id_ = item.connect('activate', self.msg_textview.clear)
    
    496
    -        self.handlers[id_] = item
    
    497
    -
    
    498
    -        menu.show_all()
    
    499
    -
    
    500
    -    def on_quote(self, widget, text):
    
    501
    -        text = '>' + text.replace('\n', '\n>') + '\n'
    
    502
    -        message_buffer = self.msg_textview.get_buffer()
    
    503
    -        message_buffer.insert_at_cursor(text)
    
    504
    -
    
    505
    -    # moved from ChatControl
    
    506
    -    def _on_banner_eventbox_button_press_event(self, widget, event):
    
    507
    -        """
    
    508
    -        If right-clicked, show popup
    
    509
    -        """
    
    510
    -        if event.button == 3:  # right click
    
    511
    -            self.parent_win.popup_menu(event)
    
    512
    -
    
    513
    -    def _on_send_button_clicked(self, widget):
    
    514
    -        """
    
    515
    -        When send button is pressed: send the current message
    
    516
    -        """
    
    517
    -        message_buffer = self.msg_textview.get_buffer()
    
    518
    -        start_iter = message_buffer.get_start_iter()
    
    519
    -        end_iter = message_buffer.get_end_iter()
    
    520
    -        message = message_buffer.get_text(start_iter, end_iter, False)
    
    521
    -        xhtml = self.msg_textview.get_xhtml()
    
    522
    -
    
    523
    -        # send the message
    
    524
    -        self.send_message(message, xhtml=xhtml)
    
    525
    -
    
    526
    -    def _paint_banner(self):
    
    527
    -        """
    
    528
    -        Repaint banner with theme color
    
    529
    -        """
    
    530
    -        theme = gajim.config.get('roster_theme')
    
    531
    -        bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
    
    532
    -        textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
    
    533
    -        # the backgrounds are colored by using an eventbox by
    
    534
    -        # setting the bg color of the eventbox and the fg of the name_label
    
    535
    -        banner_eventbox = self.xml.get_object('banner_eventbox')
    
    536
    -        banner_name_label = self.xml.get_object('banner_name_label')
    
    537
    -        self.disconnect_style_event(banner_name_label)
    
    538
    -        self.disconnect_style_event(self.banner_status_label)
    
    539
    -        if bgcolor:
    
    540
    -            color = Gdk.RGBA()
    
    541
    -            Gdk.RGBA.parse(color, bgcolor)
    
    542
    -            banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
    
    543
    -                color)
    
    544
    -            default_bg = False
    
    545
    -        else:
    
    546
    -            default_bg = True
    
    547
    -        if textcolor:
    
    548
    -            color = Gdk.RGBA()
    
    549
    -            Gdk.RGBA.parse(color, textcolor)
    
    550
    -            banner_name_label.override_color(Gtk.StateType.NORMAL,
    
    551
    -                color)
    
    552
    -            self.banner_status_label.override_color(
    
    553
    -                Gtk.StateType.NORMAL, color)
    
    554
    -            default_fg = False
    
    555
    -        else:
    
    556
    -            default_fg = True
    
    557
    -        if default_bg or default_fg:
    
    558
    -            self._on_style_set_event(banner_name_label, None, default_fg,
    
    559
    -                    default_bg)
    
    560
    -            if self.banner_status_label.get_realized():
    
    561
    -                # Widget is realized
    
    562
    -                self._on_style_set_event(self.banner_status_label, None, default_fg,
    
    563
    -                        default_bg)
    
    564
    -
    
    565
    -    def disconnect_style_event(self, widget):
    
    566
    -        # Try to find the event_id
    
    567
    -        for id_ in self.handlers.keys():
    
    568
    -            if self.handlers[id_] == widget:
    
    569
    -                widget.disconnect(id_)
    
    570
    -                del self.handlers[id_]
    
    571
    -                break
    
    572
    -
    
    573
    -    def connect_style_event(self, widget, set_fg=False, set_bg=False):
    
    574
    -        self.disconnect_style_event(widget)
    
    575
    -        id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
    
    576
    -            set_bg)
    
    577
    -        self.handlers[id_] = widget
    
    578
    -
    
    579
    -    def _on_style_set_event(self, widget, style, *opts):
    
    580
    -        """
    
    581
    -        Set style of widget from style class *.Frame.Eventbox
    
    582
    -                opts[0] == True -> set fg color
    
    583
    -                opts[1] == True -> set bg color
    
    584
    -        """
    
    585
    -        banner_eventbox = self.xml.get_object('banner_eventbox')
    
    586
    -        self.disconnect_style_event(widget)
    
    587
    -        context = widget.get_style_context()
    
    588
    -        if opts[1]:
    
    589
    -            bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
    
    590
    -            banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
    
    591
    -        if opts[0]:
    
    592
    -            fg_color = context.get_color(Gtk.StateFlags.SELECTED)
    
    593
    -            widget.override_color(Gtk.StateType.NORMAL, fg_color)
    
    594
    -        self.connect_style_event(widget, opts[0], opts[1])
    
    595
    -
    
    596
    -    def _conv_textview_key_press_event(self, widget, event):
    
    597
    -        # translate any layout to latin_layout
    
    598
    -        valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
    
    599
    -        keycode = entries[0].keycode
    
    600
    -        if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
    
    601
    -        self.keycode_c, self.keycode_ins)) or (
    
    602
    -        event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
    
    603
    -        event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
    
    604
    -            return False
    
    605
    -        self.parent_win.notebook.event(event)
    
    606
    -        return True
    
    607
    -
    
    608
    -    def show_emoticons_menu(self):
    
    609
    -        if not gajim.config.get('emoticons_theme'):
    
    610
    -            return
    
    611
    -        gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
    
    612
    -        gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
    
    613
    -
    
    614
    -    def _on_message_textview_key_press_event(self, widget, event):
    
    615
    -        if event.keyval == Gdk.KEY_space:
    
    616
    -            self.space_pressed = True
    
    617
    -
    
    618
    -        elif (self.space_pressed or self.msg_textview.undo_pressed) and \
    
    619
    -        event.keyval not in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and \
    
    620
    -        not (event.keyval == Gdk.KEY_z and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
    
    621
    -            # If the space key has been pressed and now it hasnt,
    
    622
    -            # we save the buffer into the undo list. But be carefull we're not
    
    623
    -            # pressiong Control again (as in ctrl+z)
    
    624
    -            _buffer = widget.get_buffer()
    
    625
    -            start_iter, end_iter = _buffer.get_bounds()
    
    626
    -            self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter, True))
    
    627
    -            self.space_pressed = False
    
    628
    -
    
    629
    -        # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
    
    630
    -        if self.widget_name == 'groupchat_control':
    
    631
    -            if event.keyval not in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab):
    
    632
    -                self.last_key_tabs = False
    
    633
    -        if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
    
    634
    -            # CTRL + SHIFT + TAB
    
    635
    -            if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
    
    636
    -                            event.keyval == Gdk.KEY_ISO_Left_Tab:
    
    637
    -                self.parent_win.move_to_next_unread_tab(False)
    
    638
    -                return True
    
    639
    -            # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
    
    640
    -            elif event.keyval == Gdk.KEY_Page_Down or \
    
    641
    -                            event.keyval == Gdk.KEY_Page_Up:
    
    642
    -                self.conv_textview.tv.event(event)
    
    643
    -                return True
    
    644
    -        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
    
    645
    -            if event.keyval == Gdk.KEY_Tab:  # CTRL + TAB
    
    646
    -                self.parent_win.move_to_next_unread_tab(True)
    
    647
    -                return True
    
    648
    -
    
    649
    -        message_buffer = self.msg_textview.get_buffer()
    
    650
    -        event_state = event.get_state()
    
    651
    -        if event.keyval == Gdk.KEY_Tab:
    
    652
    -            start, end = message_buffer.get_bounds()
    
    653
    -            position = message_buffer.get_insert()
    
    654
    -            end = message_buffer.get_iter_at_mark(position)
    
    655
    -            text = message_buffer.get_text(start, end, False)
    
    656
    -            splitted = text.split()
    
    657
    -            if (text.startswith(self.COMMAND_PREFIX) and not
    
    658
    -            text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
    
    659
    -                text = splitted[0]
    
    660
    -                bare = text.lstrip(self.COMMAND_PREFIX)
    
    661
    -                if len(text) == 1:
    
    662
    -                    self.command_hits = []
    
    663
    -                    for command in self.list_commands():
    
    664
    -                        for name in command.names:
    
    665
    -                            self.command_hits.append(name)
    
    666
    -                else:
    
    667
    -                    if (self.last_key_tabs and self.command_hits and
    
    668
    -                            self.command_hits[0].startswith(bare)):
    
    669
    -                        self.command_hits.append(self.command_hits.pop(0))
    
    670
    -                    else:
    
    671
    -                        self.command_hits = []
    
    672
    -                        for command in self.list_commands():
    
    673
    -                            for name in command.names:
    
    674
    -                                if name.startswith(bare):
    
    675
    -                                    self.command_hits.append(name)
    
    676
    -
    
    677
    -                if self.command_hits:
    
    678
    -                    message_buffer.delete(start, end)
    
    679
    -                    message_buffer.insert_at_cursor(self.COMMAND_PREFIX + \
    
    680
    -                        self.command_hits[0] + ' ')
    
    681
    -                    self.last_key_tabs = True
    
    682
    -                return True
    
    683
    -            if self.widget_name != 'groupchat_control':
    
    684
    -                self.last_key_tabs = False
    
    685
    -        if event.keyval == Gdk.KEY_Up:
    
    686
    -            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    687
    -                if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+UP
    
    688
    -                    self.scroll_messages('up', message_buffer, 'received')
    
    689
    -                else:  # Ctrl+UP
    
    690
    -                    self.scroll_messages('up', message_buffer, 'sent')
    
    691
    -            return True
    
    692
    -        elif event.keyval == Gdk.KEY_Down:
    
    693
    -            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    694
    -                if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+Down
    
    695
    -                    self.scroll_messages('down', message_buffer, 'received')
    
    696
    -                else:  # Ctrl+Down
    
    697
    -                    self.scroll_messages('down', message_buffer, 'sent')
    
    698
    -            return True
    
    699
    -
    
    700
    -        elif event.keyval == Gdk.KEY_Return or \
    
    701
    -        event.keyval == Gdk.KEY_KP_Enter:  # ENTER
    
    702
    -            if event_state & Gdk.ModifierType.SHIFT_MASK:
    
    703
    -                return True
    
    704
    -            message_textview = widget
    
    705
    -            message_buffer = message_textview.get_buffer()
    
    706
    -            start_iter, end_iter = message_buffer.get_bounds()
    
    707
    -            message = message_buffer.get_text(start_iter, end_iter, False)
    
    708
    -            xhtml = self.msg_textview.get_xhtml()
    
    709
    -
    
    710
    -            if gajim.config.get('send_on_ctrl_enter'):
    
    711
    -                if event_state & Gdk.ModifierType.CONTROL_MASK:  # CTRL + ENTER
    
    712
    -                    send_message = True
    
    713
    -                else:
    
    714
    -                    end_iter = message_buffer.get_end_iter()
    
    715
    -                    message_buffer.insert_at_cursor('\n')
    
    716
    -                    send_message = False
    
    717
    -
    
    718
    -            else: # send on Enter, do newline on Ctrl Enter
    
    719
    -                if event_state & Gdk.ModifierType.CONTROL_MASK:  # Ctrl + ENTER
    
    720
    -                    end_iter = message_buffer.get_end_iter()
    
    721
    -                    message_buffer.insert_at_cursor('\n')
    
    722
    -                    send_message = False
    
    723
    -                else: # ENTER
    
    724
    -                    send_message = True
    
    725
    -
    
    726
    -            if gajim.connections[self.account].connected < 2 and send_message:
    
    727
    -                # we are not connected
    
    728
    -                dialogs.ErrorDialog(_('A connection is not available'),
    
    729
    -                        _('Your message can not be sent until you are connected.'))
    
    730
    -                send_message = False
    
    731
    -
    
    732
    -            if send_message:
    
    733
    -                self.send_message(message, xhtml=xhtml) # send the message
    
    734
    -            return True
    
    735
    -        elif event.keyval == Gdk.KEY_z: # CTRL+z
    
    736
    -            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    737
    -                self.msg_textview.undo()
    
    738
    -                return True
    
    739
    -
    
    740
    -        return False
    
    741
    -
    
    742
    -    def _on_drag_data_received(self, widget, context, x, y, selection,
    
    743
    -                    target_type, timestamp):
    
    744
    -        """
    
    745
    -        Derived types SHOULD implement this
    
    746
    -        """
    
    747
    -        pass
    
    748
    -
    
    749
    -    def _on_drag_leave(self, widget, context, time):
    
    750
    -        # FIXME: DND on non editable TextView, find a better way
    
    751
    -        self.drag_entered = False
    
    752
    -        self.conv_textview.tv.set_editable(False)
    
    753
    -
    
    754
    -    def _on_drag_motion(self, widget, context, x, y, time):
    
    755
    -        # FIXME: DND on non editable TextView, find a better way
    
    756
    -        if not self.drag_entered:
    
    757
    -            # We drag new data over the TextView, make it editable to catch dnd
    
    758
    -            self.drag_entered_conv = True
    
    759
    -            self.conv_textview.tv.set_editable(True)
    
    760
    -
    
    761
    -    def get_seclabel(self):
    
    762
    -        label = None
    
    763
    -        if self.seclabel_combo is not None:
    
    764
    -            idx = self.seclabel_combo.get_active()
    
    765
    -            if idx != -1:
    
    766
    -                cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
    
    767
    -                lname = cat[2][idx]
    
    768
    -                label = cat[1][lname]
    
    769
    -        return label
    
    770
    -
    
    771
    -    def send_message(self, message, keyID='', type_='chat', chatstate=None,
    
    772
    -    msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
    
    773
    -    process_commands=True, attention=False):
    
    774
    -        """
    
    775
    -        Send the given message to the active tab. Doesn't return None if error
    
    776
    -        """
    
    777
    -        if not message or message == '\n':
    
    778
    -            return None
    
    779
    -
    
    780
    -        if process_commands and self.process_as_command(message):
    
    781
    -            return
    
    782
    -
    
    783
    -        label = self.get_seclabel()
    
    784
    -
    
    785
    -        def _cb(obj, msg, cb, *cb_args):
    
    786
    -            self.last_sent_msg = msg
    
    787
    -            if cb:
    
    788
    -                cb(obj, msg, *cb_args)
    
    789
    -
    
    790
    -        if self.correcting and self.last_sent_msg:
    
    791
    -            correction_msg = self.last_sent_msg
    
    792
    -        else:
    
    793
    -            correction_msg = None
    
    794
    -
    
    795
    -        gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
    
    796
    -            account=self.account, jid=self.contact.jid, message=message,
    
    797
    -            keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
    
    798
    -            resource=resource, user_nick=self.user_nick, xhtml=xhtml,
    
    799
    -            label=label, callback=_cb, callback_args=[callback] + callback_args,
    
    800
    -            control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
    
    801
    -
    
    802
    -        # Record the history of sent messages
    
    803
    -        self.save_message(message, 'sent')
    
    804
    -
    
    805
    -        # Be sure to send user nickname only once according to JEP-0172
    
    806
    -        self.user_nick = None
    
    807
    -
    
    808
    -        # Clear msg input
    
    809
    -        message_buffer = self.msg_textview.get_buffer()
    
    810
    -        message_buffer.set_text('') # clear message buffer (and tv of course)
    
    811
    -
    
    812
    -    def save_message(self, message, msg_type):
    
    813
    -        # save the message, so user can scroll though the list with key up/down
    
    814
    -        if msg_type == 'sent':
    
    815
    -            history = self.sent_history
    
    816
    -            pos = self.sent_history_pos
    
    817
    -        else:
    
    818
    -            history = self.received_history
    
    819
    -            pos = self.received_history_pos
    
    820
    -        size = len(history)
    
    821
    -        scroll = False if pos == size else True # are we scrolling?
    
    822
    -        # we don't want size of the buffer to grow indefinately
    
    823
    -        max_size = gajim.config.get('key_up_lines')
    
    824
    -        for i in range(size - max_size + 1):
    
    825
    -            if pos == 0:
    
    826
    -                break
    
    827
    -            history.pop(0)
    
    828
    -            pos -= 1
    
    829
    -        history.append(message)
    
    830
    -        if not scroll or msg_type == 'sent':
    
    831
    -            pos = len(history)
    
    832
    -        if msg_type == 'sent':
    
    833
    -            self.sent_history_pos = pos
    
    834
    -            self.orig_msg = None
    
    835
    -        else:
    
    836
    -            self.received_history_pos = pos
    
    837
    -
    
    838
    -    def print_conversation_line(self, text, kind, name, tim,
    
    839
    -    other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
    
    840
    -    count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
    
    841
    -    xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
    
    842
    -    msg_stanza_id=None, correct_id=None, additional_data={}):
    
    843
    -        """
    
    844
    -        Print 'chat' type messages
    
    845
    -        correct_id = (message_id, correct_id)
    
    846
    -        """
    
    847
    -        jid = self.contact.jid
    
    848
    -        full_jid = self.get_full_jid()
    
    849
    -        textview = self.conv_textview
    
    850
    -        end = False
    
    851
    -        if self.was_at_the_end or kind == 'outgoing':
    
    852
    -            end = True
    
    853
    -
    
    854
    -        textview.print_conversation_line(text, jid, kind, name, tim,
    
    855
    -            other_tags_for_name, other_tags_for_time, other_tags_for_text,
    
    856
    -            subject, old_kind, xhtml, simple=simple, graphics=graphics,
    
    857
    -            displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
    
    858
    -            correct_id=correct_id, additional_data=additional_data)
    
    859
    -
    
    860
    -        if xep0184_id is not None:
    
    861
    -            textview.show_xep0184_warning(xep0184_id)
    
    862
    -
    
    863
    -        if not count_as_new:
    
    864
    -            return
    
    865
    -        if kind in ('incoming', 'incoming_queue', 'outgoing'):
    
    866
    -            self.last_received_txt[name] = text
    
    867
    -            if correct_id:
    
    868
    -                self.last_received_id[name] = correct_id[0]
    
    869
    -        if kind == 'incoming':
    
    870
    -            if not self.type_id == message_control.TYPE_GC or \
    
    871
    -            gajim.config.get('notify_on_all_muc_messages') or \
    
    872
    -            'marked' in other_tags_for_text:
    
    873
    -                # it's a normal message, or a muc message with want to be
    
    874
    -                # notified about if quitting just after
    
    875
    -                # other_tags_for_text == ['marked'] --> highlighted gc message
    
    876
    -                gajim.last_message_time[self.account][full_jid] = time.time()
    
    877
    -
    
    878
    -        if kind in ('incoming', 'incoming_queue'):
    
    879
    -            # Record the history of received messages
    
    880
    -            self.save_message(text, 'received')
    
    881
    -
    
    882
    -        if kind in ('incoming', 'incoming_queue', 'error'):
    
    883
    -            gc_message = False
    
    884
    -            if self.type_id == message_control.TYPE_GC:
    
    885
    -                gc_message = True
    
    886
    -
    
    887
    -            if ((self.parent_win and (not self.parent_win.get_active_control() or \
    
    888
    -            self != self.parent_win.get_active_control() or \
    
    889
    -            not self.parent_win.is_active() or not end)) or \
    
    890
    -            (gc_message and \
    
    891
    -            jid in gajim.interface.minimized_controls[self.account])) and \
    
    892
    -            kind in ('incoming', 'incoming_queue', 'error'):
    
    893
    -                # we want to have save this message in events list
    
    894
    -                # other_tags_for_text == ['marked'] --> highlighted gc message
    
    895
    -                if gc_message:
    
    896
    -                    if 'marked' in other_tags_for_text:
    
    897
    -                        event_type = events.PrintedMarkedGcMsgEvent
    
    898
    -                    else:
    
    899
    -                        event_type = events.PrintedGcMsgEvent
    
    900
    -                    event = 'gc_message_received'
    
    901
    -                else:
    
    902
    -                    if self.type_id == message_control.TYPE_CHAT:
    
    903
    -                        event_type = events.PrintedChatEvent
    
    904
    -                    else:
    
    905
    -                        event_type = events.PrintedPmEvent
    
    906
    -                    event = 'message_received'
    
    907
    -                show_in_roster = notify.get_show_in_roster(event,
    
    908
    -                    self.account, self.contact, self.session)
    
    909
    -                show_in_systray = notify.get_show_in_systray(event,
    
    910
    -                    self.account, self.contact, event_type.type_)
    
    911
    -
    
    912
    -                event = event_type(text, subject, self, msg_log_id,
    
    913
    -                    show_in_roster=show_in_roster,
    
    914
    -                    show_in_systray=show_in_systray)
    
    915
    -                gajim.events.add_event(self.account, full_jid, event)
    
    916
    -                # We need to redraw contact if we show in roster
    
    917
    -                if show_in_roster:
    
    918
    -                    gajim.interface.roster.draw_contact(self.contact.jid,
    
    919
    -                        self.account)
    
    920
    -
    
    921
    -        if not self.parent_win:
    
    922
    -            return
    
    923
    -
    
    924
    -        if (not self.parent_win.get_active_control() or \
    
    925
    -        self != self.parent_win.get_active_control() or \
    
    926
    -        not self.parent_win.is_active() or not end) and \
    
    927
    -        kind in ('incoming', 'incoming_queue', 'error'):
    
    928
    -            self.parent_win.redraw_tab(self)
    
    929
    -            if not self.parent_win.is_active():
    
    930
    -                self.parent_win.show_title(True, self) # Enabled Urgent hint
    
    931
    -            else:
    
    932
    -                self.parent_win.show_title(False, self) # Disabled Urgent hint
    
    933
    -
    
    934
    -    def toggle_emoticons(self):
    
    935
    -        """
    
    936
    -        Hide show emoticons_button and make sure emoticons_menu is always there
    
    937
    -        when needed
    
    938
    -        """
    
    939
    -        emoticons_button = self.xml.get_object('emoticons_button')
    
    940
    -        if gajim.config.get('emoticons_theme'):
    
    941
    -            emoticons_button.show()
    
    942
    -            emoticons_button.set_no_show_all(False)
    
    943
    -        else:
    
    944
    -            emoticons_button.hide()
    
    945
    -            emoticons_button.set_no_show_all(True)
    
    946
    -
    
    947
    -    def append_emoticon(self, str_):
    
    948
    -        buffer_ = self.msg_textview.get_buffer()
    
    949
    -        if buffer_.get_char_count():
    
    950
    -            buffer_.insert_at_cursor(' %s ' % str_)
    
    951
    -        else: # we are the beginning of buffer
    
    952
    -            buffer_.insert_at_cursor('%s ' % str_)
    
    953
    -        self.msg_textview.grab_focus()
    
    954
    -
    
    955
    -    def on_emoticons_button_clicked(self, widget):
    
    956
    -        """
    
    957
    -        Popup emoticons menu
    
    958
    -        """
    
    959
    -        gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
    
    960
    -        gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
    
    961
    -
    
    962
    -    def on_color_menuitem_activate(self, widget):
    
    963
    -        color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
    
    964
    -        color_dialog.set_use_alpha(False)
    
    965
    -        color_dialog.connect('response', self.msg_textview.color_set)
    
    966
    -        color_dialog.show_all()
    
    967
    -
    
    968
    -    def on_font_menuitem_activate(self, widget):
    
    969
    -        font_dialog = Gtk.FontChooserDialog(None, self.parent_win.window)
    
    970
    -        start, finish = self.msg_textview.get_active_iters()
    
    971
    -        font_dialog.connect('response', self.msg_textview.font_set, start, finish)
    
    972
    -        font_dialog.show_all()
    
    973
    -
    
    974
    -    def on_formatting_menuitem_activate(self, widget):
    
    975
    -        tag = widget.get_name()
    
    976
    -        self.msg_textview.set_tag(tag)
    
    977
    -
    
    978
    -    def on_clear_formatting_menuitem_activate(self, widget):
    
    979
    -        self.msg_textview.clear_tags()
    
    980
    -
    
    981
    -    def on_actions_button_clicked(self, widget):
    
    982
    -        """
    
    983
    -        Popup action menu
    
    984
    -        """
    
    985
    -        menu = self.prepare_context_menu(hide_buttonbar_items=True)
    
    986
    -        menu.show_all()
    
    987
    -        menu.attach_to_widget(widget, None)
    
    988
    -        gtkgui_helpers.popup_emoticons_under_button(menu, widget,
    
    989
    -                self.parent_win)
    
    990
    -
    
    991
    -    def update_font(self):
    
    992
    -        font = Pango.FontDescription(gajim.config.get('conversation_font'))
    
    993
    -        self.conv_textview.tv.override_font(font)
    
    994
    -        self.msg_textview.override_font(font)
    
    995
    -
    
    996
    -    def update_tags(self):
    
    997
    -        self.conv_textview.update_tags()
    
    998
    -
    
    999
    -    def clear(self, tv):
    
    1000
    -        buffer_ = tv.get_buffer()
    
    1001
    -        start, end = buffer_.get_bounds()
    
    1002
    -        buffer_.delete(start, end)
    
    1003
    -
    
    1004
    -    def _on_history_menuitem_activate(self, widget=None, jid=None):
    
    1005
    -        """
    
    1006
    -        When history menuitem is pressed: call history window
    
    1007
    -        """
    
    1008
    -        if not jid:
    
    1009
    -            jid = self.contact.jid
    
    1010
    -
    
    1011
    -        if 'logs' in gajim.interface.instances:
    
    1012
    -            gajim.interface.instances['logs'].window.present()
    
    1013
    -            gajim.interface.instances['logs'].open_history(jid, self.account)
    
    1014
    -        else:
    
    1015
    -            gajim.interface.instances['logs'] = \
    
    1016
    -                    history_window.HistoryWindow(jid, self.account)
    
    1017
    -
    
    1018
    -    def _on_send_file(self, gc_contact=None):
    
    1019
    -        """
    
    1020
    -        gc_contact can be set when we are in a groupchat control
    
    1021
    -        """
    
    1022
    -        def _on_ok(c):
    
    1023
    -            gajim.interface.instances['file_transfers'].show_file_send_request(
    
    1024
    -                    self.account, c)
    
    1025
    -        if self.TYPE_ID == message_control.TYPE_PM:
    
    1026
    -            gc_contact = self.gc_contact
    
    1027
    -        if gc_contact:
    
    1028
    -            # gc or pm
    
    1029
    -            gc_control = gajim.interface.msg_win_mgr.get_gc_control(
    
    1030
    -                    gc_contact.room_jid, self.account)
    
    1031
    -            self_contact = gajim.contacts.get_gc_contact(self.account,
    
    1032
    -                    gc_control.room_jid, gc_control.nick)
    
    1033
    -            if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
    
    1034
    -            'owner'] and self_contact.affiliation in ['admin', 'owner']:
    
    1035
    -                contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
    
    1036
    -                if not contact or contact.sub not in ('both', 'to'):
    
    1037
    -                    prim_text = _('Really send file?')
    
    1038
    -                    sec_text = _('If you send a file to %s, he/she will know your '
    
    1039
    -                            'real Jabber ID.') % gc_contact.name
    
    1040
    -                    dialog = dialogs.NonModalConfirmationDialog(prim_text,
    
    1041
    -                        sec_text, on_response_ok=(_on_ok, gc_contact))
    
    1042
    -                    dialog.popup()
    
    1043
    -                    return
    
    1044
    -            _on_ok(gc_contact)
    
    1045
    -            return
    
    1046
    -        _on_ok(self.contact)
    
    1047
    -
    
    1048
    -    def on_minimize_menuitem_toggled(self, widget):
    
    1049
    -        """
    
    1050
    -        When a grouchat is minimized, unparent the tab, put it in roster etc
    
    1051
    -        """
    
    1052
    -        old_value = True
    
    1053
    -        non_minimized_gc = gajim.config.get_per('accounts', self.account,
    
    1054
    -                'non_minimized_gc').split()
    
    1055
    -        if self.contact.jid in non_minimized_gc:
    
    1056
    -            old_value = False
    
    1057
    -        minimize = widget.get_active()
    
    1058
    -        if not minimize and not self.contact.jid in non_minimized_gc:
    
    1059
    -            non_minimized_gc.append(self.contact.jid)
    
    1060
    -        if minimize and self.contact.jid in non_minimized_gc:
    
    1061
    -            non_minimized_gc.remove(self.contact.jid)
    
    1062
    -        if old_value != minimize:
    
    1063
    -            gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
    
    1064
    -                    ' '.join(non_minimized_gc))
    
    1065
    -
    
    1066
    -    def set_control_active(self, state):
    
    1067
    -        if state:
    
    1068
    -            jid = self.contact.jid
    
    1069
    -            if self.was_at_the_end:
    
    1070
    -                # we are at the end
    
    1071
    -                type_ = ['printed_' + self.type_id]
    
    1072
    -                if self.type_id == message_control.TYPE_GC:
    
    1073
    -                    type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
    
    1074
    -                if not gajim.events.remove_events(self.account, self.get_full_jid(),
    
    1075
    -                types=type_):
    
    1076
    -                    # There were events to remove
    
    1077
    -                    self.redraw_after_event_removed(jid)
    
    1078
    -
    
    1079
    -    def bring_scroll_to_end(self, textview, diff_y=0):
    
    1080
    -        """
    
    1081
    -        Scroll to the end of textview if end is not visible
    
    1082
    -        """
    
    1083
    -        if self.scroll_to_end_id:
    
    1084
    -            # a scroll is already planned
    
    1085
    -            return
    
    1086
    -        buffer_ = textview.get_buffer()
    
    1087
    -        end_iter = buffer_.get_end_iter()
    
    1088
    -        end_rect = textview.get_iter_location(end_iter)
    
    1089
    -        visible_rect = textview.get_visible_rect()
    
    1090
    -        # scroll only if expected end is not visible
    
    1091
    -        if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
    
    1092
    -            self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
    
    1093
    -                    textview)
    
    1094
    -
    
    1095
    -    def scroll_to_end_iter(self, textview):
    
    1096
    -        buffer_ = textview.get_buffer()
    
    1097
    -        end_iter = buffer_.get_end_iter()
    
    1098
    -        textview.scroll_to_iter(end_iter, 0, False, 1, 1)
    
    1099
    -        self.scroll_to_end_id = None
    
    1100
    -        return False
    
    1101
    -
    
    1102
    -    def on_configure_event(self, msg_textview, event):
    
    1103
    -        """
    
    1104
    -        When message_textview changes its size: if the new height will enlarge
    
    1105
    -        the window, enable the scrollbar automatic policy.  Also enable scrollbar
    
    1106
    -        automatic policy for horizontal scrollbar if message we have in
    
    1107
    -        message_textview is too big
    
    1108
    -        """
    
    1109
    -        if msg_textview.get_window() is None:
    
    1110
    -            return
    
    1111
    -
    
    1112
    -        min_height = self.conv_scrolledwindow.get_property('height-request')
    
    1113
    -        conversation_height = self.conv_textview.tv.get_window().get_size()[1]
    
    1114
    -        message_height = msg_textview.get_window().get_size()[1]
    
    1115
    -        message_width = msg_textview.get_window().get_size()[0]
    
    1116
    -        # new tab is not exposed yet
    
    1117
    -        if conversation_height < 2:
    
    1118
    -            return
    
    1119
    -
    
    1120
    -        if conversation_height < min_height:
    
    1121
    -            min_height = conversation_height
    
    1122
    -
    
    1123
    -        # we don't want to always resize in height the message_textview
    
    1124
    -        # so we have minimum on conversation_textview's scrolled window
    
    1125
    -        # but we also want to avoid window resizing so if we reach that
    
    1126
    -        # minimum for conversation_textview and maximum for message_textview
    
    1127
    -        # we set to automatic the scrollbar policy
    
    1128
    -        diff_y = message_height - event.height
    
    1129
    -        if diff_y != 0:
    
    1130
    -            if conversation_height + diff_y < min_height:
    
    1131
    -                if message_height + conversation_height - min_height > min_height:
    
    1132
    -                    policy = self.msg_scrolledwindow.get_property(
    
    1133
    -                            'vscrollbar-policy')
    
    1134
    -                    if policy != Gtk.PolicyType.AUTOMATIC:
    
    1135
    -                        self.msg_scrolledwindow.set_property('vscrollbar-policy',
    
    1136
    -                                Gtk.PolicyType.AUTOMATIC)
    
    1137
    -                        self.msg_scrolledwindow.set_property('height-request',
    
    1138
    -                                message_height + conversation_height - min_height)
    
    1139
    -            else:
    
    1140
    -                self.msg_scrolledwindow.set_property('vscrollbar-policy',
    
    1141
    -                        Gtk.PolicyType.NEVER)
    
    1142
    -                self.msg_scrolledwindow.set_property('height-request', -1)
    
    1143
    -
    
    1144
    -        self.smooth = True # reinit the flag
    
    1145
    -        # enable scrollbar automatic policy for horizontal scrollbar
    
    1146
    -        # if message we have in message_textview is too big
    
    1147
    -        if event.width > message_width:
    
    1148
    -            self.msg_scrolledwindow.set_property('hscrollbar-policy',
    
    1149
    -                    Gtk.PolicyType.AUTOMATIC)
    
    1150
    -        else:
    
    1151
    -            self.msg_scrolledwindow.set_property('hscrollbar-policy',
    
    1152
    -                    Gtk.PolicyType.NEVER)
    
    1153
    -
    
    1154
    -        return True
    
    1155
    -
    
    1156
    -    def on_conversation_vadjustment_changed(self, adjustment):
    
    1157
    -        # used to stay at the end of the textview when we shrink conversation
    
    1158
    -        # textview.
    
    1159
    -        if self.was_at_the_end:
    
    1160
    -            if self.conv_textview.at_the_end():
    
    1161
    -                # we are at the end
    
    1162
    -                self.conv_textview.bring_scroll_to_end(-18)
    
    1163
    -            else:
    
    1164
    -                self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
    
    1165
    -        self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
    
    1166
    -            - adjustment.get_page_size()) < 18
    
    1167
    -
    
    1168
    -    def on_conversation_vadjustment_value_changed(self, adjustment):
    
    1169
    -        # stop automatic scroll when we manually scroll
    
    1170
    -        if not self.conv_textview.auto_scrolling:
    
    1171
    -            self.conv_textview.stop_scrolling()
    
    1172
    -        self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
    
    1173
    -            - adjustment.get_page_size()) < 18
    
    1174
    -        if self.resource:
    
    1175
    -            jid = self.contact.get_full_jid()
    
    1176
    -        else:
    
    1177
    -            jid = self.contact.jid
    
    1178
    -        types_list = []
    
    1179
    -        type_ = self.type_id
    
    1180
    -        if type_ == message_control.TYPE_GC:
    
    1181
    -            type_ = 'gc_msg'
    
    1182
    -            types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
    
    1183
    -        else: # Not a GC
    
    1184
    -            types_list = ['printed_' + type_, type_]
    
    1185
    -
    
    1186
    -        if not len(gajim.events.get_events(self.account, jid, types_list)):
    
    1187
    -            return
    
    1188
    -        if not self.parent_win:
    
    1189
    -            return
    
    1190
    -        if self.conv_textview.at_the_end() and \
    
    1191
    -        self.parent_win.get_active_control() == self and \
    
    1192
    -        self.parent_win.window.is_active():
    
    1193
    -            # we are at the end
    
    1194
    -            if self.type_id == message_control.TYPE_GC:
    
    1195
    -                if not gajim.events.remove_events(self.account, jid,
    
    1196
    -                types=types_list):
    
    1197
    -                    self.redraw_after_event_removed(jid)
    
    1198
    -            elif self.session and self.session.remove_events(types_list):
    
    1199
    -                # There were events to remove
    
    1200
    -                self.redraw_after_event_removed(jid)
    
    1201
    -
    
    1202
    -    def redraw_after_event_removed(self, jid):
    
    1203
    -        """
    
    1204
    -        We just removed a 'printed_*' event, redraw contact in roster or
    
    1205
    -        gc_roster and titles in roster and msg_win
    
    1206
    -        """
    
    1207
    -        self.parent_win.redraw_tab(self)
    
    1208
    -        self.parent_win.show_title()
    
    1209
    -        # TODO : get the contact and check notify.get_show_in_roster()
    
    1210
    -        if self.type_id == message_control.TYPE_PM:
    
    1211
    -            room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
    
    1212
    -            groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
    
    1213
    -                    room_jid, self.account)
    
    1214
    -            if room_jid in gajim.interface.minimized_controls[self.account]:
    
    1215
    -                groupchat_control = \
    
    1216
    -                        gajim.interface.minimized_controls[self.account][room_jid]
    
    1217
    -            contact = gajim.contacts.get_contact_with_highest_priority(
    
    1218
    -                self.account, room_jid)
    
    1219
    -            if contact:
    
    1220
    -                gajim.interface.roster.draw_contact(room_jid, self.account)
    
    1221
    -            if groupchat_control:
    
    1222
    -                groupchat_control.draw_contact(nick)
    
    1223
    -                if groupchat_control.parent_win:
    
    1224
    -                    groupchat_control.parent_win.redraw_tab(groupchat_control)
    
    1225
    -        else:
    
    1226
    -            gajim.interface.roster.draw_contact(jid, self.account)
    
    1227
    -            gajim.interface.roster.show_title()
    
    1228
    -
    
    1229
    -    def scroll_messages(self, direction, msg_buf, msg_type):
    
    1230
    -        if msg_type == 'sent':
    
    1231
    -            history = self.sent_history
    
    1232
    -            pos = self.sent_history_pos
    
    1233
    -            self.received_history_pos = len(self.received_history)
    
    1234
    -        else:
    
    1235
    -            history = self.received_history
    
    1236
    -            pos = self.received_history_pos
    
    1237
    -            self.sent_history_pos = len(self.sent_history)
    
    1238
    -        size = len(history)
    
    1239
    -        if self.orig_msg is None:
    
    1240
    -            # user was typing something and then went into history, so save
    
    1241
    -            # whatever is already typed
    
    1242
    -            start_iter = msg_buf.get_start_iter()
    
    1243
    -            end_iter = msg_buf.get_end_iter()
    
    1244
    -            self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
    
    1245
    -        if pos == size and size > 0 and direction == 'up' and \
    
    1246
    -        msg_type == 'sent' and not self.correcting and (not \
    
    1247
    -        history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
    
    1248
    -            self.correcting = True
    
    1249
    -            context = self.msg_textview.get_style_context()
    
    1250
    -            state = Gtk.StateFlags.NORMAL
    
    1251
    -            self.old_message_tv_color = context.get_background_color(state)
    
    1252
    -            color = Gdk.RGBA()
    
    1253
    -            Gdk.RGBA.parse(color, 'PaleGoldenrod')
    
    1254
    -            self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
    
    1255
    -                color)
    
    1256
    -            message = history[pos - 1]
    
    1257
    -            msg_buf.set_text(message)
    
    1258
    -            return
    
    1259
    -        if self.correcting:
    
    1260
    -            # We were previously correcting
    
    1261
    -            self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
    
    1262
    -                self.old_message_tv_color)
    
    1263
    -        self.correcting = False
    
    1264
    -        pos += -1 if direction == 'up' else +1
    
    1265
    -        if pos == -1:
    
    1266
    -            return
    
    1267
    -        if pos >= size:
    
    1268
    -            pos = size
    
    1269
    -            message = self.orig_msg
    
    1270
    -            self.orig_msg = None
    
    1271
    -        else:
    
    1272
    -            message = history[pos]
    
    1273
    -        if msg_type == 'sent':
    
    1274
    -            self.sent_history_pos = pos
    
    1275
    -        else:
    
    1276
    -            self.received_history_pos = pos
    
    1277
    -            if self.orig_msg is not None:
    
    1278
    -                message = '> %s\n' % message.replace('\n', '\n> ')
    
    1279
    -        msg_buf.set_text(message)
    
    1280
    -
    
    1281
    -    def lighten_color(self, color):
    
    1282
    -        p = 0.4
    
    1283
    -        mask = 0
    
    1284
    -        color.red = int((color.red * p) + (mask * (1 - p)))
    
    1285
    -        color.green = int((color.green * p) + (mask * (1 - p)))
    
    1286
    -        color.blue = int((color.blue * p) + (mask * (1 - p)))
    
    1287
    -        return color
    
    1288
    -
    
    1289
    -    def widget_set_visible(self, widget, state):
    
    1290
    -        """
    
    1291
    -        Show or hide a widget
    
    1292
    -        """
    
    1293
    -        # make the last message visible, when changing to "full view"
    
    1294
    -        if not state:
    
    1295
    -            GLib.idle_add(self.conv_textview.scroll_to_end_iter)
    
    1296
    -
    
    1297
    -        widget.set_no_show_all(state)
    
    1298
    -        if state:
    
    1299
    -            widget.hide()
    
    1300
    -        else:
    
    1301
    -            widget.show_all()
    
    1302
    -
    
    1303
    -    def chat_buttons_set_visible(self, state):
    
    1304
    -        """
    
    1305
    -        Toggle chat buttons
    
    1306
    -        """
    
    1307
    -        MessageControl.chat_buttons_set_visible(self, state)
    
    1308
    -        self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
    
    1309
    -
    
    1310
    -    def got_connected(self):
    
    1311
    -        self.msg_textview.set_sensitive(True)
    
    1312
    -        self.msg_textview.set_editable(True)
    
    1313
    -        self.update_toolbar()
    
    1314
    -
    
    1315
    -    def got_disconnected(self):
    
    1316
    -        self.msg_textview.set_sensitive(False)
    
    1317
    -        self.msg_textview.set_editable(False)
    
    1318
    -        self.conv_textview.tv.grab_focus()
    
    1319
    -
    
    1320
    -        self.no_autonegotiation = False
    
    1321
    -        self.update_toolbar()
    
    1322
    -
    
    66
    +from chat_control_base import ChatControlBase
    
    1323 67
     
    
    1324 68
     ################################################################################
    
    1325 69
     class ChatControl(ChatControlBase):
    

  • src/chat_control_base.py
    1
    +# -*- coding:utf-8 -*-
    
    2
    +## src/chat_control_base.py
    
    3
    +##
    
    4
    +## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
    
    5
    +## Copyright (C) 2006-2014 Yann Leboulanger <asterix AT lagaule.org>
    
    6
    +## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
    
    7
    +##                         Nikos Kouremenos <kourem AT gmail.com>
    
    8
    +##                         Travis Shirk <travis AT pobox.com>
    
    9
    +## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
    
    10
    +##                    Julien Pivotto <roidelapluie AT gmail.com>
    
    11
    +## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
    
    12
    +##                         Stephan Erb <steve-e AT h3c.de>
    
    13
    +## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
    
    14
    +##
    
    15
    +## This file is part of Gajim.
    
    16
    +##
    
    17
    +## Gajim is free software; you can redistribute it and/or modify
    
    18
    +## it under the terms of the GNU General Public License as published
    
    19
    +## by the Free Software Foundation; version 3 only.
    
    20
    +##
    
    21
    +## Gajim is distributed in the hope that it will be useful,
    
    22
    +## but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    23
    +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    24
    +## GNU General Public License for more details.
    
    25
    +##
    
    26
    +## You should have received a copy of the GNU General Public License
    
    27
    +## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    28
    +##
    
    29
    +
    
    30
    +import os
    
    31
    +import time
    
    32
    +from gi.repository import Gtk
    
    33
    +from gi.repository import Gdk
    
    34
    +from gi.repository import Pango
    
    35
    +from gi.repository import GObject
    
    36
    +from gi.repository import GLib
    
    37
    +import gtkgui_helpers
    
    38
    +import message_control
    
    39
    +import dialogs
    
    40
    +import history_window
    
    41
    +import notify
    
    42
    +import re
    
    43
    +
    
    44
    +from common import events
    
    45
    +from common import gajim
    
    46
    +from common import helpers
    
    47
    +from common import ged
    
    48
    +from message_control import MessageControl
    
    49
    +from conversation_textview import ConversationTextview
    
    50
    +from message_textview import MessageTextView
    
    51
    +from common.contacts import GC_Contact
    
    52
    +from common.connection_handlers_events import MessageOutgoingEvent
    
    53
    +
    
    54
    +from command_system.implementation.middleware import ChatCommandProcessor
    
    55
    +from command_system.implementation.middleware import CommandTools
    
    56
    +
    
    57
    +try:
    
    58
    +    import gtkspell
    
    59
    +    HAS_GTK_SPELL = True
    
    60
    +except (ImportError, ValueError):
    
    61
    +    HAS_GTK_SPELL = False
    
    62
    +
    
    63
    +################################################################################
    
    64
    +class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
    
    65
    +    """
    
    66
    +    A base class containing a banner, ConversationTextview, MessageTextView
    
    67
    +    """
    
    68
    +
    
    69
    +    keymap = Gdk.Keymap.get_default()
    
    70
    +    try:
    
    71
    +        keycode_c = keymap.get_entries_for_keyval(Gdk.KEY_c)[1][0].keycode
    
    72
    +    except TypeError:
    
    73
    +        keycode_c = 54
    
    74
    +    try:
    
    75
    +        keycode_ins = keymap.get_entries_for_keyval(Gdk.KEY_Insert)[1][0].keycode
    
    76
    +    except TypeError:
    
    77
    +        keycode_ins = 118
    
    78
    +
    
    79
    +    def make_href(self, match):
    
    80
    +        url_color = gajim.config.get('urlmsgcolor')
    
    81
    +        url = match.group()
    
    82
    +        if not '://' in url:
    
    83
    +            url = 'http://' + url
    
    84
    +        return '<a href="%s"><span color="%s">%s</span></a>' % (url,
    
    85
    +                url_color, match.group())
    
    86
    +
    
    87
    +    def get_font_attrs(self):
    
    88
    +        """
    
    89
    +        Get pango font attributes for banner from theme settings
    
    90
    +        """
    
    91
    +        theme = gajim.config.get('roster_theme')
    
    92
    +        bannerfont = gajim.config.get_per('themes', theme, 'bannerfont')
    
    93
    +        bannerfontattrs = gajim.config.get_per('themes', theme, 'bannerfontattrs')
    
    94
    +
    
    95
    +        if bannerfont:
    
    96
    +            font = Pango.FontDescription(bannerfont)
    
    97
    +        else:
    
    98
    +            font = Pango.FontDescription('Normal')
    
    99
    +        if bannerfontattrs:
    
    100
    +            # B attribute is set by default
    
    101
    +            if 'B' in bannerfontattrs:
    
    102
    +                font.set_weight(Pango.Weight.HEAVY)
    
    103
    +            if 'I' in bannerfontattrs:
    
    104
    +                font.set_style(Pango.Style.ITALIC)
    
    105
    +
    
    106
    +        font_attrs = 'font_desc="%s"' % font.to_string()
    
    107
    +
    
    108
    +        # in case there is no font specified we use x-large font size
    
    109
    +        if font.get_size() == 0:
    
    110
    +            font_attrs = '%s size="x-large"' % font_attrs
    
    111
    +        font.set_weight(Pango.Weight.NORMAL)
    
    112
    +        font_attrs_small = 'font_desc="%s" size="small"' % font.to_string()
    
    113
    +        return (font_attrs, font_attrs_small)
    
    114
    +
    
    115
    +    def get_nb_unread(self):
    
    116
    +        jid = self.contact.jid
    
    117
    +        if self.resource:
    
    118
    +            jid += '/' + self.resource
    
    119
    +        type_ = self.type_id
    
    120
    +        return len(gajim.events.get_events(self.account, jid, ['printed_' + type_,
    
    121
    +                type_]))
    
    122
    +
    
    123
    +    def draw_banner(self):
    
    124
    +        """
    
    125
    +        Draw the fat line at the top of the window that houses the icon, jid, etc
    
    126
    +
    
    127
    +        Derived types MAY implement this.
    
    128
    +        """
    
    129
    +        self.draw_banner_text()
    
    130
    +        self._update_banner_state_image()
    
    131
    +        gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
    
    132
    +            self)
    
    133
    +
    
    134
    +    def update_toolbar(self):
    
    135
    +        """
    
    136
    +        update state of buttons in toolbar
    
    137
    +        """
    
    138
    +        self._update_toolbar()
    
    139
    +        gajim.plugin_manager.gui_extension_point(
    
    140
    +            'chat_control_base_update_toolbar', self)
    
    141
    +
    
    142
    +    def draw_banner_text(self):
    
    143
    +        """
    
    144
    +        Derived types SHOULD implement this
    
    145
    +        """
    
    146
    +        pass
    
    147
    +
    
    148
    +    def update_ui(self):
    
    149
    +        """
    
    150
    +        Derived types SHOULD implement this
    
    151
    +        """
    
    152
    +        self.draw_banner()
    
    153
    +
    
    154
    +    def repaint_themed_widgets(self):
    
    155
    +        """
    
    156
    +        Derived types MAY implement this
    
    157
    +        """
    
    158
    +        self._paint_banner()
    
    159
    +        self.draw_banner()
    
    160
    +
    
    161
    +    def _update_banner_state_image(self):
    
    162
    +        """
    
    163
    +        Derived types MAY implement this
    
    164
    +        """
    
    165
    +        pass
    
    166
    +
    
    167
    +    def _update_toolbar(self):
    
    168
    +        """
    
    169
    +        Derived types MAY implement this
    
    170
    +        """
    
    171
    +        pass
    
    172
    +
    
    173
    +    def _nec_our_status(self, obj):
    
    174
    +        if self.account != obj.conn.name:
    
    175
    +            return
    
    176
    +        if obj.show == 'offline' or (obj.show == 'invisible' and \
    
    177
    +        obj.conn.is_zeroconf):
    
    178
    +            self.got_disconnected()
    
    179
    +        else:
    
    180
    +            # Other code rejoins all GCs, so we don't do it here
    
    181
    +            if not self.type_id == message_control.TYPE_GC:
    
    182
    +                self.got_connected()
    
    183
    +        if self.parent_win:
    
    184
    +            self.parent_win.redraw_tab(self)
    
    185
    +
    
    186
    +    def _nec_ping_sent(self, obj):
    
    187
    +        if self.contact != obj.contact:
    
    188
    +            return
    
    189
    +        self.print_conversation(_('Ping?'), 'status')
    
    190
    +
    
    191
    +    def _nec_ping_error(self, obj):
    
    192
    +        if self.contact != obj.contact:
    
    193
    +            return
    
    194
    +        self.print_conversation(_('Error.'), 'status')
    
    195
    +
    
    196
    +    def status_url_clicked(self, widget, url):
    
    197
    +        helpers.launch_browser_mailer('url', url)
    
    198
    +
    
    199
    +    def setup_seclabel(self, combo):
    
    200
    +        self.seclabel_combo = combo
    
    201
    +        self.seclabel_combo.hide()
    
    202
    +        self.seclabel_combo.set_no_show_all(True)
    
    203
    +        lb = Gtk.ListStore(str)
    
    204
    +        self.seclabel_combo.set_model(lb)
    
    205
    +        cell = Gtk.CellRendererText()
    
    206
    +        cell.set_property('xpad', 5)  # padding for status text
    
    207
    +        self.seclabel_combo.pack_start(cell, True)
    
    208
    +        # text to show is in in first column of liststore
    
    209
    +        self.seclabel_combo.add_attribute(cell, 'text', 0)
    
    210
    +        if gajim.connections[self.account].seclabel_supported:
    
    211
    +            gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
    
    212
    +
    
    213
    +    def on_seclabels_ready(self):
    
    214
    +        lb = self.seclabel_combo.get_model()
    
    215
    +        lb.clear()
    
    216
    +        i = 0
    
    217
    +        sel = 0
    
    218
    +        catalogue = gajim.connections[self.account].seclabel_catalogues[
    
    219
    +            self.contact.jid]
    
    220
    +        for label in catalogue[2]:
    
    221
    +            lb.append([label])
    
    222
    +            if label == catalogue[3]:
    
    223
    +                sel = i
    
    224
    +            i += 1
    
    225
    +        self.seclabel_combo.set_active(sel)
    
    226
    +        self.seclabel_combo.set_no_show_all(False)
    
    227
    +        self.seclabel_combo.show_all()
    
    228
    +
    
    229
    +    def __init__(self, type_id, parent_win, widget_name, contact, acct,
    
    230
    +    resource=None):
    
    231
    +        # Undo needs this variable to know if space has been pressed.
    
    232
    +        # Initialize it to True so empty textview is saved in undo list
    
    233
    +        self.space_pressed = True
    
    234
    +
    
    235
    +        if resource is None:
    
    236
    +            # We very likely got a contact with a random resource.
    
    237
    +            # This is bad, we need the highest for caps etc.
    
    238
    +            c = gajim.contacts.get_contact_with_highest_priority(acct,
    
    239
    +                contact.jid)
    
    240
    +            if c and not isinstance(c, GC_Contact):
    
    241
    +                contact = c
    
    242
    +
    
    243
    +        MessageControl.__init__(self, type_id, parent_win, widget_name,
    
    244
    +            contact, acct, resource=resource)
    
    245
    +
    
    246
    +        widget = self.xml.get_object('history_button')
    
    247
    +        # set document-open-recent icon for history button
    
    248
    +        if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
    
    249
    +            img = self.xml.get_object('history_image')
    
    250
    +            img.set_from_icon_name('document-open-recent', Gtk.IconSize.MENU)
    
    251
    +
    
    252
    +        id_ = widget.connect('clicked', self._on_history_menuitem_activate)
    
    253
    +        self.handlers[id_] = widget
    
    254
    +
    
    255
    +        # when/if we do XHTML we will put formatting buttons back
    
    256
    +        widget = self.xml.get_object('emoticons_button')
    
    257
    +        widget.set_sensitive(False)
    
    258
    +        id_ = widget.connect('clicked', self.on_emoticons_button_clicked)
    
    259
    +        self.handlers[id_] = widget
    
    260
    +
    
    261
    +        # Create banner and connect signals
    
    262
    +        widget = self.xml.get_object('banner_eventbox')
    
    263
    +        id_ = widget.connect('button-press-event',
    
    264
    +            self._on_banner_eventbox_button_press_event)
    
    265
    +        self.handlers[id_] = widget
    
    266
    +
    
    267
    +        self.urlfinder = re.compile(
    
    268
    +            r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]")
    
    269
    +
    
    270
    +        self.banner_status_label = self.xml.get_object('banner_label')
    
    271
    +        id_ = self.banner_status_label.connect('populate_popup',
    
    272
    +            self.on_banner_label_populate_popup)
    
    273
    +        self.handlers[id_] = self.banner_status_label
    
    274
    +
    
    275
    +        # Init DND
    
    276
    +        self.TARGET_TYPE_URI_LIST = 80
    
    277
    +        self.dnd_list = [Gtk.TargetEntry.new('text/uri-list', 0,
    
    278
    +            self.TARGET_TYPE_URI_LIST), Gtk.TargetEntry.new('MY_TREE_MODEL_ROW',
    
    279
    +            Gtk.TargetFlags.SAME_APP, 0)]
    
    280
    +        id_ = self.widget.connect('drag_data_received',
    
    281
    +            self._on_drag_data_received)
    
    282
    +        self.handlers[id_] = self.widget
    
    283
    +        self.widget.drag_dest_set(Gtk.DestDefaults.MOTION |
    
    284
    +            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
    
    285
    +            self.dnd_list, Gdk.DragAction.COPY)
    
    286
    +
    
    287
    +        # Create textviews and connect signals
    
    288
    +        self.conv_textview = ConversationTextview(self.account)
    
    289
    +        id_ = self.conv_textview.connect('quote', self.on_quote)
    
    290
    +        self.handlers[id_] = self.conv_textview.tv
    
    291
    +        id_ = self.conv_textview.tv.connect('key_press_event',
    
    292
    +            self._conv_textview_key_press_event)
    
    293
    +        self.handlers[id_] = self.conv_textview.tv
    
    294
    +        # FIXME: DND on non editable TextView, find a better way
    
    295
    +        self.drag_entered = False
    
    296
    +        id_ = self.conv_textview.tv.connect('drag_data_received',
    
    297
    +            self._on_drag_data_received)
    
    298
    +        self.handlers[id_] = self.conv_textview.tv
    
    299
    +        id_ = self.conv_textview.tv.connect('drag_motion', self._on_drag_motion)
    
    300
    +        self.handlers[id_] = self.conv_textview.tv
    
    301
    +        id_ = self.conv_textview.tv.connect('drag_leave', self._on_drag_leave)
    
    302
    +        self.handlers[id_] = self.conv_textview.tv
    
    303
    +        self.conv_textview.tv.drag_dest_set(Gtk.DestDefaults.MOTION |
    
    304
    +            Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
    
    305
    +            self.dnd_list, Gdk.DragAction.COPY)
    
    306
    +
    
    307
    +        self.conv_scrolledwindow = self.xml.get_object(
    
    308
    +            'conversation_scrolledwindow')
    
    309
    +        self.conv_scrolledwindow.add(self.conv_textview.tv)
    
    310
    +        widget = self.conv_scrolledwindow.get_vadjustment()
    
    311
    +        id_ = widget.connect('value-changed',
    
    312
    +            self.on_conversation_vadjustment_value_changed)
    
    313
    +        self.handlers[id_] = widget
    
    314
    +        id_ = widget.connect('changed',
    
    315
    +            self.on_conversation_vadjustment_changed)
    
    316
    +        self.handlers[id_] = widget
    
    317
    +        self.scroll_to_end_id = None
    
    318
    +        self.was_at_the_end = True
    
    319
    +        self.correcting = False
    
    320
    +        self.last_sent_msg = None
    
    321
    +        self.last_received_txt = {} # one per name
    
    322
    +        self.last_received_id = {} # one per name
    
    323
    +
    
    324
    +        # add MessageTextView to UI and connect signals
    
    325
    +        self.msg_scrolledwindow = self.xml.get_object('message_scrolledwindow')
    
    326
    +        self.msg_textview = MessageTextView()
    
    327
    +        self.msg_scrolledwindow.add(self.msg_textview)
    
    328
    +        id_ = self.msg_textview.connect('key_press_event',
    
    329
    +            self._on_message_textview_key_press_event)
    
    330
    +        self.handlers[id_] = self.msg_textview
    
    331
    +        id_ = self.msg_textview.connect('configure-event',
    
    332
    +            self.on_configure_event)
    
    333
    +        self.handlers[id_] = self.msg_textview
    
    334
    +        id_ = self.msg_textview.connect('populate_popup',
    
    335
    +            self.on_msg_textview_populate_popup)
    
    336
    +        self.handlers[id_] = self.msg_textview
    
    337
    +        # Setup DND
    
    338
    +        id_ = self.msg_textview.connect('drag_data_received',
    
    339
    +            self._on_drag_data_received)
    
    340
    +        self.handlers[id_] = self.msg_textview
    
    341
    +        self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
    
    342
    +            Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
    
    343
    +
    
    344
    +        self.update_font()
    
    345
    +
    
    346
    +        # Hook up send button
    
    347
    +        widget = self.xml.get_object('send_button')
    
    348
    +        id_ = widget.connect('clicked', self._on_send_button_clicked)
    
    349
    +        widget.set_sensitive(False)
    
    350
    +        self.handlers[id_] = widget
    
    351
    +
    
    352
    +        # the following vars are used to keep history of user's messages
    
    353
    +        self.sent_history = []
    
    354
    +        self.sent_history_pos = 0
    
    355
    +        self.received_history = []
    
    356
    +        self.received_history_pos = 0
    
    357
    +        self.orig_msg = None
    
    358
    +
    
    359
    +        # Emoticons menu
    
    360
    +        # set image no matter if user wants at this time emoticons or not
    
    361
    +        # (so toggle works ok)
    
    362
    +        img = self.xml.get_object('emoticons_button_image')
    
    363
    +        img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'static',
    
    364
    +            'smile.png'))
    
    365
    +        self.toggle_emoticons()
    
    366
    +
    
    367
    +        # Attach speller
    
    368
    +        if gajim.config.get('use_speller') and HAS_GTK_SPELL:
    
    369
    +            self.set_speller()
    
    370
    +        self.conv_textview.tv.show()
    
    371
    +        self._paint_banner()
    
    372
    +
    
    373
    +        # For XEP-0172
    
    374
    +        self.user_nick = None
    
    375
    +
    
    376
    +        self.smooth = True
    
    377
    +
    
    378
    +        self.command_hits = []
    
    379
    +        self.last_key_tabs = False
    
    380
    +
    
    381
    +        # PluginSystem: adding GUI extension point for ChatControlBase
    
    382
    +        # instance object (also subclasses, eg. ChatControl or GroupchatControl)
    
    383
    +        gajim.plugin_manager.gui_extension_point('chat_control_base', self)
    
    384
    +
    
    385
    +        gajim.ged.register_event_handler('our-show', ged.GUI1,
    
    386
    +            self._nec_our_status)
    
    387
    +        gajim.ged.register_event_handler('ping-sent', ged.GUI1,
    
    388
    +            self._nec_ping_sent)
    
    389
    +        gajim.ged.register_event_handler('ping-reply', ged.GUI1,
    
    390
    +            self._nec_ping_reply)
    
    391
    +        gajim.ged.register_event_handler('ping-error', ged.GUI1,
    
    392
    +            self._nec_ping_error)
    
    393
    +
    
    394
    +        # This is bascially a very nasty hack to surpass the inability
    
    395
    +        # to properly use the super, because of the old code.
    
    396
    +        CommandTools.__init__(self)
    
    397
    +
    
    398
    +    def set_speller(self):
    
    399
    +        # now set the one the user selected
    
    400
    +        per_type = 'contacts'
    
    401
    +        if self.type_id == message_control.TYPE_GC:
    
    402
    +            per_type = 'rooms'
    
    403
    +        lang = gajim.config.get_per(per_type, self.contact.jid,
    
    404
    +                'speller_language')
    
    405
    +        if not lang:
    
    406
    +            # use the default one
    
    407
    +            lang = gajim.config.get('speller_language')
    
    408
    +            if not lang:
    
    409
    +                lang = gajim.LANG
    
    410
    +        if lang:
    
    411
    +            try:
    
    412
    +                self.spell = gtkspell.Spell(self.msg_textview, lang)
    
    413
    +                self.msg_textview.lang = lang
    
    414
    +                self.spell.connect('language_changed', self.on_language_changed)
    
    415
    +            except (GObject.GError, RuntimeError, TypeError, OSError):
    
    416
    +                dialogs.AspellDictError(lang)
    
    417
    +
    
    418
    +    def on_language_changed(self, spell, lang):
    
    419
    +        per_type = 'contacts'
    
    420
    +        if self.type_id == message_control.TYPE_GC:
    
    421
    +            per_type = 'rooms'
    
    422
    +        if not gajim.config.get_per(per_type, self.contact.jid):
    
    423
    +            gajim.config.add_per(per_type, self.contact.jid)
    
    424
    +        gajim.config.set_per(per_type, self.contact.jid, 'speller_language',
    
    425
    +            lang)
    
    426
    +        self.msg_textview.lang = lang
    
    427
    +
    
    428
    +    def on_banner_label_populate_popup(self, label, menu):
    
    429
    +        """
    
    430
    +        Override the default context menu and add our own menutiems
    
    431
    +        """
    
    432
    +        item = Gtk.SeparatorMenuItem.new()
    
    433
    +        menu.prepend(item)
    
    434
    +
    
    435
    +        menu2 = self.prepare_context_menu()
    
    436
    +        i = 0
    
    437
    +        for item in menu2:
    
    438
    +            menu2.remove(item)
    
    439
    +            menu.prepend(item)
    
    440
    +            menu.reorder_child(item, i)
    
    441
    +            i += 1
    
    442
    +        menu.show_all()
    
    443
    +
    
    444
    +    def shutdown(self):
    
    445
    +        super(ChatControlBase, self).shutdown()
    
    446
    +        # PluginSystem: removing GUI extension points connected with ChatControlBase
    
    447
    +        # instance object
    
    448
    +        gajim.plugin_manager.remove_gui_extension_point('chat_control_base',
    
    449
    +            self)
    
    450
    +        gajim.plugin_manager.remove_gui_extension_point(
    
    451
    +            'chat_control_base_draw_banner', self)
    
    452
    +        gajim.plugin_manager.remove_gui_extension_point('print_special_text',
    
    453
    +            self)
    
    454
    +        gajim.ged.remove_event_handler('our-show', ged.GUI1,
    
    455
    +            self._nec_our_status)
    
    456
    +
    
    457
    +    def on_msg_textview_populate_popup(self, textview, menu):
    
    458
    +        """
    
    459
    +        Override the default context menu and we prepend an option to switch
    
    460
    +        languages
    
    461
    +        """
    
    462
    +        item = Gtk.MenuItem.new_with_mnemonic(_('_Undo'))
    
    463
    +        menu.prepend(item)
    
    464
    +        id_ = item.connect('activate', self.msg_textview.undo)
    
    465
    +        self.handlers[id_] = item
    
    466
    +
    
    467
    +        item = Gtk.SeparatorMenuItem.new()
    
    468
    +        menu.prepend(item)
    
    469
    +
    
    470
    +        item = Gtk.MenuItem.new_with_mnemonic(_('_Clear'))
    
    471
    +        menu.prepend(item)
    
    472
    +        id_ = item.connect('activate', self.msg_textview.clear)
    
    473
    +        self.handlers[id_] = item
    
    474
    +
    
    475
    +        menu.show_all()
    
    476
    +
    
    477
    +    def on_quote(self, widget, text):
    
    478
    +        text = '>' + text.replace('\n', '\n>') + '\n'
    
    479
    +        message_buffer = self.msg_textview.get_buffer()
    
    480
    +        message_buffer.insert_at_cursor(text)
    
    481
    +
    
    482
    +    # moved from ChatControl
    
    483
    +    def _on_banner_eventbox_button_press_event(self, widget, event):
    
    484
    +        """
    
    485
    +        If right-clicked, show popup
    
    486
    +        """
    
    487
    +        if event.button == 3:  # right click
    
    488
    +            self.parent_win.popup_menu(event)
    
    489
    +
    
    490
    +    def _on_send_button_clicked(self, widget):
    
    491
    +        """
    
    492
    +        When send button is pressed: send the current message
    
    493
    +        """
    
    494
    +        message_buffer = self.msg_textview.get_buffer()
    
    495
    +        start_iter = message_buffer.get_start_iter()
    
    496
    +        end_iter = message_buffer.get_end_iter()
    
    497
    +        message = message_buffer.get_text(start_iter, end_iter, False)
    
    498
    +        xhtml = self.msg_textview.get_xhtml()
    
    499
    +
    
    500
    +        # send the message
    
    501
    +        self.send_message(message, xhtml=xhtml)
    
    502
    +
    
    503
    +    def _paint_banner(self):
    
    504
    +        """
    
    505
    +        Repaint banner with theme color
    
    506
    +        """
    
    507
    +        theme = gajim.config.get('roster_theme')
    
    508
    +        bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
    
    509
    +        textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
    
    510
    +        # the backgrounds are colored by using an eventbox by
    
    511
    +        # setting the bg color of the eventbox and the fg of the name_label
    
    512
    +        banner_eventbox = self.xml.get_object('banner_eventbox')
    
    513
    +        banner_name_label = self.xml.get_object('banner_name_label')
    
    514
    +        self.disconnect_style_event(banner_name_label)
    
    515
    +        self.disconnect_style_event(self.banner_status_label)
    
    516
    +        if bgcolor:
    
    517
    +            color = Gdk.RGBA()
    
    518
    +            Gdk.RGBA.parse(color, bgcolor)
    
    519
    +            banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
    
    520
    +                color)
    
    521
    +            default_bg = False
    
    522
    +        else:
    
    523
    +            default_bg = True
    
    524
    +        if textcolor:
    
    525
    +            color = Gdk.RGBA()
    
    526
    +            Gdk.RGBA.parse(color, textcolor)
    
    527
    +            banner_name_label.override_color(Gtk.StateType.NORMAL,
    
    528
    +                color)
    
    529
    +            self.banner_status_label.override_color(
    
    530
    +                Gtk.StateType.NORMAL, color)
    
    531
    +            default_fg = False
    
    532
    +        else:
    
    533
    +            default_fg = True
    
    534
    +        if default_bg or default_fg:
    
    535
    +            self._on_style_set_event(banner_name_label, None, default_fg,
    
    536
    +                    default_bg)
    
    537
    +            if self.banner_status_label.get_realized():
    
    538
    +                # Widget is realized
    
    539
    +                self._on_style_set_event(self.banner_status_label, None, default_fg,
    
    540
    +                        default_bg)
    
    541
    +
    
    542
    +    def disconnect_style_event(self, widget):
    
    543
    +        # Try to find the event_id
    
    544
    +        for id_ in self.handlers.keys():
    
    545
    +            if self.handlers[id_] == widget:
    
    546
    +                widget.disconnect(id_)
    
    547
    +                del self.handlers[id_]
    
    548
    +                break
    
    549
    +
    
    550
    +    def connect_style_event(self, widget, set_fg=False, set_bg=False):
    
    551
    +        self.disconnect_style_event(widget)
    
    552
    +        id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
    
    553
    +            set_bg)
    
    554
    +        self.handlers[id_] = widget
    
    555
    +
    
    556
    +    def _on_style_set_event(self, widget, style, *opts):
    
    557
    +        """
    
    558
    +        Set style of widget from style class *.Frame.Eventbox
    
    559
    +                opts[0] == True -> set fg color
    
    560
    +                opts[1] == True -> set bg color
    
    561
    +        """
    
    562
    +        banner_eventbox = self.xml.get_object('banner_eventbox')
    
    563
    +        self.disconnect_style_event(widget)
    
    564
    +        context = widget.get_style_context()
    
    565
    +        if opts[1]:
    
    566
    +            bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
    
    567
    +            banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
    
    568
    +        if opts[0]:
    
    569
    +            fg_color = context.get_color(Gtk.StateFlags.SELECTED)
    
    570
    +            widget.override_color(Gtk.StateType.NORMAL, fg_color)
    
    571
    +        self.connect_style_event(widget, opts[0], opts[1])
    
    572
    +
    
    573
    +    def _conv_textview_key_press_event(self, widget, event):
    
    574
    +        # translate any layout to latin_layout
    
    575
    +        valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
    
    576
    +        keycode = entries[0].keycode
    
    577
    +        if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
    
    578
    +        self.keycode_c, self.keycode_ins)) or (
    
    579
    +        event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
    
    580
    +        event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
    
    581
    +            return False
    
    582
    +        self.parent_win.notebook.event(event)
    
    583
    +        return True
    
    584
    +
    
    585
    +    def show_emoticons_menu(self):
    
    586
    +        if not gajim.config.get('emoticons_theme'):
    
    587
    +            return
    
    588
    +        gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
    
    589
    +        gajim.interface.emoticons_menu.popup(None, None, None, None, 1, 0)
    
    590
    +
    
    591
    +    def _on_message_textview_key_press_event(self, widget, event):
    
    592
    +        if event.keyval == Gdk.KEY_space:
    
    593
    +            self.space_pressed = True
    
    594
    +
    
    595
    +        elif (self.space_pressed or self.msg_textview.undo_pressed) and \
    
    596
    +        event.keyval not in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and \
    
    597
    +        not (event.keyval == Gdk.KEY_z and event.get_state() & Gdk.ModifierType.CONTROL_MASK):
    
    598
    +            # If the space key has been pressed and now it hasnt,
    
    599
    +            # we save the buffer into the undo list. But be carefull we're not
    
    600
    +            # pressiong Control again (as in ctrl+z)
    
    601
    +            _buffer = widget.get_buffer()
    
    602
    +            start_iter, end_iter = _buffer.get_bounds()
    
    603
    +            self.msg_textview.save_undo(_buffer.get_text(start_iter, end_iter, True))
    
    604
    +            self.space_pressed = False
    
    605
    +
    
    606
    +        # Ctrl [+ Shift] + Tab are not forwarded to notebook. We handle it here
    
    607
    +        if self.widget_name == 'groupchat_control':
    
    608
    +            if event.keyval not in (Gdk.KEY_ISO_Left_Tab, Gdk.KEY_Tab):
    
    609
    +                self.last_key_tabs = False
    
    610
    +        if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
    
    611
    +            # CTRL + SHIFT + TAB
    
    612
    +            if event.get_state() & Gdk.ModifierType.CONTROL_MASK and \
    
    613
    +                            event.keyval == Gdk.KEY_ISO_Left_Tab:
    
    614
    +                self.parent_win.move_to_next_unread_tab(False)
    
    615
    +                return True
    
    616
    +            # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
    
    617
    +            elif event.keyval == Gdk.KEY_Page_Down or \
    
    618
    +                            event.keyval == Gdk.KEY_Page_Up:
    
    619
    +                self.conv_textview.tv.event(event)
    
    620
    +                return True
    
    621
    +        elif event.get_state() & Gdk.ModifierType.CONTROL_MASK:
    
    622
    +            if event.keyval == Gdk.KEY_Tab:  # CTRL + TAB
    
    623
    +                self.parent_win.move_to_next_unread_tab(True)
    
    624
    +                return True
    
    625
    +
    
    626
    +        message_buffer = self.msg_textview.get_buffer()
    
    627
    +        event_state = event.get_state()
    
    628
    +        if event.keyval == Gdk.KEY_Tab:
    
    629
    +            start, end = message_buffer.get_bounds()
    
    630
    +            position = message_buffer.get_insert()
    
    631
    +            end = message_buffer.get_iter_at_mark(position)
    
    632
    +            text = message_buffer.get_text(start, end, False)
    
    633
    +            splitted = text.split()
    
    634
    +            if (text.startswith(self.COMMAND_PREFIX) and not
    
    635
    +            text.startswith(self.COMMAND_PREFIX * 2) and len(splitted) == 1):
    
    636
    +                text = splitted[0]
    
    637
    +                bare = text.lstrip(self.COMMAND_PREFIX)
    
    638
    +                if len(text) == 1:
    
    639
    +                    self.command_hits = []
    
    640
    +                    for command in self.list_commands():
    
    641
    +                        for name in command.names:
    
    642
    +                            self.command_hits.append(name)
    
    643
    +                else:
    
    644
    +                    if (self.last_key_tabs and self.command_hits and
    
    645
    +                            self.command_hits[0].startswith(bare)):
    
    646
    +                        self.command_hits.append(self.command_hits.pop(0))
    
    647
    +                    else:
    
    648
    +                        self.command_hits = []
    
    649
    +                        for command in self.list_commands():
    
    650
    +                            for name in command.names:
    
    651
    +                                if name.startswith(bare):
    
    652
    +                                    self.command_hits.append(name)
    
    653
    +
    
    654
    +                if self.command_hits:
    
    655
    +                    message_buffer.delete(start, end)
    
    656
    +                    message_buffer.insert_at_cursor(self.COMMAND_PREFIX + \
    
    657
    +                        self.command_hits[0] + ' ')
    
    658
    +                    self.last_key_tabs = True
    
    659
    +                return True
    
    660
    +            if self.widget_name != 'groupchat_control':
    
    661
    +                self.last_key_tabs = False
    
    662
    +        if event.keyval == Gdk.KEY_Up:
    
    663
    +            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    664
    +                if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+UP
    
    665
    +                    self.scroll_messages('up', message_buffer, 'received')
    
    666
    +                else:  # Ctrl+UP
    
    667
    +                    self.scroll_messages('up', message_buffer, 'sent')
    
    668
    +            return True
    
    669
    +        elif event.keyval == Gdk.KEY_Down:
    
    670
    +            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    671
    +                if event_state & Gdk.ModifierType.SHIFT_MASK: # Ctrl+Shift+Down
    
    672
    +                    self.scroll_messages('down', message_buffer, 'received')
    
    673
    +                else:  # Ctrl+Down
    
    674
    +                    self.scroll_messages('down', message_buffer, 'sent')
    
    675
    +            return True
    
    676
    +
    
    677
    +        elif event.keyval == Gdk.KEY_Return or \
    
    678
    +        event.keyval == Gdk.KEY_KP_Enter:  # ENTER
    
    679
    +            if event_state & Gdk.ModifierType.SHIFT_MASK:
    
    680
    +                return True
    
    681
    +            message_textview = widget
    
    682
    +            message_buffer = message_textview.get_buffer()
    
    683
    +            start_iter, end_iter = message_buffer.get_bounds()
    
    684
    +            message = message_buffer.get_text(start_iter, end_iter, False)
    
    685
    +            xhtml = self.msg_textview.get_xhtml()
    
    686
    +
    
    687
    +            if gajim.config.get('send_on_ctrl_enter'):
    
    688
    +                if event_state & Gdk.ModifierType.CONTROL_MASK:  # CTRL + ENTER
    
    689
    +                    send_message = True
    
    690
    +                else:
    
    691
    +                    end_iter = message_buffer.get_end_iter()
    
    692
    +                    message_buffer.insert_at_cursor('\n')
    
    693
    +                    send_message = False
    
    694
    +
    
    695
    +            else: # send on Enter, do newline on Ctrl Enter
    
    696
    +                if event_state & Gdk.ModifierType.CONTROL_MASK:  # Ctrl + ENTER
    
    697
    +                    end_iter = message_buffer.get_end_iter()
    
    698
    +                    message_buffer.insert_at_cursor('\n')
    
    699
    +                    send_message = False
    
    700
    +                else: # ENTER
    
    701
    +                    send_message = True
    
    702
    +
    
    703
    +            if gajim.connections[self.account].connected < 2 and send_message:
    
    704
    +                # we are not connected
    
    705
    +                dialogs.ErrorDialog(_('A connection is not available'),
    
    706
    +                        _('Your message can not be sent until you are connected.'))
    
    707
    +                send_message = False
    
    708
    +
    
    709
    +            if send_message:
    
    710
    +                self.send_message(message, xhtml=xhtml) # send the message
    
    711
    +            return True
    
    712
    +        elif event.keyval == Gdk.KEY_z: # CTRL+z
    
    713
    +            if event_state & Gdk.ModifierType.CONTROL_MASK:
    
    714
    +                self.msg_textview.undo()
    
    715
    +                return True
    
    716
    +
    
    717
    +        return False
    
    718
    +
    
    719
    +    def _on_drag_data_received(self, widget, context, x, y, selection,
    
    720
    +                    target_type, timestamp):
    
    721
    +        """
    
    722
    +        Derived types SHOULD implement this
    
    723
    +        """
    
    724
    +        pass
    
    725
    +
    
    726
    +    def _on_drag_leave(self, widget, context, time):
    
    727
    +        # FIXME: DND on non editable TextView, find a better way
    
    728
    +        self.drag_entered = False
    
    729
    +        self.conv_textview.tv.set_editable(False)
    
    730
    +
    
    731
    +    def _on_drag_motion(self, widget, context, x, y, time):
    
    732
    +        # FIXME: DND on non editable TextView, find a better way
    
    733
    +        if not self.drag_entered:
    
    734
    +            # We drag new data over the TextView, make it editable to catch dnd
    
    735
    +            self.drag_entered_conv = True
    
    736
    +            self.conv_textview.tv.set_editable(True)
    
    737
    +
    
    738
    +    def get_seclabel(self):
    
    739
    +        label = None
    
    740
    +        if self.seclabel_combo is not None:
    
    741
    +            idx = self.seclabel_combo.get_active()
    
    742
    +            if idx != -1:
    
    743
    +                cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
    
    744
    +                lname = cat[2][idx]
    
    745
    +                label = cat[1][lname]
    
    746
    +        return label
    
    747
    +
    
    748
    +    def send_message(self, message, keyID='', type_='chat', chatstate=None,
    
    749
    +    msg_id=None, resource=None, xhtml=None, callback=None, callback_args=[],
    
    750
    +    process_commands=True, attention=False):
    
    751
    +        """
    
    752
    +        Send the given message to the active tab. Doesn't return None if error
    
    753
    +        """
    
    754
    +        if not message or message == '\n':
    
    755
    +            return None
    
    756
    +
    
    757
    +        if process_commands and self.process_as_command(message):
    
    758
    +            return
    
    759
    +
    
    760
    +        label = self.get_seclabel()
    
    761
    +
    
    762
    +        def _cb(obj, msg, cb, *cb_args):
    
    763
    +            self.last_sent_msg = msg
    
    764
    +            if cb:
    
    765
    +                cb(obj, msg, *cb_args)
    
    766
    +
    
    767
    +        if self.correcting and self.last_sent_msg:
    
    768
    +            correction_msg = self.last_sent_msg
    
    769
    +        else:
    
    770
    +            correction_msg = None
    
    771
    +
    
    772
    +        gajim.nec.push_outgoing_event(MessageOutgoingEvent(None,
    
    773
    +            account=self.account, jid=self.contact.jid, message=message,
    
    774
    +            keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
    
    775
    +            resource=resource, user_nick=self.user_nick, xhtml=xhtml,
    
    776
    +            label=label, callback=_cb, callback_args=[callback] + callback_args,
    
    777
    +            control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
    
    778
    +
    
    779
    +        # Record the history of sent messages
    
    780
    +        self.save_message(message, 'sent')
    
    781
    +
    
    782
    +        # Be sure to send user nickname only once according to JEP-0172
    
    783
    +        self.user_nick = None
    
    784
    +
    
    785
    +        # Clear msg input
    
    786
    +        message_buffer = self.msg_textview.get_buffer()
    
    787
    +        message_buffer.set_text('') # clear message buffer (and tv of course)
    
    788
    +
    
    789
    +    def save_message(self, message, msg_type):
    
    790
    +        # save the message, so user can scroll though the list with key up/down
    
    791
    +        if msg_type == 'sent':
    
    792
    +            history = self.sent_history
    
    793
    +            pos = self.sent_history_pos
    
    794
    +        else:
    
    795
    +            history = self.received_history
    
    796
    +            pos = self.received_history_pos
    
    797
    +        size = len(history)
    
    798
    +        scroll = False if pos == size else True # are we scrolling?
    
    799
    +        # we don't want size of the buffer to grow indefinately
    
    800
    +        max_size = gajim.config.get('key_up_lines')
    
    801
    +        for i in range(size - max_size + 1):
    
    802
    +            if pos == 0:
    
    803
    +                break
    
    804
    +            history.pop(0)
    
    805
    +            pos -= 1
    
    806
    +        history.append(message)
    
    807
    +        if not scroll or msg_type == 'sent':
    
    808
    +            pos = len(history)
    
    809
    +        if msg_type == 'sent':
    
    810
    +            self.sent_history_pos = pos
    
    811
    +            self.orig_msg = None
    
    812
    +        else:
    
    813
    +            self.received_history_pos = pos
    
    814
    +
    
    815
    +    def print_conversation_line(self, text, kind, name, tim,
    
    816
    +    other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[],
    
    817
    +    count_as_new=True, subject=None, old_kind=None, xhtml=None, simple=False,
    
    818
    +    xep0184_id=None, graphics=True, displaymarking=None, msg_log_id=None,
    
    819
    +    msg_stanza_id=None, correct_id=None, additional_data={}):
    
    820
    +        """
    
    821
    +        Print 'chat' type messages
    
    822
    +        correct_id = (message_id, correct_id)
    
    823
    +        """
    
    824
    +        jid = self.contact.jid
    
    825
    +        full_jid = self.get_full_jid()
    
    826
    +        textview = self.conv_textview
    
    827
    +        end = False
    
    828
    +        if self.was_at_the_end or kind == 'outgoing':
    
    829
    +            end = True
    
    830
    +
    
    831
    +        textview.print_conversation_line(text, jid, kind, name, tim,
    
    832
    +            other_tags_for_name, other_tags_for_time, other_tags_for_text,
    
    833
    +            subject, old_kind, xhtml, simple=simple, graphics=graphics,
    
    834
    +            displaymarking=displaymarking, msg_stanza_id=msg_stanza_id,
    
    835
    +            correct_id=correct_id, additional_data=additional_data)
    
    836
    +
    
    837
    +        if xep0184_id is not None:
    
    838
    +            textview.show_xep0184_warning(xep0184_id)
    
    839
    +
    
    840
    +        if not count_as_new:
    
    841
    +            return
    
    842
    +        if kind in ('incoming', 'incoming_queue', 'outgoing'):
    
    843
    +            self.last_received_txt[name] = text
    
    844
    +            if correct_id:
    
    845
    +                self.last_received_id[name] = correct_id[0]
    
    846
    +        if kind == 'incoming':
    
    847
    +            if not self.type_id == message_control.TYPE_GC or \
    
    848
    +            gajim.config.get('notify_on_all_muc_messages') or \
    
    849
    +            'marked' in other_tags_for_text:
    
    850
    +                # it's a normal message, or a muc message with want to be
    
    851
    +                # notified about if quitting just after
    
    852
    +                # other_tags_for_text == ['marked'] --> highlighted gc message
    
    853
    +                gajim.last_message_time[self.account][full_jid] = time.time()
    
    854
    +
    
    855
    +        if kind in ('incoming', 'incoming_queue'):
    
    856
    +            # Record the history of received messages
    
    857
    +            self.save_message(text, 'received')
    
    858
    +
    
    859
    +        if kind in ('incoming', 'incoming_queue', 'error'):
    
    860
    +            gc_message = False
    
    861
    +            if self.type_id == message_control.TYPE_GC:
    
    862
    +                gc_message = True
    
    863
    +
    
    864
    +            if ((self.parent_win and (not self.parent_win.get_active_control() or \
    
    865
    +            self != self.parent_win.get_active_control() or \
    
    866
    +            not self.parent_win.is_active() or not end)) or \
    
    867
    +            (gc_message and \
    
    868
    +            jid in gajim.interface.minimized_controls[self.account])) and \
    
    869
    +            kind in ('incoming', 'incoming_queue', 'error'):
    
    870
    +                # we want to have save this message in events list
    
    871
    +                # other_tags_for_text == ['marked'] --> highlighted gc message
    
    872
    +                if gc_message:
    
    873
    +                    if 'marked' in other_tags_for_text:
    
    874
    +                        event_type = events.PrintedMarkedGcMsgEvent
    
    875
    +                    else:
    
    876
    +                        event_type = events.PrintedGcMsgEvent
    
    877
    +                    event = 'gc_message_received'
    
    878
    +                else:
    
    879
    +                    if self.type_id == message_control.TYPE_CHAT:
    
    880
    +                        event_type = events.PrintedChatEvent
    
    881
    +                    else:
    
    882
    +                        event_type = events.PrintedPmEvent
    
    883
    +                    event = 'message_received'
    
    884
    +                show_in_roster = notify.get_show_in_roster(event,
    
    885
    +                    self.account, self.contact, self.session)
    
    886
    +                show_in_systray = notify.get_show_in_systray(event,
    
    887
    +                    self.account, self.contact, event_type.type_)
    
    888
    +
    
    889
    +                event = event_type(text, subject, self, msg_log_id,
    
    890
    +                    show_in_roster=show_in_roster,
    
    891
    +                    show_in_systray=show_in_systray)
    
    892
    +                gajim.events.add_event(self.account, full_jid, event)
    
    893
    +                # We need to redraw contact if we show in roster
    
    894
    +                if show_in_roster:
    
    895
    +                    gajim.interface.roster.draw_contact(self.contact.jid,
    
    896
    +                        self.account)
    
    897
    +
    
    898
    +        if not self.parent_win:
    
    899
    +            return
    
    900
    +
    
    901
    +        if (not self.parent_win.get_active_control() or \
    
    902
    +        self != self.parent_win.get_active_control() or \
    
    903
    +        not self.parent_win.is_active() or not end) and \
    
    904
    +        kind in ('incoming', 'incoming_queue', 'error'):
    
    905
    +            self.parent_win.redraw_tab(self)
    
    906
    +            if not self.parent_win.is_active():
    
    907
    +                self.parent_win.show_title(True, self) # Enabled Urgent hint
    
    908
    +            else:
    
    909
    +                self.parent_win.show_title(False, self) # Disabled Urgent hint
    
    910
    +
    
    911
    +    def toggle_emoticons(self):
    
    912
    +        """
    
    913
    +        Hide show emoticons_button and make sure emoticons_menu is always there
    
    914
    +        when needed
    
    915
    +        """
    
    916
    +        emoticons_button = self.xml.get_object('emoticons_button')
    
    917
    +        if gajim.config.get('emoticons_theme'):
    
    918
    +            emoticons_button.show()
    
    919
    +            emoticons_button.set_no_show_all(False)
    
    920
    +        else:
    
    921
    +            emoticons_button.hide()
    
    922
    +            emoticons_button.set_no_show_all(True)
    
    923
    +
    
    924
    +    def append_emoticon(self, str_):
    
    925
    +        buffer_ = self.msg_textview.get_buffer()
    
    926
    +        if buffer_.get_char_count():
    
    927
    +            buffer_.insert_at_cursor(' %s ' % str_)
    
    928
    +        else: # we are the beginning of buffer
    
    929
    +            buffer_.insert_at_cursor('%s ' % str_)
    
    930
    +        self.msg_textview.grab_focus()
    
    931
    +
    
    932
    +    def on_emoticons_button_clicked(self, widget):
    
    933
    +        """
    
    934
    +        Popup emoticons menu
    
    935
    +        """
    
    936
    +        gajim.interface.emoticon_menuitem_clicked = self.append_emoticon
    
    937
    +        gajim.interface.popup_emoticons_under_button(widget, self.parent_win)
    
    938
    +
    
    939
    +    def on_color_menuitem_activate(self, widget):
    
    940
    +        color_dialog = Gtk.ColorChooserDialog(None, self.parent_win.window)
    
    941
    +        color_dialog.set_use_alpha(False)
    
    942
    +        color_dialog.connect('response', self.msg_textview.color_set)
    
    943
    +        color_dialog.show_all()
    
    944
    +
    
    945
    +    def on_font_menuitem_activate(self, widget):
    
    946
    +        font_dialog = Gtk.FontChooserDialog(None, self.parent_win.window)
    
    947
    +        start, finish = self.msg_textview.get_active_iters()
    
    948
    +        font_dialog.connect('response', self.msg_textview.font_set, start, finish)
    
    949
    +        font_dialog.show_all()
    
    950
    +
    
    951
    +    def on_formatting_menuitem_activate(self, widget):
    
    952
    +        tag = widget.get_name()
    
    953
    +        self.msg_textview.set_tag(tag)
    
    954
    +
    
    955
    +    def on_clear_formatting_menuitem_activate(self, widget):
    
    956
    +        self.msg_textview.clear_tags()
    
    957
    +
    
    958
    +    def on_actions_button_clicked(self, widget):
    
    959
    +        """
    
    960
    +        Popup action menu
    
    961
    +        """
    
    962
    +        menu = self.prepare_context_menu(hide_buttonbar_items=True)
    
    963
    +        menu.show_all()
    
    964
    +        menu.attach_to_widget(widget, None)
    
    965
    +        gtkgui_helpers.popup_emoticons_under_button(menu, widget,
    
    966
    +                self.parent_win)
    
    967
    +
    
    968
    +    def update_font(self):
    
    969
    +        font = Pango.FontDescription(gajim.config.get('conversation_font'))
    
    970
    +        self.conv_textview.tv.override_font(font)
    
    971
    +        self.msg_textview.override_font(font)
    
    972
    +
    
    973
    +    def update_tags(self):
    
    974
    +        self.conv_textview.update_tags()
    
    975
    +
    
    976
    +    def clear(self, tv):
    
    977
    +        buffer_ = tv.get_buffer()
    
    978
    +        start, end = buffer_.get_bounds()
    
    979
    +        buffer_.delete(start, end)
    
    980
    +
    
    981
    +    def _on_history_menuitem_activate(self, widget=None, jid=None):
    
    982
    +        """
    
    983
    +        When history menuitem is pressed: call history window
    
    984
    +        """
    
    985
    +        if not jid:
    
    986
    +            jid = self.contact.jid
    
    987
    +
    
    988
    +        if 'logs' in gajim.interface.instances:
    
    989
    +            gajim.interface.instances['logs'].window.present()
    
    990
    +            gajim.interface.instances['logs'].open_history(jid, self.account)
    
    991
    +        else:
    
    992
    +            gajim.interface.instances['logs'] = \
    
    993
    +                    history_window.HistoryWindow(jid, self.account)
    
    994
    +
    
    995
    +    def _on_send_file(self, gc_contact=None):
    
    996
    +        """
    
    997
    +        gc_contact can be set when we are in a groupchat control
    
    998
    +        """
    
    999
    +        def _on_ok(c):
    
    1000
    +            gajim.interface.instances['file_transfers'].show_file_send_request(
    
    1001
    +                    self.account, c)
    
    1002
    +        if self.TYPE_ID == message_control.TYPE_PM:
    
    1003
    +            gc_contact = self.gc_contact
    
    1004
    +        if gc_contact:
    
    1005
    +            # gc or pm
    
    1006
    +            gc_control = gajim.interface.msg_win_mgr.get_gc_control(
    
    1007
    +                    gc_contact.room_jid, self.account)
    
    1008
    +            self_contact = gajim.contacts.get_gc_contact(self.account,
    
    1009
    +                    gc_control.room_jid, gc_control.nick)
    
    1010
    +            if gc_control.is_anonymous and gc_contact.affiliation not in ['admin',
    
    1011
    +            'owner'] and self_contact.affiliation in ['admin', 'owner']:
    
    1012
    +                contact = gajim.contacts.get_contact(self.account, gc_contact.jid)
    
    1013
    +                if not contact or contact.sub not in ('both', 'to'):
    
    1014
    +                    prim_text = _('Really send file?')
    
    1015
    +                    sec_text = _('If you send a file to %s, he/she will know your '
    
    1016
    +                            'real Jabber ID.') % gc_contact.name
    
    1017
    +                    dialog = dialogs.NonModalConfirmationDialog(prim_text,
    
    1018
    +                        sec_text, on_response_ok=(_on_ok, gc_contact))
    
    1019
    +                    dialog.popup()
    
    1020
    +                    return
    
    1021
    +            _on_ok(gc_contact)
    
    1022
    +            return
    
    1023
    +        _on_ok(self.contact)
    
    1024
    +
    
    1025
    +    def on_minimize_menuitem_toggled(self, widget):
    
    1026
    +        """
    
    1027
    +        When a grouchat is minimized, unparent the tab, put it in roster etc
    
    1028
    +        """
    
    1029
    +        old_value = True
    
    1030
    +        non_minimized_gc = gajim.config.get_per('accounts', self.account,
    
    1031
    +                'non_minimized_gc').split()
    
    1032
    +        if self.contact.jid in non_minimized_gc:
    
    1033
    +            old_value = False
    
    1034
    +        minimize = widget.get_active()
    
    1035
    +        if not minimize and not self.contact.jid in non_minimized_gc:
    
    1036
    +            non_minimized_gc.append(self.contact.jid)
    
    1037
    +        if minimize and self.contact.jid in non_minimized_gc:
    
    1038
    +            non_minimized_gc.remove(self.contact.jid)
    
    1039
    +        if old_value != minimize:
    
    1040
    +            gajim.config.set_per('accounts', self.account, 'non_minimized_gc',
    
    1041
    +                    ' '.join(non_minimized_gc))
    
    1042
    +
    
    1043
    +    def set_control_active(self, state):
    
    1044
    +        if state:
    
    1045
    +            jid = self.contact.jid
    
    1046
    +            if self.was_at_the_end:
    
    1047
    +                # we are at the end
    
    1048
    +                type_ = ['printed_' + self.type_id]
    
    1049
    +                if self.type_id == message_control.TYPE_GC:
    
    1050
    +                    type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
    
    1051
    +                if not gajim.events.remove_events(self.account, self.get_full_jid(),
    
    1052
    +                types=type_):
    
    1053
    +                    # There were events to remove
    
    1054
    +                    self.redraw_after_event_removed(jid)
    
    1055
    +
    
    1056
    +    def bring_scroll_to_end(self, textview, diff_y=0):
    
    1057
    +        """
    
    1058
    +        Scroll to the end of textview if end is not visible
    
    1059
    +        """
    
    1060
    +        if self.scroll_to_end_id:
    
    1061
    +            # a scroll is already planned
    
    1062
    +            return
    
    1063
    +        buffer_ = textview.get_buffer()
    
    1064
    +        end_iter = buffer_.get_end_iter()
    
    1065
    +        end_rect = textview.get_iter_location(end_iter)
    
    1066
    +        visible_rect = textview.get_visible_rect()
    
    1067
    +        # scroll only if expected end is not visible
    
    1068
    +        if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
    
    1069
    +            self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
    
    1070
    +                    textview)
    
    1071
    +
    
    1072
    +    def scroll_to_end_iter(self, textview):
    
    1073
    +        buffer_ = textview.get_buffer()
    
    1074
    +        end_iter = buffer_.get_end_iter()
    
    1075
    +        textview.scroll_to_iter(end_iter, 0, False, 1, 1)
    
    1076
    +        self.scroll_to_end_id = None
    
    1077
    +        return False
    
    1078
    +
    
    1079
    +    def on_configure_event(self, msg_textview, event):
    
    1080
    +        """
    
    1081
    +        When message_textview changes its size: if the new height will enlarge
    
    1082
    +        the window, enable the scrollbar automatic policy.  Also enable scrollbar
    
    1083
    +        automatic policy for horizontal scrollbar if message we have in
    
    1084
    +        message_textview is too big
    
    1085
    +        """
    
    1086
    +        if msg_textview.get_window() is None:
    
    1087
    +            return
    
    1088
    +
    
    1089
    +        min_height = self.conv_scrolledwindow.get_property('height-request')
    
    1090
    +        conversation_height = self.conv_textview.tv.get_window().get_size()[1]
    
    1091
    +        message_height = msg_textview.get_window().get_size()[1]
    
    1092
    +        message_width = msg_textview.get_window().get_size()[0]
    
    1093
    +        # new tab is not exposed yet
    
    1094
    +        if conversation_height < 2:
    
    1095
    +            return
    
    1096
    +
    
    1097
    +        if conversation_height < min_height:
    
    1098
    +            min_height = conversation_height
    
    1099
    +
    
    1100
    +        # we don't want to always resize in height the message_textview
    
    1101
    +        # so we have minimum on conversation_textview's scrolled window
    
    1102
    +        # but we also want to avoid window resizing so if we reach that
    
    1103
    +        # minimum for conversation_textview and maximum for message_textview
    
    1104
    +        # we set to automatic the scrollbar policy
    
    1105
    +        diff_y = message_height - event.height
    
    1106
    +        if diff_y != 0:
    
    1107
    +            if conversation_height + diff_y < min_height:
    
    1108
    +                if message_height + conversation_height - min_height > min_height:
    
    1109
    +                    policy = self.msg_scrolledwindow.get_property(
    
    1110
    +                            'vscrollbar-policy')
    
    1111
    +                    if policy != Gtk.PolicyType.AUTOMATIC:
    
    1112
    +                        self.msg_scrolledwindow.set_property('vscrollbar-policy',
    
    1113
    +                                Gtk.PolicyType.AUTOMATIC)
    
    1114
    +                        self.msg_scrolledwindow.set_property('height-request',
    
    1115
    +                                message_height + conversation_height - min_height)
    
    1116
    +            else:
    
    1117
    +                self.msg_scrolledwindow.set_property('vscrollbar-policy',
    
    1118
    +                        Gtk.PolicyType.NEVER)
    
    1119
    +                self.msg_scrolledwindow.set_property('height-request', -1)
    
    1120
    +
    
    1121
    +        self.smooth = True # reinit the flag
    
    1122
    +        # enable scrollbar automatic policy for horizontal scrollbar
    
    1123
    +        # if message we have in message_textview is too big
    
    1124
    +        if event.width > message_width:
    
    1125
    +            self.msg_scrolledwindow.set_property('hscrollbar-policy',
    
    1126
    +                    Gtk.PolicyType.AUTOMATIC)
    
    1127
    +        else:
    
    1128
    +            self.msg_scrolledwindow.set_property('hscrollbar-policy',
    
    1129
    +                    Gtk.PolicyType.NEVER)
    
    1130
    +
    
    1131
    +        return True
    
    1132
    +
    
    1133
    +    def on_conversation_vadjustment_changed(self, adjustment):
    
    1134
    +        # used to stay at the end of the textview when we shrink conversation
    
    1135
    +        # textview.
    
    1136
    +        if self.was_at_the_end:
    
    1137
    +            if self.conv_textview.at_the_end():
    
    1138
    +                # we are at the end
    
    1139
    +                self.conv_textview.bring_scroll_to_end(-18)
    
    1140
    +            else:
    
    1141
    +                self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
    
    1142
    +        self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
    
    1143
    +            - adjustment.get_page_size()) < 18
    
    1144
    +
    
    1145
    +    def on_conversation_vadjustment_value_changed(self, adjustment):
    
    1146
    +        # stop automatic scroll when we manually scroll
    
    1147
    +        if not self.conv_textview.auto_scrolling:
    
    1148
    +            self.conv_textview.stop_scrolling()
    
    1149
    +        self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
    
    1150
    +            - adjustment.get_page_size()) < 18
    
    1151
    +        if self.resource:
    
    1152
    +            jid = self.contact.get_full_jid()
    
    1153
    +        else:
    
    1154
    +            jid = self.contact.jid
    
    1155
    +        types_list = []
    
    1156
    +        type_ = self.type_id
    
    1157
    +        if type_ == message_control.TYPE_GC:
    
    1158
    +            type_ = 'gc_msg'
    
    1159
    +            types_list = ['printed_' + type_, type_, 'printed_marked_gc_msg']
    
    1160
    +        else: # Not a GC
    
    1161
    +            types_list = ['printed_' + type_, type_]
    
    1162
    +
    
    1163
    +        if not len(gajim.events.get_events(self.account, jid, types_list)):
    
    1164
    +            return
    
    1165
    +        if not self.parent_win:
    
    1166
    +            return
    
    1167
    +        if self.conv_textview.at_the_end() and \
    
    1168
    +        self.parent_win.get_active_control() == self and \
    
    1169
    +        self.parent_win.window.is_active():
    
    1170
    +            # we are at the end
    
    1171
    +            if self.type_id == message_control.TYPE_GC:
    
    1172
    +                if not gajim.events.remove_events(self.account, jid,
    
    1173
    +                types=types_list):
    
    1174
    +                    self.redraw_after_event_removed(jid)
    
    1175
    +            elif self.session and self.session.remove_events(types_list):
    
    1176
    +                # There were events to remove
    
    1177
    +                self.redraw_after_event_removed(jid)
    
    1178
    +
    
    1179
    +    def redraw_after_event_removed(self, jid):
    
    1180
    +        """
    
    1181
    +        We just removed a 'printed_*' event, redraw contact in roster or
    
    1182
    +        gc_roster and titles in roster and msg_win
    
    1183
    +        """
    
    1184
    +        self.parent_win.redraw_tab(self)
    
    1185
    +        self.parent_win.show_title()
    
    1186
    +        # TODO : get the contact and check notify.get_show_in_roster()
    
    1187
    +        if self.type_id == message_control.TYPE_PM:
    
    1188
    +            room_jid, nick = gajim.get_room_and_nick_from_fjid(jid)
    
    1189
    +            groupchat_control = gajim.interface.msg_win_mgr.get_gc_control(
    
    1190
    +                    room_jid, self.account)
    
    1191
    +            if room_jid in gajim.interface.minimized_controls[self.account]:
    
    1192
    +                groupchat_control = \
    
    1193
    +                        gajim.interface.minimized_controls[self.account][room_jid]
    
    1194
    +            contact = gajim.contacts.get_contact_with_highest_priority(
    
    1195
    +                self.account, room_jid)
    
    1196
    +            if contact:
    
    1197
    +                gajim.interface.roster.draw_contact(room_jid, self.account)
    
    1198
    +            if groupchat_control:
    
    1199
    +                groupchat_control.draw_contact(nick)
    
    1200
    +                if groupchat_control.parent_win:
    
    1201
    +                    groupchat_control.parent_win.redraw_tab(groupchat_control)
    
    1202
    +        else:
    
    1203
    +            gajim.interface.roster.draw_contact(jid, self.account)
    
    1204
    +            gajim.interface.roster.show_title()
    
    1205
    +
    
    1206
    +    def scroll_messages(self, direction, msg_buf, msg_type):
    
    1207
    +        if msg_type == 'sent':
    
    1208
    +            history = self.sent_history
    
    1209
    +            pos = self.sent_history_pos
    
    1210
    +            self.received_history_pos = len(self.received_history)
    
    1211
    +        else:
    
    1212
    +            history = self.received_history
    
    1213
    +            pos = self.received_history_pos
    
    1214
    +            self.sent_history_pos = len(self.sent_history)
    
    1215
    +        size = len(history)
    
    1216
    +        if self.orig_msg is None:
    
    1217
    +            # user was typing something and then went into history, so save
    
    1218
    +            # whatever is already typed
    
    1219
    +            start_iter = msg_buf.get_start_iter()
    
    1220
    +            end_iter = msg_buf.get_end_iter()
    
    1221
    +            self.orig_msg = msg_buf.get_text(start_iter, end_iter, False)
    
    1222
    +        if pos == size and size > 0 and direction == 'up' and \
    
    1223
    +        msg_type == 'sent' and not self.correcting and (not \
    
    1224
    +        history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
    
    1225
    +            self.correcting = True
    
    1226
    +            context = self.msg_textview.get_style_context()
    
    1227
    +            state = Gtk.StateFlags.NORMAL
    
    1228
    +            self.old_message_tv_color = context.get_background_color(state)
    
    1229
    +            color = Gdk.RGBA()
    
    1230
    +            Gdk.RGBA.parse(color, 'PaleGoldenrod')
    
    1231
    +            self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
    
    1232
    +                color)
    
    1233
    +            message = history[pos - 1]
    
    1234
    +            msg_buf.set_text(message)
    
    1235
    +            return
    
    1236
    +        if self.correcting:
    
    1237
    +            # We were previously correcting
    
    1238
    +            self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
    
    1239
    +                self.old_message_tv_color)
    
    1240
    +        self.correcting = False
    
    1241
    +        pos += -1 if direction == 'up' else +1
    
    1242
    +        if pos == -1:
    
    1243
    +            return
    
    1244
    +        if pos >= size:
    
    1245
    +            pos = size
    
    1246
    +            message = self.orig_msg
    
    1247
    +            self.orig_msg = None
    
    1248
    +        else:
    
    1249
    +            message = history[pos]
    
    1250
    +        if msg_type == 'sent':
    
    1251
    +            self.sent_history_pos = pos
    
    1252
    +        else:
    
    1253
    +            self.received_history_pos = pos
    
    1254
    +            if self.orig_msg is not None:
    
    1255
    +                message = '> %s\n' % message.replace('\n', '\n> ')
    
    1256
    +        msg_buf.set_text(message)
    
    1257
    +
    
    1258
    +    def lighten_color(self, color):
    
    1259
    +        p = 0.4
    
    1260
    +        mask = 0
    
    1261
    +        color.red = int((color.red * p) + (mask * (1 - p)))
    
    1262
    +        color.green = int((color.green * p) + (mask * (1 - p)))
    
    1263
    +        color.blue = int((color.blue * p) + (mask * (1 - p)))
    
    1264
    +        return color
    
    1265
    +
    
    1266
    +    def widget_set_visible(self, widget, state):
    
    1267
    +        """
    
    1268
    +        Show or hide a widget
    
    1269
    +        """
    
    1270
    +        # make the last message visible, when changing to "full view"
    
    1271
    +        if not state:
    
    1272
    +            GLib.idle_add(self.conv_textview.scroll_to_end_iter)
    
    1273
    +
    
    1274
    +        widget.set_no_show_all(state)
    
    1275
    +        if state:
    
    1276
    +            widget.hide()
    
    1277
    +        else:
    
    1278
    +            widget.show_all()
    
    1279
    +
    
    1280
    +    def chat_buttons_set_visible(self, state):
    
    1281
    +        """
    
    1282
    +        Toggle chat buttons
    
    1283
    +        """
    
    1284
    +        MessageControl.chat_buttons_set_visible(self, state)
    
    1285
    +        self.widget_set_visible(self.xml.get_object('actions_hbox'), state)
    
    1286
    +
    
    1287
    +    def got_connected(self):
    
    1288
    +        self.msg_textview.set_sensitive(True)
    
    1289
    +        self.msg_textview.set_editable(True)
    
    1290
    +        self.update_toolbar()
    
    1291
    +
    
    1292
    +    def got_disconnected(self):
    
    1293
    +        self.msg_textview.set_sensitive(False)
    
    1294
    +        self.msg_textview.set_editable(False)
    
    1295
    +        self.conv_textview.tv.grab_focus()
    
    1296
    +
    
    1297
    +        self.no_autonegotiation = False
    
    1298
    +        self.update_toolbar()

  • src/config.py
    ... ... @@ -45,7 +45,7 @@ import gtkgui_helpers
    45 45
     import dialogs
    
    46 46
     import cell_renderer_image
    
    47 47
     import message_control
    
    48
    -import chat_control
    
    48
    +from chat_control_base import ChatControlBase
    
    49 49
     import dataforms_widget
    
    50 50
     import profile_window
    
    51 51
     
    
    ... ... @@ -690,7 +690,7 @@ class PreferencesWindow:
    690 690
     
    
    691 691
         def apply_speller(self):
    
    692 692
             for ctrl in self._get_all_controls():
    
    693
    -            if isinstance(ctrl, chat_control.ChatControlBase):
    
    693
    +            if isinstance(ctrl, ChatControlBase):
    
    694 694
                     try:
    
    695 695
                         spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
    
    696 696
                     except (TypeError, RuntimeError, OSError):
    
    ... ... @@ -701,7 +701,7 @@ class PreferencesWindow:
    701 701
     
    
    702 702
         def remove_speller(self):
    
    703 703
             for ctrl in self._get_all_controls():
    
    704
    -            if isinstance(ctrl, chat_control.ChatControlBase):
    
    704
    +            if isinstance(ctrl, ChatControlBase):
    
    705 705
                     try:
    
    706 706
                         spell_obj = gtkspell.get_from_text_view(ctrl.msg_textview)
    
    707 707
                     except (TypeError, RuntimeError):
    

  • src/groupchat_control.py
    ... ... @@ -54,7 +54,7 @@ from common import ged
    54 54
     from common import i18n
    
    55 55
     
    
    56 56
     from chat_control import ChatControl
    
    57
    -from chat_control import ChatControlBase
    
    57
    +from chat_control_base import ChatControlBase
    
    58 58
     from common.exceptions import GajimGeneralException
    
    59 59
     
    
    60 60
     from command_system.implementation.hosts import PrivateChatCommands
    

  • src/gui_interface.py
    ... ... @@ -61,7 +61,7 @@ import dialogs
    61 61
     import notify
    
    62 62
     import message_control
    
    63 63
     
    
    64
    -from chat_control import ChatControlBase
    
    64
    +from chat_control_base import ChatControlBase
    
    65 65
     from chat_control import ChatControl
    
    66 66
     from groupchat_control import GroupchatControl
    
    67 67
     from groupchat_control import PrivateChatControl
    

  • src/message_window.py
    ... ... @@ -38,7 +38,7 @@ import common
    38 38
     import gtkgui_helpers
    
    39 39
     import message_control
    
    40 40
     import dialogs
    
    41
    -from chat_control import ChatControlBase
    
    41
    +from chat_control_base import ChatControlBase
    
    42 42
     
    
    43 43
     from common import gajim
    
    44 44