diff --git a/src/chat_control.py b/src/chat_control.py index 8c4573b24..c8f4d8ad9 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -14,12 +14,14 @@ import os, os.path import math +import time import gtk import gtk.glade import pango import gobject import gtkgui_helpers import message_window +import dialogs from common import gajim from common import helpers @@ -289,8 +291,7 @@ class ChatControlBase(MessageControl): self.nb_unread += 1 if gajim.interface.systray_enabled and\ gajim.config.get('trayicon_notification_on_new_messages'): - gajim.interface.systray.add_jid(jid, self.account, - self.get_message_type(jid)) + gajim.interface.systray.add_jid(jid, self.account, self.type) self.redraw_tab(jid) self.show_title(urgent) @@ -395,11 +396,11 @@ class ChatControlBase(MessageControl): ################################################################################ class ChatControl(ChatControlBase): '''A control for standard 1-1 chat''' - TYPE_ID = 1 + TYPE_ID = 'chat' def __init__(self, parent_win, contact, acct): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 'chat_child_vbox', - _('Chat'), contact, acct); + _('Chat'), contact, acct) self.compact_view_always = gajim.config.get('always_compact_view_chat') self.set_compact_view(self.compact_view_always) @@ -857,3 +858,32 @@ class ChatControl(ChatControlBase): contact.our_chatstate = state if contact.our_chatstate == 'active': self.reset_kbd_mouse_timeout_vars() + + def shutdown(self): + # Send 'gone' chatstate + self.send_chatstate('gone', self.contact.jid) + self.contact.chatstate = None + self.contact.our_chatstate = None + # Disconnect timer callbacks + gobject.source_remove(self.possible_paused_timeout_id) + gobject.source_remove(self.possible_inactive_timeout_id) + if self.print_time_timeout_id: + gobject.source_remove(self.print_time_timeout_id) + # Clean up systray + if gajim.interface.systray_enabled and self.nb_unread > 0: + gajim.interface.systray.remove_jid(self.contact.jid, self.account, + self.type) + + def check_delete(self): + jid = self.contact.jid + if time.time() - gajim.last_message_time[self.account][jid] < 2: + # 2 seconds + dialog = dialogs.ConfirmationDialog( + #%s is being replaced in the code with JID + _('You just received a new message from "%s"' % jid), + _('If you close this tab and you have history disabled, '\ + 'this message will be lost.')) + if dialog.get_response() != gtk.RESPONSE_OK: + return True #stop the propagation of the event + return False + diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 688720656..cbf801d1c 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -19,12 +19,21 @@ import gobject import gtkgui_helpers from common import gajim +from chat_control import ChatControl from chat_control import ChatControlBase from conversation_textview import ConversationTextview from message_textview import MessageTextView +class PrivateChatControl(ChatControl): + TYPE_ID = 'pm' + + def __init__(self, parent_win, contact, acct): + ChatControl.__init__(self, parent_win, contact, acct) + self.TYPE_ID = 'pm' + self.display_name = _('Private char') + class GroupchatControl(ChatControlBase): - TYPE_ID = 2 + TYPE_ID = 'gc' def __init__(self, parent_win, contact, acct): ChatControlBase.__init__(self, self.TYPE_ID, parent_win, @@ -33,3 +42,42 @@ class GroupchatControl(ChatControlBase): # muc attention states (when we are mentioned in a muc) # if the room jid is in the list, the room has mentioned us self.muc_attentions = [] + + def markup_tab_label(self, label_str, chatstate): + '''Markup the label if necessary. Returns a tuple such as: + (new_label_str, color) + either of which can be None + if chatstate is given that means we have HE SENT US a chatstate''' + + num_unread = self.nb_unread + + has_focus = self.parent_win.get_property('has-toplevel-focus') + current_tab = self.parent_win.get_active_control() == self + color = None + theme = gajim.config.get('roster_theme') + if chatstate == 'attention' and (not has_focus or not current_tab): + if jid not in self.muc_attentions: + self.muc_attentions.append(jid) + color = gajim.config.get_per('themes', theme, + 'state_muc_directed_msg') + elif chatstate: + if chatstate == 'active' or (current_tab and has_focus): + if jid in self.muc_attentions: + self.muc_attentions.remove(jid) + color = gajim.config.get_per('themes', theme, + 'state_active_color') + elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ + jid not in self.muc_attentions: + color = gajim.config.get_per('themes', theme, 'state_muc_msg') + if color: + color = gtk.gdk.colormap_get_system().alloc_color(color) + # The widget state depend on whether this tab is the "current" tab + if current_tab: + nickname.modify_fg(gtk.STATE_NORMAL, color) + else: + nickname.modify_fg(gtk.STATE_ACTIVE, color) + + if num_unread: # if unread, text in the label becomes bold + label_str = '' + str(num_unread) + label_str + '' + return (label_str, color) + diff --git a/src/message_window.py b/src/message_window.py index 1d8096930..981d81cac 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -85,7 +85,12 @@ class MessageWindow: self.window.show_all() def _on_window_delete(self, win, event): - print "MessageWindow._on_window_delete:", win, event + # Make sure all controls are okay with being deleted + for ctl in self._controls.values(): + if not ctl.check_delete(): + return True # halt the delete + + # FIXME: Do based on type, main, never, peracct, pertype if gajim.config.get('saveposition'): # save the window size and position x, y = win.get_position() @@ -96,8 +101,13 @@ class MessageWindow: gajim.config.set('msgwin-height', height) return False + def _on_window_destroy(self, win): + # FIXME print "MessageWindow._on_window_destroy:", win + for ctl in self._controls.values(): + ctl.shutdown() + self._controls.clear() def new_tab(self, control): assert(not self._controls.has_key(control.contact.jid)) @@ -284,11 +294,21 @@ class MessageWindow: for ctl in self._controls.values(): ctl.update_tags() - def get_control_from_jid(self, jid): - for ctl in self._controls.values(): - if ctl.contact.jid == jid: - return ctl - return None + def get_control(self, arg): + '''Return the MessageControl for jid or n, where n is the notebook page index''' + if isinstance(arg, unicode): + jid = arg + for ctl in self._controls.values(): + if ctl.contact.jid == jid: + return ctl + return None + else: + page_num = arg + notebook = self.notebook + if page_num == None: + page_num = notebook.get_current_page() + nth_child = notebook.get_nth_page(page_num) + return self._widgetToControl(nth_child) def controls(self): for ctl in self._controls.values(): @@ -307,6 +327,41 @@ class MessageWindow: ctl.print_time_timeout_id = gobject.timeout_add(300000, ctl.print_time_timeout, None) + def move_to_next_unread_tab(self, forward): + ind = self.notebook.get_current_page() + current = ind + found = False + # loop until finding an unread tab or having done a complete cycle + while True: + if forward == True: # look for the first unread tab on the right + ind = ind + 1 + if ind >= self.notebook.get_n_pages(): + ind = 0 + else: # look for the first unread tab on the right + ind = ind - 1 + if ind < 0: + ind = self.notebook.get_n_pages() - 1 + if ind == current: + break # a complete cycle without finding an unread tab + ctl = self.get_control(ind) + if ctl.nb_unread > 0: + found = True + break # found + if found: + self.notebook.set_current_page(ind) + else: # not found + if forward: # CTRL + TAB + if current < (self.notebook.get_n_pages() - 1): + self.notebook.next_page() + else: # traverse for ever (eg. don't stop at last tab) + self.notebook.set_current_page(0) + else: # CTRL + SHIFT + TAB + if current > 0: + self.notebook.prev_page() + else: # traverse for ever (eg. don't stop at first tab) + self.notebook.set_current_page( + self.notebook.get_n_pages() - 1) + class MessageWindowMgr: '''A manager and factory for MessageWindow objects''' @@ -334,7 +389,6 @@ class MessageWindowMgr: def _new_window(self): win = MessageWindow() # we track the lifetime of this window - win.window.connect('delete-event', self._on_window_delete) win.window.connect('destroy', self._on_window_destroy) return win @@ -344,17 +398,17 @@ class MessageWindowMgr: return w return None - def _on_window_delete(self, win, event): - # FIXME - print "MessageWindowMgr._on_window_delete:", win def _on_window_destroy(self, win): - # FIXME - print "MessageWindowMgr._on_window_destroy:", win - # TODO: Clean up windows + for k in self._windows.keys(): + if self._windows[k].window == win: + del self._windows[k] + return + # How was the window not in out list?!? Assert. + assert(False) def get_window(self, jid): for win in self._windows.values(): - if win.get_control_from_jid(jid): + if win.get_control(jid): return win return None def has_window(self, jid): @@ -384,9 +438,10 @@ class MessageWindowMgr: return win def get_control(self, jid): + '''Amonst all windows, return the MessageControl for jid''' win = self.get_window(jid) if win: - return win.get_control_from_jid(jid) + return win.get_control(jid) return None def windows(self): @@ -419,8 +474,11 @@ class MessageControl(gtk.VBox): # Autoconnect glade signals self.xml.signal_autoconnect(self) + def shutdown(self): + # NOTE: Derived classes MUST implement this + assert(False) def draw_widgets(self): - pass # 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): @@ -445,7 +503,14 @@ class MessageControl(gtk.VBox): # NOTE: Derived classes SHOULD implement this return None def set_compact_view(self, state): + # NOTE: Derived classes MAY implement this self.compact_view_current = state + def check_delete(self): + '''Called when a window has been asked to delete itself. If a control is + not in a suitable shutdown state this method should return True to halt + the delete''' + # NOTE: Derived classes MAY implement this + return False def send_message(self, message, keyID = '', type = 'chat', chatstate = None): '''Send the given message to the active tab''' diff --git a/src/roster_window.py b/src/roster_window.py index 91785a953..42e0809e7 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -712,7 +712,7 @@ class RosterWindow: if gajim.interface.msg_win_mgr.has_window(contact.jid): jid = contact.jid win = gajim.interface.msg_win_mgr.get_window(contact.jid) - ctl = win.get_control_from_jid(jid) + ctl = win.get_control(jid) ctl.update_state() name = contact.name