break more circular references that keep

objects alive (the other 1/2 of #1829)
context menu for groupchat banner
This commit is contained in:
Dimitur Kirov 2006-04-18 15:36:16 +00:00
parent 365d9d4eb6
commit 5c9613db65
8 changed files with 166 additions and 114 deletions

View File

@ -108,13 +108,18 @@ class ChatControlBase(MessageControl):
id = self.widget.connect('key_press_event', self._on_keypress_event) id = self.widget.connect('key_press_event', self._on_keypress_event)
self.handlers[id] = self.widget self.handlers[id] = self.widget
widget = self.xml.get_widget('banner_eventbox')
id = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id] = widget
# Create textviews and connect signals # Create textviews and connect signals
self.conv_textview = ConversationTextview(self.account) self.conv_textview = ConversationTextview(self.account)
self.conv_textview.show_all()
self.conv_scrolledwindow = self.xml.get_widget( self.conv_scrolledwindow = self.xml.get_widget(
'conversation_scrolledwindow') 'conversation_scrolledwindow')
self.conv_scrolledwindow.add(self.conv_textview) self.conv_scrolledwindow.add(self.conv_textview.tv)
widget = self.conv_scrolledwindow.get_vadjustment() widget = self.conv_scrolledwindow.get_vadjustment()
id = widget.connect('value-changed', id = widget.connect('value-changed',
self.on_conversation_vadjustment_value_changed) self.on_conversation_vadjustment_value_changed)
@ -170,6 +175,12 @@ class ChatControlBase(MessageControl):
gajim.config.set('use_speller', False) gajim.config.set('use_speller', False)
self.style_event_id = 0 self.style_event_id = 0
self.conv_textview.tv.show()
# moved from ChatControl
def _on_banner_eventbox_button_press_event(self, widget, event):
'''If right-clicked, show popup'''
if event.button == 3: # right click
self.parent_win.popup_menu(event)
def _on_send_button_clicked(self, widget): def _on_send_button_clicked(self, widget):
'''When send button is pressed: send the current message''' '''When send button is pressed: send the current message'''
@ -241,7 +252,7 @@ class ChatControlBase(MessageControl):
if event.state & gtk.gdk.CONTROL_MASK: if event.state & gtk.gdk.CONTROL_MASK:
# CTRL + l|L: clear conv_textview # CTRL + l|L: clear conv_textview
if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L: if event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L:
self.conv_textview.get_buffer().set_text('') self.conv_textview.tv.get_buffer().set_text('')
return True return True
# CTRL + v: Paste into msg_textview # CTRL + v: Paste into msg_textview
elif event.keyval == gtk.keysyms.v: elif event.keyval == gtk.keysyms.v:
@ -313,7 +324,7 @@ class ChatControlBase(MessageControl):
# SHIFT + PAGE_[UP|DOWN]: send to conv_textview # SHIFT + PAGE_[UP|DOWN]: send to conv_textview
elif event.keyval == gtk.keysyms.Page_Down or \ elif event.keyval == gtk.keysyms.Page_Down or \
event.keyval == gtk.keysyms.Page_Up: event.keyval == gtk.keysyms.Page_Up:
self.conv_textview.emit('key_press_event', event) self.conv_textview.tv.emit('key_press_event', event)
return True return True
elif event.state & gtk.gdk.CONTROL_MASK: elif event.state & gtk.gdk.CONTROL_MASK:
if event.keyval == gtk.keysyms.Tab: # CTRL + TAB if event.keyval == gtk.keysyms.Tab: # CTRL + TAB
@ -327,7 +338,7 @@ class ChatControlBase(MessageControl):
# we pressed a control key or ctrl+sth: we don't block # we pressed a control key or ctrl+sth: we don't block
# the event in order to let ctrl+c (copy text) and # the event in order to let ctrl+c (copy text) and
# others do their default work # others do their default work
self.conv_textview.emit('key_press_event', event) self.conv_textview.tv.emit('key_press_event', event)
return False return False
def _on_message_textview_mykeypress_event(self, widget, event_keyval, def _on_message_textview_mykeypress_event(self, widget, event_keyval,
@ -394,7 +405,7 @@ class ChatControlBase(MessageControl):
return False return False
if message == '/clear': if message == '/clear':
self.conv_textview.clear() # clear conversation self.conv_textview.tv.clear() # clear conversation
self.clear(self.msg_textview) # clear message textview too self.clear(self.msg_textview) # clear message textview too
return True return True
elif message == '/compact': elif message == '/compact':
@ -501,7 +512,7 @@ class ChatControlBase(MessageControl):
def update_font(self): def update_font(self):
font = pango.FontDescription(gajim.config.get('conversation_font')) font = pango.FontDescription(gajim.config.get('conversation_font'))
self.conv_textview.modify_font(font) self.conv_textview.tv.modify_font(font)
self.msg_textview.modify_font(font) self.msg_textview.modify_font(font)
def update_tags(self): def update_tags(self):
@ -516,7 +527,7 @@ class ChatControlBase(MessageControl):
'''When history menuitem is pressed: call history window''' '''When history menuitem is pressed: call history window'''
if not jid: if not jid:
jid = self.contact.jid jid = self.contact.jid
if gajim.interface.instances['logs'].has_key(jid): if gajim.interface.instances['logs'].has_key(jid):
gajim.interface.instances['logs'][jid].window.present() gajim.interface.instances['logs'][jid].window.present()
else: else:
@ -569,7 +580,7 @@ class ChatControlBase(MessageControl):
return return
min_height = self.conv_scrolledwindow.get_property('height-request') min_height = self.conv_scrolledwindow.get_property('height-request')
conversation_height = self.conv_textview.window.get_size()[1] conversation_height = self.conv_textview.tv.window.get_size()[1]
message_height = msg_textview.window.get_size()[1] message_height = msg_textview.window.get_size()[1]
message_width = msg_textview.window.get_size()[0] message_width = msg_textview.window.get_size()[0]
# new tab is not exposed yet # new tab is not exposed yet
@ -731,11 +742,6 @@ class ChatControl(ChatControlBase):
id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed) id = message_tv_buffer.connect('changed', self._on_message_tv_buffer_changed)
self.handlers[id] = message_tv_buffer self.handlers[id] = message_tv_buffer
widget = self.xml.get_widget('banner_eventbox')
id = widget.connect('button-press-event',
self._on_banner_eventbox_button_press_event)
self.handlers[id] = widget
widget = self.xml.get_widget('avatar_eventbox') widget = self.xml.get_widget('avatar_eventbox')
id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event) id = widget.connect('enter-notify-event', self.on_avatar_eventbox_enter_notify_event)
self.handlers[id] = widget self.handlers[id] = widget
@ -1198,19 +1204,25 @@ class ChatControl(ChatControlBase):
# connect signals # connect signals
history_menuitem.connect('activate', id = history_menuitem.connect('activate',
self._on_history_menuitem_activate) self._on_history_menuitem_activate)
send_file_menuitem.connect('activate', self.handlers[id] = history_menuitem
id = send_file_menuitem.connect('activate',
self._on_send_file_menuitem_activate) self._on_send_file_menuitem_activate)
compact_view_menuitem.connect('activate', self.handlers[id] = send_file_menuitem
id = compact_view_menuitem.connect('activate',
self._on_compact_view_menuitem_activate) self._on_compact_view_menuitem_activate)
add_to_roster_menuitem.connect('activate', self.handlers[id] = compact_view_menuitem
id = add_to_roster_menuitem.connect('activate',
self._on_add_to_roster_menuitem_activate) self._on_add_to_roster_menuitem_activate)
toggle_gpg_menuitem.connect('activate', self.handlers[id] = add_to_roster_menuitem
id = toggle_gpg_menuitem.connect('activate',
self._on_toggle_gpg_menuitem_activate) self._on_toggle_gpg_menuitem_activate)
information_menuitem.connect('activate', self.handlers[id] = toggle_gpg_menuitem
id = information_menuitem.connect('activate',
self._on_contact_information_menuitem_activate) self._on_contact_information_menuitem_activate)
menu.connect('selection-done', gtkgui_helpers.destroy_widget) self.handlers[id] = information_menuitem
menu.connect('selection-done', lambda w:w.destroy())
return menu return menu
def send_chatstate(self, state, contact = None): def send_chatstate(self, state, contact = None):
@ -1311,8 +1323,11 @@ class ChatControl(ChatControlBase):
# remove all register handlers on wigets, created by self.xml # remove all register handlers on wigets, created by self.xml
# to prevent circular references among objects # to prevent circular references among objects
for i in self.handlers.keys(): for i in self.handlers.keys():
self.handlers[i].disconnect(i) if self.handlers[i].handler_is_connected(i):
self.handlers[i].disconnect(i)
del self.handlers[i] del self.handlers[i]
self.conv_textview.del_handlers()
self.msg_textview.destroy()
def allow_shutdown(self): def allow_shutdown(self):
@ -1334,11 +1349,6 @@ class ChatControl(ChatControlBase):
# update chatstate in tab for this chat # update chatstate in tab for this chat
self.parent_win.redraw_tab(self, self.contact.chatstate) self.parent_win.redraw_tab(self, self.contact.chatstate)
def _on_banner_eventbox_button_press_event(self, widget, event):
'''If right-clicked, show popup'''
if event.button == 3: # right click
self.parent_win.popup_menu(event)
def set_control_active(self, state): def set_control_active(self, state):
ChatControlBase.set_control_active(self, state) ChatControlBase.set_control_active(self, state)
# send chatstate inactive to the one we're leaving # send chatstate inactive to the one we're leaving

View File

@ -44,33 +44,38 @@ gtk.glade.textdomain(APP)
GTKGUI_GLADE = 'gtkgui.glade' GTKGUI_GLADE = 'gtkgui.glade'
class ConversationTextview(gtk.TextView): class ConversationTextview:
'''Class for the conversation textview (where user reads already said messages) '''Class for the conversation textview (where user reads already said messages)
for chat/groupchat windows''' for chat/groupchat windows'''
def __init__(self, account): def __init__(self, account):
gtk.TextView.__init__(self) # no need to inherit TextView, use it as property is safer
self.tv = gtk.TextView()
# set properties # set properties
self.set_border_width(1) self.tv.set_border_width(1)
self.set_accepts_tab(True) self.tv.set_accepts_tab(True)
self.set_editable(False) self.tv.set_editable(False)
self.set_cursor_visible(False) self.tv.set_cursor_visible(False)
self.set_wrap_mode(gtk.WRAP_WORD) self.tv.set_wrap_mode(gtk.WRAP_WORD)
self.set_left_margin(2) self.tv.set_left_margin(2)
self.set_right_margin(2) self.tv.set_right_margin(2)
self.handlers = {}
# connect signals # connect signals
self.connect('motion_notify_event', self.on_textview_motion_notify_event) id = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event)
self.connect('populate_popup', self.on_textview_populate_popup) self.handlers[id] = self.tv
self.connect('button_press_event', self.on_textview_button_press_event) id = self.tv.connect('populate_popup', self.on_textview_populate_popup)
self.handlers[id] = self.tv
id = self.tv.connect('button_press_event', self.on_textview_button_press_event)
self.handlers[id] = self.tv
self.account = account self.account = account
self.change_cursor = None self.change_cursor = None
self.last_time_printout = 0 self.last_time_printout = 0
font = pango.FontDescription(gajim.config.get('conversation_font')) font = pango.FontDescription(gajim.config.get('conversation_font'))
self.modify_font(font) self.tv.modify_font(font)
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
buffer.create_mark('end', end_iter, False) buffer.create_mark('end', end_iter, False)
@ -105,12 +110,14 @@ class ConversationTextview(gtk.TextView):
color = gajim.config.get('urlmsgcolor') color = gajim.config.get('urlmsgcolor')
tag.set_property('foreground', color) tag.set_property('foreground', color)
tag.set_property('underline', pango.UNDERLINE_SINGLE) tag.set_property('underline', pango.UNDERLINE_SINGLE)
tag.connect('event', self.hyperlink_handler, 'url') id = tag.connect('event', self.hyperlink_handler, 'url')
self.handlers[id] = tag
tag = buffer.create_tag('mail') tag = buffer.create_tag('mail')
tag.set_property('foreground', color) tag.set_property('foreground', color)
tag.set_property('underline', pango.UNDERLINE_SINGLE) tag.set_property('underline', pango.UNDERLINE_SINGLE)
tag.connect('event', self.hyperlink_handler, 'mail') id = tag.connect('event', self.hyperlink_handler, 'mail')
self.handlers[id] = tag
tag = buffer.create_tag('bold') tag = buffer.create_tag('bold')
tag.set_property('weight', pango.WEIGHT_BOLD) tag.set_property('weight', pango.WEIGHT_BOLD)
@ -125,6 +132,14 @@ class ConversationTextview(gtk.TextView):
self.line_tooltip = tooltips.BaseTooltip() self.line_tooltip = tooltips.BaseTooltip()
def del_handlers(self):
for i in self.handlers.keys():
self.handlers[i].disconnect(i)
del self.handlers
self.tv.destroy()
#TODO
# self.line_tooltip.destroy()
def update_tags(self): def update_tags(self):
self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor'))
self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor'))
@ -132,44 +147,44 @@ class ConversationTextview(gtk.TextView):
gajim.config.get('statusmsgcolor')) gajim.config.get('statusmsgcolor'))
def at_the_end(self): def at_the_end(self):
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
end_rect = self.get_iter_location(end_iter) end_rect = self.tv.get_iter_location(end_iter)
visible_rect = self.get_visible_rect() visible_rect = self.tv.get_visible_rect()
if end_rect.y <= (visible_rect.y + visible_rect.height): if end_rect.y <= (visible_rect.y + visible_rect.height):
return True return True
return False return False
def scroll_to_end(self): def scroll_to_end(self):
parent = self.get_parent() parent = self.tv.get_parent()
buffer = self.get_buffer() buffer = self.tv.get_buffer()
self.scroll_to_mark(buffer.get_mark('end'), 0, True, 0, 1) self.tv.scroll_to_mark(buffer.get_mark('end'), 0, True, 0, 1)
adjustment = parent.get_hadjustment() adjustment = parent.get_hadjustment()
adjustment.set_value(0) adjustment.set_value(0)
return False # when called in an idle_add, just do it once return False # when called in an idle_add, just do it once
def bring_scroll_to_end(self, diff_y = 0): def bring_scroll_to_end(self, diff_y = 0):
''' scrolls to the end of textview if end is not visible ''' ''' scrolls to the end of textview if end is not visible '''
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
end_rect = self.get_iter_location(end_iter) end_rect = self.tv.get_iter_location(end_iter)
visible_rect = self.get_visible_rect() visible_rect = self.tv.get_visible_rect()
# scroll only if expected end is not visible # scroll only if expected end is not visible
if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
gobject.idle_add(self.scroll_to_end_iter) gobject.idle_add(self.scroll_to_end_iter)
def scroll_to_end_iter(self): def scroll_to_end_iter(self):
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
self.scroll_to_iter(end_iter, 0, False, 1, 1) self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once return False # when called in an idle_add, just do it once
def show_line_tooltip(self): def show_line_tooltip(self):
pointer = self.get_pointer() pointer = self.tv.get_pointer()
x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0], x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0],
pointer[1]) pointer[1])
tags = self.get_iter_at_location(x, y).get_tags() tags = self.tv.get_iter_at_location(x, y).get_tags()
tag_table = self.get_buffer().get_tag_table() tag_table = self.tv.get_buffer().get_tag_table()
over_line = False over_line = False
for tag in tags: for tag in tags:
if tag == tag_table.lookup('focus-out-line'): if tag == tag_table.lookup('focus-out-line'):
@ -177,26 +192,26 @@ class ConversationTextview(gtk.TextView):
break break
if over_line and not self.line_tooltip.win: if over_line and not self.line_tooltip.win:
# check if the current pointer is still over the line # check if the current pointer is still over the line
position = self.window.get_origin() position = self.tv.window.get_origin()
win = self.get_toplevel() win = self.tv.get_toplevel()
self.line_tooltip.show_tooltip(_('Text below this line is what has ' self.line_tooltip.show_tooltip(_('Text below this line is what has '
'been said since the last time you paid attention to this group chat'), 8, position[1] + pointer[1]) 'been said since the last time you paid attention to this group chat'), 8, position[1] + pointer[1])
def on_textview_motion_notify_event(self, widget, event): def on_textview_motion_notify_event(self, widget, event):
'''change the cursor to a hand when we are over a mail or an url''' '''change the cursor to a hand when we are over a mail or an url'''
pointer_x, pointer_y, spam = self.window.get_pointer() pointer_x, pointer_y, spam = self.tv.window.get_pointer()
x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x,
pointer_y) pointer_y)
tags = self.get_iter_at_location(x, y).get_tags() tags = self.tv.get_iter_at_location(x, y).get_tags()
if self.change_cursor: if self.change_cursor:
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.XTERM)) gtk.gdk.Cursor(gtk.gdk.XTERM))
self.change_cursor = None self.change_cursor = None
tag_table = self.get_buffer().get_tag_table() tag_table = self.tv.get_buffer().get_tag_table()
over_line = False over_line = False
for tag in tags: for tag in tags:
if tag in (tag_table.lookup('url'), tag_table.lookup('mail')): if tag in (tag_table.lookup('url'), tag_table.lookup('mail')):
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.HAND2)) gtk.gdk.Cursor(gtk.gdk.HAND2))
self.change_cursor = tag self.change_cursor = tag
elif tag == tag_table.lookup('focus-out-line'): elif tag == tag_table.lookup('focus-out-line'):
@ -209,13 +224,13 @@ class ConversationTextview(gtk.TextView):
if over_line and not self.line_tooltip.win: if over_line and not self.line_tooltip.win:
self.line_tooltip.timeout = gobject.timeout_add(500, self.line_tooltip.timeout = gobject.timeout_add(500,
self.show_line_tooltip) self.show_line_tooltip)
self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
self.change_cursor = tag self.change_cursor = tag
def clear(self, tv = None): def clear(self, tv = None):
'''clear text in the textview''' '''clear text in the textview'''
buffer = self.get_buffer() buffer = self.tv.get_buffer()
start, end = buffer.get_bounds() start, end = buffer.get_bounds()
buffer.delete(start, end) buffer.delete(start, end)
@ -231,7 +246,8 @@ class ConversationTextview(gtk.TextView):
menu.prepend(item) menu.prepend(item)
item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) item = gtk.ImageMenuItem(gtk.STOCK_CLEAR)
menu.prepend(item) menu.prepend(item)
item.connect('activate', self.clear) id = item.connect('activate', self.clear)
self.handlers[id] = item
if self.selected_phrase: if self.selected_phrase:
s = self.selected_phrase s = self.selected_phrase
if len(s) > 25: if len(s) > 25:
@ -249,7 +265,8 @@ class ConversationTextview(gtk.TextView):
link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\ link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\
% (gajim.LANG, self.selected_phrase) % (gajim.LANG, self.selected_phrase)
item = gtk.MenuItem(_('Read _Wikipedia Article')) item = gtk.MenuItem(_('Read _Wikipedia Article'))
item.connect('activate', self.visit_url_from_menuitem, link) id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item
submenu.append(item) submenu.append(item)
item = gtk.MenuItem(_('Look it up in _Dictionary')) item = gtk.MenuItem(_('Look it up in _Dictionary'))
@ -263,7 +280,8 @@ class ConversationTextview(gtk.TextView):
else: else:
link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\ link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\
% (gajim.LANG, self.selected_phrase) % (gajim.LANG, self.selected_phrase)
item.connect('activate', self.visit_url_from_menuitem, link) id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item
else: else:
if dict_link.find('%s') == -1: if dict_link.find('%s') == -1:
#we must have %s in the url if not WIKTIONARY #we must have %s in the url if not WIKTIONARY
@ -271,7 +289,8 @@ class ConversationTextview(gtk.TextView):
item.set_property('sensitive', False) item.set_property('sensitive', False)
else: else:
link = dict_link % self.selected_phrase link = dict_link % self.selected_phrase
item.connect('activate', self.visit_url_from_menuitem, link) id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item
submenu.append(item) submenu.append(item)
@ -283,7 +302,8 @@ class ConversationTextview(gtk.TextView):
else: else:
item = gtk.MenuItem(_('Web _Search for it')) item = gtk.MenuItem(_('Web _Search for it'))
link = search_link % self.selected_phrase link = search_link % self.selected_phrase
item.connect('activate', self.visit_url_from_menuitem, link) id = item.connect('activate', self.visit_url_from_menuitem, link)
self.handlers[id] = item
submenu.append(item) submenu.append(item)
menu.show_all() menu.show_all()
@ -297,10 +317,10 @@ class ConversationTextview(gtk.TextView):
if event.button != 3: # if not right click if event.button != 3: # if not right click
return False return False
win = self.get_window(gtk.TEXT_WINDOW_TEXT) win = self.tv.get_window(gtk.TEXT_WINDOW_TEXT)
x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
int(event.x), int(event.y)) int(event.x), int(event.y))
iter = self.get_iter_at_location(x, y) iter = self.tv.get_iter_at_location(x, y)
tags = iter.get_tags() tags = iter.get_tags()
@ -313,7 +333,7 @@ class ConversationTextview(gtk.TextView):
# we check if sth was selected and if it was we assign # we check if sth was selected and if it was we assign
# selected_phrase variable # selected_phrase variable
# so on_conversation_textview_populate_popup can use it # so on_conversation_textview_populate_popup can use it
buffer = self.get_buffer() buffer = self.tv.get_buffer()
return_val = buffer.get_selection_bounds() return_val = buffer.get_selection_bounds()
if return_val: # if sth was selected when we right-clicked if return_val: # if sth was selected when we right-clicked
# get the selected text # get the selected text
@ -352,8 +372,10 @@ class ConversationTextview(gtk.TextView):
menu = xml.get_widget('chat_context_menu') menu = xml.get_widget('chat_context_menu')
childs = menu.get_children() childs = menu.get_children()
if kind == 'url': if kind == 'url':
childs[0].connect('activate', self.on_copy_link_activate, text) id = childs[0].connect('activate', self.on_copy_link_activate, text)
childs[1].connect('activate', self.on_open_link_activate, kind, text) self.handlers[id] = childs[0]
id = childs[1].connect('activate', self.on_open_link_activate, kind, text)
self.handlers[id] = childs[1]
childs[2].hide() # copy mail address childs[2].hide() # copy mail address
childs[3].hide() # open mail composer childs[3].hide() # open mail composer
childs[4].hide() # jid section separator childs[4].hide() # jid section separator
@ -361,11 +383,15 @@ class ConversationTextview(gtk.TextView):
childs[6].hide() # join group chat childs[6].hide() # join group chat
childs[7].hide() # add to roster childs[7].hide() # add to roster
else: # It's a mail or a JID else: # It's a mail or a JID
childs[2].connect('activate', self.on_copy_link_activate, text) id = childs[2].connect('activate', self.on_copy_link_activate, text)
childs[3].connect('activate', self.on_open_link_activate, kind, text) self.handlers[id] = childs[2]
childs[5].connect('activate', self.on_start_chat_activate, text) id = childs[3].connect('activate', self.on_open_link_activate, kind, text)
childs[6].connect('activate', self.handlers[id] = childs[3]
id = childs[5].connect('activate', self.on_start_chat_activate, text)
self.handlers[id] = childs[5]
id = childs[6].connect('activate',
self.on_join_group_chat_menuitem_activate, text) self.on_join_group_chat_menuitem_activate, text)
self.handlers[id] = childs[6]
allow_add = False allow_add = False
c = gajim.contacts.get_first_contact_from_jid(self.account, text) c = gajim.contacts.get_first_contact_from_jid(self.account, text)
@ -376,7 +402,8 @@ class ConversationTextview(gtk.TextView):
allow_add = True allow_add = True
if allow_add: if allow_add:
childs[7].connect('activate', self.on_add_to_roster_activate, text) id = childs[7].connect('activate', self.on_add_to_roster_activate, text)
self.handlers[id] = childs[7]
childs[7].show() # show add to roster menuitem childs[7].show() # show add to roster menuitem
else: else:
childs[7].hide() # hide add to roster menuitem childs[7].hide() # hide add to roster menuitem
@ -396,7 +423,7 @@ class ConversationTextview(gtk.TextView):
# we get the end of the tag # we get the end of the tag
while not end_iter.ends_tag(texttag): while not end_iter.ends_tag(texttag):
end_iter.forward_char() end_iter.forward_char()
word = self.get_buffer().get_text(begin_iter, end_iter).decode('utf-8') word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode('utf-8')
if event.button == 3: # right click if event.button == 3: # right click
self.make_link_menu(event, kind, word) self.make_link_menu(event, kind, word)
else: else:
@ -411,7 +438,7 @@ class ConversationTextview(gtk.TextView):
after *last* special text, so we can print it in after *last* special text, so we can print it in
print_conversation_line()''' print_conversation_line()'''
buffer = self.get_buffer() buffer = self.tv.get_buffer()
start = 0 start = 0
end = 0 end = 0
@ -445,7 +472,7 @@ class ConversationTextview(gtk.TextView):
use_other_tags = True use_other_tags = True
show_ascii_formatting_chars = \ show_ascii_formatting_chars = \
gajim.config.get('show_ascii_formatting_chars') gajim.config.get('show_ascii_formatting_chars')
buffer = self.get_buffer() buffer = self.tv.get_buffer()
possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS
if gajim.config.get('emoticons_theme') and \ if gajim.config.get('emoticons_theme') and \
@ -458,7 +485,7 @@ class ConversationTextview(gtk.TextView):
img.set_from_file(gajim.interface.emoticons[emot_ascii]) img.set_from_file(gajim.interface.emoticons[emot_ascii])
img.show() img.show()
#add with possible animation #add with possible animation
self.add_child_at_anchor(img, anchor) self.tv.add_child_at_anchor(img, anchor)
elif special_text.startswith('http://') or \ elif special_text.startswith('http://') or \
special_text.startswith('www.') or \ special_text.startswith('www.') or \
special_text.startswith('ftp://') or \ special_text.startswith('ftp://') or \
@ -533,7 +560,7 @@ class ConversationTextview(gtk.TextView):
buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags) buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags)
def print_empty_line(self): def print_empty_line(self):
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
buffer.insert(end_iter, '\n') buffer.insert(end_iter, '\n')
@ -546,7 +573,7 @@ class ConversationTextview(gtk.TextView):
# kind = info, we print things as if it was a status: same color, ... # kind = info, we print things as if it was a status: same color, ...
if kind == 'info': if kind == 'info':
kind = 'status' kind = 'status'
buffer = self.get_buffer() buffer = self.tv.get_buffer()
buffer.begin_user_action() buffer.begin_user_action()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
at_the_end = False at_the_end = False
@ -620,7 +647,7 @@ class ConversationTextview(gtk.TextView):
def print_name(self, name, kind, other_tags_for_name): def print_name(self, name, kind, other_tags_for_name):
if name: if name:
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
name_tags = other_tags_for_name[:] # create a new list name_tags = other_tags_for_name[:] # create a new list
name_tags.append(kind) name_tags.append(kind)
@ -632,14 +659,14 @@ class ConversationTextview(gtk.TextView):
def print_subject(self, subject): def print_subject(self, subject):
if subject: # if we have subject, show it too! if subject: # if we have subject, show it too!
subject = _('Subject: %s\n') % subject subject = _('Subject: %s\n') % subject
buffer = self.get_buffer() buffer = self.tv.get_buffer()
end_iter = buffer.get_end_iter() end_iter = buffer.get_end_iter()
buffer.insert(end_iter, subject) buffer.insert(end_iter, subject)
self.print_empty_line() self.print_empty_line()
def print_real_text(self, text, text_tags = [], name = None): def print_real_text(self, text, text_tags = [], name = None):
'''this adds normal and special text. call this to add text''' '''this adds normal and special text. call this to add text'''
buffer = self.get_buffer() buffer = self.tv.get_buffer()
# /me is replaced by name if name is given # /me is replaced by name if name is given
if name and (text.startswith('/me ') or text.startswith('/me\n')): if name and (text.startswith('/me ') or text.startswith('/me\n')):
text = '* ' + name + text[3:] text = '* ' + name + text[3:]

View File

@ -299,7 +299,7 @@ class GroupchatControl(ChatControlBase):
self.got_disconnected() #init some variables self.got_disconnected() #init some variables
self.update_ui() self.update_ui()
self.conv_textview.grab_focus() self.conv_textview.tv.grab_focus()
self.widget.show_all() self.widget.show_all()
def notify_on_new_messages(self): def notify_on_new_messages(self):
@ -568,7 +568,7 @@ class GroupchatControl(ChatControlBase):
return return
print_focus_out_line = False print_focus_out_line = False
buffer = self.conv_textview.get_buffer() buffer = self.conv_textview.tv.get_buffer()
if self.focus_out_end_iter_offset is None: if self.focus_out_end_iter_offset is None:
# this happens only first time we focus out on this room # this happens only first time we focus out on this room
@ -1179,7 +1179,8 @@ class GroupchatControl(ChatControlBase):
# remove all register handlers on wigets, created by self.xml # remove all register handlers on wigets, created by self.xml
# to prevent circular references among objects # to prevent circular references among objects
for i in self.handlers.keys(): for i in self.handlers.keys():
self.handlers[i].disconnect(i) if self.handlers[i].handler_is_connected(i):
self.handlers[i].disconnect(i)
del self.handlers[i] del self.handlers[i]
def allow_shutdown(self): def allow_shutdown(self):

View File

@ -18028,9 +18028,6 @@ Maybe I'll refactor later</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property> <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property> <property name="focus_on_map">True</property>
<signal name="delete_event" handler="_on_window_delete" last_modification_time="Thu, 12 Jan 2006 02:55:53 GMT"/>
<signal name="destroy" handler="_on_window_destroy" last_modification_time="Thu, 12 Jan 2006 02:56:25 GMT"/>
<signal name="focus_in_event" handler="_on_window_focus" last_modification_time="Thu, 12 Jan 2006 02:56:55 GMT"/>
<child> <child>
<widget class="GtkAlignment" id="msg_window_alignment"> <widget class="GtkAlignment" id="msg_window_alignment">
@ -18623,7 +18620,6 @@ Status message</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="relief">GTK_RELIEF_NONE</property> <property name="relief">GTK_RELIEF_NONE</property>
<property name="focus_on_click">True</property> <property name="focus_on_click">True</property>
<signal name="clicked" handler="on_close_button_clicked" last_modification_time="Sat, 12 Mar 2005 00:12:43 GMT"/>
<child> <child>
<widget class="GtkImage" id="image1329"> <widget class="GtkImage" id="image1329">
@ -18674,7 +18670,6 @@ Status message</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="visible_window">True</property> <property name="visible_window">True</property>
<property name="above_child">False</property> <property name="above_child">False</property>
<signal name="button_press_event" handler="on_banner_eventbox_button_press_event" last_modification_time="Mon, 08 Aug 2005 15:32:39 GMT"/>
<child> <child>
<widget class="GtkLabel" id="banner_name_label"> <widget class="GtkLabel" id="banner_name_label">
@ -18694,7 +18689,6 @@ topic</property>
<property name="width_chars">-1</property> <property name="width_chars">-1</property>
<property name="single_line_mode">False</property> <property name="single_line_mode">False</property>
<property name="angle">0</property> <property name="angle">0</property>
<signal name="button_press_event" handler="on_banner_label_button_press_event" last_modification_time="Sun, 07 Aug 2005 15:07:13 GMT"/>
</widget> </widget>
</child> </child>
</widget> </widget>
@ -18856,7 +18850,6 @@ topic</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property> <property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property> <property name="focus_on_click">True</property>
<signal name="clicked" handler="on_emoticons_button_clicked" last_modification_time="Thu, 17 Nov 2005 18:06:37 GMT"/>
<child> <child>
<widget class="GtkHBox" id="hbox3014"> <widget class="GtkHBox" id="hbox3014">
@ -19114,7 +19107,6 @@ topic</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="visible_window">False</property> <property name="visible_window">False</property>
<property name="above_child">False</property> <property name="above_child">False</property>
<signal name="button_press_event" handler="on_tab_eventbox_button_press_event" last_modification_time="Thu, 04 Aug 2005 09:38:11 GMT"/>
<child> <child>
<widget class="GtkHBox" id="hbox3017"> <widget class="GtkHBox" id="hbox3017">
@ -19170,7 +19162,6 @@ topic</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="relief">GTK_RELIEF_NONE</property> <property name="relief">GTK_RELIEF_NONE</property>
<property name="focus_on_click">True</property> <property name="focus_on_click">True</property>
<signal name="clicked" handler="on_close_button_clicked" last_modification_time="Sat, 12 Mar 2005 00:12:43 GMT"/>
<child> <child>
<widget class="GtkImage" id="image1347"> <widget class="GtkImage" id="image1347">

View File

@ -47,12 +47,13 @@ screen_h = gtk.gdk.screen_height()
def popup_emoticons_under_button(menu, button, parent_win): def popup_emoticons_under_button(menu, button, parent_win):
''' pops emoticons menu under button, which is in parent_win''' ''' pops emoticons menu under button, which is in parent_win'''
window_x1, window_y1 = parent_win.get_origin()
def position_menu_under_button(menu): def position_menu_under_button(menu):
# inline function, which will not keep refs, when used as CB # inline function, which will not keep refs, when used as CB
button_x, button_y = button.allocation.x, button.allocation.y button_x, button_y = button.allocation.x, button.allocation.y
# now convert them to X11-relative # now convert them to X11-relative
window_x, window_y = parent_win.get_origin() window_x, window_y = window_x1, window_y1
x = window_x + button_x x = window_x + button_x
y = window_y + button_y y = window_y + button_y
@ -70,7 +71,7 @@ def popup_emoticons_under_button(menu, button, parent_win):
# push_in is True so all the menuitems are always inside screen # push_in is True so all the menuitems are always inside screen
push_in = True push_in = True
return (x, y, push_in) return (x, y, push_in)
menu.popup(None, None, position_menu_under_button, 1, 0) menu.popup(None, None, position_menu_under_button, 1, 0)
def get_theme_font_for_option(theme, option): def get_theme_font_for_option(theme, option):

View File

@ -69,8 +69,8 @@ class HistoryWindow:
self.calendar = xml.get_widget('calendar') self.calendar = xml.get_widget('calendar')
scrolledwindow = xml.get_widget('scrolledwindow') scrolledwindow = xml.get_widget('scrolledwindow')
self.history_textview = conversation_textview.ConversationTextview(account) self.history_textview = conversation_textview.ConversationTextview(account)
scrolledwindow.add(self.history_textview) scrolledwindow.add(self.history_textview.tv)
self.history_buffer = self.history_textview.get_buffer() self.history_buffer = self.history_textview.tv.get_buffer()
self.history_buffer.create_tag('highlight', background = 'yellow') self.history_buffer.create_tag('highlight', background = 'yellow')
self.query_entry = xml.get_widget('query_entry') self.query_entry = xml.get_widget('query_entry')
self.search_button = xml.get_widget('search_button') self.search_button = xml.get_widget('search_button')
@ -142,6 +142,7 @@ class HistoryWindow:
# if user destroys the window, and we have a generator filling mark days # if user destroys the window, and we have a generator filling mark days
# stop him! # stop him!
gobject.source_remove(self.mark_days_idle_call_id) gobject.source_remove(self.mark_days_idle_call_id)
self.history_textview.del_handlers()
del gajim.interface.instances['logs'][self.jid] del gajim.interface.instances['logs'][self.jid]
def on_close_button_clicked(self, widget): def on_close_button_clicked(self, widget):
@ -391,4 +392,4 @@ class HistoryWindow:
match_start_mark = self.history_buffer.create_mark('match_start', match_start_mark = self.history_buffer.create_mark('match_start',
match_start_iter, True) match_start_iter, True)
self.history_textview.scroll_to_mark(match_start_mark, 0, True) self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True)

View File

@ -50,6 +50,9 @@ class MessageTextView(gtk.TextView):
self.set_pixels_above_lines(2) self.set_pixels_above_lines(2)
self.set_pixels_below_lines(2) self.set_pixels_below_lines(2)
def destroy(self):
import gc
gobject.idle_add(lambda:gc.collect())
if gobject.pygtk_version < (2, 8, 0): if gobject.pygtk_version < (2, 8, 0):
gobject.type_register(MessageTextView) gobject.type_register(MessageTextView)

View File

@ -55,11 +55,19 @@ class MessageWindow:
self.account = acct self.account = acct
# If None, the window is not tied to any specific type # If None, the window is not tied to any specific type
self.type = type self.type = type
# dict { handler id: widget}. Keeps callbacks, which
# lead to cylcular references
self.handlers = {}
self.widget_name = 'message_window' self.widget_name = 'message_window'
self.xml = gtk.glade.XML(GTKGUI_GLADE, self.widget_name, APP) self.xml = gtk.glade.XML(GTKGUI_GLADE, self.widget_name, APP)
self.xml.signal_autoconnect(self)
self.window = self.xml.get_widget(self.widget_name) self.window = self.xml.get_widget(self.widget_name)
id = self.window.connect('delete-event', self._on_window_delete)
self.handlers[id] = self.window
id = self.window.connect('destroy', self._on_window_destroy)
self.handlers[id] = self.window
id = self.window.connect('focus-in-event', self._on_window_focus)
self.handlers[id] = self.window
# gtk+ doesn't make use of the motion notify on gtkwindow by default # gtk+ doesn't make use of the motion notify on gtkwindow by default
# so this line adds that # so this line adds that
@ -67,10 +75,12 @@ class MessageWindow:
self.alignment = self.xml.get_widget('alignment') self.alignment = self.xml.get_widget('alignment')
self.notebook = self.xml.get_widget('notebook') self.notebook = self.xml.get_widget('notebook')
self.notebook.connect('switch-page', id = self.notebook.connect('switch-page',
self._on_notebook_switch_page) self._on_notebook_switch_page)
self.notebook.connect('key-press-event', self.handlers[id] = self.notebook
id = self.notebook.connect('key-press-event',
self._on_notebook_key_press) self._on_notebook_key_press)
self.handlers[id] = self.notebook
# Remove the glade pages # Remove the glade pages
while self.notebook.get_n_pages(): while self.notebook.get_n_pages():
@ -96,6 +106,8 @@ class MessageWindow:
# set up DnD # set up DnD
self.hid = self.notebook.connect('drag_data_received', self.hid = self.notebook.connect('drag_data_received',
self.on_tab_label_drag_data_received_cb) self.on_tab_label_drag_data_received_cb)
self.handlers[self.hid] = self.notebook
self.notebook.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.DND_TARGETS, self.notebook.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.DND_TARGETS,
gtk.gdk.ACTION_MOVE) gtk.gdk.ACTION_MOVE)
@ -133,6 +145,11 @@ class MessageWindow:
for ctrl in self.controls(): for ctrl in self.controls():
ctrl.shutdown() ctrl.shutdown()
self._controls.clear() self._controls.clear()
for i in self.handlers.keys():
if self.handlers[i].handler_is_connected(i):
self.handlers[i].disconnect(i)
del self.handlers[i]
del self.handlers
def new_tab(self, control): def new_tab(self, control):
if not self._controls.has_key(control.account): if not self._controls.has_key(control.account):
@ -499,6 +516,7 @@ class MessageWindow:
tab_label = self.notebook.get_tab_label(child) tab_label = self.notebook.get_tab_label(child)
tab_label.dnd_handler = tab_label.connect('drag_data_get', tab_label.dnd_handler = tab_label.connect('drag_data_get',
self.on_tab_label_drag_data_get_cb) self.on_tab_label_drag_data_get_cb)
self.handlers[tab_label.dnd_handler] = tab_label
tab_label.drag_source_set(gtk.gdk.BUTTON1_MASK, self.DND_TARGETS, tab_label.drag_source_set(gtk.gdk.BUTTON1_MASK, self.DND_TARGETS,
gtk.gdk.ACTION_MOVE) gtk.gdk.ACTION_MOVE)
tab_label.page_num = self.notebook.page_num(child) tab_label.page_num = self.notebook.page_num(child)