diff --git a/src/chat_control.py b/src/chat_control.py
index f354c34f1..a81a939a0 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -31,20 +31,267 @@ _ = i18n._
APP = i18n.APP
GTKGUI_GLADE = 'gtkgui.glade'
-####################
-class ChatControl(message_window.MessageControl):
- '''A MessageControl for standard 1-1 chat'''
+class ChatControlBase(MessageControl):
+ # FIXME
+ '''TODO
+ Contains a banner, ConversationTextview, MessageTextView
+ '''
+
+ def draw_banner(self):
+ self._paint_banner()
+ self._update_banner_state_image()
+ # Derived types SHOULD implement this
+ def update_state(self):
+ self.draw_banner()
+ # Derived types SHOULD implement this
+ def draw_widgets(self):
+ self.draw_banner()
+ # Derived types MUST implement this
+ def repaint_themed_widgets(self):
+ self.draw_banner()
+ # NOTE: Derived classes MAY implement this
+ def _update_banner_state_image(self):
+ pass # Derived types MAY implement this
+
+ def __init__(self, widget_name, contact):
+ MessageControl.__init__(self, widget_name, contact);
+
+ # FIXME: These are hidden from 0.8 on, but IMO all these things need
+ # to be shown optionally. Esp. the never-used Send button
+ for w in ('bold_togglebutton', 'italic_togglebutton',
+ 'underline_togglebutton'):
+ self.xml.get_widget(w).set_no_show_all(True)
+
+ # Create textviews and connect signals
+ self.conv_textview = ConversationTextview(None) # FIXME: remove account arg
+ self.conv_textview.show_all()
+ scrolledwindow = self.xml.get_widget('conversation_scrolledwindow')
+ scrolledwindow.add(self.conv_textview)
+ self.conv_textview.connect('key_press_event',
+ self.on_conversation_textview_key_press_event)
+ # add MessageTextView to UI and connect signals
+ message_scrolledwindow = self.xml.get_widget('message_scrolledwindow')
+ self.msg_textview = MessageTextView()
+ self.msg_textview.connect('mykeypress',
+ self.on_message_textview_mykeypress_event)
+ message_scrolledwindow.add(self.msg_textview)
+ self.msg_textview.connect('key_press_event',
+ self.on_message_textview_key_press_event)
+
+ def _paint_banner(self):
+ '''Repaint banner with theme color'''
+ theme = gajim.config.get('roster_theme')
+ bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
+ textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
+ # the backgrounds are colored by using an eventbox by
+ # setting the bg color of the eventbox and the fg of the name_label
+ banner_eventbox = self.xml.get_widget('banner_eventbox')
+ banner_name_label = self.xml.get_widget('banner_name_label')
+ if bgcolor:
+ banner_eventbox.modify_bg(gtk.STATE_NORMAL,
+ gtk.gdk.color_parse(bgcolor))
+ else:
+ banner_eventbox.modify_bg(gtk.STATE_NORMAL, None)
+ if textcolor:
+ banner_name_label.modify_fg(gtk.STATE_NORMAL,
+ gtk.gdk.color_parse(textcolor))
+ else:
+ banner_name_label.modify_fg(gtk.STATE_NORMAL, None)
+
+
+ def on_conversation_textview_key_press_event(self, widget, event):
+ '''Handle events from the ConversationTextview'''
+ print "ChatControl.on_conversation_textview_key_press_event", event
+ if event.state & gtk.gdk.CONTROL_MASK:
+ # CTRL + l|L
+ if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
+ self.conv_textview.get_buffer().set_text('')
+ # CTRL + v
+ elif event.keyval == gtk.keysyms.v:
+ if not self.msg_textview.is_focus():
+ self.msg_textview.grab_focus()
+ self.msg_textview.emit('key_press_event', event)
+
+ def on_message_textview_key_press_event(self, widget, event):
+ print "ChatControl.on_message_textview_key_press_event", event
+
+ if event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN
+ if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
+ self.notebook.emit('key_press_event', event)
+ if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN
+ self.conv_textview.emit('key_press_event', event)
+ elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP
+ if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP
+ self.notebook.emit('key_press_event', event)
+ if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP
+ self.conv_textview.emit('key_press_event', event)
+
+ def on_message_textview_mykeypress_event(self, widget, event_keyval,
+ event_keymod):
+ '''When a key is pressed:
+ if enter is pressed without the shift key, message (if not empty) is sent
+ and printed in the conversation'''
+ # FIXME: Need send_message
+ assert(False)
+
+ # NOTE: handles mykeypress which is custom signal connected to this
+ # CB in new_tab(). for this singal see message_textview.py
+ jid = self.contact.jid
+ message_textview = widget
+ message_buffer = message_textview.get_buffer()
+ start_iter, end_iter = message_buffer.get_bounds()
+ message = message_buffer.get_text(start_iter, end_iter, False).decode('utf-8')
+
+ # construct event instance from binding
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0 # assign current time
+
+ if event.keyval == gtk.keysyms.Up:
+ if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP
+ self.sent_messages_scroll(jid, 'up', widget.get_buffer())
+ return
+ elif event.keyval == gtk.keysyms.Down:
+ if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down
+ self.sent_messages_scroll(jid, 'down', widget.get_buffer())
+ return
+ elif event.keyval == gtk.keysyms.Return or \
+ event.keyval == gtk.keysyms.KP_Enter: # ENTER
+ # NOTE: SHIFT + ENTER is not needed to be emulated as it is not
+ # binding at all (textview's default action is newline)
+
+ if gajim.config.get('send_on_ctrl_enter'):
+ # here, we emulate GTK default action on ENTER (add new line)
+ # normally I would add in keypress but it gets way to complex
+ # to get instant result on changing this advanced setting
+ if event.state == 0: # no ctrl, no shift just ENTER add newline
+ end_iter = message_buffer.get_end_iter()
+ message_buffer.insert_at_cursor('\n')
+ send_message = False
+ elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER
+ send_message = True
+ else: # send on Enter, do newline on Ctrl Enter
+ if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER
+ end_iter = message_buffer.get_end_iter()
+ message_buffer.insert_at_cursor('\n')
+ send_message = False
+ else: # ENTER
+ send_message = True
+
+ if gajim.connections[self.account].connected < 2: # we are not connected
+ dialogs.ErrorDialog(_('A connection is not available'),
+ _('Your message can not be sent until you are connected.')).get_response()
+ send_message = False
+
+ # FIXME: Define send_message (base class??)
+ if send_message:
+ self.send_message(message) # send the message
+
+
+class ChatControl(ChatControlBase):
+ '''A control for standard 1-1 chat'''
def __init__(self, contact):
- MessageControl.__init__(self, 'chat_child_vbox', contact);
+ ChatControlBase.__init__(self, 'chat_child_vbox', contact);
self.compact_view = gajim.config.get('always_compact_view_chat')
def draw_widgets(self):
# The name banner is drawn here
- MessageControl.draw_widgets(self)
+ ChatControlBase.draw_widgets(self)
+
+ def _update_banner_state_image(self):
+ contact = self.contact
+ show = contact.show
+ jid = contact.jid
+
+ # Set banner image
+ img_32 = gajim.interface.roster.get_appropriate_state_images(jid,
+ size = '32')
+ img_16 = gajim.interface.roster.get_appropriate_state_images(jid)
+ if img_32.has_key(show) and img_32[show].get_pixbuf():
+ # we have 32x32! use it!
+ banner_image = img_32[show]
+ use_size_32 = True
+ else:
+ banner_image = img_16[show]
+ use_size_32 = False
+
+ banner_status_img = self.xml.get_widget('banner_status_image')
+ if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION:
+ banner_status_img.set_from_animation(banner_image.get_animation())
+ else:
+ pix = banner_image.get_pixbuf()
+ if use_size_32:
+ banner_status_img.set_from_pixbuf(pix)
+ else: # we need to scale 16x16 to 32x32
+ scaled_pix = pix.scale_simple(32, 32,
+ gtk.gdk.INTERP_BILINEAR)
+ banner_status_img.set_from_pixbuf(scaled_pix)
+
+ self._update_gpg()
+
+ def draw_banner(self):
+ '''Draw the fat line at the top of the window that
+ houses the status icon, name, jid, and avatar'''
+ ChatControlBase.draw_banner(self)
+
+ contact = self.contact
+ jid = contact.jid
+
+ banner_name_label = self.xml.get_widget('banner_name_label')
+ name = gtkgui_helpers.escape_for_pango_markup(contact.name)
+
+ status = contact.status
+ if status is not None:
+ banner_name_label.set_ellipsize(pango.ELLIPSIZE_END)
+ status = gtkgui_helpers.reduce_chars_newlines(status, 0, 2)
+ status = gtkgui_helpers.escape_for_pango_markup(status)
+
+ #FIXME: uncomment me when we support sending messages to specific resource
+ # composing full jid
+ #fulljid = jid
+ #if self.contacts[jid].resource:
+ # fulljid += '/' + self.contacts[jid].resource
+ #label_text = '%s\n%s' \
+ # % (name, fulljid)
+
+ st = gajim.config.get('chat_state_notifications')
+ cs = contact.chatstate
+ if cs and st in ('composing_only', 'all'):
+ if contact.show == 'offline':
+ chatstate = ''
+ elif st == 'all':
+ chatstate = helpers.get_uf_chatstate(cs)
+ else: # 'composing_only'
+ if chatstate in ('composing', 'paused'):
+ # only print composing, paused
+ chatstate = helpers.get_uf_chatstate(cs)
+ else:
+ chatstate = ''
+ label_text = \
+ '%s %s' % (name,
+ chatstate)
+ else:
+ label_text = '%s' % name
+
+ if status is not None:
+ label_text += '\n%s' % status
+
+ # setup the label that holds name and jid
+ banner_name_label.set_markup(label_text)
+
+ def _update_gpg(self):
+ tb = self.xml.get_widget('gpg_togglebutton')
+ if self.contact.keyID: # we can do gpg
+ tb.set_sensitive(True)
+ tt = _('OpenPGP Encryption')
+ else:
+ tb.set_sensitive(False)
+ #we talk about a contact here
+ tt = _('%s has not broadcasted an OpenPGP key nor you have '\
+ 'assigned one') % self.contact.name
+ gtk.Tooltips().set_tip(self.xml.get_widget('gpg_eventbox'), tt)
+
+
-# FIXME: Move this to a muc_control.py
-class MultiUserChatControl(message_window.MessageControl):
- def __init__(self, contact):
- MessageControl.__init__(self, 'muc_child_vbox', contact);
- self.compact_view = gajim.config.get('always_compact_view_gc')
diff --git a/src/message_window.py b/src/message_window.py
index 52982583b..9df0467b8 100644
--- a/src/message_window.py
+++ b/src/message_window.py
@@ -79,19 +79,67 @@ class MessageWindow:
def new_tab(self, control):
assert(not self._controls.has_key(control.contact.jid))
-
self._controls[control.contact.jid] = control
+ control.widget.connect('key_press_event',
+ self.on_conversation_textview_key_press_event)
+ # FIXME: need to get this event without access to message_textvier
+ #control.widget.connect('mykeypress',
+ # self.on_message_textview_mykeypress_event)
+ control.widget.connect('key_press_event',
+ self.on_message_textview_key_press_event)
+
# Add notebook page and connect up to the tab's close button
xml = gtk.glade.XML(GTKGUI_GLADE, 'chat_tab_ebox', APP)
tab_label_box = xml.get_widget('chat_tab_ebox')
xml.signal_connect('on_close_button_clicked', self.on_close_button_clicked,
control.contact)
self.notebook.append_page(control.widget, tab_label_box)
-
+ self.redraw_tab(control.contact)
self.window.show_all()
-
+
+ def on_message_textview_mykeypress_event(self, widget, event_keyval,
+ event_keymod):
+ # FIXME: Not called yet
+ print "MessageWindow.on_message_textview_mykeypress_event:", event
+ # NOTE: handles mykeypress which is custom signal; see message_textview.py
+
+ # construct event instance from binding
+ event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here
+ event.keyval = event_keyval
+ event.state = event_keymod
+ event.time = 0 # assign current time
+
+ if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB
+ if event.state & gtk.gdk.CONTROL_MASK: # CTRL + SHIFT + TAB
+ self.notebook.emit('key_press_event', event)
+ if event.keyval == gtk.keysyms.Tab:
+ if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB
+ self.notebook.emit('key_press_event', event)
+
+ def on_message_textview_key_press_event(self, widget, event):
+ print "MessageWindow.on_message_textview_key_press_event:", event
+ if event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN
+ if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN
+ self.notebook.emit('key_press_event', event)
+ elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP
+ if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP
+ self.notebook.emit('key_press_event', event)
+
+ def on_conversation_textview_key_press_event(self, widget, event):
+ '''Do not block these events and send them to the notebook'''
+ print "MessageWindow.on_conversation_textview_key_press_event:", event
+ if event.state & gtk.gdk.CONTROL_MASK:
+ if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
+ self.notebook.emit('key_press_event', event)
+ elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB
+ self.notebook.emit('key_press_event', event)
+ elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN
+ self.notebook.emit('key_press_event', event)
+ elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP
+ self.notebook.emit('key_press_event', event)
+
def on_close_button_clicked(self, button, contact):
'''When close button is pressed: close a tab'''
self.remove_tab(contact)
@@ -100,6 +148,43 @@ class MessageWindow:
# TODO
print "MessageWindow.remove_tab"
+ def redraw_tab(self, contact):
+ ctl = self._controls[contact.jid]
+ ctl.update_state()
+
+ hbox = self.notebook.get_tab_label(ctl.widget).get_children()[0]
+ status_img = hbox.get_children()[0]
+ nick_label = hbox.get_children()[1]
+
+ # Optionally hide close button
+ close_button = hbox.get_children()[2]
+ if gajim.config.get('tabs_close_button'):
+ close_button.show()
+ else:
+ close_button.hide()
+
+ # FIXME: Handle nb_unread
+ num_unread = 0
+
+ # Update nick
+ nick_label.set_markup(contact.name)
+
+ # Set tab image (always 16x16); unread messages show the 'message' image
+ img_16 = gajim.interface.roster.get_appropriate_state_images(contact.jid)
+ if num_unread and gajim.config.get('show_unread_tab_icon'):
+ tab_img = img_16['message']
+ else:
+ tab_img = img_16[contact.show]
+ if tab_img.get_storage_type() == gtk.IMAGE_ANIMATION:
+ status_img.set_from_animation(tab_img.get_animation())
+ else:
+ status_img.set_from_pixbuf(tab_img.get_pixbuf())
+
+ def repaint_themed_widgets(self):
+ '''Repaint controls in the window with theme color'''
+ # iterate through controls and repaint
+ for ctl in self._controls.values():
+ ctl.repaint_themed_widgets()
class MessageWindowMgr:
'''A manager and factory for MessageWindow objects'''
@@ -118,7 +203,7 @@ class MessageWindowMgr:
CONFIG_ALWAYS: The key is MessageWindowMgr.MAIN_WIN
CONFIG_PERACCT: The key is the account name
CONFIG_PERTYPE: The key is a message type constant'''
- self._windows = {}
+ self.windows = {}
# Map the mode to a int constant for frequent compares
mode = gajim.config.get('one_message_window')
self.mode = common.config.opt_one_window_types.index(mode)
@@ -132,9 +217,8 @@ class MessageWindowMgr:
return win
def _gtkWinToMsgWin(self, gtk_win):
- for w in self._windows:
- win = self._windows[w].window
- if win == gtk_win:
+ for w in self.windows.values():
+ if w.window == gtk_win:
return w
return None
@@ -142,12 +226,11 @@ class MessageWindowMgr:
# FIXME
print "MessageWindowMgr._on_window_delete:", win
msg_win = self._gtkWinToMsgWin(win)
- # TODO
def _on_window_destroy(self, win):
# FIXME
print "MessageWindowMgr._on_window_destroy:", win
- # TODO: Clean up _windows
+ # TODO: Clean up windows
def get_window(self, contact, acct, type):
key = None
@@ -162,12 +245,12 @@ class MessageWindowMgr:
win = None
try:
- win = self._windows[key]
+ win = self.windows[key]
except KeyError:
# FIXME
print "Creating tabbed chat window for '%s'" % str(key)
win = self._new_window()
- self._windows[key] = win
+ self.windows[key] = win
assert(win)
return win
@@ -184,11 +267,9 @@ class MessageControl(gtk.VBox):
self.xml = gtk.glade.XML(GTKGUI_GLADE, widget_name, APP)
self.widget = self.xml.get_widget(widget_name)
- self.draw_widgets()
-
- def draw_banner(self):
- # TODO
- pass
def draw_widgets(self):
- self.draw_banner()
- # NOTE: Derived classes should implement this
+ pass # NOTE: Derived classes should implement this
+ def repaint_themed_widgets(self, theme):
+ pass # NOTE: Derived classes SHOULD implement this
+ def update_state(self):
+ pass # NOTE: Derived classes SHOULD implement this
diff --git a/src/roster_window.py b/src/roster_window.py
index 6f7649b48..231b16a68 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -2225,12 +2225,9 @@ _('If "%s" accepts this request you will know his or her status.') %jid)
def repaint_themed_widgets(self):
'''Notify windows that contain themed widgets to repaint them'''
+ for win in self.msg_win_mgr.windows():
+ win.repaint_themed_widgets()
for account in gajim.connections:
- # Update opened chat windows/tabs
- for jid in gajim.interface.instances[account]['chats']:
- gajim.interface.instances[account]['chats'][jid].repaint_colored_widgets()
- for jid in gajim.interface.instances[account]['gc']:
- gajim.interface.instances[account]['gc'][jid].repaint_colored_widgets()
for addr in gajim.interface.instances[account]['disco']:
gajim.interface.instances[account]['disco'][addr].paint_banner()