clicompanion-devs team mailing list archive
-
clicompanion-devs team
-
Mailing list archive
-
Message #00340
[Merge] lp:~dcaro/clicompanion/fix-923535 into lp:clicompanion
David Caro has proposed merging lp:~dcaro/clicompanion/fix-923535 into lp:clicompanion with lp:~dcaro/clicompanion/fix-623475 as a prerequisite.
Requested reviews:
CLI Companion Development Team (clicompanion-devs)
Related bugs:
Bug #923535 in CLI Companion: "Make urls clickable (and extendible with plugins)"
https://bugs.launchpad.net/clicompanion/+bug/923535
For more details, see:
https://code.launchpad.net/~dcaro/clicompanion/fix-923535/+merge/90633
Added the urls plugins to allow the user to click on urls to open them, added the standard (http/https/ftp/ftps/file/news/sip/etc.) and launchpad (lp:bugcode or lp:branch) to the pluginslist.
--
https://code.launchpad.net/~dcaro/clicompanion/fix-923535/+merge/90633
Your team CLI Companion Development Team is requested to review the proposed merge of lp:~dcaro/clicompanion/fix-923535 into lp:clicompanion.
=== modified file 'clicompanionlib/plugins.py'
--- clicompanionlib/plugins.py 2012-01-29 23:59:23 +0000
+++ clicompanionlib/plugins.py 2012-01-29 23:59:23 +0000
@@ -47,6 +47,7 @@
import sys
import os
import inspect
+import webbrowser
from clicompanionlib.utils import dbg
@@ -97,7 +98,7 @@
if capability in capabilities \
and plugin in self.allowed:
plugins.append((plugin, pclass))
- dbg('Matching plugin %s for %s' % (plugin, capability))
+ dbg('Matching plugin %s for capability %s' % (plugin, capability))
return plugins
def get_plugin_conf(self, plugin):
@@ -207,3 +208,42 @@
def __init__(self, config):
Plugin.__init__(self)
self.config = config
+
+class URLPlugin(Plugin):
+ '''
+ Plugion that matches an url in the screen and executes some action.
+ '''
+ __capabilities__ = [ 'URL' ]
+
+
+ def __init__(self, config):
+ Plugin.__init__(self)
+ self.config = config
+ ## This is the regexp that will trigger the callback
+ matches = ['']
+
+ def callback(self, url, matchnum):
+ ## When the regexp is found, this function will be called
+ pass
+
+ def open_url(self, url):
+ """
+ Open a given URL, generic for all the URL plugins to use
+ """
+ oldstyle = False
+ if gtk.gtk_version < (2, 14, 0) or \
+ not hasattr(gtk, 'show_uri') or \
+ not hasattr(gtk.gdk, 'CURRENT_TIME'):
+ oldstyle = True
+ if not oldstyle:
+ try:
+ gtk.show_uri(None, url, gtk.gdk.CURRENT_TIME)
+ except:
+ oldstyle = True
+ if oldstyle:
+ dbg('Old gtk (%s,%s,%s), calling xdg-open' % gtk.gtk_version)
+ try:
+ subprocess.Popen(["xdg-open", url])
+ except:
+ dbg('xdg-open did not work, falling back to webbrowser.open')
+ webbrowser.open(url)
=== modified file 'clicompanionlib/tabs.py'
--- clicompanionlib/tabs.py 2012-01-29 23:59:23 +0000
+++ clicompanionlib/tabs.py 2012-01-29 23:59:23 +0000
@@ -56,12 +56,15 @@
()),
}
- def __init__(self, title, config, profile='default', directory=None):
+ def __init__(self, title, config, profile='default', directory=None,
+ pluginloader=None):
gtk.ScrolledWindow.__init__(self)
self.config = config
+ self.pluginloader = pluginloader
self.title = title
self.profile = 'profile::' + profile
self.vte = vte.Terminal()
+ self.matches = {}
self.add(self.vte)
self.vte.connect("child-exited", lambda *x: self.emit('quit'))
self.update_records = self.config.getboolean(self.profile,
@@ -78,8 +81,9 @@
logutmp=self.update_records,
logwtmp=self.update_records,
loglastlog=self.update_records)
- self.vte.connect("button_press_event", self.copy_paste_menu)
+ self.vte.connect("button_press_event", self.on_click)
self.update_config()
+ self.load_url_plugins()
self.show_all()
def update_config(self, config=None, preview=False):
@@ -206,7 +210,31 @@
self.vte.set_allow_bold(config.getboolean(self.profile, 'bold_text'))
self.vte.set_word_chars(config.get(self.profile, 'sel_word'))
- def copy_paste_menu(self, vte, event):
+ def check_for_match(self, event):
+ """
+ Check if the mouse is over a URL
+ """
+ return (self.vte.match_check(int(event.x / self.vte.get_char_width()),
+ int(event.y / self.vte.get_char_height())))
+
+ def run_match_callback(self, match):
+ url = match[0]
+ match = match[1]
+ for plg, m_plg in self.matches.items():
+ if match in m_plg[1]:
+ dbg('Matched %s for url %s' % (plg, url))
+ matchnum = m_plg[1].index(match)
+ m_plg[0].callback(url, matchnum)
+
+ def on_click(self, vte, event):
+ ## left click
+ if event.button == 1:
+ # Ctrl+leftclick on a URL should open it
+ if event.state & gtk.gdk.CONTROL_MASK == gtk.gdk.CONTROL_MASK:
+ match = self.check_for_match(event)
+ if match:
+ self.run_match_callback(match)
+ ## Rght click menu
if event.button == 3:
time = event.time
## right-click popup menu Copy
@@ -366,6 +394,13 @@
self.profile = 'profile::' + profile
self.update_config()
+ def load_url_plugins(self):
+ for pg_name, pg_class in self.pluginloader.get_plugins(['URL']):
+ self.matches[pg_name] = (pg_class(self.config), [])
+ for match in self.matches[pg_name][0].matches:
+ dbg('Adding match %s for plugin %s' % (match, pg_name))
+ self.matches[pg_name][1].append(self.vte.match_add(match))
+
class TerminalsNotebook(gtk.Notebook):
__gsignals__ = {
@@ -375,11 +410,12 @@
()),
}
- def __init__(self, config):
+ def __init__(self, config, pluginloader):
gtk.Notebook.__init__(self)
#definition gcp - global page count, how many pages have been created
self.gcp = 0
self.global_config = config
+ self.pluginloader = pluginloader
## The "Add Tab" tab
add_tab_button = gtk.Button("+")
## tooltip for "Add Tab" tab
@@ -428,9 +464,11 @@
current_page = self.get_nth_page(self.get_current_page())
cwd = cc_utils.get_pid_cwd(current_page.pid)
if cwd:
- newtab = TerminalTab(title, self.global_config, directory=cwd)
+ newtab = TerminalTab(title, self.global_config, directory=cwd,
+ pluginloader=self.pluginloader)
else:
- newtab = TerminalTab(title, self.global_config)
+ newtab = TerminalTab(title, self.global_config,
+ pluginloader=self.pluginloader)
label = self.create_tab_label(title, newtab)
self.insert_page(newtab, label, self.get_n_pages() - 1)
self.set_current_page(self.get_n_pages() - 2)
=== modified file 'clicompanionlib/view.py'
--- clicompanionlib/view.py 2012-01-29 23:59:23 +0000
+++ clicompanionlib/view.py 2012-01-29 23:59:23 +0000
@@ -223,7 +223,8 @@
## set various parameters on the main window (size, etc)
self.init_config()
- self.term_notebook = cc_tabs.TerminalsNotebook(self.config)
+ self.term_notebook = cc_tabs.TerminalsNotebook(self.config,
+ self.pluginloader)
###########################
#### Here we create the commands notebook for the expander
=== added file 'plugins/LaunchpadURL.py'
--- plugins/LaunchpadURL.py 1970-01-01 00:00:00 +0000
+++ plugins/LaunchpadURL.py 2012-01-29 23:59:23 +0000
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# LaunchpadURL.py - URL plugin for launchpad bugs, repos and code
+#
+# Copyright 2012 David Caro <david.caro.estevez@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+import pygtk
+pygtk.require('2.0')
+import gobject
+import webbrowser
+
+try:
+ import gtk
+except:
+ ## do not use gtk, just print
+ print _("You need to install the python gtk bindings package"
+ "'python-gtk2'")
+ sys.exit(1)
+
+from clicompanionlib.utils import dbg
+import clicompanionlib.plugins as plugins
+
+
+class LaunchpadURL(plugins.URLPlugin):
+ '''
+ Match launchpad urls and open them on the browser
+ '''
+ __authors__ = 'David Caro <david.caro.estevez@xxxxxxxxx>'
+ __info__ = ('This plugins enables launchpad urls to be matched.')
+ __title__ = 'Launchpad URLS'
+
+ def __init__(self, config):
+ plugins.URLPlugin.__init__(self, config)
+ self.matches = ['lp:[0-9]+',
+ 'lp:.*']
+
+ def callback(self, url, matchnum):
+ dbg('Openeing launchpad url ' + url)
+ if matchnum == 0:
+ url = 'http://bugs.launchpad.net/bugs/' + url[3:]
+ else:
+ url = 'http://code.launchpad.net/+branch/' + url[3:]
+ self.open_url(url)
=== added file 'plugins/StandardURLs.py'
--- plugins/StandardURLs.py 1970-01-01 00:00:00 +0000
+++ plugins/StandardURLs.py 2012-01-29 23:59:23 +0000
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# StandardURLs.py - URL plugin for common urls (http, ftp, etc.)
+#
+# Copyright 2012 David Caro <david.caro.estevez@xxxxxxxxx>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+import pygtk
+pygtk.require('2.0')
+import gobject
+import webbrowser
+
+try:
+ import gtk
+except:
+ ## do not use gtk, just print
+ print _("You need to install the python gtk bindings package"
+ "'python-gtk2'")
+ sys.exit(1)
+
+from clicompanionlib.utils import dbg
+import clicompanionlib.plugins as plugins
+
+
+class StandardURLs(plugins.URLPlugin):
+ '''
+ Match launchpad urls and open them on the browser
+ '''
+ __authors__ = 'David Caro <david.caro.estevez@xxxxxxxxx>'
+ __info__ = ('This plugins enables some common urls to be matched.')
+ __title__ = 'Standard URLS'
+
+ def __init__(self, config):
+ plugins.URLPlugin.__init__(self, config)
+ self.matches = []
+
+ userchars = "-A-Za-z0-9"
+ passchars = "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
+ hostchars = "-A-Za-z0-9"
+ pathchars = "-A-Za-z0-9_$.+!*(),;:@&=?/~#%'\""
+ schemes = ("(news:|telnet:|nntp:|https?:|ftps?:|webcal:)//")
+ user = "([" + userchars + "]+(:[" + passchars + "]+)?)?"
+ urlpath = "/[" + pathchars + "]*[^]'.}>) \t\r\n,\\\"]"
+ email = ("[a-zA-Z0-9][a-zA-Z0-9.+-]*@[a-zA-Z0-9][a-zA-Z0-9-]*"
+ "\.[a-zA-Z0-9][a-zA-Z0-9-]+[.a-zA-Z0-9-]*")
+
+ lboundry = "\\<"
+ rboundry = "\\>"
+
+ ## http/https/ftp/ftps/webcal/nntp/telnet urls
+ self.matches.append(schemes + user + "[" + hostchars + "]*\.["
+ + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?")
+ ## file
+ self.matches.append('file:///[' + pathchars + "]*")
+ ## SIP
+ self.matches.append('(callto:|h323:|sip:)'
+ + "[" + userchars + "+]["
+ + userchars + ".]*(:[0-9]+)?@?["
+ + pathchars + "]+"
+ + rboundry)
+ ## mail
+ self.matches.append("(mailto:)?" + email)
+ ## news
+ self.matches.append('news:[-A-Z\^_a-z{|}~!"#$%&\'()*+'
+ + ',./0-9;:=?`]+@' + "[-A-Za-z0-9.]+(:[0-9]+)?")
+ ## General url (www.host.com or ftp.host.com)
+ self.matches.append("(www|ftp)[" + hostchars + "]*\.["
+ + hostchars + ".]+(:[0-9]+)?(" + urlpath + ")?/?")
+
+ def callback(self, url, matchnum):
+ dbg('Opening common url ' + url)
+ if matchnum == 5:
+ if url[:3] == 'www':
+ url = 'http://' + url
+ else:
+ url = 'ftp://' + url
+ self.open_url(url)