def available_plugins_treeview_selection_changed(self, treeview_selection): model, iter = treeview_selection.get_selected() self.xml.get_object('scrolledwindow1').get_children()[0].destroy() self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow1') sw.add(self.plugin_description_textview) sw.show_all() if iter: self.plugin_name_label1.set_text(model.get_value(iter, C_NAME)) self.plugin_authors_label1.set_text(model.get_value(iter, C_AUTHORS)) self.plugin_homepage_linkbutton1.set_uri(model.get_value(iter, C_HOMEPAGE)) self.plugin_homepage_linkbutton1.set_label(model.get_value(iter, C_HOMEPAGE)) label = self.plugin_homepage_linkbutton1.get_children()[0] label.set_ellipsize(pango.ELLIPSIZE_END) self.plugin_homepage_linkbutton1.set_property('sensitive', True) desc = _(model.get_value(iter, C_DESCRIPTION)) if not desc.startswith('<body '): desc = "<body xmlns='http://www.w3.org/1999/xhtml'>" + \ desc + ' </body>' desc = desc.replace('\n', '<br/>') self.plugin_description_textview.display_html(desc, self.plugin_description_textview, None) self.plugin_description_textview.set_property('sensitive', True) else: self._clear_available_plugin_info()
def initialize_window(self): """Show the intro page""" self.html = HtmlTextView() self.html.connect("url-clicked", self.link_clicked) self.html.display_html( '<div><span style="color: red; text-decoration:underline">Welcome to</span><br/>\n' ' <img src="http://comptune.com/images/penguin5.gif" alt="penguin" height="48" width="48" /><br/>\n' ' <span style="font-size: 500%; font-family: serif">crowbar</span>\n' '</div>\n') self.sw1.add(self.html) #~ add a search comboboxentry1 and 3 buttons self.liststore1 = gtk.ListStore(str) self.comboboxentry1 = gtk.ComboBoxEntry(self.liststore1, 0) popular = ['#teamfollowback', '#tfb', '#f4f'] trending = api.GetTrendsCurrent() trends = [x.name for x in trending] for row in popular: self.liststore1.append([row]) for row in trends: self.liststore1.append([row]) # insert a horizontal box with combo and buttons self.hbox2 = gtk.HBox(homogeneous=False) self.hbox2.pack_start(self.comboboxentry1) button = gtk.Button() button.set_label('search') button.set_property('width-request', 55) button.connect('clicked', self.search_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('friends') button.set_property('width-request', 55) button.connect('clicked', self.timeline_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('user') button.set_property('width-request', 40) button.connect('clicked', self.user_timeline_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('dm') button.set_property('width-request', 35) button.connect('clicked', self.dm_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('mention') button.set_property('width-request', 70) button.connect('clicked', self.mentions_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) #~ testify.test(self) self.vbox1.pack_start(self.hbox2, expand=False) self.html.show_all() self.hbox2.show_all()
def __init__(self): '''Initialize Plugins window''' self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui') self.window = self.xml.get_object('plugins_window') self.window.set_transient_for(gajim.interface.roster.window) widgets_to_extract = ('plugins_notebook', 'plugin_name_label', 'plugin_version_label', 'plugin_authors_label', 'plugin_homepage_linkbutton', 'uninstall_plugin_button', 'configure_plugin_button', 'installed_plugins_treeview') for widget_name in widgets_to_extract: setattr(self, widget_name, self.xml.get_object(widget_name)) self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow2') sw.add(self.plugin_description_textview) self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool, GdkPixbuf.Pixbuf) self.installed_plugins_treeview.set_model(self.installed_plugins_model) self.installed_plugins_treeview.set_rules_hint(True) renderer = Gtk.CellRendererText() col = Gtk.TreeViewColumn(_('Plugin'))#, renderer, text=NAME) cell = Gtk.CellRendererPixbuf() col.pack_start(cell, False) col.add_attribute(cell, 'pixbuf', ICON) col.pack_start(renderer, True) col.add_attribute(renderer, 'text', NAME) col.set_property('expand', True) self.installed_plugins_treeview.append_column(col) renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.installed_plugins_toggled_cb) col = Gtk.TreeViewColumn(_('Active'), renderer, active=ACTIVE, activatable=ACTIVATABLE) self.installed_plugins_treeview.append_column(col) self.def_icon = gtkgui_helpers.get_icon_pixmap('preferences-desktop') # connect signal for selection change selection = self.installed_plugins_treeview.get_selection() selection.connect('changed', self.installed_plugins_treeview_selection_changed) selection.set_mode(Gtk.SelectionMode.SINGLE) self._clear_installed_plugin_info() self.fill_installed_plugins_model() root_iter = self.installed_plugins_model.get_iter_first() if root_iter: selection.select_iter(root_iter ) self.xml.connect_signals(self) self.plugins_notebook.set_current_page(0) self.xml.get_object('close_button').grab_focus() self.window.show_all() gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
def __init__(self): w=gtk.Window() print (sys.getdefaultencoding()) msg1="""<span><p>This is a <a href="http://eq2.us">TEST</a>.</p>Enjoy.</span> """ print type(msg1) htmlview = HtmlTextView() def url_cb(view, url, type_): print ("url-clicked", url, type_) webbrowser.open(url) htmlview.connect("url-clicked", url_cb) w.connect("delete-event",self.window_close) w.set_property('width-request',300) w.set_property('height-request',200) htmlview.display_html(msg1) w.add(htmlview) w.show_all()
def initialize_window(self): """Show the intro page""" self.html = HtmlTextView() self.html.connect("url-clicked",self.link_clicked) self.html.display_html('<div><span style="color: red; text-decoration:underline">Welcome to</span><br/>\n' ' <img src="http://comptune.com/images/penguin5.gif" alt="penguin" height="48" width="48" /><br/>\n' ' <span style="font-size: 500%; font-family: serif">crowbar</span>\n' '</div>\n') self.sw1.add(self.html) #~ add a search comboboxentry1 and 3 buttons self.liststore1 = gtk.ListStore(str) self.comboboxentry1 = gtk.ComboBoxEntry(self.liststore1, 0) popular = ['#teamfollowback','#tfb','#f4f'] trending=api.GetTrendsCurrent() trends = [x.name for x in trending] for row in popular: self.liststore1.append([row]) for row in trends: self.liststore1.append([row]) # insert a horizontal box with combo and buttons self.hbox2 = gtk.HBox(homogeneous = False) self.hbox2.pack_start(self.comboboxentry1) button = gtk.Button() button.set_label('search') button.set_property('width-request',55) button.connect('clicked',self.search_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('friends') button.set_property('width-request',55) button.connect('clicked',self.timeline_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('user') button.set_property('width-request',40) button.connect('clicked',self.user_timeline_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('dm') button.set_property('width-request',35) button.connect('clicked',self.dm_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('mention') button.set_property('width-request',70) button.connect('clicked',self.mentions_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) #~ testify.test(self) self.vbox1.pack_start(self.hbox2,expand = False) self.html.show_all() self.hbox2.show_all()
def on_menuitem_activate(self, widget, email): self.window = gtk.Window() self.window.set_border_width(5) self.window.set_title(_("Conversation log for %s") % email) self.window.set_default_size(650,350) self.window.set_position(gtk.WIN_POS_CENTER) textview = gtk.TextView() self.textBuffer = textview.get_buffer() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = HtmlTextView(self.controller, self.textBuffer, scroll) self.textview.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.textview.set_left_margin(6) self.textview.set_right_margin(6) self.textview.set_editable(False) self.textview.set_cursor_visible(False) scroll.add_with_viewport(self.textview) hbox = gtk.HBox(False, 5) saveButton = gtk.Button(stock=gtk.STOCK_SAVE) saveButton.connect("clicked", self.on_save_logs) refreshButton = gtk.Button(stock=gtk.STOCK_REFRESH) refreshButton.connect("clicked", self.on_refresh_log, email) closeButton = gtk.Button(stock=gtk.STOCK_CLOSE) closeButton.connect("clicked", lambda w: self.window.hide()) hbox.pack_end(saveButton, False, False) hbox.pack_end(refreshButton, False, False) hbox.pack_end(closeButton, False, False) textRenderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Date"), textRenderer, markup=0) self.datesListstore = gtk.ListStore(str) self.datesTree = gtk.TreeView(self.datesListstore) self.datesTree.connect("cursor-changed", self.on_dates_cursor_change) self.datesTree.append_column(column) self.datesTree.set_headers_visible(False) datesScroll = gtk.ScrolledWindow() datesScroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) datesScroll.add_with_viewport(self.datesTree) datesScroll.set_size_request(150, -1) hPaned = gtk.HPaned() hPaned.pack1(datesScroll) hPaned.pack2(scroll) vbox = gtk.VBox(spacing=5) vbox.pack_start(hPaned) vbox.pack_start(hbox, False, False) vbox.pack_start(gtk.Statusbar(), False, False) self.datesStamps = {} logsCant = len(self.fill_dates_tree(email)) if not logsCant > 0: dialog.information(_("No logs were found for %s") % (email)) else: self.window.add(vbox) self.window.show_all()
def __init__(self, controlState, xml): controlState.displayHeaderBar = True controlState.windowIcon = 'abouttoinstall.png' controlState.windowTitle = "Summary of installation settings" controlState.windowText = \ "Review the summary of the installation settings" htmlTextView = HtmlTextView() htmlTextView.set_wrap_mode(gtk.WRAP_NONE) context = htmlTextView.get_pango_context() initialFont = context.get_font_description() # The review uses monospace, so get the font description for that at the # same size as the default font used by the text view. metrics = context.get_metrics(pango.FontDescription( 'monospace, %d' % (initialFont.get_size() / pango.SCALE))) # Generate the list of tab stops from the column widths specified in # the review module. tabPosition = 0 ta = pango.TabArray(len(review.COLUMN_WIDTHS), True) for index, charWidth in enumerate(review.COLUMN_WIDTHS): tabPosition += pango.PIXELS( metrics.get_approximate_char_width() * charWidth) ta.set_tab(index, pango.TAB_LEFT, tabPosition) htmlTextView.set_tabs(ta) buf = review.produceText() htmlTextView.display_html(buf) viewPort = xml.get_widget('ReviewViewPort') assert(viewPort) for child in viewPort.get_children(): viewPort.remove(child) viewPort.add(htmlTextView) htmlTextView.show()
def __init__(self): w = gtk.Window() print(sys.getdefaultencoding()) msg1 = """<span><p>This is a <a href="http://eq2.us">TEST</a>.</p>Enjoy.</span> """ print type(msg1) htmlview = HtmlTextView() def url_cb(view, url, type_): print("url-clicked", url, type_) webbrowser.open(url) htmlview.connect("url-clicked", url_cb) w.connect("delete-event", self.window_close) w.set_property('width-request', 300) w.set_property('height-request', 200) htmlview.display_html(msg1) w.add(htmlview) w.show_all()
class crowbar: """Class for viewing and posting status updates. """ def get_username(self): """Get the user login name""" cachedir = self.get_cachedir() names = os.listdir(cachedir) names = [a[:-5] for a in names if a[-5:] == '.auth'] self.myname = dialog.select('Twitter username?', 'Login:'******'t42Eq9aoZWuOCQEvhrTe9A' self.consumer_secret = 'xVrGA1o0njIwtTtuw0n4GAhmfvPpndMcnl6fabbDPI' me = self.get_username() # load cached authorization file cachedir = self.get_cachedir() cachefile = cachedir + me + '.auth' if os.path.exists(cachefile): with open(cachefile) as f: data = f.read() access_token = twitter.simplejson.loads(data) return access_token request_token_url = 'https://api.twitter.com/oauth/request_token' access_token_url = 'https://api.twitter.com/oauth/access_token' authorize_url = 'https://api.twitter.com/oauth/authorize' consumer = oauth.Consumer(self.consumer_key, self.consumer_secret) client = oauth.Client(consumer) # Step 1: Get a request token. This is a temporary token that is used for # having the user authorize an access token and to sign the request to obtain # said access token. resp, content = client.request(request_token_url, "GET") if resp['status'] != '200': raise Exception("Invalid response %s." % resp['status']) request_token = dict(urlparse.parse_qsl(content)) # Step 2: Redirect to the provider. Since this is a CLI script we do not url = "%s?oauth_token=%s" % (authorize_url, request_token['oauth_token']) webbrowser.open(url) # After the user has granted access to you, the consumer, the provider will # redirect you to whatever URL you have told them to redirect to. You can # usually define this in the oauth_callback argument as well. oauth_verifier = dialog.getText( 'What is the pin?', gtk.MESSAGE_QUESTION, 'pin:', 'Fill out the form in the web page that pops up.\n' 'You will be given a PIN number.\n' 'Come back and enter that number here.').strip() # Step 3: Once the consumer has redirected the user back to the oauth_callback # URL you can request the access token the user has approved. You use the # request token to sign this request. After this is done you throw away the # request token and use the access token returned. You should store this # access token somewhere safe, like a database, for future use. token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) token.set_verifier(oauth_verifier) client = oauth.Client(consumer, token) resp, content = client.request(access_token_url, "POST") access_token = dict(urlparse.parse_qsl(content)) data = twitter.simplejson.dumps(access_token) if '<error' not in data: with open(cachefile, 'w') as f: f.write(data) return access_token def update_followers_count(self): """Updates the count of followers""" self.me = api.GetUser(screen_name=self.myname) fo = str(self.me.followers_count) fr = str(self.me.friends_count) self.window2.set_title(fr + " friends " + fo + " followers") def get_cachedir(self): """Returns the application's Cache dir:""" cachedir = os.path.expanduser('~/.crowbar_cache/') if not os.path.exists(cachedir): os.mkdir(cachedir) return cachedir def getshouts(self, s): """get names of people mentioned in s[].text""" names = [] for a in s: try: atext = re.match("[^@]*(.*)", a.text).groups()[0] for b in atext.split("@")[1:]: al = re.match("(\w+)", b).groups()[0].lower() if al not in names: names.append(al) except: pass return names def get_screen_names(self, s): """get s[].user.screen_name from search results""" names = [] for a in s: al = a.user.screen_name.lower() if al not in names: names.append(al) return names def subem(self, sub): """sub everybody in sub""" global friends for a in sub: al = a.lower() if al not in friends: try: api.CreateFriendship(al) friends.append(al) except: pass def gtk_main_quit(self, widget, data=None): gtk.main_quit() return False # return False destroys window. def delete_event(self, widget, data=None): widget.hide() # hide the window return True # return True does not destroy window. def link_clicked(self, view, url, type_): """follow a url or reply link""" url = url.replace('%26', '&') url = url.replace('&', '&') print("url-clicked %s" % url) if 'http://' not in url: if '@' in url: self.entry1.set_text(url) elif '#' == url[0]: self.comboboxentry1.child.set_text(url) self.search_clicked(None, self.comboboxentry1.child.get_text) elif '☆' in url: api.CreateFavorite( api.GetStatus(int(str(url).replace('☆', '')))) elif '☞' in url: api.PostRetweet(int(str(url).replace('☞', ''))) elif '☠' in url: api.DestroyDirectMessage(int(str(url).replace('☠', ''))) elif '✗' in url: api.DestroyStatus(int(str(url).replace('✗', ''))) else: webbrowser.open(url) def post_update(self, widget, data=None): if data(): api.PostUpdate(data()) self.entry1.set_text('') return True def image_from_url(self, url, fname): """return a gtk.Image from url""" #~ cache img to speed up loading cachedir = self.get_cachedir() if url: ext = '.' + url[url.rindex('.') + 1:].lower() if ext not in ['.jpg', '.png']: ext = '.jpg' fname = str(fname) + ext cachefile = cachedir + fname if os.path.exists(cachefile): #~ cache hit pb = gtk.gdk.pixbuf_new_from_file(cachefile) pbs = pb.scale_simple(48, 48, 0) return gtk.image_new_from_pixbuf(pbs) fp = urllib.urlopen(url) pbl = gtk.gdk.PixbufLoader() data = fp.read() #~ read url into pixbuf if pbl.write(data): pb = pbl.get_pixbuf() try: pbs = pb.scale_simple(48, 48, 0) except: try: fp.close() pbl.close() except: pass print('could not scale image for %s ' % fname) return gtk.image_new_from_file('blank.jpg') #~ pbs = pb #~ create image from pixbuf image = gtk.Image() image.set_from_pixbuf(pbs) #~ save cached copy if ext != '.png': pb.save(cachefile, "jpeg") else: pb.save(cachefile, "png") try: pbl.close() except: print('%s truncated or incomplete.' % url) else: #~ todo: make this a broken image print('broken image for %s' % fname) image = gtk.image_new_from_file('blank.jpg') fp.close() else: print('no url for image%' % fname) image = gtk.image_new_from_file('blank.jpg') return image def escape(self, s): s = s.replace('&', '&') s = s.replace('<', '<') s = s.replace('>', '>') #~ s = s.replace('"','"') return s def unescape(self, s): p = htmllib.HTMLParser(None) p.save_bgn() p.feed(str(s)) s = p.save_end() #~ s = s.replace('<','<') #~ s = s.replace('>','>') s = s.replace(' rel="nofollow"', '') #~ s = s.replace('"e;','"') s = s.replace('&', '%26') s = s.replace('[1]', '') return s def process_urls(self, s): s = self.escape(s) s = re.sub( "(http://[^ ]+)", lambda m: "<a href='%s'>%s</a>" % (m.group(1).replace('&', '&'), m.group(1).replace( '&', '&')), s) s = re.sub( "(@)(\w+)", lambda m: "%s<a href='%s'>%s</a>" % (m.group(1), 'http://twitter.com/' + m.group(2), m.group(2)), s) s = re.sub( "(#)(\w+)", lambda m: "%s<a href='%s'>%s</a>" % (m.group(1), '#' + m.group(2), m.group(2)), s) return s def html_view(self, s, dm=False): """show search results and timelines in htmlview""" # clear, add viewport & scrolling table if self.sw1.get_child() is self.html: self.sw1.remove(self.html) self.sw1.set_property("hscrollbar-policy", gtk.POLICY_NEVER) self.viewport1 = gtk.Viewport() self.sw1.add(self.viewport1) else: self.viewport1.remove(self.table1) self.table1 = gtk.Table(23, 2, False) self.table1.set_row_spacings(2) self.table1.set_col_spacings(2) self.viewport1.add(self.table1) self.pic = [] self.msg = [] t = [''] rows = 0 #display each user pic and instant message for x in s: if not dm: user = x.GetUser() else: user = api.GetUser(x.sender_screen_name) img = user.GetProfileImageUrl() usn = str(user.GetScreenName()) t[0] = x reply = usn shouts = self.getshouts(t) star = '☆' if not dm: if x.favorited: star = '★' if shouts: reply += ' @' + string.join(self.getshouts(t), ' @') self.pic.append(self.image_from_url(img, usn)) text = self.process_urls(str(x.text)) #~ (re)construct html message self.msg.append(HtmlTextView()) self.msg[rows].connect("url-clicked", self.link_clicked) h = '<span>' if not dm: h += '<a title="Favorite" href="' + star + str( x.id) + '">' + star + '</a>' h += '<span style="font-weight: bold">' + '<a href="http://twitter.com/' + usn + '">' + usn + '</a></span>: ' + text + '<br /><span style="font-size:small">' if not dm: h += x.relative_created_at + ' via ' + self.unescape( x.source) + ' | ' if dm: h += '<a href="@' + reply + '">reply</a> | <a href="☠' + str( x.id) + '" title="Delete this.">Delete</a>' elif (usn == self.me.screen_name): h += '<a href="✗' + str( x.id) + '" title="Delete this tweet.">Delete</a>' else: h += '<a href="@' + reply + '">reply</a> | <a href="☞' + str( x.id) + '">retweet</a>' h += '</span></span>' try: self.msg[rows].display_html(str(h)) except: print('Error displaying:') print(h + '\n') self.table1.attach(self.pic[rows], 0, 1, rows, rows + 1) self.table1.attach(self.msg[rows], 1, 2, rows, rows + 1) rows += 1 #~ self.table1.attach(self.html,0,2,rows,rows+2) self.blank = gtk.Label() self.blank.set_property('height-request', 100) self.table1.attach(self.blank, 0, 2, rows, rows + 2) #~ self.table1.set_property("border-width", 5) self.sw1.show_all() def initialize_window(self): """Show the intro page""" self.html = HtmlTextView() self.html.connect("url-clicked", self.link_clicked) self.html.display_html( '<div><span style="color: red; text-decoration:underline">Welcome to</span><br/>\n' ' <img src="http://comptune.com/images/penguin5.gif" alt="penguin" height="48" width="48" /><br/>\n' ' <span style="font-size: 500%; font-family: serif">crowbar</span>\n' '</div>\n') self.sw1.add(self.html) #~ add a search comboboxentry1 and 3 buttons self.liststore1 = gtk.ListStore(str) self.comboboxentry1 = gtk.ComboBoxEntry(self.liststore1, 0) popular = ['#teamfollowback', '#tfb', '#f4f'] trending = api.GetTrendsCurrent() trends = [x.name for x in trending] for row in popular: self.liststore1.append([row]) for row in trends: self.liststore1.append([row]) # insert a horizontal box with combo and buttons self.hbox2 = gtk.HBox(homogeneous=False) self.hbox2.pack_start(self.comboboxentry1) button = gtk.Button() button.set_label('search') button.set_property('width-request', 55) button.connect('clicked', self.search_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('friends') button.set_property('width-request', 55) button.connect('clicked', self.timeline_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('user') button.set_property('width-request', 40) button.connect('clicked', self.user_timeline_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('dm') button.set_property('width-request', 35) button.connect('clicked', self.dm_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) button = gtk.Button() button.set_label('mention') button.set_property('width-request', 70) button.connect('clicked', self.mentions_clicked, self.comboboxentry1.child.get_text) self.hbox2.pack_start(button, expand=False, fill=True) #~ testify.test(self) self.vbox1.pack_start(self.hbox2, expand=False) self.html.show_all() self.hbox2.show_all() def display_results(self, s, getnames, dm=False): """display results of searches and timelines""" lnames = '' if getnames: snames = self.get_screen_names(s) lnames = "@" + string.join(snames, " @") shouts = self.getshouts(s) self.textview1.set_wrap_mode(gtk.WRAP_WORD) buf = self.textview1.get_buffer() buf.set_text(lnames + "@" + string.join(shouts, " @")) self.html_view(s, dm) self.window2.show_all() def get_list(self, widget): it = widget.get_iter('0') data = [] while 1: try: data.append(widget.get_value(it, 0)) it = widget.iter_next(it) except: return data def update_search(self, text): l = self.get_list(self.liststore1) if text and (text not in l): self.liststore1.append([text]) def search_clicked(self, widget, method=None): """get names and shouts from api search and display them""" text = method() s = api.GetSearch(text) self.display_results(s, True) self.last_action = self.search_clicked self.last_method = method self.update_search(text) self.update_followers_count() def timeline_clicked(self, widget, method=None): """get friends' timeline""" text = method() getnames = False if text: getnames = True if '#' in text: getmames = False text = None s = api.GetUserTimeline(text) else: s = api.GetHomeTimeline() self.display_results(s, getnames) self.last_action = self.timeline_clicked self.last_method = method self.update_search(text) self.update_followers_count() def user_timeline_clicked(self, widget, method=None): """get user timeline""" text = method() getnames = False s = api.GetUserTimeline(text) self.display_results(s, getnames) self.last_action = self.user_timeline_clicked self.last_method = method self.update_search(text) self.update_followers_count() def mentions_clicked(self, widget, method=None): """get mentions""" s = api.GetMentions() self.display_results(s, True) self.last_action = self.mentions_clicked self.last_method = method self.update_followers_count() def dm_clicked(self, widget, method=None): """get mentions""" s = api.GetDirectMessages() self.display_results(s, False, True) self.last_action = self.mentions_clicked self.last_method = method self.update_followers_count() def refresh_clicked(self, widget, method=None): self.liststore1.clear() popular = ['#teamfollowback', '#tfb', '#f4f'] trending = api.GetTrendsCurrent() trends = [x.name for x in trending] for row in popular: self.liststore1.append([row]) for row in trends: self.liststore1.append([row]) self.last_action(None, self.last_method) def get_friendIDs(self): """Get friend and follower IDs""" global friendIDs, followerIDs if not globals().has_key('friendIDs'): print('getting friends list...') try: friendIDs = api.GetFriendIDs()['ids'] except: print( "User not properly authenticated. Will not be able to post updates." ) return False if not globals().has_key('followerIDs'): print('getting list of followers...') followerIDs = api.GetFollowerIDs()['ids'] return True def follow_clicked(self, widget, data=None): """follow all poeple in textview1 by name""" #~ iterate through the text buffer buf = self.textview1.get_buffer() (iter_first, iter_last) = buf.get_bounds() text = buf.get_text(iter_first, iter_last) #~ remove spaces and build a list to follow text = text.replace(' ', '') fol = [] #~ create fol list for x in text.split('@')[1:]: if x not in fol: fol.append(x) #~ ask if self.warn and (not dialog.ok("Follow %s people.\n" "Are you sure?" % len(fol))): return else: self.warn = False # load nofollow list if it exists me = self.me.screen_name nofol = [] cachedir = self.get_cachedir() nofollow = cachedir + me + '.nofollow' if os.path.exists(nofollow): with open(nofollow) as f: nofol = [x.strip() for x in f.readlines()] nofollen = len(nofol) #~ friend everybody unless nofol, add to nofol for a in fol: if a not in nofol: try: api.CreateFriendship(a) print('followed %s' % a) except TwitterError as err: print(err) if 'follow-limits' in str(err): dialog.alert( "You have reached Twitter's follow limit!") break nofol.append(a) #~ Write nofol to disk. These could be invalid names, #~ existing friends, or blocked accounts. #~ We don't care. We just don't follow them. if nofollen < len(nofol): with open(nofollow, 'a') as f: f.writelines([x + '\n' for x in nofol[nofollen:]]) def follow_followers(self, widget, data=None): """follow followers by id not by name""" global friendIDs, followerIDs if not self.get_friendIDs(): return #~ safety feature in case twitter f's up if len(friendIDs) < 100: print('not enough friends') return #~ load protected and pending list me = self.me.screen_name cachedir = self.get_cachedir() protfile = cachedir + me + '.prot' newprot = [] prot = [] if os.path.exists(protfile): with open(protfile) as f: prot = [int(x.strip()) for x in f.readlines()] #~ follow everyone in followerIDs for a in followerIDs: if (a not in friendIDs) and (a not in prot): try: ret = api.CreateFriendship(a) friendIDs.append(a) print('followed %s' % ret.screen_name) except TwitterError as err: print(err) if not 'follow-limits' in str(err): newprot.append(a) else: dialog.alert( "You have reached Twitter's follow limit!") break #~ those we can not follow are probably protected with open(protfile, 'a') as f: f.writelines([str(x) + '\n' for x in newprot]) def unfollow_non_followers(self, widget, data=None): """unfollow everybody who is not following me todo: add safelist""" if not self.get_friendIDs(): return me = self.me.screen_name # load safelist cachedir = self.get_cachedir() safefile = cachedir + me + '.safe' safe = [] unsub = [] if os.path.exists(safefile): with open(safefile) as f: safe = [x.strip() for x in f.readlines()] #~ safety feature in case twitter f*s up if len(followerIDs) < 100: print('Need at least 100 followers to use this feature.') return for a in friendIDs: if (a not in followerIDs) and (a not in safe): unsub.append(a) #~ unsub.remove(self.me.id) #~ save nofollow list so we do not re-follow #~ (unless they follow us, of course) unsub_names = [] if dialog.ok('Unfriend all ' + str(len(unsub)) + ' non-followers?'): #UNSUB EVERYBODY IN unsub for a in unsub: try: u = api.DestroyFriendship(a) print('unfriended %s' % u.screen_name) unsub_names.append(u.screen_name) except TwitterError as err: print(err) nofollow = cachedir + me + '.nofollow' with open(nofollow, 'a') as f: f.writelines([x + '\n' for x in unsub_names]) def __init__(self): """main initialization routine""" global api, friendIDs, friends friends = [] tok = self.authorize() self.warn = True api = twitter.Api(self.consumer_key, self.consumer_secret, tok['oauth_token'], tok['oauth_token_secret']) builder = gtk.Builder() builder.add_from_file("crowbar.glade") self.window1 = builder.get_object("window1") self.vbox1 = builder.get_object("vbox1") self.sw1 = builder.get_object("sw1") self.window2 = builder.get_object("window2") self.textview1 = builder.get_object("textview1") self.entry1 = builder.get_object("entry1") self.button1 = builder.get_object("button1") builder.connect_signals(self) self.window2.connect("delete-event", self.delete_event) self.button1.connect("clicked", self.post_update, self.entry1.get_text) self.update_followers_count() self.window1.set_title("Crowbar @" + self.me.screen_name) self.initialize_window() #~ some variables to remember what we did last self.last_action = None self.last_method = None
class ConversationTextview(GObject.GObject): """ Class for the conversation textview (where user reads already said messages) for chat/groupchat windows """ __gsignals__ = dict( quote = (GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, None, # return value (str, ) # arguments ) ) # smooth scroll constants MAX_SCROLL_TIME = 0.4 # seconds SCROLL_DELAY = 33 # milliseconds def __init__(self, used_in_history_window = False): """ If used_in_history_window is True, then we do not show Clear menuitem in context menu """ GObject.GObject.__init__(self) self.used_in_history_window = used_in_history_window #self.fc = FuzzyClock() # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() #self.tv.hyperlink_handler = self.hyperlink_handler # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) self.tv.set_editable(False) self.tv.set_cursor_visible(False) self.tv.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self.tv.set_left_margin(2) self.tv.set_right_margin(2) self.handlers = {} self.images = [] self.image_cache = {} self.xep0184_marks = {} self.xep0184_shown = {} self.last_sent_message_marks = [None, None] # A pair per occupant. Key is '' in normal chat self.last_received_message_marks = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False # connect signals id_ = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) self.handlers[id_] = self.tv 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 id_ = self.tv.connect('draw', self.on_textview_draw) self.handlers[id_] = self.tv self.change_cursor = False self.last_time_printout = 0 #font = Pango.FontDescription(gajim.config.get('conversation_font')) #self.tv.override_font(font) buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() buffer_.create_mark('end', end_iter, False) #self.tagIn = buffer_.create_tag('incoming') #color = gajim.config.get('inmsgcolor') #font = Pango.FontDescription(gajim.config.get('inmsgfont')) #self.tagIn.set_property('foreground', color) #self.tagIn.set_property('font-desc', font) #self.tagOut = buffer_.create_tag('outgoing') #color = gajim.config.get('outmsgcolor') #font = Pango.FontDescription(gajim.config.get('outmsgfont')) #self.tagOut.set_property('foreground', color) #self.tagOut.set_property('font-desc', font) #self.tagStatus = buffer_.create_tag('status') #color = gajim.config.get('statusmsgcolor') #font = Pango.FontDescription(gajim.config.get('satusmsgfont')) #self.tagStatus.set_property('foreground', color) #self.tagStatus.set_property('font-desc', font) #self.tagInText = buffer_.create_tag('incomingtxt') #color = gajim.config.get('inmsgtxtcolor') #font = Pango.FontDescription(gajim.config.get('inmsgtxtfont')) #if color: # self.tagInText.set_property('foreground', color) #self.tagInText.set_property('font-desc', font) #self.tagOutText = buffer_.create_tag('outgoingtxt') #color = gajim.config.get('outmsgtxtcolor') #if color: # font = Pango.FontDescription(gajim.config.get('outmsgtxtfont')) #self.tagOutText.set_property('foreground', color) #self.tagOutText.set_property('font-desc', font) #colors = gajim.config.get('gc_nicknames_colors') #colors = colors.split(':') #for i, color in enumerate(colors): # tagname = 'gc_nickname_color_' + str(i) # tag = buffer_.create_tag(tagname) # tag.set_property('foreground', color) #self.tagMarked = buffer_.create_tag('marked') #color = gajim.config.get('markedmsgcolor') #self.tagMarked.set_property('foreground', color) #self.tagMarked.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('time_sometimes') #tag.set_property('foreground', 'darkgrey') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag.set_property('justification', Gtk.Justification.CENTER) #tag = buffer_.create_tag('small') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag = buffer_.create_tag('restored_message') #color = gajim.config.get('restored_messages_color') #tag.set_property('foreground', color) #self.tv.create_tags() #tag = buffer_.create_tag('bold') #tag.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('italic') #tag.set_property('style', Pango.Style.ITALIC) #tag = buffer_.create_tag('underline') #tag.set_property('underline', Pango.Underline.SINGLE) #buffer_.create_tag('focus-out-line', justification = Gtk.Justification.CENTER) #self.displaymarking_tags = {} #tag = buffer_.create_tag('xep0184-warning') #tag.set_property('foreground', '#cc0000') #tag = buffer_.create_tag('xep0184-received') #tag.set_property('foreground', '#73d216') # One mark at the begining then 2 marks between each lines #size = gajim.config.get('max_conversation_lines') #size = 2 * size - 1 #self.marks_queue = queue.Queue(size) self.allow_focus_out_line = True # holds a mark at the end of --- line self.focus_out_end_mark = None #self.xep0184_warning_tooltip = tooltips.BaseTooltip() #self.line_tooltip = tooltips.BaseTooltip() self.smooth_id = None self.just_cleared = False size = 500 size = 2 * size - 1 self.marks_queue = queue.Queue(size) def print_conversation_line(self, text, jid, kind, name): """ Print 'chat' type messages """ buffer_ = self.tv.get_buffer() buffer_.begin_user_action() if self.marks_queue.full(): # remove oldest line m1 = self.marks_queue.get() m2 = self.marks_queue.get() i1 = buffer_.get_iter_at_mark(m1) i2 = buffer_.get_iter_at_mark(m2) buffer_.delete(i1, i2) buffer_.delete_mark(m1) end_iter = buffer_.get_end_iter() end_offset = end_iter.get_offset() at_the_end = self.at_the_end() move_selection = False if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ get_offset() == end_offset: move_selection = True # Create one mark and add it to queue once if it's the first line # else twice (one for end bound, one for start bound) mark = None if buffer_.get_char_count() > 0: mark = buffer_.create_mark(None, end_iter, left_gravity=True) self.marks_queue.put(mark) if not mark: mark = buffer_.create_mark(None, end_iter, left_gravity=True) self.marks_queue.put(mark) if kind == 'incoming_queue': kind = 'incoming' # print the time stamp # We don't have tim for outgoing messages... import time tim = time.localtime() direction_mark = '' # don't apply direction mark if it's status message timestamp_str = self.get_time_to_show(tim, direction_mark) timestamp = time.strftime(timestamp_str, tim) timestamp = timestamp + ' ' buffer_.insert (end_iter, timestamp) self.print_name(name, kind, direction_mark=direction_mark, iter_=end_iter) #if kind == 'incoming': # text_tags.append('incomingtxt') # mark1 = mark #elif kind == 'outgoing': # text_tags.append('outgoingtxt') # mark1 = mark #subject = None #self.print_subject(subject, iter_=end_iter) self.print_real_text(text, name, iter_=end_iter) # scroll to the end of the textview if at_the_end or kind == 'outgoing': # we are at the end or we are sending something # scroll to the end (via idle in case the scrollbar has appeared) if True: GLib.idle_add(self.smooth_scroll_to_end) else: GLib.idle_add(self.scroll_to_end) self.just_cleared = False buffer_.end_user_action() return end_iter # Smooth scrolling inspired by Pidgin code def smooth_scroll(self): parent = self.tv.get_parent() if not parent: return False vadj = parent.get_vadjustment() max_val = vadj.get_upper() - vadj.get_page_size() + 1 cur_val = vadj.get_value() # scroll by 1/3rd of remaining distance onethird = cur_val + ((max_val - cur_val) / 3.0) self.auto_scrolling = True vadj.set_value(onethird) self.auto_scrolling = False if max_val - onethird < 0.01: self.smooth_id = None self.smooth_scroll_timer.cancel() return False return True def smooth_scroll_timeout(self): GLib.idle_add(self.do_smooth_scroll_timeout) return def do_smooth_scroll_timeout(self): if not self.smooth_id: # we finished scrolling return GLib.source_remove(self.smooth_id) self.smooth_id = None parent = self.tv.get_parent() if parent: vadj = parent.get_vadjustment() self.auto_scrolling = True vadj.set_value(vadj.get_upper() - vadj.get_page_size() + 1) self.auto_scrolling = False def smooth_scroll_to_end(self): if None != self.smooth_id: # already scrolling return False self.smooth_id = GLib.timeout_add(self.SCROLL_DELAY, self.smooth_scroll) self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, self.smooth_scroll_timeout) self.smooth_scroll_timer.start() return False def print_name(self, name, kind, direction_mark='', iter_=None): if name: buffer_ = self.tv.get_buffer() if iter_: end_iter = iter_ else: end_iter = buffer_.get_end_iter() before_str = '' after_str = ':' format_ = before_str + name + direction_mark + after_str + ' ' buffer_.insert(end_iter, format_) def print_real_text(self, text, name, iter_=None): """ Add normal and special text. call this to add text """ # /me is replaced by name if name is given if name and (text.startswith('/me ') or text.startswith('/me\n')): text = '* ' + name + text[3:] #text_tags.append('italic') # detect urls formatting and if the user has it on emoticons buffer_ = self.tv.get_buffer() buffer_.insert (iter_, text + "\n") #return self.detect_and_print_special_text(text, iter_=iter_) def get_time_to_show(self, tim, direction_mark=''): from calendar import timegm import time """ Get the time, with the day before if needed and return it. It DOESN'T format a fuzzy time """ format_ = '' # get difference in days since epoch (86400 = 24*3600) # number of days since epoch for current time (in GMT) - # number of days since epoch for message (in GMT) diff_day = int(int(timegm(time.localtime())) / 86400 -\ int(timegm(tim)) / 86400) timestamp_str = '[%X]' format_ += timestamp_str tim_format = time.strftime(format_, tim) return tim_format def on_textview_motion_notify_event(self, widget, event): """ Change the cursor to a hand when we are over a mail or an url """ w = self.tv.get_window(Gtk.TextWindowType.TEXT) device = w.get_display().get_device_manager().get_client_pointer() pointer = w.get_device_position(device) x, y = self.tv.window_to_buffer_coords(Gtk.TextWindowType.TEXT, pointer[1], pointer[2]) tags = self.tv.get_iter_at_location(x, y).get_tags() if self.change_cursor: w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.XTERM)) self.change_cursor = False tag_table = self.tv.get_buffer().get_tag_table() over_line = False xep0184_warning = False for tag in tags: if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \ tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2)) self.change_cursor = True elif tag == tag_table.lookup('focus-out-line'): over_line = True elif tag == tag_table.lookup('xep0184-warning'): xep0184_warning = True #if self.line_tooltip.timeout != 0: # Check if we should hide the line tooltip # if not over_line: # self.line_tooltip.hide_tooltip() #if self.xep0184_warning_tooltip.timeout != 0: # Check if we should hide the XEP-184 warning tooltip # if not xep0184_warning: # self.xep0184_warning_tooltip.hide_tooltip() if over_line and not self.line_tooltip.win: self.line_tooltip.timeout = GLib.timeout_add(500, self.show_line_tooltip) w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) self.change_cursor = True if xep0184_warning and not self.xep0184_warning_tooltip.win: self.xep0184_warning_tooltip.timeout = GLib.timeout_add(500, self.show_xep0184_warning_tooltip) w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) self.change_cursor = True def on_textview_populate_popup(self, textview, menu): """ Override the default context menu and we prepend Clear (only if used_in_history_window is False) and if we have sth selected we show a submenu with actions on the phrase (see on_conversation_textview_button_press_event) """ separator_menuitem_was_added = False menu.show_all() def on_textview_button_press_event(self, widget, event): # If we clicked on a taged text do NOT open the standard popup menu # if normal text check if we have sth selected self.selected_phrase = '' # do not move belove event button check! if event.button != 3: # if not right click return False x, y = self.tv.window_to_buffer_coords(Gtk.TextWindowType.TEXT, int(event.x), int(event.y)) iter_ = self.tv.get_iter_at_location(x, y) tags = iter_.get_tags() if tags: # we clicked on sth special (it can be status message too) for tag in tags: tag_name = tag.get_property('name') if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): return True # we block normal context menu # we check if sth was selected and if it was we assign # selected_phrase variable # so on_conversation_textview_populate_popup can use it buffer_ = self.tv.get_buffer() return_val = buffer_.get_selection_bounds() if return_val: # if sth was selected when we right-clicked # get the selected text start_sel, finish_sel = return_val[0], return_val[1] self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True) elif iter_.get_char() and ord(iter_.get_char()) > 31: # we clicked on a word, do as if it's selected for context menu start_sel = iter_.copy() if not start_sel.starts_word(): start_sel.backward_word_start() finish_sel = iter_.copy() if not finish_sel.ends_word(): finish_sel.forward_word_end() self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True) def on_textview_draw(self, widget, ctx): return #TODO expalloc = event.area exp_x0 = expalloc.x exp_y0 = expalloc.y exp_x1 = exp_x0 + expalloc.width exp_y1 = exp_y0 + expalloc.height try: tryfirst = [self.image_cache[(exp_x0, exp_y0)]] except KeyError: tryfirst = [] for image in tryfirst + self.images: imgalloc = image.allocation img_x0 = imgalloc.x img_y0 = imgalloc.y img_x1 = img_x0 + imgalloc.width img_y1 = img_y0 + imgalloc.height if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ exp_x1 <= img_x1 and exp_y1 <= img_y1: self.image_cache[(img_x0, img_y0)] = image widget.propagate_expose(image, event) return True return False def at_the_end(self): buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() end_rect = self.tv.get_iter_location(end_iter) visible_rect = self.tv.get_visible_rect() if end_rect.y <= (visible_rect.y + visible_rect.height): return True return False def bring_scroll_to_end(self, diff_y = 0, use_smooth=True): ''' scrolls to the end of textview if end is not visible ''' buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() end_rect = self.tv.get_iter_location(end_iter) visible_rect = self.tv.get_visible_rect() # scroll only if expected end is not visible if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): if use_smooth: GLib.idle_add(self.smooth_scroll_to_end) else: GLib.idle_add(self.scroll_to_end_iter)
def html_view(self, s, dm=False): """show search results and timelines in htmlview""" # clear, add viewport & scrolling table if self.sw1.get_child() is self.html: self.sw1.remove(self.html) self.sw1.set_property("hscrollbar-policy", gtk.POLICY_NEVER) self.viewport1 = gtk.Viewport() self.sw1.add(self.viewport1) else: self.viewport1.remove(self.table1) self.table1 = gtk.Table(23, 2, False) self.table1.set_row_spacings(2) self.table1.set_col_spacings(2) self.viewport1.add(self.table1) self.pic = [] self.msg = [] t = [''] rows = 0 #display each user pic and instant message for x in s: if not dm: user = x.GetUser() else: user = api.GetUser(x.sender_screen_name) img = user.GetProfileImageUrl() usn = str(user.GetScreenName()) t[0] = x reply = usn shouts = self.getshouts(t) star = '☆' if not dm: if x.favorited: star = '★' if shouts: reply += ' @' + string.join(self.getshouts(t), ' @') self.pic.append(self.image_from_url(img, usn)) text = self.process_urls(str(x.text)) #~ (re)construct html message self.msg.append(HtmlTextView()) self.msg[rows].connect("url-clicked", self.link_clicked) h = '<span>' if not dm: h += '<a title="Favorite" href="' + star + str( x.id) + '">' + star + '</a>' h += '<span style="font-weight: bold">' + '<a href="http://twitter.com/' + usn + '">' + usn + '</a></span>: ' + text + '<br /><span style="font-size:small">' if not dm: h += x.relative_created_at + ' via ' + self.unescape( x.source) + ' | ' if dm: h += '<a href="@' + reply + '">reply</a> | <a href="☠' + str( x.id) + '" title="Delete this.">Delete</a>' elif (usn == self.me.screen_name): h += '<a href="✗' + str( x.id) + '" title="Delete this tweet.">Delete</a>' else: h += '<a href="@' + reply + '">reply</a> | <a href="☞' + str( x.id) + '">retweet</a>' h += '</span></span>' try: self.msg[rows].display_html(str(h)) except: print('Error displaying:') print(h + '\n') self.table1.attach(self.pic[rows], 0, 1, rows, rows + 1) self.table1.attach(self.msg[rows], 1, 2, rows, rows + 1) rows += 1 #~ self.table1.attach(self.html,0,2,rows,rows+2) self.blank = gtk.Label() self.blank.set_property('height-request', 100) self.table1.attach(self.blank, 0, 2, rows, rows + 2) #~ self.table1.set_property("border-width", 5) self.sw1.show_all()
def __init__(self, account, used_in_history_window = False): '''if used_in_history_window is True, then we do not show Clear menuitem in context menu''' self.used_in_history_window = used_in_history_window # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() self.tv.html_hyperlink_handler = self.html_hyperlink_handler # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) self.tv.set_editable(False) self.tv.set_cursor_visible(False) self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.tv.set_left_margin(2) self.tv.set_right_margin(2) self.handlers = {} self.images = [] self.image_cache = {} self.xep0184_marks = {} self.xep0184_shown = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False # connect signals id = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) self.handlers[id] = self.tv 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 id = self.tv.connect('expose-event', self.on_textview_expose_event) self.handlers[id] = self.tv self.account = account self.change_cursor = None self.last_time_printout = 0 font = pango.FontDescription(gajim.config.get('conversation_font')) self.tv.modify_font(font) buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() buffer.create_mark('end', end_iter, False) self.tagIn = buffer.create_tag('incoming') color = gajim.config.get('inmsgcolor') self.tagIn.set_property('foreground', color) self.tagOut = buffer.create_tag('outgoing') color = gajim.config.get('outmsgcolor') self.tagOut.set_property('foreground', color) self.tagStatus = buffer.create_tag('status') color = gajim.config.get('statusmsgcolor') self.tagStatus.set_property('foreground', color) colors = gajim.config.get('gc_nicknames_colors') colors = colors.split(':') for i,color in enumerate(colors): tagname = 'gc_nickname_color_' + str(i) tag = buffer.create_tag(tagname) tag.set_property('foreground', color) tag = buffer.create_tag('marked') color = gajim.config.get('markedmsgcolor') tag.set_property('foreground', color) tag.set_property('weight', pango.WEIGHT_BOLD) tag = buffer.create_tag('time_sometimes') tag.set_property('foreground', 'darkgrey') tag.set_property('scale', pango.SCALE_SMALL) tag.set_property('justification', gtk.JUSTIFY_CENTER) tag = buffer.create_tag('small') tag.set_property('scale', pango.SCALE_SMALL) tag = buffer.create_tag('restored_message') color = gajim.config.get('restored_messages_color') tag.set_property('foreground', color) self.tagURL = buffer.create_tag('url') color = gajim.config.get('urlmsgcolor') self.tagURL.set_property('foreground', color) self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE) id = self.tagURL.connect('event', self.hyperlink_handler, 'url') self.handlers[id] = self.tagURL self.tagMail = buffer.create_tag('mail') self.tagMail.set_property('foreground', color) self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE) id = self.tagMail.connect('event', self.hyperlink_handler, 'mail') self.handlers[id] = self.tagMail tag = buffer.create_tag('bold') tag.set_property('weight', pango.WEIGHT_BOLD) tag = buffer.create_tag('italic') tag.set_property('style', pango.STYLE_ITALIC) tag = buffer.create_tag('underline') tag.set_property('underline', pango.UNDERLINE_SINGLE) buffer.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) tag = buffer.create_tag('xep0184-warning') # One mark at the begining then 2 marks between each lines size = gajim.config.get('max_conversation_lines') size = 2 * size - 1 self.marks_queue = Queue.Queue(size) self.allow_focus_out_line = True # holds a mark at the end of --- line self.focus_out_end_mark = None self.xep0184_warning_tooltip = tooltips.BaseTooltip() self.line_tooltip = tooltips.BaseTooltip() # use it for hr too self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF self.smooth_id = None
def on_menuitem_activate(self, widget, email): self.window = gtk.Window() self.window.set_border_width(5) self.window.set_title(_("Conversation log for %s") % email) self.window.set_default_size(650,350) self.window.set_position(gtk.WIN_POS_CENTER) textview = gtk.TextView() self.textBuffer = textview.get_buffer() self.textBuffer.create_tag('highlight', background = 'yellow') scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = HtmlTextView(self.controller, self.textBuffer, scroll) self.textview.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.textview.set_left_margin(6) self.textview.set_right_margin(6) self.textview.set_editable(False) self.textview.set_cursor_visible(False) scroll.add(self.textview) hbox = gtk.HBox(False, 5) saveButton = gtk.Button(stock=gtk.STOCK_SAVE) saveButton.connect("clicked", self.on_save_logs) refreshButton = gtk.Button(stock=gtk.STOCK_REFRESH) refreshButton.connect("clicked", self.on_refresh_log, email) closeButton = gtk.Button(stock=gtk.STOCK_CLOSE) closeButton.connect("clicked", lambda w: self.window.hide()) ############ Search TreeView ################### self.search_active = False self.searchStore = gtk.ListStore(str, str, str, str) self.searchTree = gtk.TreeView(self.searchStore) self.searchTree.connect("row-activated", self.set_cursor) self.searchTree.set_rules_hint(True) cell = gtk.CellRendererText() nameCol = gtk.TreeViewColumn(_("Date"), cell, text=0) dateCol = gtk.TreeViewColumn(_("Time"), cell, text=1) timeCol = gtk.TreeViewColumn(_("Name"), cell, text=2) msgCol = gtk.TreeViewColumn(_("Message"), cell, text=3) nameCol.set_resizable(True) dateCol.set_resizable(True) timeCol.set_resizable(True) msgCol.set_resizable(True) self.searchTree.append_column(nameCol) self.searchTree.append_column(dateCol) self.searchTree.append_column(timeCol) self.searchTree.append_column(msgCol) self.searchSw = gtk.ScrolledWindow() self.searchSw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.searchSw.add(self.searchTree) self.searchSw.set_size_request(100, 30) ################################################## #search box searchLabel = gtk.Label(_("Search:")) self.searchBox = gtk.Entry() self.searchBox.connect("key-press-event", self.enter_pressed) hbox.pack_end(saveButton, False, False) hbox.pack_end(refreshButton, False, False) hbox.pack_end(closeButton, False, False) hbox.pack_end(self.searchBox, False, False) hbox.pack_end(searchLabel, False, False) textRenderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Date"), textRenderer, markup=0) self.datesListstore = gtk.ListStore(str) self.datesTree = gtk.TreeView(self.datesListstore) self.datesTree.connect("cursor-changed", self.on_dates_cursor_change) self.datesTree.append_column(column) self.datesTree.set_headers_visible(False) datesScroll = gtk.ScrolledWindow() datesScroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) datesScroll.add_with_viewport(self.datesTree) datesScroll.set_size_request(150, -1) hPaned = gtk.HPaned() hPaned.pack1(datesScroll) hPaned.pack2(scroll) vbox = gtk.VBox(spacing=5) vbox.pack_start(hPaned) vbox.pack_start(hbox, False, False) vbox.pack_start(self.searchSw) vbox.pack_start(gtk.Statusbar(), False, False) self.datesStamps = {} logsCant = len(self.fill_dates_tree(email)) if not logsCant > 0: dialog.information(_("No logs were found for %s") % (email)) else: self.window.add(vbox) self.window.show_all() self.searchSw.hide_all()
class crowbar: """Class for viewing and posting status updates. """ def get_username(self): """Get the user login name""" cachedir = self.get_cachedir() names = os.listdir(cachedir) names = [a[:-5] for a in names if a[-5:]=='.auth'] self.myname = dialog.select('Twitter username?','Login:'******'t42Eq9aoZWuOCQEvhrTe9A' self.consumer_secret = 'xVrGA1o0njIwtTtuw0n4GAhmfvPpndMcnl6fabbDPI' me = self.get_username() # load cached authorization file cachedir = self.get_cachedir() cachefile = cachedir+me+'.auth' if os.path.exists(cachefile): with open(cachefile) as f: data=f.read() access_token = twitter.simplejson.loads(data) return access_token request_token_url = 'https://api.twitter.com/oauth/request_token' access_token_url = 'https://api.twitter.com/oauth/access_token' authorize_url = 'https://api.twitter.com/oauth/authorize' consumer = oauth.Consumer(self.consumer_key, self.consumer_secret) client = oauth.Client(consumer) # Step 1: Get a request token. This is a temporary token that is used for # having the user authorize an access token and to sign the request to obtain # said access token. resp, content = client.request(request_token_url, "GET") if resp['status'] != '200': raise Exception("Invalid response %s." % resp['status']) request_token = dict(urlparse.parse_qsl(content)) # Step 2: Redirect to the provider. Since this is a CLI script we do not url="%s?oauth_token=%s" % (authorize_url, request_token['oauth_token']) webbrowser.open(url) # After the user has granted access to you, the consumer, the provider will # redirect you to whatever URL you have told them to redirect to. You can # usually define this in the oauth_callback argument as well. oauth_verifier = dialog.getText('What is the pin?',gtk.MESSAGE_QUESTION,'pin:', 'Fill out the form in the web page that pops up.\n' 'You will be given a PIN number.\n' 'Come back and enter that number here.').strip() # Step 3: Once the consumer has redirected the user back to the oauth_callback # URL you can request the access token the user has approved. You use the # request token to sign this request. After this is done you throw away the # request token and use the access token returned. You should store this # access token somewhere safe, like a database, for future use. token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) token.set_verifier(oauth_verifier) client = oauth.Client(consumer, token) resp, content = client.request(access_token_url, "POST") access_token = dict(urlparse.parse_qsl(content)) data=twitter.simplejson.dumps(access_token) if '<error' not in data: with open(cachefile,'w') as f: f.write(data) return access_token def update_followers_count(self): """Updates the count of followers""" self.me = api.GetUser(screen_name=self.myname) fo=str(self.me.followers_count) fr=str(self.me.friends_count) self.window2.set_title(fr+" friends "+fo+" followers") def get_cachedir(self): """Returns the application's Cache dir:""" cachedir = os.path.expanduser('~/.crowbar_cache/') if not os.path.exists(cachedir): os.mkdir(cachedir) return cachedir def getshouts(self,s): """get names of people mentioned in s[].text""" names = [] for a in s: try: atext = re.match("[^@]*(.*)",a.text).groups()[0] for b in atext.split("@")[1:]: al = re.match("(\w+)",b).groups()[0].lower() if al not in names: names.append(al) except: pass return names def get_screen_names(self,s): """get s[].user.screen_name from search results""" names = [] for a in s: al = a.user.screen_name.lower() if al not in names: names.append(al) return names def subem(self,sub): """sub everybody in sub""" global friends for a in sub: al = a.lower() if al not in friends: try: api.CreateFriendship(al) friends.append(al) except: pass def gtk_main_quit(self,widget,data = None): gtk.main_quit() return False # return False destroys window. def delete_event(self,widget,data = None): widget.hide() # hide the window return True # return True does not destroy window. def link_clicked(self,view,url,type_): """follow a url or reply link""" url = url.replace('%26','&') url = url.replace('&','&') print("url-clicked %s" % url) if 'http://' not in url: if '@' in url: self.entry1.set_text(url) elif '#' == url[0]: self.comboboxentry1.child.set_text(url) self.search_clicked(None,self.comboboxentry1.child.get_text) elif '☆' in url: api.CreateFavorite(api.GetStatus(int(str(url).replace('☆','')))) elif '☞' in url: api.PostRetweet(int(str(url).replace('☞',''))) elif '☠' in url: api.DestroyDirectMessage(int(str(url).replace('☠',''))) elif '✗' in url: api.DestroyStatus(int(str(url).replace('✗',''))) else: webbrowser.open(url) def post_update(self,widget,data = None): if data(): api.PostUpdate(data()) self.entry1.set_text('') return True def image_from_url(self,url,fname): """return a gtk.Image from url""" #~ cache img to speed up loading cachedir = self.get_cachedir() if url: ext = '.'+url[url.rindex('.')+1:].lower() if ext not in ['.jpg','.png']: ext = '.jpg' fname = str(fname)+ext cachefile = cachedir+fname if os.path.exists(cachefile): #~ cache hit pb = gtk.gdk.pixbuf_new_from_file(cachefile) pbs = pb.scale_simple(48,48,0) return gtk.image_new_from_pixbuf(pbs) fp = urllib.urlopen(url) pbl = gtk.gdk.PixbufLoader() data = fp.read() #~ read url into pixbuf if pbl.write(data): pb = pbl.get_pixbuf() try: pbs = pb.scale_simple(48,48,0) except: try: fp.close() pbl.close() except: pass print('could not scale image for %s '% fname) return gtk.image_new_from_file('blank.jpg') #~ pbs = pb #~ create image from pixbuf image = gtk.Image() image.set_from_pixbuf(pbs) #~ save cached copy if ext != '.png': pb.save(cachefile,"jpeg") else: pb.save(cachefile,"png") try: pbl.close() except: print('%s truncated or incomplete.' % url) else: #~ todo: make this a broken image print('broken image for %s'%fname) image = gtk.image_new_from_file('blank.jpg') fp.close() else: print('no url for image%'%fname) image = gtk.image_new_from_file('blank.jpg') return image def escape(self,s): s = s.replace('&','&') s = s.replace('<','<') s = s.replace('>','>') #~ s = s.replace('"','"') return s def unescape(self,s): p = htmllib.HTMLParser(None) p.save_bgn() p.feed(str(s)) s = p.save_end() #~ s = s.replace('<','<') #~ s = s.replace('>','>') s = s.replace(' rel="nofollow"','') #~ s = s.replace('"e;','"') s = s.replace('&','%26') s = s.replace('[1]','') return s def process_urls(self,s): s = self.escape(s) s = re.sub("(http://[^ ]+)", lambda m: "<a href='%s'>%s</a>" % (m.group(1).replace('&','&'), m.group(1).replace('&','&')),s) s = re.sub("(@)(\w+)", lambda m: "%s<a href='%s'>%s</a>" % (m.group(1),'http://twitter.com/'+m.group(2),m.group(2)),s) s = re.sub("(#)(\w+)", lambda m: "%s<a href='%s'>%s</a>" % (m.group(1),'#'+m.group(2),m.group(2)),s) return s def html_view(self,s,dm=False): """show search results and timelines in htmlview""" # clear, add viewport & scrolling table if self.sw1.get_child() is self.html: self.sw1.remove(self.html) self.sw1.set_property("hscrollbar-policy",gtk.POLICY_NEVER) self.viewport1 = gtk.Viewport() self.sw1.add(self.viewport1) else: self.viewport1.remove(self.table1) self.table1 = gtk.Table(23,2,False) self.table1.set_row_spacings(2) self.table1.set_col_spacings(2) self.viewport1.add(self.table1) self.pic = [] self.msg = [] t = [''] rows = 0 #display each user pic and instant message for x in s: if not dm: user = x.GetUser() else: user = api.GetUser(x.sender_screen_name) img = user.GetProfileImageUrl() usn = str(user.GetScreenName()) t[0] = x reply = usn shouts = self.getshouts(t) star = '☆' if not dm: if x.favorited: star = '★' if shouts: reply+=' @'+string.join(self.getshouts(t),' @') self.pic.append(self.image_from_url(img,usn)) text = self.process_urls(str(x.text)) #~ (re)construct html message self.msg.append(HtmlTextView()) self.msg[rows].connect("url-clicked",self.link_clicked) h = '<span>' if not dm: h+='<a title="Favorite" href="'+star+str(x.id)+'">'+star +'</a>' h+='<span style="font-weight: bold">'+'<a href="http://twitter.com/'+usn+'">' +usn+'</a></span>: '+ text + '<br /><span style="font-size:small">' if not dm: h+=x.relative_created_at+' via '+self.unescape(x.source)+' | ' if dm: h+='<a href="@'+reply+'">reply</a> | <a href="☠'+str(x.id)+'" title="Delete this.">Delete</a>' elif (usn==self.me.screen_name): h+='<a href="✗'+str(x.id)+'" title="Delete this tweet.">Delete</a>' else: h+='<a href="@'+reply+'">reply</a> | <a href="☞' +str(x.id)+'">retweet</a>' h+='</span></span>' try: self.msg[rows].display_html(str(h)) except: print('Error displaying:') print(h+'\n') self.table1.attach(self.pic[rows],0,1,rows,rows+1) self.table1.attach(self.msg[rows],1,2,rows,rows+1) rows+=1 #~ self.table1.attach(self.html,0,2,rows,rows+2) self.blank = gtk.Label() self.blank.set_property('height-request',100) self.table1.attach(self.blank,0,2,rows,rows+2) #~ self.table1.set_property("border-width", 5) self.sw1.show_all() def initialize_window(self): """Show the intro page""" self.html = HtmlTextView() self.html.connect("url-clicked",self.link_clicked) self.html.display_html('<div><span style="color: red; text-decoration:underline">Welcome to</span><br/>\n' ' <img src="http://comptune.com/images/penguin5.gif" alt="penguin" height="48" width="48" /><br/>\n' ' <span style="font-size: 500%; font-family: serif">crowbar</span>\n' '</div>\n') self.sw1.add(self.html) #~ add a search comboboxentry1 and 3 buttons self.liststore1 = gtk.ListStore(str) self.comboboxentry1 = gtk.ComboBoxEntry(self.liststore1, 0) popular = ['#teamfollowback','#tfb','#f4f'] trending=api.GetTrendsCurrent() trends = [x.name for x in trending] for row in popular: self.liststore1.append([row]) for row in trends: self.liststore1.append([row]) # insert a horizontal box with combo and buttons self.hbox2 = gtk.HBox(homogeneous = False) self.hbox2.pack_start(self.comboboxentry1) button = gtk.Button() button.set_label('search') button.set_property('width-request',55) button.connect('clicked',self.search_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('friends') button.set_property('width-request',55) button.connect('clicked',self.timeline_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('user') button.set_property('width-request',40) button.connect('clicked',self.user_timeline_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('dm') button.set_property('width-request',35) button.connect('clicked',self.dm_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) button = gtk.Button() button.set_label('mention') button.set_property('width-request',70) button.connect('clicked',self.mentions_clicked,self.comboboxentry1.child.get_text) self.hbox2.pack_start(button,expand = False,fill = True) #~ testify.test(self) self.vbox1.pack_start(self.hbox2,expand = False) self.html.show_all() self.hbox2.show_all() def display_results(self,s,getnames,dm=False): """display results of searches and timelines""" lnames = '' if getnames: snames = self.get_screen_names(s) lnames = "@"+string.join(snames," @") shouts = self.getshouts(s) self.textview1.set_wrap_mode(gtk.WRAP_WORD) buf = self.textview1.get_buffer() buf.set_text(lnames+"@"+string.join(shouts," @")) self.html_view(s,dm) self.window2.show_all() def get_list(self,widget): it = widget.get_iter('0') data = [] while 1: try: data.append(widget.get_value(it,0)) it = widget.iter_next(it) except: return data def update_search(self,text): l = self.get_list(self.liststore1) if text and (text not in l): self.liststore1.append([text]) def search_clicked(self,widget,method = None): """get names and shouts from api search and display them""" text = method() s = api.GetSearch(text) self.display_results(s,True) self.last_action = self.search_clicked self.last_method = method self.update_search(text) self.update_followers_count() def timeline_clicked(self,widget,method = None): """get friends' timeline""" text = method() getnames = False if text: getnames = True if '#' in text: getmames = False text = None s = api.GetUserTimeline(text) else: s = api.GetHomeTimeline() self.display_results(s,getnames) self.last_action = self.timeline_clicked self.last_method = method self.update_search(text) self.update_followers_count() def user_timeline_clicked(self,widget,method = None): """get user timeline""" text = method() getnames = False s = api.GetUserTimeline(text) self.display_results(s,getnames) self.last_action = self.user_timeline_clicked self.last_method = method self.update_search(text) self.update_followers_count() def mentions_clicked(self,widget,method = None): """get mentions""" s = api.GetMentions() self.display_results(s,True) self.last_action = self.mentions_clicked self.last_method = method self.update_followers_count() def dm_clicked(self,widget,method = None): """get mentions""" s = api.GetDirectMessages() self.display_results(s,False,True) self.last_action = self.mentions_clicked self.last_method = method self.update_followers_count() def refresh_clicked(self,widget,method = None): self.liststore1.clear() popular = ['#teamfollowback','#tfb','#f4f'] trending=api.GetTrendsCurrent() trends = [x.name for x in trending] for row in popular: self.liststore1.append([row]) for row in trends: self.liststore1.append([row]) self.last_action(None,self.last_method) def get_friendIDs(self): """Get friend and follower IDs""" global friendIDs,followerIDs if not globals().has_key('friendIDs'): print('getting friends list...') try: friendIDs=api.GetFriendIDs()['ids'] except: print("User not properly authenticated. Will not be able to post updates.") return False if not globals().has_key('followerIDs'): print('getting list of followers...') followerIDs=api.GetFollowerIDs()['ids'] return True def follow_clicked(self,widget,data = None): """follow all poeple in textview1 by name""" #~ iterate through the text buffer buf = self.textview1.get_buffer() (iter_first, iter_last) = buf.get_bounds() text = buf.get_text(iter_first, iter_last) #~ remove spaces and build a list to follow text = text.replace(' ','') fol = [] #~ create fol list for x in text.split('@')[1:]: if x not in fol: fol.append(x) #~ ask if self.warn and (not dialog.ok("Follow %s people.\n" "Are you sure?" % len(fol))): return else: self.warn=False # load nofollow list if it exists me = self.me.screen_name nofol = [] cachedir = self.get_cachedir() nofollow = cachedir+me+'.nofollow' if os.path.exists(nofollow): with open(nofollow) as f: nofol = [x.strip() for x in f.readlines()] nofollen=len(nofol) #~ friend everybody unless nofol, add to nofol for a in fol: if a not in nofol: try: api.CreateFriendship(a) print('followed %s' % a) except TwitterError as err: print(err) if 'follow-limits' in str(err): dialog.alert("You have reached Twitter's follow limit!") break nofol.append(a) #~ Write nofol to disk. These could be invalid names, #~ existing friends, or blocked accounts. #~ We don't care. We just don't follow them. if nofollen < len(nofol): with open(nofollow,'a') as f: f.writelines([x+'\n' for x in nofol[nofollen:]]) def follow_followers(self,widget,data = None): """follow followers by id not by name""" global friendIDs,followerIDs if not self.get_friendIDs(): return #~ safety feature in case twitter f's up if len(friendIDs) < 100: print('not enough friends') return #~ load protected and pending list me = self.me.screen_name cachedir = self.get_cachedir() protfile = cachedir+me+'.prot' newprot = []; prot=[] if os.path.exists(protfile): with open(protfile) as f: prot = [int(x.strip()) for x in f.readlines()] #~ follow everyone in followerIDs for a in followerIDs: if (a not in friendIDs) and (a not in prot): try: ret = api.CreateFriendship(a) friendIDs.append(a) print('followed %s' % ret.screen_name) except TwitterError as err: print(err) if not 'follow-limits' in str(err): newprot.append(a) else: dialog.alert("You have reached Twitter's follow limit!") break #~ those we can not follow are probably protected with open(protfile,'a') as f: f.writelines([str(x)+'\n' for x in newprot]) def unfollow_non_followers(self,widget,data = None): """unfollow everybody who is not following me todo: add safelist""" if not self.get_friendIDs(): return me = self.me.screen_name # load safelist cachedir = self.get_cachedir() safefile = cachedir+me+'.safe' safe = []; unsub = [] if os.path.exists(safefile): with open(safefile) as f: safe = [x.strip() for x in f.readlines()] #~ safety feature in case twitter f*s up if len(followerIDs) < 100: print('Need at least 100 followers to use this feature.') return for a in friendIDs: if (a not in followerIDs) and (a not in safe): unsub.append(a) #~ unsub.remove(self.me.id) #~ save nofollow list so we do not re-follow #~ (unless they follow us, of course) unsub_names=[] if dialog.ok('Unfriend all '+str(len(unsub))+' non-followers?'): #UNSUB EVERYBODY IN unsub for a in unsub: try: u=api.DestroyFriendship(a) print('unfriended %s' % u.screen_name) unsub_names.append(u.screen_name) except TwitterError as err: print(err) nofollow = cachedir+me+'.nofollow' with open(nofollow,'a') as f: f.writelines([x+'\n' for x in unsub_names]) def __init__(self): """main initialization routine""" global api,friendIDs,friends friends=[] tok=self.authorize() self.warn=True api = twitter.Api(self.consumer_key,self.consumer_secret, tok['oauth_token'],tok['oauth_token_secret']) builder = gtk.Builder() builder.add_from_file("crowbar.glade") self.window1 = builder.get_object("window1") self.vbox1 = builder.get_object("vbox1") self.sw1 = builder.get_object("sw1") self.window2 = builder.get_object("window2") self.textview1 = builder.get_object("textview1") self.entry1 = builder.get_object("entry1") self.button1 = builder.get_object("button1") builder.connect_signals(self) self.window2.connect("delete-event",self.delete_event) self.button1.connect("clicked",self.post_update,self.entry1.get_text) self.update_followers_count() self.window1.set_title("Crowbar @"+self.me.screen_name) self.initialize_window() #~ some variables to remember what we did last self.last_action = None self.last_method = None
def __init__(self, controller, parentConversation): gtk.VBox.__init__(self, spacing=3) self.set_border_width(0) self.parentConversation = parentConversation self.controller = controller self.config = self.controller.config self.parser = controller.unifiedParser self.header = Header(self, controller) self.tabWidget = TabWidget(self, controller) self.tabWidget.show() self.input = InputWidget(self, controller, \ parentConversation.isCurrent) self.input.show() self.status = gtk.Statusbar() self.toolbarinput = gtk.HBox() self.toolbarinput.show() self.listOfUsers = UserList.UserList(self.controller, \ self.controller.theme, self.config, False) self.scrollList = gtk.ScrolledWindow() self.scrollList.set_shadow_type(gtk.SHADOW_IN) self.scrollList.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrollList.set_size_request(111, 0) self.scrollList.add(self.listOfUsers) self.scroll = gtk.ScrolledWindow() self.scroll.set_shadow_type(gtk.SHADOW_IN) self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = HtmlTextView(controller, \ self.parentConversation.textBuffer, self.scroll) self.textview.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.textview.set_left_margin(6) self.textview.set_right_margin(6) self.textview.set_editable(False) self.textview.set_cursor_visible(False) self.textview.connect('key-press-event', self.onTextviewKeyPress) self.scroll.add(self.textview) self.scroll.show_all() self.remoteAvatar = AvatarHBox(self, controller, self.parentConversation.switchboard.firstUser) self.controller.msn.connect('user-attr-changed', self.onUserAttrChanged) self.vboxside = gtk.VBox() self.vboxside.pack_start(self.remoteAvatar, False, False) self.vboxside.pack_start(self.scrollList, True, True) self.vboxside.show() self.hbox = gtk.HBox(spacing=2) self.hbox.set_border_width(2) self.hbox.pack_start(self.scroll, True, True) self.hbox.pack_start(self.vboxside, False, False) self.hbox.show() self.toolbarinput.pack_start(self.input, True, True) self.toolbarinput.connect('size-allocate', self.onToolbarinputResize) vpaned = gtk.VPaned() vpaned.pack1(self.hbox, True, True) vpaned.pack2(self.toolbarinput, False) vpaned.show() self.transfers = FileTransferUI.FtBarWidget(self.controller, self, parentConversation) self.transfers.set_no_show_all(True) self.pack_start(self.header, False, False) self.pack_start(vpaned, True, True) self.pack_start(self.transfers, False, False) self.pack_start(self.status, False, False) self.messageWaiting = {} self.contactTyping = {} self.typingTimeoutID = 0 self.closed = False self.last_mark = None self.show() self.update()
class PluginInstaller(GajimPlugin): @log_calls('PluginInstallerPlugin') def init(self): self.config_dialog = PluginInstallerPluginConfigDialog(self) self.config_default_values = {'http_server': ('https://ftp.gajim.org', ''), 'check_update': (True, ''), } self.window = None self.progressbar = None self.available_plugins_model = None self.upgrading = False # True when opened from upgrade popup dialog self.timeout_id = 0 self.connected_ids = {} icon = gtk.Image() self.def_icon = icon.render_icon(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) if gajim.version.startswith('0.15'): self.server_folder = 'plugins_0.15' elif gajim.version.startswith('0.16.10'): self.server_folder = 'plugins_gtk3' else: self.server_folder = 'plugins_0.16_zip' @log_calls('PluginInstallerPlugin') def activate(self): self.pl_menuitem = gajim.interface.roster.xml.get_object( 'plugins_menuitem') self.id_ = self.pl_menuitem.connect_after('activate', self.on_activate) if 'plugins' in gajim.interface.instances: self.on_activate(None) if self.config['check_update']: self.timeout_id = gobject.timeout_add_seconds(30, self.check_update) @log_calls('PluginInstallerPlugin') def warn_update(self, plugins): def open_update(dummy): self.upgrading = True self.pl_menuitem.activate() nb = gajim.interface.instances['plugins'].plugins_notebook page = nb.page_num(self.hpaned) gobject.idle_add(nb.set_current_page, page) if plugins: plugins_str = '\n'.join(plugins) YesNoDialog(_('Plugins updates'), _('Some updates are available for' ' your installer plugins. Do you want to update those plugins:' '\n%s') % plugins_str, on_response_yes=open_update) def parse_manifest(self, buf): ''' given the buffer of the zipfile, returns the list of plugin manifests ''' zip_file = zipfile.ZipFile(buf) manifest_list = zip_file.namelist() plugins = [] for filename in manifest_list: config = ConfigParser.ConfigParser() config.readfp(zip_file.open(filename)) if not config.has_section('info'): continue plugins.append(config) return plugins def retrieve_path(self, directory, fname): server = self.config['http_server'] if not server: server = self.config_default_values['http_server'][0] if not urlparse.urlparse(server).scheme: server = 'https://' + server if urlparse.urlparse(server).scheme != 'https': log.warn('Warning: not using HTTPS is a ' 'very serious security issue!') location = posixpath.join(directory, fname) uri = urlparse.urljoin(server, location) log.debug('Fetching {}'.format(uri)) request = urllib2.urlopen(uri) manifest_buffer = io.BytesIO(request.read()) return manifest_buffer def retrieve_manifest(self): return self.retrieve_path(self.server_folder, 'manifests.zip') @log_calls('PluginInstallerPlugin') def check_update(self): def _run(): try: to_update = [] zipbuf = self.retrieve_manifest() plugin_manifests = self.parse_manifest(zipbuf) for config in plugin_manifests: opts = config.options('info') if 'name' not in opts or 'version' not in opts or \ 'description' not in opts or 'authors' not in opts or \ 'homepage' not in opts: continue local_version = ftp.get_plugin_version(config.get( 'info', 'name')) if local_version: local = convert_version_to_list(local_version) remote = convert_version_to_list(config.get('info', 'version')) if remote > local: to_update.append(config.get('info', 'name')) gobject.idle_add(self.warn_update, to_update) except Exception as e: log.exception('Ftp error when check for updates: ') ftp = Ftp(self) ftp.run = _run ftp.start() self.timeout_id = 0 @log_calls('PluginInstallerPlugin') def deactivate(self): self.pl_menuitem.disconnect(self.id_) if hasattr(self, 'page_num'): self.notebook.remove_page(self.notebook.page_num(self.hpaned)) self.notebook.set_current_page(0) for id_, widget in self.connected_ids.items(): widget.disconnect(id_) del self.page_num if hasattr(self, 'ftp'): del self.ftp if self.timeout_id > 0: gobject.source_remove(self.timeout_id) self.timeout_id = 0 def on_activate(self, widget): if 'plugins' not in gajim.interface.instances: return if hasattr(self, 'page_num'): # 'Available' tab exists return self.installed_plugins_model = gajim.interface.instances[ 'plugins'].installed_plugins_model self.notebook = gajim.interface.instances['plugins'].plugins_notebook id_ = self.notebook.connect('switch-page', self.on_notebook_switch_page) self.connected_ids[id_] = self.notebook self.window = gajim.interface.instances['plugins'].window id_ = self.window.connect('destroy', self.on_win_destroy) self.connected_ids[id_] = self.window self.GTK_BUILDER_FILE_PATH = self.local_file_path('config_dialog.ui') self.xml = gtk.Builder() self.xml.set_translation_domain('gajim_plugins') self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['hpaned2', 'image1']) self.hpaned = self.xml.get_object('hpaned2') self.page_num = self.notebook.append_page(self.hpaned, gtk.Label(_('Available'))) widgets_to_extract = ('plugin_name_label1', 'available_treeview', 'progressbar', 'inslall_upgrade_button', 'plugin_authors_label1', 'plugin_authors_label1', 'plugin_homepage_linkbutton1') for widget_name in widgets_to_extract: setattr(self, widget_name, self.xml.get_object(widget_name)) attr_list = pango.AttrList() attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) self.plugin_name_label1.set_attributes(attr_list) self.available_plugins_model = gtk.ListStore(gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT) self.available_treeview.set_model(self.available_plugins_model) self.available_treeview.set_rules_hint(True) self.available_plugins_model.set_sort_column_id(2, gtk.SORT_ASCENDING) self.progressbar.set_property('no-show-all', True) renderer = gtk.CellRendererText() col = gtk.TreeViewColumn(_('Plugin')) cell = gtk.CellRendererPixbuf() col.pack_start(cell, False) col.add_attribute(cell, 'pixbuf', C_PIXBUF) col.pack_start(renderer, True) col.add_attribute(renderer, 'text', C_NAME) col.set_resizable(True) col.set_property('expand', True) col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY) self.available_treeview.append_column(col) col = gtk.TreeViewColumn(_('Installed\nversion'), renderer, text=C_LOCAL_VERSION) self.available_treeview.append_column(col) col = gtk.TreeViewColumn(_('Available\nversion'), renderer, text=C_VERSION) col.set_property('expand', False) self.available_treeview.append_column(col) renderer = gtk.CellRendererToggle() renderer.set_property('activatable', True) renderer.connect('toggled', self.available_plugins_toggled_cb) col = gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer, active=C_UPGRADE) self.available_treeview.append_column(col) if gobject.signal_lookup('error_signal', self.window) is 0: gobject.signal_new('error_signal', self.window, gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING, (gobject.TYPE_STRING,)) gobject.signal_new('plugin_downloaded', self.window, gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING, (gobject.TYPE_PYOBJECT,)) id_ = self.window.connect('error_signal', self.on_some_ftp_error) self.connected_ids[id_] = self.window id_ = self.window.connect('plugin_downloaded', self.on_plugin_downloaded) self.connected_ids[id_] = self.window selection = self.available_treeview.get_selection() selection.connect('changed', self.available_plugins_treeview_selection_changed) selection.set_mode(gtk.SELECTION_SINGLE) self._clear_available_plugin_info() self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow1') sw.add(self.plugin_description_textview) self.xml.connect_signals(self) self.window.show_all() def on_win_destroy(self, widget): if hasattr(self, 'ftp'): del self.ftp if hasattr(self, 'page_num'): del self.page_num def available_plugins_toggled_cb(self, cell, path): is_active = self.available_plugins_model[path][C_UPGRADE] self.available_plugins_model[path][C_UPGRADE] = not is_active dir_list = [] for i in xrange(len(self.available_plugins_model)): if self.available_plugins_model[i][C_UPGRADE]: dir_list.append(self.available_plugins_model[i][C_DIR]) if not dir_list: self.inslall_upgrade_button.set_property('sensitive', False) else: self.inslall_upgrade_button.set_property('sensitive', True) def on_notebook_switch_page(self, widget, page, page_num): tab_label_text = self.notebook.get_tab_label_text(self.hpaned) if tab_label_text != (_('Available')): return if not hasattr(self, 'ftp'): self.available_plugins_model.clear() self.progressbar.show() self.ftp = Ftp(self) self.ftp.remote_dirs = None self.ftp.upgrading = True self.ftp.start() def on_inslall_upgrade_clicked(self, widget): self.inslall_upgrade_button.set_property('sensitive', False) dir_list = [] for i in xrange(len(self.available_plugins_model)): if self.available_plugins_model[i][C_UPGRADE]: dir_list.append(self.available_plugins_model[i][C_DIR]) ftp = Ftp(self) ftp.remote_dirs = dir_list ftp.start() def on_some_ftp_error(self, widget, error_text): for i in xrange(len(self.available_plugins_model)): self.available_plugins_model[i][C_UPGRADE] = False self.progressbar.hide() log.error(error_text) traceback.print_exc() WarningDialog(_('Ftp error'), error_text, self.window) def on_plugin_downloaded(self, widget, plugin_dirs): dialog = HigDialog(None, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, '', _('All selected plugins downloaded')) dialog.set_modal(False) dialog.set_transient_for(self.window) for _dir in plugin_dirs: is_active = False plugins = None plugin_dir = os.path.join(gajim.PLUGINS_DIRS[1], _dir) plugin = gajim.plugin_manager.get_plugin_by_path(plugin_dir) if plugin: if plugin.active: is_active = True gobject.idle_add(gajim.plugin_manager.deactivate_plugin, plugin) gajim.plugin_manager.plugins.remove(plugin) model = self.installed_plugins_model for row in xrange(len(model)): if plugin == model[row][0]: model.remove(model.get_iter((row, 0))) break plugins = self.scan_dir_for_plugin(plugin_dir) if not plugins: continue gajim.plugin_manager.add_plugin(plugins[0]) plugin = gajim.plugin_manager.plugins[-1] for row in xrange(len(self.available_plugins_model)): if plugin.name == self.available_plugins_model[row][C_NAME]: self.available_plugins_model[row][C_LOCAL_VERSION] = \ plugin.version self.available_plugins_model[row][C_UPGRADE] = False if is_active: gobject.idle_add(gajim.plugin_manager.activate_plugin, plugin) # get plugin icon icon_file = os.path.join(plugin.__path__, os.path.split( plugin.__path__)[1]) + '.png' icon = self.def_icon if os.path.isfile(icon_file): icon = gtk.gdk.pixbuf_new_from_file_at_size(icon_file, 16, 16) if not hasattr(plugin, 'activatable'): # version 0.15 plugin.activatable = False max_row = [plugin, plugin.name, is_active, plugin.activatable, icon] # support old plugin system if len(self.installed_plugins_model): row_len = len(self.installed_plugins_model[0]) else: row_len = 5 row = max_row[0: row_len] self.installed_plugins_model.append(row) dialog.popup() def available_plugins_treeview_selection_changed(self, treeview_selection): model, iter = treeview_selection.get_selected() self.xml.get_object('scrolledwindow1').get_children()[0].destroy() self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow1') sw.add(self.plugin_description_textview) sw.show_all() if iter: self.plugin_name_label1.set_text(model.get_value(iter, C_NAME)) self.plugin_authors_label1.set_text(model.get_value(iter, C_AUTHORS)) self.plugin_homepage_linkbutton1.set_uri(model.get_value(iter, C_HOMEPAGE)) self.plugin_homepage_linkbutton1.set_label(model.get_value(iter, C_HOMEPAGE)) label = self.plugin_homepage_linkbutton1.get_children()[0] label.set_ellipsize(pango.ELLIPSIZE_END) self.plugin_homepage_linkbutton1.set_property('sensitive', True) desc = _(model.get_value(iter, C_DESCRIPTION)) if not desc.startswith('<body '): desc = "<body xmlns='http://www.w3.org/1999/xhtml'>" + \ desc + ' </body>' desc = desc.replace('\n', '<br/>') self.plugin_description_textview.display_html(desc, self.plugin_description_textview, None) self.plugin_description_textview.set_property('sensitive', True) else: self._clear_available_plugin_info() def _clear_available_plugin_info(self): self.plugin_name_label1.set_text('') self.plugin_authors_label1.set_text('') self.plugin_homepage_linkbutton1.set_uri('') self.plugin_homepage_linkbutton1.set_label('') self.plugin_homepage_linkbutton1.set_property('sensitive', False) def scan_dir_for_plugin(self, path): plugins_found = [] conf = ConfigParser.ConfigParser() fields = ('name', 'short_name', 'version', 'description', 'authors', 'homepage') if not os.path.isdir(path): return plugins_found dir_list = os.listdir(path) dir_, mod = os.path.split(path) sys.path.insert(0, dir_) manifest_path = os.path.join(path, 'manifest.ini') if not os.path.isfile(manifest_path): return plugins_found for elem_name in dir_list: file_path = os.path.join(path, elem_name) module = None if os.path.isfile(file_path) and fnmatch.fnmatch(file_path, '*.py'): module_name = os.path.splitext(elem_name)[0] if module_name == '__init__': continue try: full_module_name = '%s.%s' % (mod, module_name) if full_module_name in sys.modules: module = reload(sys.modules[full_module_name]) else: module = __import__(full_module_name) except ValueError, value_error: pass except ImportError, import_error: pass except AttributeError, attribute_error: pass
def __init__(self, used_in_history_window = False): """ If used_in_history_window is True, then we do not show Clear menuitem in context menu """ GObject.GObject.__init__(self) self.used_in_history_window = used_in_history_window #self.fc = FuzzyClock() # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() #self.tv.hyperlink_handler = self.hyperlink_handler # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) self.tv.set_editable(False) self.tv.set_cursor_visible(False) self.tv.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self.tv.set_left_margin(2) self.tv.set_right_margin(2) self.handlers = {} self.images = [] self.image_cache = {} self.xep0184_marks = {} self.xep0184_shown = {} self.last_sent_message_marks = [None, None] # A pair per occupant. Key is '' in normal chat self.last_received_message_marks = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False # connect signals id_ = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) self.handlers[id_] = self.tv 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 id_ = self.tv.connect('draw', self.on_textview_draw) self.handlers[id_] = self.tv self.change_cursor = False self.last_time_printout = 0 #font = Pango.FontDescription(gajim.config.get('conversation_font')) #self.tv.override_font(font) buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() buffer_.create_mark('end', end_iter, False) #self.tagIn = buffer_.create_tag('incoming') #color = gajim.config.get('inmsgcolor') #font = Pango.FontDescription(gajim.config.get('inmsgfont')) #self.tagIn.set_property('foreground', color) #self.tagIn.set_property('font-desc', font) #self.tagOut = buffer_.create_tag('outgoing') #color = gajim.config.get('outmsgcolor') #font = Pango.FontDescription(gajim.config.get('outmsgfont')) #self.tagOut.set_property('foreground', color) #self.tagOut.set_property('font-desc', font) #self.tagStatus = buffer_.create_tag('status') #color = gajim.config.get('statusmsgcolor') #font = Pango.FontDescription(gajim.config.get('satusmsgfont')) #self.tagStatus.set_property('foreground', color) #self.tagStatus.set_property('font-desc', font) #self.tagInText = buffer_.create_tag('incomingtxt') #color = gajim.config.get('inmsgtxtcolor') #font = Pango.FontDescription(gajim.config.get('inmsgtxtfont')) #if color: # self.tagInText.set_property('foreground', color) #self.tagInText.set_property('font-desc', font) #self.tagOutText = buffer_.create_tag('outgoingtxt') #color = gajim.config.get('outmsgtxtcolor') #if color: # font = Pango.FontDescription(gajim.config.get('outmsgtxtfont')) #self.tagOutText.set_property('foreground', color) #self.tagOutText.set_property('font-desc', font) #colors = gajim.config.get('gc_nicknames_colors') #colors = colors.split(':') #for i, color in enumerate(colors): # tagname = 'gc_nickname_color_' + str(i) # tag = buffer_.create_tag(tagname) # tag.set_property('foreground', color) #self.tagMarked = buffer_.create_tag('marked') #color = gajim.config.get('markedmsgcolor') #self.tagMarked.set_property('foreground', color) #self.tagMarked.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('time_sometimes') #tag.set_property('foreground', 'darkgrey') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag.set_property('justification', Gtk.Justification.CENTER) #tag = buffer_.create_tag('small') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag = buffer_.create_tag('restored_message') #color = gajim.config.get('restored_messages_color') #tag.set_property('foreground', color) #self.tv.create_tags() #tag = buffer_.create_tag('bold') #tag.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('italic') #tag.set_property('style', Pango.Style.ITALIC) #tag = buffer_.create_tag('underline') #tag.set_property('underline', Pango.Underline.SINGLE) #buffer_.create_tag('focus-out-line', justification = Gtk.Justification.CENTER) #self.displaymarking_tags = {} #tag = buffer_.create_tag('xep0184-warning') #tag.set_property('foreground', '#cc0000') #tag = buffer_.create_tag('xep0184-received') #tag.set_property('foreground', '#73d216') # One mark at the begining then 2 marks between each lines #size = gajim.config.get('max_conversation_lines') #size = 2 * size - 1 #self.marks_queue = queue.Queue(size) self.allow_focus_out_line = True # holds a mark at the end of --- line self.focus_out_end_mark = None #self.xep0184_warning_tooltip = tooltips.BaseTooltip() #self.line_tooltip = tooltips.BaseTooltip() self.smooth_id = None self.just_cleared = False size = 500 size = 2 * size - 1 self.marks_queue = queue.Queue(size)
def on_activate(self, widget): if 'plugins' not in gajim.interface.instances: return if hasattr(self, 'page_num'): # 'Available' tab exists return self.installed_plugins_model = gajim.interface.instances[ 'plugins'].installed_plugins_model self.notebook = gajim.interface.instances['plugins'].plugins_notebook id_ = self.notebook.connect('switch-page', self.on_notebook_switch_page) self.connected_ids[id_] = self.notebook self.window = gajim.interface.instances['plugins'].window id_ = self.window.connect('destroy', self.on_win_destroy) self.connected_ids[id_] = self.window self.GTK_BUILDER_FILE_PATH = self.local_file_path('config_dialog.ui') self.xml = gtk.Builder() self.xml.set_translation_domain('gajim_plugins') self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['hpaned2', 'image1']) self.hpaned = self.xml.get_object('hpaned2') self.page_num = self.notebook.append_page(self.hpaned, gtk.Label(_('Available'))) widgets_to_extract = ('plugin_name_label1', 'available_treeview', 'progressbar', 'inslall_upgrade_button', 'plugin_authors_label1', 'plugin_authors_label1', 'plugin_homepage_linkbutton1') for widget_name in widgets_to_extract: setattr(self, widget_name, self.xml.get_object(widget_name)) attr_list = pango.AttrList() attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) self.plugin_name_label1.set_attributes(attr_list) self.available_plugins_model = gtk.ListStore(gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT) self.available_treeview.set_model(self.available_plugins_model) self.available_treeview.set_rules_hint(True) self.available_plugins_model.set_sort_column_id(2, gtk.SORT_ASCENDING) self.progressbar.set_property('no-show-all', True) renderer = gtk.CellRendererText() col = gtk.TreeViewColumn(_('Plugin')) cell = gtk.CellRendererPixbuf() col.pack_start(cell, False) col.add_attribute(cell, 'pixbuf', C_PIXBUF) col.pack_start(renderer, True) col.add_attribute(renderer, 'text', C_NAME) col.set_resizable(True) col.set_property('expand', True) col.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY) self.available_treeview.append_column(col) col = gtk.TreeViewColumn(_('Installed\nversion'), renderer, text=C_LOCAL_VERSION) self.available_treeview.append_column(col) col = gtk.TreeViewColumn(_('Available\nversion'), renderer, text=C_VERSION) col.set_property('expand', False) self.available_treeview.append_column(col) renderer = gtk.CellRendererToggle() renderer.set_property('activatable', True) renderer.connect('toggled', self.available_plugins_toggled_cb) col = gtk.TreeViewColumn(_('Install /\nUpgrade'), renderer, active=C_UPGRADE) self.available_treeview.append_column(col) if gobject.signal_lookup('error_signal', self.window) is 0: gobject.signal_new('error_signal', self.window, gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING, (gobject.TYPE_STRING,)) gobject.signal_new('plugin_downloaded', self.window, gobject.SIGNAL_RUN_LAST, gobject.TYPE_STRING, (gobject.TYPE_PYOBJECT,)) id_ = self.window.connect('error_signal', self.on_some_ftp_error) self.connected_ids[id_] = self.window id_ = self.window.connect('plugin_downloaded', self.on_plugin_downloaded) self.connected_ids[id_] = self.window selection = self.available_treeview.get_selection() selection.connect('changed', self.available_plugins_treeview_selection_changed) selection.set_mode(gtk.SELECTION_SINGLE) self._clear_available_plugin_info() self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow1') sw.add(self.plugin_description_textview) self.xml.connect_signals(self) self.window.show_all()
def __init__(self, used_in_history_window=False): """ If used_in_history_window is True, then we do not show Clear menuitem in context menu """ GObject.GObject.__init__(self) self.used_in_history_window = used_in_history_window #self.fc = FuzzyClock() # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() #self.tv.hyperlink_handler = self.hyperlink_handler # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) self.tv.set_editable(False) self.tv.set_cursor_visible(False) self.tv.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self.tv.set_left_margin(2) self.tv.set_right_margin(2) self.handlers = {} self.images = [] self.image_cache = {} self.xep0184_marks = {} self.xep0184_shown = {} self.last_sent_message_marks = [None, None] # A pair per occupant. Key is '' in normal chat self.last_received_message_marks = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False # connect signals id_ = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) self.handlers[id_] = self.tv 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 id_ = self.tv.connect('draw', self.on_textview_draw) self.handlers[id_] = self.tv self.change_cursor = False self.last_time_printout = 0 #font = Pango.FontDescription(gajim.config.get('conversation_font')) #self.tv.override_font(font) buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() buffer_.create_mark('end', end_iter, False) #self.tagIn = buffer_.create_tag('incoming') #color = gajim.config.get('inmsgcolor') #font = Pango.FontDescription(gajim.config.get('inmsgfont')) #self.tagIn.set_property('foreground', color) #self.tagIn.set_property('font-desc', font) #self.tagOut = buffer_.create_tag('outgoing') #color = gajim.config.get('outmsgcolor') #font = Pango.FontDescription(gajim.config.get('outmsgfont')) #self.tagOut.set_property('foreground', color) #self.tagOut.set_property('font-desc', font) #self.tagStatus = buffer_.create_tag('status') #color = gajim.config.get('statusmsgcolor') #font = Pango.FontDescription(gajim.config.get('satusmsgfont')) #self.tagStatus.set_property('foreground', color) #self.tagStatus.set_property('font-desc', font) #self.tagInText = buffer_.create_tag('incomingtxt') #color = gajim.config.get('inmsgtxtcolor') #font = Pango.FontDescription(gajim.config.get('inmsgtxtfont')) #if color: # self.tagInText.set_property('foreground', color) #self.tagInText.set_property('font-desc', font) #self.tagOutText = buffer_.create_tag('outgoingtxt') #color = gajim.config.get('outmsgtxtcolor') #if color: # font = Pango.FontDescription(gajim.config.get('outmsgtxtfont')) #self.tagOutText.set_property('foreground', color) #self.tagOutText.set_property('font-desc', font) #colors = gajim.config.get('gc_nicknames_colors') #colors = colors.split(':') #for i, color in enumerate(colors): # tagname = 'gc_nickname_color_' + str(i) # tag = buffer_.create_tag(tagname) # tag.set_property('foreground', color) #self.tagMarked = buffer_.create_tag('marked') #color = gajim.config.get('markedmsgcolor') #self.tagMarked.set_property('foreground', color) #self.tagMarked.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('time_sometimes') #tag.set_property('foreground', 'darkgrey') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag.set_property('justification', Gtk.Justification.CENTER) #tag = buffer_.create_tag('small') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag = buffer_.create_tag('restored_message') #color = gajim.config.get('restored_messages_color') #tag.set_property('foreground', color) #self.tv.create_tags() #tag = buffer_.create_tag('bold') #tag.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('italic') #tag.set_property('style', Pango.Style.ITALIC) #tag = buffer_.create_tag('underline') #tag.set_property('underline', Pango.Underline.SINGLE) #buffer_.create_tag('focus-out-line', justification = Gtk.Justification.CENTER) #self.displaymarking_tags = {} #tag = buffer_.create_tag('xep0184-warning') #tag.set_property('foreground', '#cc0000') #tag = buffer_.create_tag('xep0184-received') #tag.set_property('foreground', '#73d216') # One mark at the begining then 2 marks between each lines #size = gajim.config.get('max_conversation_lines') #size = 2 * size - 1 #self.marks_queue = queue.Queue(size) self.allow_focus_out_line = True # holds a mark at the end of --- line self.focus_out_end_mark = None #self.xep0184_warning_tooltip = tooltips.BaseTooltip() #self.line_tooltip = tooltips.BaseTooltip() self.smooth_id = None self.just_cleared = False size = 500 size = 2 * size - 1 self.marks_queue = queue.Queue(size)
class ConversationTextview(GObject.GObject): """ Class for the conversation textview (where user reads already said messages) for chat/groupchat windows """ __gsignals__ = dict(quote=( GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, None, # return value (str, ) # arguments )) # smooth scroll constants MAX_SCROLL_TIME = 0.4 # seconds SCROLL_DELAY = 33 # milliseconds def __init__(self, used_in_history_window=False): """ If used_in_history_window is True, then we do not show Clear menuitem in context menu """ GObject.GObject.__init__(self) self.used_in_history_window = used_in_history_window #self.fc = FuzzyClock() # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() #self.tv.hyperlink_handler = self.hyperlink_handler # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) self.tv.set_editable(False) self.tv.set_cursor_visible(False) self.tv.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self.tv.set_left_margin(2) self.tv.set_right_margin(2) self.handlers = {} self.images = [] self.image_cache = {} self.xep0184_marks = {} self.xep0184_shown = {} self.last_sent_message_marks = [None, None] # A pair per occupant. Key is '' in normal chat self.last_received_message_marks = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False # connect signals id_ = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) self.handlers[id_] = self.tv 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 id_ = self.tv.connect('draw', self.on_textview_draw) self.handlers[id_] = self.tv self.change_cursor = False self.last_time_printout = 0 #font = Pango.FontDescription(gajim.config.get('conversation_font')) #self.tv.override_font(font) buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() buffer_.create_mark('end', end_iter, False) #self.tagIn = buffer_.create_tag('incoming') #color = gajim.config.get('inmsgcolor') #font = Pango.FontDescription(gajim.config.get('inmsgfont')) #self.tagIn.set_property('foreground', color) #self.tagIn.set_property('font-desc', font) #self.tagOut = buffer_.create_tag('outgoing') #color = gajim.config.get('outmsgcolor') #font = Pango.FontDescription(gajim.config.get('outmsgfont')) #self.tagOut.set_property('foreground', color) #self.tagOut.set_property('font-desc', font) #self.tagStatus = buffer_.create_tag('status') #color = gajim.config.get('statusmsgcolor') #font = Pango.FontDescription(gajim.config.get('satusmsgfont')) #self.tagStatus.set_property('foreground', color) #self.tagStatus.set_property('font-desc', font) #self.tagInText = buffer_.create_tag('incomingtxt') #color = gajim.config.get('inmsgtxtcolor') #font = Pango.FontDescription(gajim.config.get('inmsgtxtfont')) #if color: # self.tagInText.set_property('foreground', color) #self.tagInText.set_property('font-desc', font) #self.tagOutText = buffer_.create_tag('outgoingtxt') #color = gajim.config.get('outmsgtxtcolor') #if color: # font = Pango.FontDescription(gajim.config.get('outmsgtxtfont')) #self.tagOutText.set_property('foreground', color) #self.tagOutText.set_property('font-desc', font) #colors = gajim.config.get('gc_nicknames_colors') #colors = colors.split(':') #for i, color in enumerate(colors): # tagname = 'gc_nickname_color_' + str(i) # tag = buffer_.create_tag(tagname) # tag.set_property('foreground', color) #self.tagMarked = buffer_.create_tag('marked') #color = gajim.config.get('markedmsgcolor') #self.tagMarked.set_property('foreground', color) #self.tagMarked.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('time_sometimes') #tag.set_property('foreground', 'darkgrey') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag.set_property('justification', Gtk.Justification.CENTER) #tag = buffer_.create_tag('small') #Pango.SCALE_SMALL #tag.set_property('scale', 0.8333333333333) #tag = buffer_.create_tag('restored_message') #color = gajim.config.get('restored_messages_color') #tag.set_property('foreground', color) #self.tv.create_tags() #tag = buffer_.create_tag('bold') #tag.set_property('weight', Pango.Weight.BOLD) #tag = buffer_.create_tag('italic') #tag.set_property('style', Pango.Style.ITALIC) #tag = buffer_.create_tag('underline') #tag.set_property('underline', Pango.Underline.SINGLE) #buffer_.create_tag('focus-out-line', justification = Gtk.Justification.CENTER) #self.displaymarking_tags = {} #tag = buffer_.create_tag('xep0184-warning') #tag.set_property('foreground', '#cc0000') #tag = buffer_.create_tag('xep0184-received') #tag.set_property('foreground', '#73d216') # One mark at the begining then 2 marks between each lines #size = gajim.config.get('max_conversation_lines') #size = 2 * size - 1 #self.marks_queue = queue.Queue(size) self.allow_focus_out_line = True # holds a mark at the end of --- line self.focus_out_end_mark = None #self.xep0184_warning_tooltip = tooltips.BaseTooltip() #self.line_tooltip = tooltips.BaseTooltip() self.smooth_id = None self.just_cleared = False size = 500 size = 2 * size - 1 self.marks_queue = queue.Queue(size) def print_conversation_line(self, text, jid, kind, name): """ Print 'chat' type messages """ buffer_ = self.tv.get_buffer() buffer_.begin_user_action() if self.marks_queue.full(): # remove oldest line m1 = self.marks_queue.get() m2 = self.marks_queue.get() i1 = buffer_.get_iter_at_mark(m1) i2 = buffer_.get_iter_at_mark(m2) buffer_.delete(i1, i2) buffer_.delete_mark(m1) end_iter = buffer_.get_end_iter() end_offset = end_iter.get_offset() at_the_end = self.at_the_end() move_selection = False if buffer_.get_has_selection() and buffer_.get_selection_bounds()[1].\ get_offset() == end_offset: move_selection = True # Create one mark and add it to queue once if it's the first line # else twice (one for end bound, one for start bound) mark = None if buffer_.get_char_count() > 0: mark = buffer_.create_mark(None, end_iter, left_gravity=True) self.marks_queue.put(mark) if not mark: mark = buffer_.create_mark(None, end_iter, left_gravity=True) self.marks_queue.put(mark) if kind == 'incoming_queue': kind = 'incoming' # print the time stamp # We don't have tim for outgoing messages... import time tim = time.localtime() direction_mark = '' # don't apply direction mark if it's status message timestamp_str = self.get_time_to_show(tim, direction_mark) timestamp = time.strftime(timestamp_str, tim) timestamp = timestamp + ' ' buffer_.insert(end_iter, timestamp) self.print_name(name, kind, direction_mark=direction_mark, iter_=end_iter) #if kind == 'incoming': # text_tags.append('incomingtxt') # mark1 = mark #elif kind == 'outgoing': # text_tags.append('outgoingtxt') # mark1 = mark #subject = None #self.print_subject(subject, iter_=end_iter) self.print_real_text(text, name, iter_=end_iter) # scroll to the end of the textview if at_the_end or kind == 'outgoing': # we are at the end or we are sending something # scroll to the end (via idle in case the scrollbar has appeared) if True: GLib.idle_add(self.smooth_scroll_to_end) else: GLib.idle_add(self.scroll_to_end) self.just_cleared = False buffer_.end_user_action() return end_iter # Smooth scrolling inspired by Pidgin code def smooth_scroll(self): parent = self.tv.get_parent() if not parent: return False vadj = parent.get_vadjustment() max_val = vadj.get_upper() - vadj.get_page_size() + 1 cur_val = vadj.get_value() # scroll by 1/3rd of remaining distance onethird = cur_val + ((max_val - cur_val) / 3.0) self.auto_scrolling = True vadj.set_value(onethird) self.auto_scrolling = False if max_val - onethird < 0.01: self.smooth_id = None self.smooth_scroll_timer.cancel() return False return True def smooth_scroll_timeout(self): GLib.idle_add(self.do_smooth_scroll_timeout) return def do_smooth_scroll_timeout(self): if not self.smooth_id: # we finished scrolling return GLib.source_remove(self.smooth_id) self.smooth_id = None parent = self.tv.get_parent() if parent: vadj = parent.get_vadjustment() self.auto_scrolling = True vadj.set_value(vadj.get_upper() - vadj.get_page_size() + 1) self.auto_scrolling = False def smooth_scroll_to_end(self): if None != self.smooth_id: # already scrolling return False self.smooth_id = GLib.timeout_add(self.SCROLL_DELAY, self.smooth_scroll) self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, self.smooth_scroll_timeout) self.smooth_scroll_timer.start() return False def print_name(self, name, kind, direction_mark='', iter_=None): if name: buffer_ = self.tv.get_buffer() if iter_: end_iter = iter_ else: end_iter = buffer_.get_end_iter() before_str = '' after_str = ':' format_ = before_str + name + direction_mark + after_str + ' ' buffer_.insert(end_iter, format_) def print_real_text(self, text, name, iter_=None): """ Add normal and special text. call this to add text """ # /me is replaced by name if name is given if name and (text.startswith('/me ') or text.startswith('/me\n')): text = '* ' + name + text[3:] #text_tags.append('italic') # detect urls formatting and if the user has it on emoticons buffer_ = self.tv.get_buffer() buffer_.insert(iter_, text + "\n") #return self.detect_and_print_special_text(text, iter_=iter_) def get_time_to_show(self, tim, direction_mark=''): from calendar import timegm import time """ Get the time, with the day before if needed and return it. It DOESN'T format a fuzzy time """ format_ = '' # get difference in days since epoch (86400 = 24*3600) # number of days since epoch for current time (in GMT) - # number of days since epoch for message (in GMT) diff_day = int(int(timegm(time.localtime())) / 86400 -\ int(timegm(tim)) / 86400) timestamp_str = '[%X]' format_ += timestamp_str tim_format = time.strftime(format_, tim) return tim_format def on_textview_motion_notify_event(self, widget, event): """ Change the cursor to a hand when we are over a mail or an url """ w = self.tv.get_window(Gtk.TextWindowType.TEXT) device = w.get_display().get_device_manager().get_client_pointer() pointer = w.get_device_position(device) x, y = self.tv.window_to_buffer_coords(Gtk.TextWindowType.TEXT, pointer[1], pointer[2]) tags = self.tv.get_iter_at_location(x, y).get_tags() if self.change_cursor: w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.XTERM)) self.change_cursor = False tag_table = self.tv.get_buffer().get_tag_table() over_line = False xep0184_warning = False for tag in tags: if tag in (tag_table.lookup('url'), tag_table.lookup('mail'), \ tag_table.lookup('xmpp'), tag_table.lookup('sth_at_sth')): w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2)) self.change_cursor = True elif tag == tag_table.lookup('focus-out-line'): over_line = True elif tag == tag_table.lookup('xep0184-warning'): xep0184_warning = True #if self.line_tooltip.timeout != 0: # Check if we should hide the line tooltip # if not over_line: # self.line_tooltip.hide_tooltip() #if self.xep0184_warning_tooltip.timeout != 0: # Check if we should hide the XEP-184 warning tooltip # if not xep0184_warning: # self.xep0184_warning_tooltip.hide_tooltip() if over_line and not self.line_tooltip.win: self.line_tooltip.timeout = GLib.timeout_add( 500, self.show_line_tooltip) w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) self.change_cursor = True if xep0184_warning and not self.xep0184_warning_tooltip.win: self.xep0184_warning_tooltip.timeout = GLib.timeout_add( 500, self.show_xep0184_warning_tooltip) w.set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) self.change_cursor = True def on_textview_populate_popup(self, textview, menu): """ Override the default context menu and we prepend Clear (only if used_in_history_window is False) and if we have sth selected we show a submenu with actions on the phrase (see on_conversation_textview_button_press_event) """ separator_menuitem_was_added = False menu.show_all() def on_textview_button_press_event(self, widget, event): # If we clicked on a taged text do NOT open the standard popup menu # if normal text check if we have sth selected self.selected_phrase = '' # do not move belove event button check! if event.button != 3: # if not right click return False x, y = self.tv.window_to_buffer_coords(Gtk.TextWindowType.TEXT, int(event.x), int(event.y)) iter_ = self.tv.get_iter_at_location(x, y) tags = iter_.get_tags() if tags: # we clicked on sth special (it can be status message too) for tag in tags: tag_name = tag.get_property('name') if tag_name in ('url', 'mail', 'xmpp', 'sth_at_sth'): return True # we block normal context menu # we check if sth was selected and if it was we assign # selected_phrase variable # so on_conversation_textview_populate_popup can use it buffer_ = self.tv.get_buffer() return_val = buffer_.get_selection_bounds() if return_val: # if sth was selected when we right-clicked # get the selected text start_sel, finish_sel = return_val[0], return_val[1] self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True) elif iter_.get_char() and ord(iter_.get_char()) > 31: # we clicked on a word, do as if it's selected for context menu start_sel = iter_.copy() if not start_sel.starts_word(): start_sel.backward_word_start() finish_sel = iter_.copy() if not finish_sel.ends_word(): finish_sel.forward_word_end() self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True) def on_textview_draw(self, widget, ctx): return #TODO expalloc = event.area exp_x0 = expalloc.x exp_y0 = expalloc.y exp_x1 = exp_x0 + expalloc.width exp_y1 = exp_y0 + expalloc.height try: tryfirst = [self.image_cache[(exp_x0, exp_y0)]] except KeyError: tryfirst = [] for image in tryfirst + self.images: imgalloc = image.allocation img_x0 = imgalloc.x img_y0 = imgalloc.y img_x1 = img_x0 + imgalloc.width img_y1 = img_y0 + imgalloc.height if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ exp_x1 <= img_x1 and exp_y1 <= img_y1: self.image_cache[(img_x0, img_y0)] = image widget.propagate_expose(image, event) return True return False def at_the_end(self): buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() end_rect = self.tv.get_iter_location(end_iter) visible_rect = self.tv.get_visible_rect() if end_rect.y <= (visible_rect.y + visible_rect.height): return True return False def bring_scroll_to_end(self, diff_y=0, use_smooth=True): ''' scrolls to the end of textview if end is not visible ''' buffer_ = self.tv.get_buffer() end_iter = buffer_.get_end_iter() end_rect = self.tv.get_iter_location(end_iter) visible_rect = self.tv.get_visible_rect() # scroll only if expected end is not visible if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): if use_smooth: GLib.idle_add(self.smooth_scroll_to_end) else: GLib.idle_add(self.scroll_to_end_iter)
def __init__(self, controlState, xml): controlState.displayHeaderBar = True controlState.windowIcon = 'abouttoinstall.png' controlState.windowTitle = "Summary of installation settings" controlState.windowText = \ "Review the summary of the installation settings" htmlTextView = HtmlTextView() htmlTextView.set_wrap_mode(gtk.WRAP_NONE) context = htmlTextView.get_pango_context() initialFont = context.get_font_description() # The review uses monospace, so get the font description for that at the # same size as the default font used by the text view. metrics = context.get_metrics( pango.FontDescription('monospace, %d' % (initialFont.get_size() / pango.SCALE))) # Generate the list of tab stops from the column widths specified in # the review module. tabPosition = 0 ta = pango.TabArray(len(review.COLUMN_WIDTHS), True) for index, charWidth in enumerate(review.COLUMN_WIDTHS): tabPosition += pango.PIXELS(metrics.get_approximate_char_width() * charWidth) ta.set_tab(index, pango.TAB_LEFT, tabPosition) htmlTextView.set_tabs(ta) buf = review.produceText() htmlTextView.display_html(buf) viewPort = xml.get_widget('ReviewViewPort') assert (viewPort) for child in viewPort.get_children(): viewPort.remove(child) viewPort.add(htmlTextView) htmlTextView.show()
class PluginsWindow(object): '''Class for Plugins window''' @log_calls('PluginsWindow') def __init__(self): '''Initialize Plugins window''' self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui') self.window = self.xml.get_object('plugins_window') self.window.set_transient_for(gajim.interface.roster.window) widgets_to_extract = ('plugins_notebook', 'plugin_name_label', 'plugin_version_label', 'plugin_authors_label', 'plugin_homepage_linkbutton', 'uninstall_plugin_button', 'configure_plugin_button', 'installed_plugins_treeview') for widget_name in widgets_to_extract: setattr(self, widget_name, self.xml.get_object(widget_name)) self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow2') sw.add(self.plugin_description_textview) self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool, GdkPixbuf.Pixbuf) self.installed_plugins_treeview.set_model(self.installed_plugins_model) self.installed_plugins_treeview.set_rules_hint(True) renderer = Gtk.CellRendererText() col = Gtk.TreeViewColumn(_('Plugin')) #, renderer, text=NAME) cell = Gtk.CellRendererPixbuf() col.pack_start(cell, False) col.add_attribute(cell, 'pixbuf', ICON) col.pack_start(renderer, True) col.add_attribute(renderer, 'text', NAME) col.set_property('expand', True) self.installed_plugins_treeview.append_column(col) renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.installed_plugins_toggled_cb) col = Gtk.TreeViewColumn(_('Active'), renderer, active=ACTIVE, activatable=ACTIVATABLE) self.installed_plugins_treeview.append_column(col) self.def_icon = gtkgui_helpers.get_icon_pixmap('preferences-desktop') # connect signal for selection change selection = self.installed_plugins_treeview.get_selection() selection.connect('changed', self.installed_plugins_treeview_selection_changed) selection.set_mode(Gtk.SelectionMode.SINGLE) self._clear_installed_plugin_info() self.fill_installed_plugins_model() root_iter = self.installed_plugins_model.get_iter_first() if root_iter: selection.select_iter(root_iter) self.xml.connect_signals(self) self.plugins_notebook.set_current_page(0) self.xml.get_object('close_button').grab_focus() self.window.show_all() gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) def on_plugins_notebook_switch_page(self, widget, page, page_num): GLib.idle_add(self.xml.get_object('close_button').grab_focus) @log_calls('PluginsWindow') def installed_plugins_treeview_selection_changed(self, treeview_selection): model, iter = treeview_selection.get_selected() if iter: plugin = model.get_value(iter, PLUGIN) plugin_name = model.get_value(iter, NAME) is_active = model.get_value(iter, ACTIVE) self._display_installed_plugin_info(plugin) else: self._clear_installed_plugin_info() def _display_installed_plugin_info(self, plugin): self.plugin_name_label.set_text(plugin.name) self.plugin_version_label.set_text(plugin.version) self.plugin_authors_label.set_text(plugin.authors) self.plugin_homepage_linkbutton.set_uri(plugin.homepage) self.plugin_homepage_linkbutton.set_label(plugin.homepage) label = self.plugin_homepage_linkbutton.get_children()[0] label.set_ellipsize(Pango.EllipsizeMode.END) self.plugin_homepage_linkbutton.set_property('sensitive', True) desc_textbuffer = self.plugin_description_textview.get_buffer() desc_textbuffer.set_text('') txt = plugin.description txt.replace('</body>', '') if plugin.available_text: txt += '<br/><br/>' + _('Warning: %s') % plugin.available_text if not txt.startswith('<body '): txt = '<body xmlns=\'http://www.w3.org/1999/xhtml\'>' + txt txt += ' </body>' self.plugin_description_textview.display_html( txt, self.plugin_description_textview, None) self.plugin_description_textview.set_property('sensitive', True) self.uninstall_plugin_button.set_property( 'sensitive', gajim.PLUGINS_DIRS[1] in plugin.__path__) self.configure_plugin_button.set_property( 'sensitive', not plugin.config_dialog is None) def _clear_installed_plugin_info(self): self.plugin_name_label.set_text('') self.plugin_version_label.set_text('') self.plugin_authors_label.set_text('') self.plugin_homepage_linkbutton.set_uri('') self.plugin_homepage_linkbutton.set_label('') self.plugin_homepage_linkbutton.set_property('sensitive', False) desc_textbuffer = self.plugin_description_textview.get_buffer() desc_textbuffer.set_text('') self.plugin_description_textview.set_property('sensitive', False) self.uninstall_plugin_button.set_property('sensitive', False) self.configure_plugin_button.set_property('sensitive', False) @log_calls('PluginsWindow') def fill_installed_plugins_model(self): pm = gajim.plugin_manager self.installed_plugins_model.clear() self.installed_plugins_model.set_sort_column_id( 1, Gtk.SortType.ASCENDING) for plugin in pm.plugins: icon = self.get_plugin_icon(plugin) self.installed_plugins_model.append([ plugin, plugin.name, plugin.active and plugin.activatable, plugin.activatable, icon ]) def get_plugin_icon(self, plugin): icon_file = os.path.join(plugin.__path__, os.path.split(plugin.__path__)[1]) + '.png' icon = self.def_icon if os.path.isfile(icon_file): icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16) return icon @log_calls('PluginsWindow') def installed_plugins_toggled_cb(self, cell, path): is_active = self.installed_plugins_model[path][ACTIVE] plugin = self.installed_plugins_model[path][PLUGIN] if is_active: gajim.plugin_manager.deactivate_plugin(plugin) else: try: gajim.plugin_manager.activate_plugin(plugin) except GajimPluginActivateException as e: WarningDialog(_('Plugin failed'), str(e), transient_for=self.window) return self.installed_plugins_model[path][ACTIVE] = not is_active @log_calls('PluginsWindow') def on_plugins_window_destroy(self, widget): '''Close window''' del gajim.interface.instances['plugins'] @log_calls('PluginsWindow') def on_close_button_clicked(self, widget): self.window.destroy() @log_calls('PluginsWindow') def on_configure_plugin_button_clicked(self, widget): #log.debug('widget: %s'%(widget)) selection = self.installed_plugins_treeview.get_selection() model, iter = selection.get_selected() if iter: plugin = model.get_value(iter, PLUGIN) plugin_name = model.get_value(iter, NAME) is_active = model.get_value(iter, ACTIVE) result = plugin.config_dialog.run(self.window) else: # No plugin selected. this should never be reached. As configure # plugin button should only be clickable when plugin is selected. # XXX: maybe throw exception here? pass @log_calls('PluginsWindow') def on_uninstall_plugin_button_clicked(self, widget): selection = self.installed_plugins_treeview.get_selection() model, iter = selection.get_selected() if iter: plugin = model.get_value(iter, PLUGIN) plugin_name = model.get_value(iter, NAME) is_active = model.get_value(iter, ACTIVE) try: gajim.plugin_manager.remove_plugin(plugin) except PluginsystemError as e: WarningDialog(_('Unable to properly remove the plugin'), str(e), self.window) return model.remove(iter) @log_calls('PluginsWindow') def on_install_plugin_button_clicked(self, widget): def show_warn_dialog(): text = _('Archive is malformed') dialog = WarningDialog(text, '', transient_for=self.window) dialog.set_modal(False) dialog.popup() def _on_plugin_exists(zip_filename): def on_yes(is_checked): plugin = gajim.plugin_manager.install_from_zip( zip_filename, True) if not plugin: show_warn_dialog() return model = self.installed_plugins_model for row in list(range(len(model))): if plugin == model[row][PLUGIN]: model.remove(model.get_iter((row, PLUGIN))) break iter_ = model.append([ plugin, plugin.name, False, plugin.activatable, self.get_plugin_icon(plugin) ]) sel = self.installed_plugins_treeview.get_selection() sel.select_iter(iter_) YesNoDialog(_('Plugin already exists'), sectext=_('Overwrite?'), on_response_yes=on_yes, transient_for=self.window) def _try_install(zip_filename): try: plugin = gajim.plugin_manager.install_from_zip(zip_filename) except PluginsystemError as er_type: error_text = str(er_type) if error_text == _('Plugin already exists'): _on_plugin_exists(zip_filename) return WarningDialog(error_text, '"%s"' % zip_filename, self.window) return if not plugin: show_warn_dialog() return model = self.installed_plugins_model iter_ = model.append( [plugin, plugin.name, False, plugin.activatable], self.get_plugin_icon(plugin)) sel = self.installed_plugins_treeview.get_selection() sel.select_iter(iter_) self.dialog = ArchiveChooserDialog(on_response_ok=_try_install)
class ConversationUI(gtk.VBox): '''this class represent all the widgets that are inside a tab also hold the tab widget because there is no better place than this to hold it...''' def __init__(self, controller, parentConversation): gtk.VBox.__init__(self, spacing=3) self.set_border_width(0) self.parentConversation = parentConversation self.controller = controller self.config = self.controller.config self.parser = controller.unifiedParser self.header = Header(self, controller) self.tabWidget = TabWidget(self, controller) self.tabWidget.show() self.input = InputWidget(self, controller, \ parentConversation.isCurrent) self.input.show() self.status = gtk.Statusbar() self.toolbarinput = gtk.HBox() self.toolbarinput.show() self.listOfUsers = UserList.UserList(self.controller, \ self.controller.theme, self.config, False) self.scrollList = gtk.ScrolledWindow() self.scrollList.set_shadow_type(gtk.SHADOW_IN) self.scrollList.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrollList.set_size_request(111, 0) self.scrollList.add(self.listOfUsers) self.scroll = gtk.ScrolledWindow() self.scroll.set_shadow_type(gtk.SHADOW_IN) self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = HtmlTextView(controller, \ self.parentConversation.textBuffer, self.scroll) self.textview.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.textview.set_left_margin(6) self.textview.set_right_margin(6) self.textview.set_editable(False) self.textview.set_cursor_visible(False) self.textview.connect('key-press-event', self.onTextviewKeyPress) self.scroll.add(self.textview) self.scroll.show_all() self.remoteAvatar = AvatarHBox(self, controller, self.parentConversation.switchboard.firstUser) self.controller.msn.connect('user-attr-changed', self.onUserAttrChanged) self.vboxside = gtk.VBox() self.vboxside.pack_start(self.remoteAvatar, False, False) self.vboxside.pack_start(self.scrollList, True, True) self.vboxside.show() self.hbox = gtk.HBox(spacing=2) self.hbox.set_border_width(2) self.hbox.pack_start(self.scroll, True, True) self.hbox.pack_start(self.vboxside, False, False) self.hbox.show() self.toolbarinput.pack_start(self.input, True, True) self.toolbarinput.connect('size-allocate', self.onToolbarinputResize) vpaned = gtk.VPaned() vpaned.pack1(self.hbox, True, True) vpaned.pack2(self.toolbarinput, False) vpaned.show() self.transfers = FileTransferUI.FtBarWidget(self.controller, self, parentConversation) self.transfers.set_no_show_all(True) self.pack_start(self.header, False, False) self.pack_start(vpaned, True, True) self.pack_start(self.transfers, False, False) self.pack_start(self.status, False, False) self.messageWaiting = {} self.contactTyping = {} self.typingTimeoutID = 0 self.closed = False self.last_mark = None self.show() self.update() def onToolbarinputResize(self, *args): alloc = self.toolbarinput.get_allocation() self.config.user['convInputHeight'] = alloc.height self.scrollToBottom() def onTextviewKeyPress(self, widget, event): if event.keyval == gtk.keysyms.Control_L or \ event.keyval == gtk.keysyms.Control_R or \ event.state & gtk.gdk.CONTROL_MASK: return self.input.input.emit('key-press-event', event) self.input.grabFocus() def close(self): self.closed = True def setInputEnabled(self, enable): self.input.input.set_sensitive(enable) def onUserAttrChanged(self, msn, contact): if contact.email in self.parentConversation.getMembers(): self.update() def noneIsTyping(self): try: for contact in self.email_list: if self.contactTyping[contact] == True: return False return True except: return False def update(self): self.header.update() self.tabWidget.update() self.input.update() self.email_list = [mail.email for mail in self.parentConversation.getMembersDict().values()] for email in self.email_list: if email not in self.contactTyping.keys(): self.contactTyping[email] = False if email not in self.messageWaiting.keys(): self.messageWaiting[email] = False for email in self.contactTyping.keys(): if self.contactTyping[email]: self.setTyping(email) else: self.setDefault(email) isGroupChat = len(self.parentConversation.getMembers()) > 1 if isGroupChat: members = self.parentConversation.getMembersDict() d = {'members' : emesenelib.ContactData.Group('members')} for member in members.values(): d['members'].setUser(member.email, member) self.listOfUsers.fill(d) if isGroupChat: self.showUserList() self.remoteAvatar.hide() elif self.config.user['showAvatars']: self.hideUserList() self.remoteAvatar.show_all() self.remoteAvatar.update() else: self.hideUserList() self.remoteAvatar.hide() if not self.config.user['showHeader']: self.header.hide() else: self.header.show() if not self.config.user['showTabCloseButton']: self.tabWidget.closeButton.hide() else: self.tabWidget.closeButton.show() if not self.config.user['showStatusBar']: self.status.hide() else: self.status.show() inputHeight = self.config.user['convInputHeight'] self.toolbarinput.set_size_request(0, inputHeight) def scrollToBottom(self): '''scroll to the end of the conversation''' self.textview.scrollToBottom() def showUserList(self): self.scrollList.show_all() self.listOfUsers.show() def hideUserList(self): self.scrollList.hide() self.listOfUsers.hide() def rebuildStatusText(self): '''Builds the text displayed in the statusbar, based in self.contactTyping. The "output" is a comma separated list of (full) mails and "is/are typing a message..." ''' mails = [x for x in self.contactTyping.keys() \ if self.contactTyping[x] == True] string = '' if len(mails) > 0: comma = ', ' # TODO: gettext? for mail in mails: if self.config.user['showMailTyping']: string += str(mail) else: contact = self.controller.getContact(mail) if contact: parts = self.parser.getParser(contact.nick).get() for part in parts: string += str(part) else: string += str(mail) string += comma string = str(unicode(string)[:-len(comma)]) if len(mails) == 1: string += ' ' + _('is typing a message...') else: string += ' ' + _('are typing a message...') self.status.get_children()[0].get_children()[0].set_text(string) def setMessageWaiting(self, mail): if self.parentConversation.isCurrent: return self.setDefault(mail) self.tabWidget.setMessageWaiting() self.header.setDefault() if mail: self.contactTyping[mail] = False self.messageWaiting[mail] = True self.rebuildStatusText() def setTyping(self, mail): if self.messageWaiting.has_key(mail) and self.messageWaiting[mail]: return self.header.setTyping() self.tabWidget.setTyping() self.contactTyping[mail] = True self.rebuildStatusText() if self.typingTimeoutID > 0: gobject.source_remove(self.typingTimeoutID) self.typingTimeoutID = gobject.timeout_add(8000, \ self.clearTyping, mail) def clearTyping(self, mail): if mail in self.messageWaiting and self.messageWaiting[mail]: self.setMessageWaiting(mail) else: self.setDefault(mail) self.contactTyping[mail] = False return False def setDefault(self, mail): self.tabWidget.setDefault() self.header.setDefault() if mail: self.contactTyping[mail] = False self.messageWaiting[mail] = False self.rebuildStatusText()
def __init__(self): '''Initialize Plugins window''' self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui') self.window = self.xml.get_object('plugins_window') self.window.set_transient_for(gajim.interface.roster.window) widgets_to_extract = ('plugins_notebook', 'plugin_name_label', 'plugin_version_label', 'plugin_authors_label', 'plugin_homepage_linkbutton', 'uninstall_plugin_button', 'configure_plugin_button', 'installed_plugins_treeview') for widget_name in widgets_to_extract: setattr(self, widget_name, self.xml.get_object(widget_name)) self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow2') sw.add(self.plugin_description_textview) self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool, GdkPixbuf.Pixbuf) self.installed_plugins_treeview.set_model(self.installed_plugins_model) self.installed_plugins_treeview.set_rules_hint(True) renderer = Gtk.CellRendererText() col = Gtk.TreeViewColumn(_('Plugin')) #, renderer, text=NAME) cell = Gtk.CellRendererPixbuf() col.pack_start(cell, False) col.add_attribute(cell, 'pixbuf', ICON) col.pack_start(renderer, True) col.add_attribute(renderer, 'text', NAME) col.set_property('expand', True) self.installed_plugins_treeview.append_column(col) renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.installed_plugins_toggled_cb) col = Gtk.TreeViewColumn(_('Active'), renderer, active=ACTIVE, activatable=ACTIVATABLE) self.installed_plugins_treeview.append_column(col) self.def_icon = gtkgui_helpers.get_icon_pixmap('preferences-desktop') # connect signal for selection change selection = self.installed_plugins_treeview.get_selection() selection.connect('changed', self.installed_plugins_treeview_selection_changed) selection.set_mode(Gtk.SelectionMode.SINGLE) self._clear_installed_plugin_info() self.fill_installed_plugins_model() root_iter = self.installed_plugins_model.get_iter_first() if root_iter: selection.select_iter(root_iter) self.xml.connect_signals(self) self.plugins_notebook.set_current_page(0) self.xml.get_object('close_button').grab_focus() self.window.show_all() gtkgui_helpers.possibly_move_window_in_current_desktop(self.window)
class PluginsWindow(object): '''Class for Plugins window''' @log_calls('PluginsWindow') def __init__(self): '''Initialize Plugins window''' self.xml = gtkgui_helpers.get_gtk_builder('plugins_window.ui') self.window = self.xml.get_object('plugins_window') self.window.set_transient_for(gajim.interface.roster.window) widgets_to_extract = ('plugins_notebook', 'plugin_name_label', 'plugin_version_label', 'plugin_authors_label', 'plugin_homepage_linkbutton', 'uninstall_plugin_button', 'configure_plugin_button', 'installed_plugins_treeview') for widget_name in widgets_to_extract: setattr(self, widget_name, self.xml.get_object(widget_name)) self.plugin_description_textview = HtmlTextView() sw = self.xml.get_object('scrolledwindow2') sw.add(self.plugin_description_textview) self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool, GdkPixbuf.Pixbuf) self.installed_plugins_treeview.set_model(self.installed_plugins_model) self.installed_plugins_treeview.set_rules_hint(True) renderer = Gtk.CellRendererText() col = Gtk.TreeViewColumn(_('Plugin'))#, renderer, text=NAME) cell = Gtk.CellRendererPixbuf() col.pack_start(cell, False) col.add_attribute(cell, 'pixbuf', ICON) col.pack_start(renderer, True) col.add_attribute(renderer, 'text', NAME) col.set_property('expand', True) self.installed_plugins_treeview.append_column(col) renderer = Gtk.CellRendererToggle() renderer.connect('toggled', self.installed_plugins_toggled_cb) col = Gtk.TreeViewColumn(_('Active'), renderer, active=ACTIVE, activatable=ACTIVATABLE) self.installed_plugins_treeview.append_column(col) icon = Gtk.Image() self.def_icon = icon.render_icon_pixbuf(Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU) # connect signal for selection change selection = self.installed_plugins_treeview.get_selection() selection.connect('changed', self.installed_plugins_treeview_selection_changed) selection.set_mode(Gtk.SelectionMode.SINGLE) self._clear_installed_plugin_info() self.fill_installed_plugins_model() root_iter = self.installed_plugins_model.get_iter_first() if root_iter: selection.select_iter(root_iter ) self.xml.connect_signals(self) self.plugins_notebook.set_current_page(0) self.xml.get_object('close_button').grab_focus() self.window.show_all() gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) def on_plugins_notebook_switch_page(self, widget, page, page_num): GLib.idle_add(self.xml.get_object('close_button').grab_focus) @log_calls('PluginsWindow') def installed_plugins_treeview_selection_changed(self, treeview_selection): model, iter = treeview_selection.get_selected() if iter: plugin = model.get_value(iter, PLUGIN) plugin_name = model.get_value(iter, NAME) is_active = model.get_value(iter, ACTIVE) self._display_installed_plugin_info(plugin) else: self._clear_installed_plugin_info() def _display_installed_plugin_info(self, plugin): self.plugin_name_label.set_text(plugin.name) self.plugin_version_label.set_text(plugin.version) self.plugin_authors_label.set_text(plugin.authors) self.plugin_homepage_linkbutton.set_uri(plugin.homepage) self.plugin_homepage_linkbutton.set_label(plugin.homepage) label = self.plugin_homepage_linkbutton.get_children()[0] label.set_ellipsize(Pango.EllipsizeMode.END) self.plugin_homepage_linkbutton.set_property('sensitive', True) desc_textbuffer = self.plugin_description_textview.get_buffer() desc_textbuffer.set_text('') txt = plugin.description txt.replace('</body>', '') if plugin.available_text: txt += '<br/><br/>' + _('Warning: %s') % plugin.available_text if not txt.startswith('<body '): txt = '<body xmlns=\'http://www.w3.org/1999/xhtml\'>' + txt txt += ' </body>' self.plugin_description_textview.display_html(txt, self.plugin_description_textview, None) self.plugin_description_textview.set_property('sensitive', True) self.uninstall_plugin_button.set_property('sensitive', gajim.PLUGINS_DIRS[1] in plugin.__path__) self.configure_plugin_button.set_property( 'sensitive', not plugin.config_dialog is None) def _clear_installed_plugin_info(self): self.plugin_name_label.set_text('') self.plugin_version_label.set_text('') self.plugin_authors_label.set_text('') self.plugin_homepage_linkbutton.set_uri('') self.plugin_homepage_linkbutton.set_label('') self.plugin_homepage_linkbutton.set_property('sensitive', False) desc_textbuffer = self.plugin_description_textview.get_buffer() desc_textbuffer.set_text('') self.plugin_description_textview.set_property('sensitive', False) self.uninstall_plugin_button.set_property('sensitive', False) self.configure_plugin_button.set_property('sensitive', False) @log_calls('PluginsWindow') def fill_installed_plugins_model(self): pm = gajim.plugin_manager self.installed_plugins_model.clear() self.installed_plugins_model.set_sort_column_id(1, Gtk.SortType.ASCENDING) for plugin in pm.plugins: icon = self.get_plugin_icon(plugin) self.installed_plugins_model.append([plugin, plugin.name, plugin.active and plugin.activatable, plugin.activatable, icon]) def get_plugin_icon(self, plugin): icon_file = os.path.join(plugin.__path__, os.path.split( plugin.__path__)[1]) + '.png' icon = self.def_icon if os.path.isfile(icon_file): icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16) return icon @log_calls('PluginsWindow') def installed_plugins_toggled_cb(self, cell, path): is_active = self.installed_plugins_model[path][ACTIVE] plugin = self.installed_plugins_model[path][PLUGIN] if is_active: gajim.plugin_manager.deactivate_plugin(plugin) else: try: gajim.plugin_manager.activate_plugin(plugin) except GajimPluginActivateException as e: WarningDialog(_('Plugin failed'), str(e), transient_for=self.window) return self.installed_plugins_model[path][ACTIVE] = not is_active @log_calls('PluginsWindow') def on_plugins_window_destroy(self, widget): '''Close window''' del gajim.interface.instances['plugins'] @log_calls('PluginsWindow') def on_close_button_clicked(self, widget): self.window.destroy() @log_calls('PluginsWindow') def on_configure_plugin_button_clicked(self, widget): #log.debug('widget: %s'%(widget)) selection = self.installed_plugins_treeview.get_selection() model, iter = selection.get_selected() if iter: plugin = model.get_value(iter, PLUGIN) plugin_name = model.get_value(iter, NAME) is_active = model.get_value(iter, ACTIVE) result = plugin.config_dialog.run(self.window) else: # No plugin selected. this should never be reached. As configure # plugin button should only be clickable when plugin is selected. # XXX: maybe throw exception here? pass @log_calls('PluginsWindow') def on_uninstall_plugin_button_clicked(self, widget): selection = self.installed_plugins_treeview.get_selection() model, iter = selection.get_selected() if iter: plugin = model.get_value(iter, PLUGIN) plugin_name = model.get_value(iter, NAME) is_active = model.get_value(iter, ACTIVE) try: gajim.plugin_manager.remove_plugin(plugin) except PluginsystemError as e: WarningDialog(_('Unable to properly remove the plugin'), str(e), self.window) return model.remove(iter) @log_calls('PluginsWindow') def on_install_plugin_button_clicked(self, widget): def show_warn_dialog(): text = _('Archive is malformed') dialog = WarningDialog(text, '', transient_for=self.window) dialog.set_modal(False) dialog.popup() def _on_plugin_exists(zip_filename): def on_yes(is_checked): plugin = gajim.plugin_manager.install_from_zip(zip_filename, True) if not plugin: show_warn_dialog() return model = self.installed_plugins_model for row in list(range(len(model))): if plugin == model[row][PLUGIN]: model.remove(model.get_iter((row, PLUGIN))) break iter_ = model.append([plugin, plugin.name, False, plugin.activatable, self.get_plugin_icon(plugin)]) sel = self.installed_plugins_treeview.get_selection() sel.select_iter(iter_) YesNoDialog(_('Plugin already exists'), sectext=_('Overwrite?'), on_response_yes=on_yes, transient_for=self.window) def _try_install(zip_filename): try: plugin = gajim.plugin_manager.install_from_zip(zip_filename) except PluginsystemError as er_type: error_text = str(er_type) if error_text == _('Plugin already exists'): _on_plugin_exists(zip_filename) return WarningDialog(error_text, '"%s"' % zip_filename, self.window) return if not plugin: show_warn_dialog() return model = self.installed_plugins_model iter_ = model.append([plugin, plugin.name, False, plugin.activatable], self.get_plugin_icon(plugin)) sel = self.installed_plugins_treeview.get_selection() sel.select_iter(iter_) self.dialog = ArchiveChooserDialog(on_response_ok=_try_install)
class ConversationTextview: '''Class for the conversation textview (where user reads already said messages) for chat/groupchat windows''' FOCUS_OUT_LINE_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join( gajim.DATA_DIR, 'pixmaps', 'muc_separator.png')) XEP0184_WARNING_PIXBUF = gtk.gdk.pixbuf_new_from_file(os.path.join( gajim.DATA_DIR, 'pixmaps', 'receipt_missing.png')) # smooth scroll constants MAX_SCROLL_TIME = 0.4 # seconds SCROLL_DELAY = 33 # milliseconds def __init__(self, account, used_in_history_window = False): '''if used_in_history_window is True, then we do not show Clear menuitem in context menu''' self.used_in_history_window = used_in_history_window # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() self.tv.html_hyperlink_handler = self.html_hyperlink_handler # set properties self.tv.set_border_width(1) self.tv.set_accepts_tab(True) self.tv.set_editable(False) self.tv.set_cursor_visible(False) self.tv.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.tv.set_left_margin(2) self.tv.set_right_margin(2) self.handlers = {} self.images = [] self.image_cache = {} self.xep0184_marks = {} self.xep0184_shown = {} # It's True when we scroll in the code, so we can detect scroll from user self.auto_scrolling = False # connect signals id = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) self.handlers[id] = self.tv 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 id = self.tv.connect('expose-event', self.on_textview_expose_event) self.handlers[id] = self.tv self.account = account self.change_cursor = None self.last_time_printout = 0 font = pango.FontDescription(gajim.config.get('conversation_font')) self.tv.modify_font(font) buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() buffer.create_mark('end', end_iter, False) self.tagIn = buffer.create_tag('incoming') color = gajim.config.get('inmsgcolor') self.tagIn.set_property('foreground', color) self.tagOut = buffer.create_tag('outgoing') color = gajim.config.get('outmsgcolor') self.tagOut.set_property('foreground', color) self.tagStatus = buffer.create_tag('status') color = gajim.config.get('statusmsgcolor') self.tagStatus.set_property('foreground', color) colors = gajim.config.get('gc_nicknames_colors') colors = colors.split(':') for i,color in enumerate(colors): tagname = 'gc_nickname_color_' + str(i) tag = buffer.create_tag(tagname) tag.set_property('foreground', color) tag = buffer.create_tag('marked') color = gajim.config.get('markedmsgcolor') tag.set_property('foreground', color) tag.set_property('weight', pango.WEIGHT_BOLD) tag = buffer.create_tag('time_sometimes') tag.set_property('foreground', 'darkgrey') tag.set_property('scale', pango.SCALE_SMALL) tag.set_property('justification', gtk.JUSTIFY_CENTER) tag = buffer.create_tag('small') tag.set_property('scale', pango.SCALE_SMALL) tag = buffer.create_tag('restored_message') color = gajim.config.get('restored_messages_color') tag.set_property('foreground', color) self.tagURL = buffer.create_tag('url') color = gajim.config.get('urlmsgcolor') self.tagURL.set_property('foreground', color) self.tagURL.set_property('underline', pango.UNDERLINE_SINGLE) id = self.tagURL.connect('event', self.hyperlink_handler, 'url') self.handlers[id] = self.tagURL self.tagMail = buffer.create_tag('mail') self.tagMail.set_property('foreground', color) self.tagMail.set_property('underline', pango.UNDERLINE_SINGLE) id = self.tagMail.connect('event', self.hyperlink_handler, 'mail') self.handlers[id] = self.tagMail tag = buffer.create_tag('bold') tag.set_property('weight', pango.WEIGHT_BOLD) tag = buffer.create_tag('italic') tag.set_property('style', pango.STYLE_ITALIC) tag = buffer.create_tag('underline') tag.set_property('underline', pango.UNDERLINE_SINGLE) buffer.create_tag('focus-out-line', justification = gtk.JUSTIFY_CENTER) tag = buffer.create_tag('xep0184-warning') # One mark at the begining then 2 marks between each lines size = gajim.config.get('max_conversation_lines') size = 2 * size - 1 self.marks_queue = Queue.Queue(size) self.allow_focus_out_line = True # holds a mark at the end of --- line self.focus_out_end_mark = None self.xep0184_warning_tooltip = tooltips.BaseTooltip() self.line_tooltip = tooltips.BaseTooltip() # use it for hr too self.tv.focus_out_line_pixbuf = ConversationTextview.FOCUS_OUT_LINE_PIXBUF self.smooth_id = None def del_handlers(self): for i in self.handlers.keys(): if self.handlers[i].handler_is_connected(i): self.handlers[i].disconnect(i) del self.handlers self.tv.destroy() #FIXME: # self.line_tooltip.destroy() def update_tags(self): self.tagIn.set_property('foreground', gajim.config.get('inmsgcolor')) self.tagOut.set_property('foreground', gajim.config.get('outmsgcolor')) self.tagStatus.set_property('foreground', gajim.config.get('statusmsgcolor')) self.tagURL.set_property('foreground', gajim.config.get('urlmsgcolor')) self.tagMail.set_property('foreground', gajim.config.get('urlmsgcolor')) def at_the_end(self): buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() end_rect = self.tv.get_iter_location(end_iter) visible_rect = self.tv.get_visible_rect() if end_rect.y <= (visible_rect.y + visible_rect.height): return True return False # Smooth scrolling inspired by Pidgin code def smooth_scroll(self): parent = self.tv.get_parent() if not parent: return False vadj = parent.get_vadjustment() max_val = vadj.upper - vadj.page_size + 1 cur_val = vadj.get_value() # scroll by 1/3rd of remaining distance onethird = cur_val + ((max_val - cur_val) / 3.0) self.auto_scrolling = True vadj.set_value(onethird) self.auto_scrolling = False if max_val - onethird < 0.01: self.smooth_id = None self.smooth_scroll_timer.cancel() return False return True def smooth_scroll_timeout(self): gobject.idle_add(self.do_smooth_scroll_timeout) return def do_smooth_scroll_timeout(self): if not self.smooth_id: # we finished scrolling return gobject.source_remove(self.smooth_id) self.smooth_id = None parent = self.tv.get_parent() if parent: vadj = parent.get_vadjustment() self.auto_scrolling = True vadj.set_value(vadj.upper - vadj.page_size + 1) self.auto_scrolling = False def smooth_scroll_to_end(self): if None != self.smooth_id: # already scrolling return False self.smooth_id = gobject.timeout_add(self.SCROLL_DELAY, self.smooth_scroll) self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME, self.smooth_scroll_timeout) self.smooth_scroll_timer.start() return False def scroll_to_end(self): parent = self.tv.get_parent() buffer = self.tv.get_buffer() end_mark = buffer.get_mark('end') if not end_mark: return False self.auto_scrolling = True self.tv.scroll_to_mark(end_mark, 0, True, 0, 1) adjustment = parent.get_hadjustment() adjustment.set_value(0) self.auto_scrolling = False return False # when called in an idle_add, just do it once def bring_scroll_to_end(self, diff_y = 0, use_smooth=gajim.config.get('use_smooth_scrolling')): ''' scrolls to the end of textview if end is not visible ''' buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() end_rect = self.tv.get_iter_location(end_iter) visible_rect = self.tv.get_visible_rect() # scroll only if expected end is not visible if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): if use_smooth: gobject.idle_add(self.smooth_scroll_to_end) else: gobject.idle_add(self.scroll_to_end_iter) def scroll_to_end_iter(self): buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() if not end_iter: return False self.tv.scroll_to_iter(end_iter, 0, False, 1, 1) return False # when called in an idle_add, just do it once def stop_scrolling(self): if self.smooth_id: gobject.source_remove(self.smooth_id) self.smooth_id = None self.smooth_scroll_timer.cancel() def show_xep0184_warning(self, id_): if id_ in self.xep0184_marks: return buffer = self.tv.get_buffer() buffer.begin_user_action() self.xep0184_marks[id_] = buffer.create_mark(None, buffer.get_end_iter(), left_gravity=True) self.xep0184_shown[id_] = NOT_SHOWN def show_it(): if (not id_ in self.xep0184_shown) or \ self.xep0184_shown[id_] == ALREADY_RECEIVED: return False end_iter = buffer.get_iter_at_mark( self.xep0184_marks[id_]) buffer.insert(end_iter, ' ') buffer.insert_pixbuf(end_iter, ConversationTextview.XEP0184_WARNING_PIXBUF) before_img_iter = buffer.get_iter_at_mark( self.xep0184_marks[id_]) before_img_iter.forward_char() post_img_iter = before_img_iter.copy() post_img_iter.forward_char() buffer.apply_tag_by_name('xep0184-warning', before_img_iter, post_img_iter) self.xep0184_shown[id_] = SHOWN return False gobject.timeout_add_seconds(2, show_it) buffer.end_user_action() def hide_xep0184_warning(self, id_): if id_ not in self.xep0184_marks: return if self.xep0184_shown[id_] == NOT_SHOWN: self.xep0184_shown[id_] = ALREADY_RECEIVED return buffer = self.tv.get_buffer() buffer.begin_user_action() begin_iter = buffer.get_iter_at_mark(self.xep0184_marks[id_]) end_iter = begin_iter.copy() # XXX: Is there a nicer way? end_iter.forward_char() end_iter.forward_char() buffer.delete(begin_iter, end_iter) buffer.delete_mark(self.xep0184_marks[id_]) buffer.end_user_action() del self.xep0184_marks[id_] del self.xep0184_shown[id_] def show_focus_out_line(self): if not self.allow_focus_out_line: # if room did not receive focus-in from the last time we added # --- line then do not readd return print_focus_out_line = False buffer = self.tv.get_buffer() if self.focus_out_end_mark is None: # this happens only first time we focus out on this room print_focus_out_line = True else: focus_out_end_iter = buffer.get_iter_at_mark(self.focus_out_end_mark) focus_out_end_iter_offset = focus_out_end_iter.get_offset() if focus_out_end_iter_offset != buffer.get_end_iter().get_offset(): # this means after last-focus something was printed # (else end_iter's offset is the same as before) # only then print ---- line (eg. we avoid printing many following # ---- lines) print_focus_out_line = True if print_focus_out_line and buffer.get_char_count() > 0: buffer.begin_user_action() # remove previous focus out line if such focus out line exists if self.focus_out_end_mark is not None: end_iter_for_previous_line = buffer.get_iter_at_mark( self.focus_out_end_mark) begin_iter_for_previous_line = end_iter_for_previous_line.copy() # img_char+1 (the '\n') begin_iter_for_previous_line.backward_chars(2) # remove focus out line buffer.delete(begin_iter_for_previous_line, end_iter_for_previous_line) buffer.delete_mark(self.focus_out_end_mark) # add the new focus out line end_iter = buffer.get_end_iter() buffer.insert(end_iter, '\n') buffer.insert_pixbuf(end_iter, ConversationTextview.FOCUS_OUT_LINE_PIXBUF) end_iter = buffer.get_end_iter() before_img_iter = end_iter.copy() # one char back (an image also takes one char) before_img_iter.backward_char() buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) self.allow_focus_out_line = False # update the iter we hold to make comparison the next time self.focus_out_end_mark = buffer.create_mark(None, buffer.get_end_iter(), left_gravity=True) buffer.end_user_action() # scroll to the end (via idle in case the scrollbar has appeared) gobject.idle_add(self.scroll_to_end) def show_xep0184_warning_tooltip(self): pointer = self.tv.get_pointer() x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0], pointer[1]) tags = self.tv.get_iter_at_location(x, y).get_tags() tag_table = self.tv.get_buffer().get_tag_table() xep0184_warning = False for tag in tags: if tag == tag_table.lookup('xep0184-warning'): xep0184_warning = True break if xep0184_warning and not self.xep0184_warning_tooltip.win: # check if the current pointer is still over the line position = self.tv.window.get_origin() self.xep0184_warning_tooltip.show_tooltip(_('This icon indicates that ' 'this message has not yet\nbeen received by the remote end. ' "If this icon stays\nfor a long time, it's likely the message got " 'lost.'), 8, position[1] + pointer[1]) def show_line_tooltip(self): pointer = self.tv.get_pointer() x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer[0], pointer[1]) tags = self.tv.get_iter_at_location(x, y).get_tags() tag_table = self.tv.get_buffer().get_tag_table() over_line = False for tag in tags: if tag == tag_table.lookup('focus-out-line'): over_line = True break if over_line and not self.line_tooltip.win: # check if the current pointer is still over the line position = self.tv.window.get_origin() self.line_tooltip.show_tooltip(_('Text below this line is what has ' 'been said since the\nlast time you paid attention to this group ' 'chat'), 8, position[1] + pointer[1]) def on_textview_expose_event(self, widget, event): expalloc = event.area exp_x0 = expalloc.x exp_y0 = expalloc.y exp_x1 = exp_x0 + expalloc.width exp_y1 = exp_y0 + expalloc.height try: tryfirst = [self.image_cache[(exp_x0, exp_y0)]] except KeyError: tryfirst = [] for image in tryfirst + self.images: imgalloc = image.allocation img_x0 = imgalloc.x img_y0 = imgalloc.y img_x1 = img_x0 + imgalloc.width img_y1 = img_y0 + imgalloc.height if img_x0 <= exp_x0 and img_y0 <= exp_y0 and \ exp_x1 <= img_x1 and exp_y1 <= img_y1: self.image_cache[(img_x0, img_y0)] = image widget.propagate_expose(image, event) return True return False def on_textview_motion_notify_event(self, widget, event): '''change the cursor to a hand when we are over a mail or an url''' pointer_x, pointer_y, spam = self.tv.window.get_pointer() x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, pointer_x, pointer_y) tags = self.tv.get_iter_at_location(x, y).get_tags() if self.change_cursor: self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( gtk.gdk.Cursor(gtk.gdk.XTERM)) self.change_cursor = None tag_table = self.tv.get_buffer().get_tag_table() over_line = False xep0184_warning = False for tag in tags: if tag in (tag_table.lookup('url'), tag_table.lookup('mail')): self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( gtk.gdk.Cursor(gtk.gdk.HAND2)) self.change_cursor = tag elif tag == tag_table.lookup('focus-out-line'): over_line = True elif tag == tag_table.lookup('xep0184-warning'): xep0184_warning = True if self.line_tooltip.timeout != 0: # Check if we should hide the line tooltip if not over_line: self.line_tooltip.hide_tooltip() if over_line and not self.line_tooltip.win: self.line_tooltip.timeout = gobject.timeout_add(500, self.show_line_tooltip) self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) self.change_cursor = tag if xep0184_warning and not self.xep0184_warning_tooltip.win: self.xep0184_warning_tooltip.timeout = \ gobject.timeout_add(500, self.show_xep0184_warning_tooltip) self.tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)) self.change_cursor = tag def clear(self, tv = None): '''clear text in the textview''' buffer = self.tv.get_buffer() start, end = buffer.get_bounds() buffer.delete(start, end) size = gajim.config.get('max_conversation_lines') size = 2 * size - 1 self.marks_queue = Queue.Queue(size) self.focus_out_end_mark = None def visit_url_from_menuitem(self, widget, link): '''basically it filters out the widget instance''' helpers.launch_browser_mailer('url', link) def on_textview_populate_popup(self, textview, menu): '''we override the default context menu and we prepend Clear (only if used_in_history_window is False) and if we have sth selected we show a submenu with actions on the phrase (see on_conversation_textview_button_press_event)''' separator_menuitem_was_added = False if not self.used_in_history_window: item = gtk.SeparatorMenuItem() menu.prepend(item) separator_menuitem_was_added = True item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) menu.prepend(item) id = item.connect('activate', self.clear) self.handlers[id] = item if self.selected_phrase: if not separator_menuitem_was_added: item = gtk.SeparatorMenuItem() menu.prepend(item) self.selected_phrase = helpers.reduce_chars_newlines( self.selected_phrase, 25, 2) item = gtk.MenuItem(_('_Actions for "%s"') % self.selected_phrase) menu.prepend(item) submenu = gtk.Menu() item.set_submenu(submenu) always_use_en = gajim.config.get('always_english_wikipedia') if always_use_en: link = 'http://en.wikipedia.org/wiki/Special:Search?search=%s'\ % self.selected_phrase else: link = 'http://%s.wikipedia.org/wiki/Special:Search?search=%s'\ % (gajim.LANG, self.selected_phrase) item = gtk.MenuItem(_('Read _Wikipedia Article')) id = item.connect('activate', self.visit_url_from_menuitem, link) self.handlers[id] = item submenu.append(item) item = gtk.MenuItem(_('Look it up in _Dictionary')) dict_link = gajim.config.get('dictionary_url') if dict_link == 'WIKTIONARY': # special link (yeah undocumented but default) always_use_en = gajim.config.get('always_english_wiktionary') if always_use_en: link = 'http://en.wiktionary.org/wiki/Special:Search?search=%s'\ % self.selected_phrase else: link = 'http://%s.wiktionary.org/wiki/Special:Search?search=%s'\ % (gajim.LANG, self.selected_phrase) id = item.connect('activate', self.visit_url_from_menuitem, link) self.handlers[id] = item else: if dict_link.find('%s') == -1: # we must have %s in the url if not WIKTIONARY item = gtk.MenuItem(_( 'Dictionary URL is missing an "%s" and it is not WIKTIONARY')) item.set_property('sensitive', False) else: link = dict_link % self.selected_phrase id = item.connect('activate', self.visit_url_from_menuitem, link) self.handlers[id] = item submenu.append(item) search_link = gajim.config.get('search_engine') if search_link.find('%s') == -1: # we must have %s in the url item = gtk.MenuItem(_('Web Search URL is missing an "%s"')) item.set_property('sensitive', False) else: item = gtk.MenuItem(_('Web _Search for it')) link = search_link % self.selected_phrase id = item.connect('activate', self.visit_url_from_menuitem, link) self.handlers[id] = item submenu.append(item) item = gtk.MenuItem(_('Open as _Link')) id = item.connect('activate', self.visit_url_from_menuitem, link) self.handlers[id] = item submenu.append(item) menu.show_all() def on_textview_button_press_event(self, widget, event): # If we clicked on a taged text do NOT open the standard popup menu # if normal text check if we have sth selected self.selected_phrase = '' # do not move belove event button check! if event.button != 3: # if not right click return False x, y = self.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(event.x), int(event.y)) iter = self.tv.get_iter_at_location(x, y) tags = iter.get_tags() if tags: # we clicked on sth special (it can be status message too) for tag in tags: tag_name = tag.get_property('name') if tag_name in ('url', 'mail'): return True # we block normal context menu # we check if sth was selected and if it was we assign # selected_phrase variable # so on_conversation_textview_populate_popup can use it buffer = self.tv.get_buffer() return_val = buffer.get_selection_bounds() if return_val: # if sth was selected when we right-clicked # get the selected text start_sel, finish_sel = return_val[0], return_val[1] self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode( 'utf-8') elif ord(iter.get_char()) > 31: # we clicked on a word, do as if it's selected for context menu start_sel = iter.copy() if not start_sel.starts_word(): start_sel.backward_word_start() finish_sel = iter.copy() if not finish_sel.ends_word(): finish_sel.forward_word_end() self.selected_phrase = buffer.get_text(start_sel, finish_sel).decode( 'utf-8') def on_open_link_activate(self, widget, kind, text): helpers.launch_browser_mailer(kind, text) def on_copy_link_activate(self, widget, text): clip = gtk.clipboard_get() clip.set_text(text) def on_start_chat_activate(self, widget, jid): gajim.interface.new_chat_from_jid(self.account, jid) def on_join_group_chat_menuitem_activate(self, widget, room_jid): if 'join_gc' in gajim.interface.instances[self.account]: instance = gajim.interface.instances[self.account]['join_gc'] instance.xml.get_widget('room_jid_entry').set_text(room_jid) gajim.interface.instances[self.account]['join_gc'].window.present() else: try: gajim.interface.instances[self.account]['join_gc'] = \ dialogs.JoinGroupchatWindow(self.account, room_jid) except GajimGeneralException: pass def on_add_to_roster_activate(self, widget, jid): dialogs.AddNewContactWindow(self.account, jid) def make_link_menu(self, event, kind, text): xml = gtkgui_helpers.get_glade('chat_context_menu.glade') menu = xml.get_widget('chat_context_menu') childs = menu.get_children() if kind == 'url': id = childs[0].connect('activate', self.on_copy_link_activate, 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[3].hide() # open mail composer childs[4].hide() # jid section separator childs[5].hide() # start chat childs[6].hide() # join group chat childs[7].hide() # add to roster else: # It's a mail or a JID # load muc icon join_group_chat_menuitem = xml.get_widget('join_group_chat_menuitem') muc_icon = gtkgui_helpers.load_icon('muc_active') if muc_icon: join_group_chat_menuitem.set_image(muc_icon) text = text.lower() id = childs[2].connect('activate', self.on_copy_link_activate, text) self.handlers[id] = childs[2] id = childs[3].connect('activate', self.on_open_link_activate, kind, text) 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.handlers[id] = childs[6] allow_add = False c = gajim.contacts.get_first_contact_from_jid(self.account, text) if c and not gajim.contacts.is_pm_from_contact(self.account, c): if _('Not in Roster') in c.groups: allow_add = True else: # he or she's not at all in the account contacts allow_add = True if allow_add: 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 else: childs[7].hide() # hide add to roster menuitem childs[0].hide() # copy link location childs[1].hide() # open link in browser menu.popup(None, None, None, event.button, event.time) def hyperlink_handler(self, texttag, widget, event, iter_, kind): if event.type == gtk.gdk.BUTTON_PRESS: begin_iter = iter_.copy() # we get the begining of the tag while not begin_iter.begins_tag(texttag): begin_iter.backward_char() end_iter = iter_.copy() # we get the end of the tag while not end_iter.ends_tag(texttag): end_iter.forward_char() word = self.tv.get_buffer().get_text(begin_iter, end_iter).decode( 'utf-8') if event.button == 3: # right click self.make_link_menu(event, kind, word) else: # we launch the correct application helpers.launch_browser_mailer(kind, word) def html_hyperlink_handler(self, texttag, widget, event, iter_, kind, href): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 3: # right click self.make_link_menu(event, kind, href) return True else: # we launch the correct application helpers.launch_browser_mailer(kind, href) def detect_and_print_special_text(self, otext, other_tags): '''detects special text (emots & links & formatting) prints normal text before any special text it founts, then print special text (that happens many times until last special text is printed) and then returns the index after *last* special text, so we can print it in print_conversation_line()''' buffer = self.tv.get_buffer() start = 0 end = 0 index = 0 # basic: links + mail + formatting is always checked (we like that) if gajim.config.get('emoticons_theme'): # search for emoticons & urls iterator = gajim.interface.emot_and_basic_re.finditer(otext) else: # search for just urls + mail + formatting iterator = gajim.interface.basic_pattern_re.finditer(otext) for match in iterator: start, end = match.span() special_text = otext[start:end] if start != 0: text_before_special_text = otext[index:start] end_iter = buffer.get_end_iter() # we insert normal text buffer.insert_with_tags_by_name(end_iter, text_before_special_text, *other_tags) index = end # update index # now print it self.print_special_text(special_text, other_tags) return index # the position after *last* special text def latex_to_image(self, str_): result = None exitcode = 0 # some latex commands are really bad blacklist = ['\\def', '\\let', '\\futurelet', '\\newcommand', '\\renewcomment', '\\else', '\\fi', '\\write', '\\input', '\\include', '\\chardef', '\\catcode', '\\makeatletter', '\\noexpand', '\\toksdef', '\\every', '\\errhelp', '\\errorstopmode', '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\csname', '\\newhelp', '\\relax', '\\afterground', '\\afterassignment', '\\expandafter', '\\noexpand', '\\special', '\\command', '\\loop', '\\repeat', '\\toks', '\\output', '\\line', '\\mathcode', '\\name', '\\item', '\\section', '\\mbox', '\\DeclareRobustCommand', '\\[', '\\]'] str_ = str_[2:len(str_)-2] # filter latex code with bad commands for word in blacklist: if word in str_: exitcode = 1 break if exitcode == 0: random.seed() tmpfile = os.path.join(gettempdir(), 'gajimtex_' + random.randint(0, 100).__str__()) # build latex string texstr = '\\documentclass[12pt]{article}\\usepackage[dvips]{graphicx}' texstr += '\\usepackage{amsmath}\\usepackage{amssymb}' texstr += '\\pagestyle{empty}' texstr += '\\begin{document}\\begin{large}\\begin{gather*}' texstr += str_ texstr += '\\end{gather*}\\end{large}\\end{document}' file = open(os.path.join(tmpfile + '.tex'), 'w+') file.write(texstr) file.flush() file.close() p = Popen(['latex', '--interaction=nonstopmode', tmpfile + '.tex'], cwd=gettempdir()) exitcode = p.wait() if exitcode == 0: latex_png_dpi = gajim.config.get('latex_png_dpi') p = Popen(['dvipng', '-bg', 'white', '-T', 'tight', '-D', latex_png_dpi, tmpfile + '.dvi', '-o', tmpfile + '.png'], cwd=gettempdir()) exitcode = p.wait() extensions = ['.tex', '.log', '.aux', '.dvi'] for ext in extensions: try: os.remove(tmpfile + ext) except Exception: pass if exitcode == 0: result = tmpfile + '.png' return result def print_special_text(self, special_text, other_tags): '''is called by detect_and_print_special_text and prints special text (emots, links, formatting)''' tags = [] use_other_tags = True text_is_valid_uri = False show_ascii_formatting_chars = \ gajim.config.get('show_ascii_formatting_chars') buffer = self.tv.get_buffer() # Check if we accept this as an uri schemes = gajim.config.get('uri_schemes').split() for scheme in schemes: if special_text.startswith(scheme + ':'): text_is_valid_uri = True possible_emot_ascii_caps = special_text.upper() # emoticons keys are CAPS if gajim.config.get('emoticons_theme') and \ possible_emot_ascii_caps in gajim.interface.emoticons.keys(): # it's an emoticon emot_ascii = possible_emot_ascii_caps end_iter = buffer.get_end_iter() anchor = buffer.create_child_anchor(end_iter) img = TextViewImage(anchor) animations = gajim.interface.emoticons_animations if not emot_ascii in animations: animations[emot_ascii] = gtk.gdk.PixbufAnimation( gajim.interface.emoticons[emot_ascii]) img.set_from_animation(animations[emot_ascii]) img.show() self.images.append(img) # add with possible animation self.tv.add_child_at_anchor(img, anchor) elif special_text.startswith('www.') or \ special_text.startswith('ftp.') or \ text_is_valid_uri: tags.append('url') use_other_tags = False elif special_text.startswith('mailto:') or \ gajim.interface.sth_at_sth_dot_sth_re.match(special_text): # it's a mail tags.append('mail') use_other_tags = False elif special_text.startswith('*'): # it's a bold text tags.append('bold') if special_text[1] == '/' and special_text[-2] == '/' and\ len(special_text) > 4: # it's also italic tags.append('italic') if not show_ascii_formatting_chars: special_text = special_text[2:-2] # remove */ /* elif special_text[1] == '_' and special_text[-2] == '_' and \ len(special_text) > 4: # it's also underlined tags.append('underline') if not show_ascii_formatting_chars: special_text = special_text[2:-2] # remove *_ _* else: if not show_ascii_formatting_chars: special_text = special_text[1:-1] # remove * * elif special_text.startswith('/'): # it's an italic text tags.append('italic') if special_text[1] == '*' and special_text[-2] == '*' and \ len(special_text) > 4: # it's also bold tags.append('bold') if not show_ascii_formatting_chars: special_text = special_text[2:-2] # remove /* */ elif special_text[1] == '_' and special_text[-2] == '_' and \ len(special_text) > 4: # it's also underlined tags.append('underline') if not show_ascii_formatting_chars: special_text = special_text[2:-2] # remove /_ _/ else: if not show_ascii_formatting_chars: special_text = special_text[1:-1] # remove / / elif special_text.startswith('_'): # it's an underlined text tags.append('underline') if special_text[1] == '*' and special_text[-2] == '*' and \ len(special_text) > 4: # it's also bold tags.append('bold') if not show_ascii_formatting_chars: special_text = special_text[2:-2] # remove _* *_ elif special_text[1] == '/' and special_text[-2] == '/' and \ len(special_text) > 4: # it's also italic tags.append('italic') if not show_ascii_formatting_chars: special_text = special_text[2:-2] # remove _/ /_ else: if not show_ascii_formatting_chars: special_text = special_text[1:-1] # remove _ _ elif special_text.startswith('$$') and special_text.endswith('$$'): imagepath = self.latex_to_image(special_text) end_iter = buffer.get_end_iter() anchor = buffer.create_child_anchor(end_iter) if imagepath is not None: img = gtk.Image() img.set_from_file(imagepath) img.show() # add self.tv.add_child_at_anchor(img, anchor) # delete old file try: os.remove(imagepath) except Exception: pass else: buffer.insert(end_iter, special_text) use_other_tags = False else: # It's nothing special if use_other_tags: end_iter = buffer.get_end_iter() buffer.insert_with_tags_by_name(end_iter, special_text, *other_tags) if len(tags) > 0: end_iter = buffer.get_end_iter() all_tags = tags[:] if use_other_tags: all_tags += other_tags buffer.insert_with_tags_by_name(end_iter, special_text, *all_tags) def print_empty_line(self): buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() buffer.insert_with_tags_by_name(end_iter, '\n', 'eol') def print_conversation_line(self, text, jid, kind, name, tim, other_tags_for_name=[], other_tags_for_time=[], other_tags_for_text=[], subject=None, old_kind=None, xhtml=None, simple=False): '''prints 'chat' type messages''' buffer = self.tv.get_buffer() buffer.begin_user_action() if self.marks_queue.full(): # remove oldest line m1 = self.marks_queue.get() m2 = self.marks_queue.get() i1 = buffer.get_iter_at_mark(m1) i2 = buffer.get_iter_at_mark(m2) buffer.delete(i1, i2) buffer.delete_mark(m1) end_iter = buffer.get_end_iter() at_the_end = False if self.at_the_end(): at_the_end = True # Create one mark and add it to queue once if it's the first line # else twice (one for end bound, one for start bound) mark = None if buffer.get_char_count() > 0: if not simple: buffer.insert_with_tags_by_name(end_iter, '\n', 'eol') mark = buffer.create_mark(None, end_iter, left_gravity=True) self.marks_queue.put(mark) if not mark: mark = buffer.create_mark(None, end_iter, left_gravity=True) self.marks_queue.put(mark) if kind == 'incoming_queue': kind = 'incoming' if old_kind == 'incoming_queue': old_kind = 'incoming' # print the time stamp if not tim: # We don't have tim for outgoing messages... tim = time.localtime() current_print_time = gajim.config.get('print_time') if current_print_time == 'always' and kind != 'info' and not simple: timestamp_str = self.get_time_to_show(tim) timestamp = time.strftime(timestamp_str, tim) buffer.insert_with_tags_by_name(end_iter, timestamp, *other_tags_for_time) elif current_print_time == 'sometimes' and kind != 'info' and not simple: every_foo_seconds = 60 * gajim.config.get( 'print_ichat_every_foo_minutes') seconds_passed = time.mktime(tim) - self.last_time_printout if seconds_passed > every_foo_seconds: self.last_time_printout = time.mktime(tim) end_iter = buffer.get_end_iter() if gajim.config.get('print_time_fuzzy') > 0: fc = FuzzyClock() fc.setTime(time.strftime('%H:%M', tim)) ft = fc.getFuzzyTime(gajim.config.get('print_time_fuzzy')) tim_format = ft.decode(locale.getpreferredencoding()) else: tim_format = self.get_time_to_show(tim) buffer.insert_with_tags_by_name(end_iter, tim_format + '\n', 'time_sometimes') # kind = info, we print things as if it was a status: same color, ... if kind == 'info': kind = 'status' other_text_tag = self.detect_other_text_tag(text, kind) text_tags = other_tags_for_text[:] # create a new list if other_text_tag: # note that color of /me may be overwritten in gc_control text_tags.append(other_text_tag) else: # not status nor /me if gajim.config.get( 'chat_merge_consecutive_nickname'): if kind != old_kind: self.print_name(name, kind, other_tags_for_name) else: self.print_real_text(gajim.config.get( 'chat_merge_consecutive_nickname_indent')) else: self.print_name(name, kind, other_tags_for_name) self.print_subject(subject) self.print_real_text(text, text_tags, name, xhtml) # scroll to the end of the textview if at_the_end or kind == 'outgoing': # we are at the end or we are sending something # scroll to the end (via idle in case the scrollbar has appeared) if gajim.config.get('use_smooth_scrolling'): gobject.idle_add(self.smooth_scroll_to_end) else: gobject.idle_add(self.scroll_to_end) buffer.end_user_action() def get_time_to_show(self, tim): '''Get the time, with the day before if needed and return it. It DOESN'T format a fuzzy time''' format = '' # get difference in days since epoch (86400 = 24*3600) # number of days since epoch for current time (in GMT) - # number of days since epoch for message (in GMT) diff_day = int(timegm(time.localtime())) / 86400 -\ int(timegm(tim)) / 86400 if diff_day == 0: day_str = '' elif diff_day == 1: day_str = _('Yesterday') else: #the number is >= 2 # %i is day in year (1-365), %d (1-31) we want %i day_str = _('%i days ago') % diff_day if day_str: format += day_str + ' ' timestamp_str = gajim.config.get('time_stamp') timestamp_str = helpers.from_one_line(timestamp_str) format += timestamp_str tim_format = time.strftime(format, tim) if locale.getpreferredencoding() != 'KOI8-R': # if tim_format comes as unicode because of day_str. # we convert it to the encoding that we want (and that is utf-8) tim_format = helpers.ensure_utf8_string(tim_format) return tim_format def detect_other_text_tag(self, text, kind): if kind == 'status': return kind elif text.startswith('/me ') or text.startswith('/me\n'): return kind def print_name(self, name, kind, other_tags_for_name): if name: buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() name_tags = other_tags_for_name[:] # create a new list name_tags.append(kind) before_str = gajim.config.get('before_nickname') before_str = helpers.from_one_line(before_str) after_str = gajim.config.get('after_nickname') after_str = helpers.from_one_line(after_str) format = before_str + name + after_str + ' ' buffer.insert_with_tags_by_name(end_iter, format, *name_tags) def print_subject(self, subject): if subject: # if we have subject, show it too! subject = _('Subject: %s\n') % subject buffer = self.tv.get_buffer() end_iter = buffer.get_end_iter() buffer.insert(end_iter, subject) self.print_empty_line() def print_real_text(self, text, text_tags = [], name = None, xhtml = None): '''this adds normal and special text. call this to add text''' if xhtml: try: if name and (text.startswith('/me ') or text.startswith('/me\n')): xhtml = xhtml.replace('/me', '<dfn>%s</dfn>'% (name,), 1) self.tv.display_html(xhtml.encode('utf-8')) return except Exception, e: gajim.log.debug(str('Error processing xhtml')+str(e)) gajim.log.debug(str('with |'+xhtml+'|')) buffer = self.tv.get_buffer() # /me is replaced by name if name is given if name and (text.startswith('/me ') or text.startswith('/me\n')): text = '* ' + name + text[3:] text_tags.append('italic') # detect urls formatting and if the user has it on emoticons index = self.detect_and_print_special_text(text, text_tags) # add the rest of text located in the index and after end_iter = buffer.get_end_iter() buffer.insert_with_tags_by_name(end_iter, text[index:], *text_tags)
class MainClass(Plugin.Plugin): '''Main plugin class''' def __init__(self, controller, msn): '''Contructor''' Plugin.Plugin.__init__(self, controller, msn, 1000) self.description = 'Log all events to a file' self.authors = {'Mariano Guerra' : 'luismarianoguerra at gmail dot com'} self.website = 'http://emesene.org' self.displayName = 'Logger' self.name = 'Logger' self.controller = controller self.config = controller.config self.config.readPluginConfig(self.name) self.path = self.config.getPluginValue(self.name, 'path', self.msn.cacheDir) self.file_name = os.path.join(self.path, self.msn.user + '.db') self.logger = Logger(self.file_name) self.ids = [] self.signals = {} self.signals['self-nick-changed'] = self._cb_self_nick_changed self.signals['personal-message-changed'] = \ self._cb_personal_message_changed self.signals['self-status-changed'] = self._cb_self_status_changed self.signals['self-personal-message-changed'] = \ self._cb_self_personal_message_changed self.signals['self-current-media-changed'] = \ self._cb_self_current_media_changed self.signals['nick-changed'] = self._cb_nick_changed self.signals['contact-status-change'] = \ self._cb_contact_status_change self.signals['initial-status-change'] = \ self._cb_initial_status_change self.signals['user-offline'] = self._cb_user_offline self.signals['display-picture-changed'] = \ self._cb_display_picture_changed self.signals['switchboard::message'] = self._cb_sb_message self.signals['switchboard::ink-message'] = self._cb_sb_ink_message self.signals['switchboard::nudge'] = self._cb_sb_nudge self.signals['switchboard::message-sent'] = self._cb_sb_message_sent self.signals['switchboard::ink-sent'] = self._cb_sb_ink_message_sent self.signals['switchboard::nudge-sent'] = self._cb_sb_nudge_sent self.signals['switchboard::action-message'] = \ self._cb_sb_action_message self.signals['switchboard::action-sent'] = \ self._cb_sb_action_message_sent self.signals['switchboard::custom-emoticon-received'] = \ self._cb_sb_custom_emoticon_received self.queued = [] self.queuedtag = 0 self.to_source = None all_events = ','.join(self.signals.keys()) self.menuItemId = 0 def start(self): '''start the plugin''' if sqlite is None: return self.enabled = True self.events_enabled = [] for signal in self.signals.keys(): if bool(int(self.config.getPluginValue( self.name, signal, True ))): self.events_enabled.append(signal) for (key, value) in self.signals.iteritems(): if key in self.events_enabled: self.ids.append(self.msn.connect(key, self.callback, value)) self.menuItemId = self.controller.connect("usermenu-item-add", self.add_usermenu_item) def stop(self): '''stop the plugin''' for identifier in self.ids: self.msn.disconnect(identifier) self.ids = [] self.controller.disconnect(self.menuItemId) self.enabled = False def callback(self, *args): # sorry, pylint, i know you don't like this '''Adds the function in the last argument to the queue, to be called later when the mainloop is idle. sqlite isn't exactly fast''' params, func = args[:-1], args[-1] params += (time.time(), ) self.queued.append((func, params)) if self.queuedtag == 0: self.queuedtag = gobject.idle_add(self.process_queue) def process_queue(self): '''Takes the signal queue and calls one function. It's called on a idle_add loop. If there are no function, it quits the loop returning false and removing the queuedtag''' if self.queued: func, params = self.queued.pop(0) func(*params) return True else: self.queuedtag = 0 return False def check(self): '''check if everything is OK to start the plugin return a tuple whith a boolean and a message if OK -> (True , 'some message') else -> (False , 'error message')''' if sqlite is None: return (False, 'sqlite not available, please install it.') else: return (True, 'Ok') def get_last_status(self, account, num = 1): '''return the last status of a contact, if num is > 1 then return the last num status, the format of the list is a list of lists with the timestamp and the status''' query = ''' select e.stamp, ue.data from event e, user_event ue, user u where e.id = ue.id_event and u.id = ue.id_user and e.name = "status-changed" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_nick(self, account, num = 1): '''return the last nick of a contact, if num is > 1 then return the last num nicks, the format of the list is a list of lists with the timestamp and the nick''' query = ''' select e.stamp, ue.data from event e, user_event ue, user u where e.id = ue.id_event and u.id = ue.id_user and e.name = "nick-changed" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_personal_message(self, account, num = 1): '''return the last pm of a contact, if num is > 1 then return the last num pms, the format of the list is a list of lists with the timestamp and the pm''' query = ''' select e.stamp, ue.data from event e, user_event ue, user u where e.id = ue.id_event and u.id = ue.id_user and e.name = "personal-message-changed" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_just_last_personal_message(self, account): '''return the last personal message as a string or None rather than a list''' result = self.get_last_personal_message(account) if result: return result[0][1] return None def get_just_last_nick(self, account): '''return the last nick as a string or None rather than a list''' result = self.get_last_nick(account) if result: return result[0][1] return None def get_just_last_status(self, account): '''return the last status as a string or None rather than a list''' result = self.get_last_status(account) if result: return result[0][1] return None def get_last_message(self, account, num = 1): '''return the last message of a contact, if num is > 1 then return the last num messages, the format of the list is a list of lists with the timestamp and the message''' query = ''' select e.stamp, ce.data from event e, conversation_event ce, user u where e.id = ce.id_event and u.id = ce.id_user and e.name = "message" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_display_picture(self, account, num = 1): '''return the last dp of a contact, if num is > 1 then return the last num dps, the format of the list is a list of lists with the timestamp and the dp''' query = ''' select e.stamp, ue.data from event e, user_event ue, user u where e.id = ue.id_event and u.id = ue.id_user and e.name = "display-picture" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_custom_emoticon(self, account, num = 1): '''return the last ce of a contact, if num is > 1 then return the last num ces, the format of the list is a list of lists with the timestamp and the ce''' query = ''' select e.stamp, ce.data from event e, conversation_event ce, user u where e.id = ce.id_event and u.id = ce.id_user and e.name = "custom-emoticon" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_conversation(self, account, num = 1): '''return the last message that was said on a conversation where account was present, the format of the list is a list od lists with the timestamp, email and message''' query = ''' select e.stamp, u.account, ce.data from event e, conversation_event ce, user u where e.id = ce.id_event and u.id = ce.id_user and ce.id_conversation in (select distinct c.id from conversation c, event e, conversation_event ce, user u where c.id = ce.id_conversation and e.id = ce.id_event and ce.id_user = u.id and u.account = "%s") and e.name = "message" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def add_usermenu_item(self, controller, userMenu): self.logMenuItem = userMenu.newImageMenuItem( _( "View conversation _log" ), gtk.STOCK_FILE ) userMenu.add( self.logMenuItem ) self.logMenuItem.show() self.logMenuItem.connect( 'activate', self.on_menuitem_activate, userMenu.user.email ) def on_menuitem_activate(self, widget, email): self.window = gtk.Window() self.window.set_border_width(5) self.window.set_title(_("Conversation log for %s" % email)) self.window.set_default_size(650,350) textview = gtk.TextView() self.textBuffer = textview.get_buffer() scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = HtmlTextView(self.controller, self.textBuffer, scroll) self.textview.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.textview.set_left_margin(6) self.textview.set_right_margin(6) self.textview.set_editable(False) self.textview.set_cursor_visible(False) scroll.add_with_viewport(self.textview) hbox = gtk.HBox(False, 5) saveButton = gtk.Button(stock=gtk.STOCK_SAVE) saveButton.connect("clicked", self.on_save_logs) refreshButton = gtk.Button(stock=gtk.STOCK_REFRESH) refreshButton.connect("clicked", self.on_refresh_log, email) closeButton = gtk.Button(stock=gtk.STOCK_CLOSE) closeButton.connect("clicked", lambda w: self.window.hide()) hbox.pack_end(saveButton, False, False) hbox.pack_end(refreshButton, False, False) hbox.pack_end(closeButton, False, False) textRenderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Date"), textRenderer, markup=0) self.datesListstore = gtk.ListStore(str) self.datesTree = gtk.TreeView(self.datesListstore) self.datesTree.connect("cursor-changed", self.on_dates_cursor_change) self.datesTree.append_column(column) self.datesTree.set_headers_visible(False) datesScroll = gtk.ScrolledWindow() datesScroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) datesScroll.add_with_viewport(self.datesTree) datesScroll.set_size_request(210, -1) hPaned = gtk.HPaned() hPaned.pack1(datesScroll) hPaned.pack2(scroll) vbox = gtk.VBox(spacing=5) vbox.pack_start(hPaned) vbox.pack_start(hbox, False, False) vbox.pack_start(gtk.Statusbar(), False, False) self.datesStamps = {} logsCant = len(self.fill_dates_tree(email)) if not logsCant > 0: dialog.information(_("No logs were found for %s" % (email) )) else: self.window.add(vbox) self.window.show_all() def fill_dates_tree(self, mail): '''fill the treeview with the logs dates''' for (id_conversation, stamp) in self.logger.get_conversation_ids(mail): ctime = str(time.ctime(float(stamp))) self.datesListstore.append([ctime]) self.datesStamps[ctime] = id_conversation return self.datesStamps def on_dates_cursor_change(self, *args): try: selected = self.datesListstore.get( self.datesTree.get_selection().get_selected()[1], 0)[0] except (TypeError, AttributeError): return id_conversation = self.datesStamps[selected] put_new_line = False nick_cache = {} self.textview.get_buffer().set_text("") for (stamp, mail, message) in \ self.logger.get_conversation(id_conversation, 10000): if mail in nick_cache: if nick_cache[mail]['next'] is not None and \ nick_cache[mail]['next'] <= stamp: nick = self.logger.get_user_nick(mail, stamp) next = self.logger.get_next_nick_stamp(mail, stamp) nick_cache[mail] = {'nick': nick, 'next' : next} else: nick = nick_cache[mail]['nick'] else: nick = self.logger.get_user_nick(mail, stamp) next = self.logger.get_next_nick_stamp(mail, stamp) nick_cache[mail] = {'nick': nick, 'next' : next} date = time.ctime(float(stamp)) (_format, encoding, text) = message.split('\r\n', 2) style = parse_format(_format) nick = self.controller.unifiedParser.getParser(nick).get() text = self.controller.unifiedParser.getParser(text).get() text = text.replace("\r\n", "\n").replace("\n", "<br />") try: self.textview.display_html('<body><b>%s: </b>' '<span style="%s">%s</span></body>' % (nick, style, text)) except: pass put_new_line = True def on_save_logs(self, button): title = (_("Save conversation log")) dialog = gtk.FileChooserDialog(title, None, \ gtk.FILE_CHOOSER_ACTION_SAVE, \ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, \ gtk.STOCK_SAVE, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) dialog.show_all() response = dialog.run() while True: if response != gtk.RESPONSE_OK: break if dialog.get_filename(): path = dialog.get_filename() f = open(path, "w") f.write(self.textBuffer.get_text(self.textBuffer.get_start_iter(),\ self.textBuffer.get_end_iter() )) f.close() break dialog.hide() def on_refresh_log(self, button, email): self.on_dates_cursor_change() def _cb_self_nick_changed(self, msn, old, new, stamp=None): '''called when we change our own nick''' if new != self.get_last_nick(self.msn.user): self.logger.user_event_add('nick-changed', self.msn.user, new, stamp) def _cb_personal_message_changed(self, msn, email, personal_message, stamp=None): '''called when an user changes the personal message''' if personal_message != '' and \ self.get_just_last_personal_message(email) != personal_message: self.logger.user_event_add('personal-message-changed', email, personal_message, stamp) def _cb_self_status_changed(self, msn, status, stamp=None): '''called when we change the status''' if self.get_last_status(msn.user) != status: self.logger.user_event_add('status-changed', self.msn.user, status, stamp) def _cb_self_personal_message_changed(self, msn, ourmail, personal_message, stamp=None): '''called when we change our personal message''' if personal_message != '' and \ self.get_just_last_personal_message(ourmail) != personal_message: self.logger.user_event_add('personal-message-changed', ourmail, personal_message, stamp) def _cb_self_current_media_changed(self, msn, ourmail, personal_message, dict_, stamp=None): '''called when we change our current media''' if personal_message != '' and \ self.get_just_last_personal_message(ourmail) != personal_message: self.logger.user_event_add('personal-message-changed', ourmail, personal_message, stamp) def _cb_nick_changed(self, msn, email, nick, stamp=None): '''called when someone change his nick''' if nick != self.get_last_nick(email): self.logger.user_event_add('nick-changed', email, nick, stamp) def _cb_contact_status_change(self, msn, email, status, stamp=None): '''called when someone change his status''' if self.get_last_status(email) != status: self.logger.user_event_add('status-changed', email, status, stamp) def _cb_initial_status_change(self, msn, command, tid, params, stamp=None): '''called when someone change his status when we come online''' data = params.split(' ') status = data[0] email = data[1].lower() if self.get_last_status(email) != status: self.logger.user_event_add('status-changed', email, status, stamp) def _cb_user_offline(self, msn, email, stamp=None): '''called when someone goes offline''' self.logger.user_event_add('status-changed', email, 'FLN', stamp) def _cb_sb_user_join(self, msn, switchboard, signal, args, stamp=None): '''called when an user join to the conversation''' mail = args[0] self.logger.conversation_event_add('user-join', switchboard.started, mail, '', stamp) def _cb_sb_user_leave(self, msn, switchboard, signal, args, stamp=None): '''called when an user leaves the conversation''' mail = args[0] self.logger.conversation_event_add('user-leave', switchboard.started, mail, '', stamp) def _cb_sb_message(self, msn, switchboard, signal, args, stamp=None): '''called when we receive a message''' mail, nick, body, format, charset = args message = '%s\r\n%s\r\n%s' % (format, charset, body.replace('\\', '\\\\')) self.logger.conversation_event_add('message', switchboard.started, mail, message, stamp) def _cb_sb_action_message(self, msn, switchboard, signal, args, stamp=None): '''called when an action message is received''' mail, data = args self.logger.conversation_event_add('action-message', switchboard.started, mail, data, stamp) def _cb_sb_ink_message(self, msn, switchboard, signal, args, stamp=None): '''called when an ink message is received''' signal, filename = args self.logger.conversation_event_add('ink-message', switchboard.started, mail, filename, stamp) def _cb_sb_nudge(self, msn, switchboard, signal, args, stamp=None): '''called when a nudge is received''' mail = args[0] self.logger.conversation_event_add('nudge', switchboard.started, mail, '', stamp) def _cb_sb_message_sent(self, msn, switchboard, signal, args, stamp=None): '''called when we send a message''' body, format, charset = args try: format = format.split('X-MMS-IM-Format: ')[1] except IndexError: format = '' message = '%s\r\n%s\r\n%s' % (format, charset, body) self.logger.conversation_event_add('message', switchboard.started, self.msn.user, message, stamp) def _cb_sb_action_message_sent(self, msn, switchboard, signal, args, stamp=None): '''called when an action message is received''' data = args[0] self.logger.conversation_event_add('action-message', switchboard.started, self.msn.user, data, stamp) def _cb_sb_ink_message_sent(self, msn, switchboard, signal, args, stamp=None): '''called when an ink message is sent''' self.logger.conversation_event_add('ink-message', switchboard.started, self.msn.user, '', stamp) def _cb_sb_nudge_sent(self, msn, switchboard, signal, args, stamp=None): '''called when a nudge is sent''' self.logger.conversation_event_add('nudge', switchboard.started, self.msn.user, '', stamp) def _cb_sb_custom_emoticon_received(self, msn, switchboard, signal, args, stamp=None): '''called when a custom emoticon is received (du'h)''' shortcut, msnobj = args filename = shortcut + '_' + msnobj.sha1d + '.tmp' filename = urllib.quote(filename).replace('/', '_') complete_filename = self.msn.cacheDir + os.sep + filename self.logger.conversation_event_add('custom-emoticon', switchboard.started, self.msn.user, shortcut + ' ' + complete_filename, stamp) def _cb_display_picture_changed(self, msn, switchboard, msnobj, mail, stamp=None): '''called when a new display picture is received''' contact = self.msn.contactManager.getContact(mail) if contact is None: return filename = contact.displayPicturePath result = self.get_last_display_picture(mail) if result: old_filename = result[0][1] else: old_filename = None if old_filename != filename: self.logger.user_event_add('display-picture', mail, \ filename, stamp) def configure( self ): configuration = [] configuration.append(Plugin.Option('', gtk.Widget, '', '', gtk.Label("Enable/disable events"))) for signal in self.signals: configuration.append(Plugin.Option(signal, bool, signal.replace('-',' '), \ '', bool(int(self.config.getPluginValue(self.name, signal, True))))) configWindow = Plugin.ConfigWindow( 'Logger', configuration ) response = configWindow.run() if response != None: for signal in self.signals: if response.has_key(signal): self.config.setPluginValue( self.name, signal, str(int(response[signal].value)) ) self.stop() self.start() return True
class MainClass(Plugin.Plugin): '''Main plugin class''' description = _('Log all events and conversations in a database') authors = {'Mariano Guerra' : 'luismarianoguerra at gmail dot com'} website = 'http://emesene.org' displayName = 'Logger' name = 'Logger' def __init__(self, controller, msn): '''Contructor''' Plugin.Plugin.__init__(self, controller, msn, 1000) self.description = _('Log all events and conversations in a database') self.authors = {'Mariano Guerra' : 'luismarianoguerra at gmail dot com'} self.website = 'http://emesene.org' self.displayName = 'Logger' self.name = 'Logger' self.controller = controller self.config = controller.config self.config.readPluginConfig(self.name) self.path = self.config.getPluginValue(self.name, 'path', self.msn.cacheDir) self.file_name = os.path.join(self.path, self.msn.user + '.db') self.logger = Logger(self.file_name) self.ids = [] self.signals = {} self.signals['self-nick-changed'] = self._cb_self_nick_changed self.signals['personal-message-changed'] = \ self._cb_personal_message_changed self.signals['self-status-changed'] = self._cb_self_status_changed self.signals['self-personal-message-changed'] = \ self._cb_self_personal_message_changed self.signals['self-current-media-changed'] = \ self._cb_self_current_media_changed self.signals['nick-changed'] = self._cb_nick_changed self.signals['contact-status-change'] = \ self._cb_contact_status_change self.signals['initial-status-change'] = \ self._cb_initial_status_change self.signals['user-offline'] = self._cb_user_offline self.signals['display-picture-changed'] = \ self._cb_display_picture_changed self.signals['switchboard::message'] = self._cb_sb_message self.signals['switchboard::ink-message'] = self._cb_sb_ink_message self.signals['switchboard::nudge'] = self._cb_sb_nudge self.signals['switchboard::message-sent'] = self._cb_sb_message_sent self.signals['switchboard::ink-sent'] = self._cb_sb_ink_message_sent self.signals['switchboard::nudge-sent'] = self._cb_sb_nudge_sent self.signals['switchboard::action-message'] = \ self._cb_sb_action_message self.signals['switchboard::action-sent'] = \ self._cb_sb_action_message_sent self.signals['switchboard::custom-emoticon-received'] = \ self._cb_sb_custom_emoticon_received self.signals['switchboard::user-join'] = self._cb_sb_user_join self.signals_labels = {} self.signals_labels['self-nick-changed'] = _('Self nick changed') self.signals_labels['personal-message-changed'] = _('PM changed') self.signals_labels['self-status-changed'] = _('Self status changed') self.signals_labels['self-personal-message-changed'] = _('Self PM changed') self.signals_labels['self-current-media-changed'] = _('Self current media changed') self.signals_labels['nick-changed'] = _('Nick changed') self.signals_labels['contact-status-change'] = _('Contact status changed') self.signals_labels['initial-status-change'] = _('Initial status changed') self.signals_labels['user-offline'] = _('User offline') self.signals_labels['display-picture-changed'] = _('Display picture changed') self.signals_labels['switchboard::message'] = _('Message received') self.signals_labels['switchboard::ink-message'] = _('Ink message received') self.signals_labels['switchboard::nudge'] = _('Nudge received') self.signals_labels['switchboard::message-sent'] = _('Message sent') self.signals_labels['switchboard::ink-sent'] = _('Ink message sent') self.signals_labels['switchboard::nudge-sent'] = _('Nudge sent') self.signals_labels['switchboard::action-message'] = _('Action message received') self.signals_labels['switchboard::action-sent'] = _('Action message sent') self.signals_labels['switchboard::custom-emoticon-received'] = _('Custom emoticon received') self.signals_labels['switchboard::user-join'] = _('User joined conversation') # functions to be called on mainloop idleness self.queued = [] self.queuedtag = 0 # timeout source to disconect it self.to_source = None all_events = ','.join(self.signals.keys()) self.menuItemId = 0 def start(self): '''start the plugin''' if sqlite is None: return self.enabled = True self.events_enabled = [] for signal in self.signals.keys(): if bool(int(self.config.getPluginValue( self.name, signal, True ))): self.events_enabled.append(signal) for (key, value) in self.signals.iteritems(): if key in self.events_enabled: self.ids.append(self.msn.connect(key, self.callback, value)) self.menuItemId = self.controller.connect("usermenu-item-add", self.add_usermenu_item) def stop(self): '''stop the plugin''' # disconnect msn signals for identifier in self.ids: self.msn.disconnect(identifier) self.ids = [] self.controller.disconnect(self.menuItemId) self.enabled = False def callback(self, *args): # sorry, pylint, i know you don't like this '''Adds the function in the last argument to the queue, to be called later when the mainloop is idle. sqlite isn't exactly fast''' params, func = args[:-1], args[-1] # add the stamp parameter params += (time.time(), ) self.queued.append((func, params)) # if the process_queue loop is dead, start it if self.queuedtag == 0: self.queuedtag = gobject.idle_add(self.process_queue) def process_queue(self): '''Takes the signal queue and calls one function. It's called on a idle_add loop. If there are no function, it quits the loop returning false and removing the queuedtag''' if self.queued: func, params = self.queued.pop(0) func(*params) return True else: self.queuedtag = 0 return False def check(self): '''check if everything is OK to start the plugin return a tuple whith a boolean and a message if OK -> (True , 'some message') else -> (False , 'error message')''' if sqlite is None: return (False, 'sqlite not available, please install it.') else: return (True, 'Ok') def get_last_status(self, account, num = 1): '''return the last status of a contact, if num is > 1 then return the last num status, the format of the list is a list of lists with the timestamp and the status''' query = ''' select e.stamp, ue.data from user u, event e, user_event ue where e.id = ue.id_event and u.id = ue.id_user and e.name = "status-changed" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_nick(self, account, num = 1): '''return the last nick of a contact, if num is > 1 then return the last num nicks, the format of the list is a list of lists with the timestamp and the nick''' query = ''' select e.stamp, ue.data from user u, event e, user_event ue where e.id = ue.id_event and u.id = ue.id_user and e.name = "nick-changed" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_personal_message(self, account, num = 1): '''return the last pm of a contact, if num is > 1 then return the last num pms, the format of the list is a list of lists with the timestamp and the pm''' query = ''' select e.stamp, ue.data from user u, event e, user_event ue where e.id = ue.id_event and u.id = ue.id_user and e.name = "personal-message-changed" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_just_last_personal_message(self, account): '''return the last personal message as a string or None rather than a list''' result = self.get_last_personal_message(account) if result: return result[0][1] return None def get_just_last_nick(self, account): '''return the last nick as a string or None rather than a list''' result = self.get_last_nick(account) if result: return result[0][1] return None def get_just_last_status(self, account): '''return the last status as a string or None rather than a list''' result = self.get_last_status(account) if result: return result[0][1] return None def get_last_message(self, account, num = 1): '''return the last message of a contact, if num is > 1 then return the last num messages, the format of the list is a list of lists with the timestamp and the message''' query = ''' select e.stamp, ce.data from user u, event e, conversation_event ce where e.id = ce.id_event and u.id = ce.id_user and e.name = "message" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_display_picture(self, account, num = 1): '''return the last dp of a contact, if num is > 1 then return the last num dps, the format of the list is a list of lists with the timestamp and the dp''' query = ''' select e.stamp, ue.data from user u, event e, user_event ue where e.id = ue.id_event and u.id = ue.id_user and e.name = "display-picture" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_custom_emoticon(self, account, num = 1): '''return the last ce of a contact, if num is > 1 then return the last num ces, the format of the list is a list of lists with the timestamp and the ce''' query = ''' select e.stamp, ce.data from user u, event e, conversation_event ce where e.id = ce.id_event and u.id = ce.id_user and e.name = "custom-emoticon" and u.account = "%s" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def get_last_conversation(self, account, num = 1): '''return the last message that was said on a conversation where account was present, the format of the list is a list od lists with the timestamp, email and message''' # in the inner select we select all the id of conversations # where account was present, then on the outer conversation # we select all the messages from that conversations query = ''' select e.stamp, u.account, ce.data from event e, conversation_event ce, user u where e.id = ce.id_event and u.id = ce.id_user and ce.id_conversation in (select distinct c.id from conversation c, event e, conversation_event ce, user u where c.id = ce.id_conversation and e.id = ce.id_event and ce.id_user = u.id and u.account = "%s") and e.name = "message" order by e.stamp desc limit %s ''' return self.logger.query(query % (account, num)) def add_usermenu_item(self, controller, userMenu): self.logMenuItem = userMenu.newImageMenuItem( _( "Conversation _log" ), gtk.STOCK_FILE ) userMenu.viewMenu.add( self.logMenuItem ) self.logMenuItem.show() self.logMenuItem.connect( 'activate', self.on_menuitem_activate, userMenu.user.email ) def on_menuitem_activate(self, widget, email): self.window = gtk.Window() self.window.set_border_width(5) self.window.set_title(_("Conversation log for %s") % email) self.window.set_default_size(650,350) self.window.set_position(gtk.WIN_POS_CENTER) textview = gtk.TextView() self.textBuffer = textview.get_buffer() self.textBuffer.create_tag('highlight', background = 'yellow') scroll = gtk.ScrolledWindow() scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.textview = HtmlTextView(self.controller, self.textBuffer, scroll) self.textview.set_wrap_mode(gtk.WRAP_WORD_CHAR) self.textview.set_left_margin(6) self.textview.set_right_margin(6) self.textview.set_editable(False) self.textview.set_cursor_visible(False) scroll.add(self.textview) hbox = gtk.HBox(False, 5) saveButton = gtk.Button(stock=gtk.STOCK_SAVE) saveButton.connect("clicked", self.on_save_logs) refreshButton = gtk.Button(stock=gtk.STOCK_REFRESH) refreshButton.connect("clicked", self.on_refresh_log, email) closeButton = gtk.Button(stock=gtk.STOCK_CLOSE) closeButton.connect("clicked", lambda w: self.window.hide()) ############ Search TreeView ################### self.search_active = False self.searchStore = gtk.ListStore(str, str, str, str) self.searchTree = gtk.TreeView(self.searchStore) self.searchTree.connect("row-activated", self.set_cursor) self.searchTree.set_rules_hint(True) cell = gtk.CellRendererText() nameCol = gtk.TreeViewColumn(_("Date"), cell, text=0) dateCol = gtk.TreeViewColumn(_("Time"), cell, text=1) timeCol = gtk.TreeViewColumn(_("Name"), cell, text=2) msgCol = gtk.TreeViewColumn(_("Message"), cell, text=3) nameCol.set_resizable(True) dateCol.set_resizable(True) timeCol.set_resizable(True) msgCol.set_resizable(True) self.searchTree.append_column(nameCol) self.searchTree.append_column(dateCol) self.searchTree.append_column(timeCol) self.searchTree.append_column(msgCol) self.searchSw = gtk.ScrolledWindow() self.searchSw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.searchSw.add(self.searchTree) self.searchSw.set_size_request(100, 30) ################################################## #search box searchLabel = gtk.Label(_("Search:")) self.searchBox = gtk.Entry() self.searchBox.connect("key-press-event", self.enter_pressed) hbox.pack_end(saveButton, False, False) hbox.pack_end(refreshButton, False, False) hbox.pack_end(closeButton, False, False) hbox.pack_end(self.searchBox, False, False) hbox.pack_end(searchLabel, False, False) textRenderer = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Date"), textRenderer, markup=0) self.datesListstore = gtk.ListStore(str) self.datesTree = gtk.TreeView(self.datesListstore) self.datesTree.connect("cursor-changed", self.on_dates_cursor_change) self.datesTree.append_column(column) self.datesTree.set_headers_visible(False) datesScroll = gtk.ScrolledWindow() datesScroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) datesScroll.add_with_viewport(self.datesTree) datesScroll.set_size_request(150, -1) hPaned = gtk.HPaned() hPaned.pack1(datesScroll) hPaned.pack2(scroll) vbox = gtk.VBox(spacing=5) vbox.pack_start(hPaned) vbox.pack_start(hbox, False, False) vbox.pack_start(self.searchSw) vbox.pack_start(gtk.Statusbar(), False, False) self.datesStamps = {} logsCant = len(self.fill_dates_tree(email)) if not logsCant > 0: dialog.information(_("No logs were found for %s") % (email)) else: self.window.add(vbox) self.window.show_all() self.searchSw.hide_all() def set_cursor(self, treeview, path, col): (model, iter) = self.searchTree.get_selection().get_selected() dateSearch = self.searchStore.get_value(iter, 0) timeSearch = self.searchStore.get_value(iter, 1) #Search in the datesTree self.datesTree.set_cursor_on_cell(0) (model, iter) = self.datesTree.get_selection().get_selected() while iter: date = self.datesListstore.get_value(iter, 0) if date == dateSearch: self.datesTree.set_cursor_on_cell(model.get_path(iter)) break iter = model.iter_next(iter) self.scroll_to_result(timeSearch) def scroll_to_result(self, timee): start_iter = self.textBuffer.get_start_iter() result = start_iter.forward_search(timee, gtk.TEXT_SEARCH_VISIBLE_ONLY, None) if result is not None: match_start_iter, match_end_iter = result match_start_iter.backward_char() #include '(' or other character before time match_end_iter.forward_line() #highlight all message not just time self.textBuffer.apply_tag_by_name('highlight', match_start_iter, match_end_iter) match_start_mark = self.textBuffer.create_mark('match_start', match_start_iter, True) self.textview.scroll_to_mark(match_start_mark, 0, True) def enter_pressed(self, button, event): if len(self.searchBox.get_text()) == 0: self.searchSw.hide_all() elif event.keyval == 65293 or event.keyval == 65421: #If enter key is pressed self.searchStore.clear() text = self.searchBox.get_text() self.searchSw.show_all() self.datesTree.set_cursor_on_cell(0) (model, iter) = self.datesTree.get_selection().get_selected() self.search_active = True while iter: self.searchIter = iter self.searchText = text self.datesTree.set_cursor_on_cell(model.get_path(iter)) iter = model.iter_next(iter) self.search_active = False self.textview.get_buffer().set_text("") def fill_dates_tree(self, mail): '''fill the treeview with the logs dates''' dates = [] for (id_conversation, stamp) in self.logger.get_conversation_ids(mail): ctime = str(time.ctime(float(stamp))) date = ctime.split() date = " ".join(date[0:3]) + " " + date[4] if date in self.datesStamps.keys(): self.datesStamps[date].append(id_conversation) else: self.datesStamps[date] = [id_conversation] dates.append(date) dates.reverse() for date in dates: self.datesListstore.append([date]) return self.datesStamps #self.datesListstore.append([ctime]) #self.datesStamps[ctime] = id_conversation return self.datesStamps def on_dates_cursor_change(self, *args): try: selected = self.datesListstore.get( self.datesTree.get_selection().get_selected()[1], 0)[0] except (TypeError, AttributeError): return string1 = (_('Conversation opened')) string2 = (_('Conversation closed')) ids_conversation = self.datesStamps[selected] nick_cache = {} self.textview.get_buffer().set_text("") for id_conversation in ids_conversation: listMessages = self.logger.get_conversation(id_conversation, 10000) if (len(listMessages)!= 0): self.textview.display_html('<body><span style=\"font-weight: bold;\"><b>*** '+string1+' ***</b>' '</span></body>') for (stamp, mail, message) in listMessages: if mail in nick_cache: if nick_cache[mail]['next'] is not None and \ nick_cache[mail]['next'] <= stamp: nick = self.logger.get_user_nick(mail, stamp) next = self.logger.get_next_nick_stamp(mail, stamp) nick_cache[mail] = {'nick': nick, 'next' : next} else: nick = nick_cache[mail]['nick'] else: nick = self.logger.get_user_nick(mail, stamp) next = self.logger.get_next_nick_stamp(mail, stamp) nick_cache[mail] = {'nick': nick, 'next' : next} date = time.ctime(float(stamp)).split()[3] (_format, encoding, text) = message.split('\r\n', 2) style = parse_format(_format) nick = self.controller.unifiedParser.getParser(nick).get() text = self.controller.unifiedParser.getParser(text).get() text = text.replace("\r\n", "\n").replace("\n", "<br />") if self.search_active: date2 = self.datesListstore.get_value(self.searchIter, 0) if text.find(self.searchText) != -1: self.searchStore.append([date2, date, nick, text]) try: self.textview.display_html('<body>(%s) <b>%s: </b>' '<span style="%s">%s</span></body>' % (date, nick, style, text)) except: # hide messages that we can't display pass self.textview.display_html('<body><span style=\"font-weight: bold;\"><b>*** '+string2+' ***</b>' '</span></body>') self.textview.display_html('<br/>') else: pass def on_save_logs(self, button): title = (_("Save conversation log")) dialog = gtk.FileChooserDialog(title, None, \ gtk.FILE_CHOOSER_ACTION_SAVE, \ (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, \ gtk.STOCK_SAVE, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) dialog.show_all() response = dialog.run() while True: if response != gtk.RESPONSE_OK: break if dialog.get_filename(): path = dialog.get_filename() f = open(path, "w") f.write(self.textBuffer.get_text(self.textBuffer.get_start_iter(),\ self.textBuffer.get_end_iter() )) f.close() break dialog.hide() def on_refresh_log(self, button, email): self.on_dates_cursor_change() def _cb_self_nick_changed(self, msn, old, new, stamp=None): '''called when we change our own nick''' if new != self.get_last_nick(self.msn.user): self.logger.user_event_add('nick-changed', self.msn.user, new, stamp) def _cb_personal_message_changed(self, msn, email, personal_message, stamp=None): '''called when an user changes the personal message''' if personal_message != '' and \ self.get_just_last_personal_message(email) != personal_message: self.logger.user_event_add('personal-message-changed', email, personal_message, stamp) def _cb_self_status_changed(self, msn, status, stamp=None): '''called when we change the status''' if self.get_last_status(msn.user) != status: self.logger.user_event_add('status-changed', self.msn.user, status, stamp) def _cb_self_personal_message_changed(self, msn, ourmail, personal_message, stamp=None): '''called when we change our personal message''' if personal_message != '' and \ self.get_just_last_personal_message(ourmail) != personal_message: self.logger.user_event_add('personal-message-changed', ourmail, personal_message, stamp) def _cb_self_current_media_changed(self, msn, ourmail, personal_message, dict_, stamp=None): '''called when we change our current media''' if personal_message != '' and \ self.get_just_last_personal_message(ourmail) != personal_message: self.logger.user_event_add('personal-message-changed', ourmail, personal_message, stamp) def _cb_nick_changed(self, msn, email, nick, stamp=None): '''called when someone change his nick''' if nick != self.get_last_nick(email): self.logger.user_event_add('nick-changed', email, nick, stamp) def _cb_contact_status_change(self, msn, email, status, stamp=None): '''called when someone change his status''' if self.get_last_status(email) != status: self.logger.user_event_add('status-changed', email, status, stamp) def _cb_initial_status_change(self, msn, command, tid, params, stamp=None): '''called when someone change his status when we come online''' data = params.split(' ') status = data[0] email = data[1].lower() if self.get_last_status(email) != status: self.logger.user_event_add('status-changed', email, status, stamp) def _cb_user_offline(self, msn, email, stamp=None): '''called when someone goes offline''' self.logger.user_event_add('status-changed', email, 'FLN', stamp) def _cb_sb_user_join(self, msn, switchboard, signal, args, stamp=None): '''called when an user join to the conversation''' mail = args[0] self.logger.conversation_event_add('user-join', switchboard.started, mail, '', stamp) def _cb_sb_user_leave(self, msn, switchboard, signal, args, stamp=None): '''called when an user leaves the conversation''' mail = args[0] self.logger.conversation_event_add('user-leave', switchboard.started, mail, '', stamp) def _cb_sb_message(self, msn, switchboard, signal, args, stamp=None): '''called when we receive a message''' mail, nick, body, format, charset, p4c = args message = '%s\r\n%s\r\n%s' % (format, charset, body.replace('\\', '\\\\')) self.logger.conversation_event_add('message', switchboard.started, mail, message, stamp) def _cb_sb_action_message(self, msn, switchboard, signal, args, stamp=None): '''called when an action message is received''' mail, data = args self.logger.conversation_event_add('action-message', switchboard.started, mail, data, stamp) def _cb_sb_ink_message(self, msn, switchboard, signal, args, stamp=None): '''called when an ink message is received''' signal, filename = args self.logger.conversation_event_add('ink-message', switchboard.started, mail, filename, stamp) def _cb_sb_nudge(self, msn, switchboard, signal, args, stamp=None): '''called when a nudge is received''' mail = args[0] self.logger.conversation_event_add('nudge', switchboard.started, mail, '', stamp) def _cb_sb_message_sent(self, msn, switchboard, signal, args, stamp=None): '''called when we send a message''' body, format, charset = args try: format = format.split('X-MMS-IM-Format: ')[1] except IndexError: format = '' message = '%s\r\n%s\r\n%s' % (format, charset, body) self.logger.conversation_event_add('message', switchboard.started, self.msn.user, message, stamp) def _cb_sb_action_message_sent(self, msn, switchboard, signal, args, stamp=None): '''called when an action message is received''' data = args[0] self.logger.conversation_event_add('action-message', switchboard.started, self.msn.user, data, stamp) def _cb_sb_ink_message_sent(self, msn, switchboard, signal, args, stamp=None): '''called when an ink message is sent''' self.logger.conversation_event_add('ink-message', switchboard.started, self.msn.user, '', stamp) def _cb_sb_nudge_sent(self, msn, switchboard, signal, args, stamp=None): '''called when a nudge is sent''' self.logger.conversation_event_add('nudge', switchboard.started, self.msn.user, '', stamp) def _cb_sb_custom_emoticon_received(self, msn, switchboard, signal, args, stamp=None): '''called when a custom emoticon is received (du'h)''' shortcut, msnobj = args filename = shortcut + '_' + msnobj.sha1d + '.tmp' filename = urllib.quote(filename).replace('/', '_') complete_filename = self.msn.cacheDir + os.sep + filename self.logger.conversation_event_add('custom-emoticon', switchboard.started, self.msn.user, shortcut + ' ' + complete_filename, stamp) def _cb_display_picture_changed(self, msn, switchboard, msnobj, mail, stamp=None): '''called when a new display picture is received''' contact = self.msn.contactManager.getContact(mail) if contact is None: return filename = contact.displayPicturePath result = self.get_last_display_picture(mail) if result: old_filename = result[0][1] else: old_filename = None if old_filename != filename: self.logger.user_event_add('display-picture', mail, \ filename, stamp) def configure( self ): configuration = [] configuration.append(Plugin.Option('', gtk.Widget, '', '', gtk.Label(_("Enable/disable events to be logged:")))) for signal in self.signals: configuration.append(Plugin.Option(signal, bool, self.signals_labels[signal], \ '', bool(int(self.config.getPluginValue(self.name, signal, True))))) configWindow = Plugin.ConfigWindow( 'Logger', configuration ) configWindow.vbox.set_spacing(0) response = configWindow.run() if response != None: for signal in self.signals: if response.has_key(signal): self.config.setPluginValue( self.name, signal, str(int(response[signal].value)) ) self.stop() self.start() return True