Refactor link context menu

- Move hyperlink handling into HtmlTextView
- Use actions on the menuitems
This commit is contained in:
Philipp Hörist 2018-09-22 11:59:07 +02:00
parent 4c49d1469c
commit d295472a8e
7 changed files with 176 additions and 318 deletions

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import Gtk
from gi.repository import Gdk
from gajim.common import app
from gajim.common import helpers
from gajim.common.app import interface
@ -130,24 +133,29 @@ def on_service_disco(action, param):
pass
def on_join_gc(action, param):
account = None
def on_join_gc(_action, param):
account, jid = None, None
if param is None:
if not app.get_connected_accounts():
return
else:
account = param.get_string()
account, jid = param.get_strv()
if not jid:
jid = None
window = app.get_app_window(JoinGroupchatWindow)
if window is None:
JoinGroupchatWindow(account, None)
JoinGroupchatWindow(account, jid)
else:
window.present()
def on_add_contact(action, param):
window = app.get_app_window(AddNewContactWindow)
def on_add_contact(_action, param):
account, jid = param.get_strv()
if not jid:
jid = None
window = app.get_app_window(AddNewContactWindow, account)
if window is None:
AddNewContactWindow(param.get_string())
AddNewContactWindow(account, jid)
else:
window.present()
@ -337,3 +345,25 @@ def show_next_pending_event(action, param):
if not event:
return
app.interface.handle_event(account, jid, event.type_)
def open_link(_action, param):
kind, link = param.get_strv()
helpers.launch_browser_mailer(kind, link)
def copy_link(_action, param):
text = param.get_string()
clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clip.set_text(text, -1)
def start_chat(_action, param):
account, jid = param.get_strv()
app.interface.new_chat_from_jid(account, jid)
def join_groupchat(_action, param):
account, jid = param.get_strv()
room_jid = jid.split('?')[0]
app.interface.join_gc_minimal(account, room_jid)

View File

@ -363,6 +363,14 @@ class GajimApplication(Gtk.Application):
act.connect("activate", app_actions.on_add_contact_jid)
self.add_action(act)
act = Gio.SimpleAction.new('copy-link', GLib.VariantType.new('s'))
act.connect("activate", app_actions.copy_link)
self.add_action(act)
act = Gio.SimpleAction.new('open-link', GLib.VariantType.new('as'))
act.connect("activate", app_actions.open_link)
self.add_action(act)
for action in general_actions:
action_name, func = action
act = Gio.SimpleAction.new(action_name, None)
@ -388,8 +396,9 @@ class GajimApplication(Gtk.Application):
return [
('-start-single-chat', a.on_single_message, 'online', 's'),
('-join-groupchat', a.on_join_gc, 'online', 's'),
('-add-contact', a.on_add_contact, 'online', 's'),
('-start-chat', a.start_chat, 'online', 'as'),
('-join-groupchat', a.on_join_gc, 'online', 'as'),
('-add-contact', a.on_add_contact, 'online', 'as'),
('-services', a.on_service_disco, 'online', 's'),
('-profile', a.on_profile, 'feature', 's'),
('-xml-console', a.on_xml_console, 'always', 's'),

View File

@ -23,21 +23,20 @@
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import time
import os
import queue
import urllib
import logging
from calendar import timegm
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Pango
from gi.repository import GObject
from gi.repository import GLib
import time
import os
import queue
import urllib
from gajim.gtk import AddNewContactWindow
from gajim.gtk import util
from gajim.gtk.util import load_icon
from gajim.gtk.util import get_builder
from gajim.gtk.util import get_cursor
from gajim.gtk.emoji_data import emoji_pixbufs
from gajim.gtk.emoji_data import is_emoji
@ -45,7 +44,7 @@ from gajim.gtk.emoji_data import get_emoji_pixbuf
from gajim.common import app
from gajim.common import helpers
from gajim.common import i18n
from calendar import timegm
from gajim.common.fuzzyclock import FuzzyClock
from gajim.common.const import StyleAttr
@ -55,7 +54,6 @@ NOT_SHOWN = 0
ALREADY_RECEIVED = 1
SHOWN = 2
import logging
log = logging.getLogger('gajim.conversation_textview')
def is_selection_modified(mark):
@ -172,10 +170,7 @@ class ConversationTextview(GObject.GObject):
self.fc = FuzzyClock()
# no need to inherit TextView, use it as atrribute is safer
self.tv = HtmlTextView()
# we have to override HtmlTextView Event handlers
# because we don't inherit
self.tv.hyperlink_handler = self.hyperlink_handler
self.tv = HtmlTextView(account)
self.tv.connect_tooltip(self.query_tooltip)
# set properties
@ -658,146 +653,6 @@ class ConversationTextview(GObject.GObject):
finish_sel.forward_word_end()
self.selected_phrase = buffer_.get_text(start_sel, finish_sel, True)
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(Gdk.SELECTION_CLIPBOARD)
clip.set_text(text, -1)
def on_start_chat_activate(self, widget, jid):
app.interface.new_chat_from_jid(self.account, jid)
def on_join_group_chat_menuitem_activate(self, widget, room_jid):
# Remove ?join
room_jid = room_jid.split('?')[0]
app.interface.join_gc_minimal(self.account, room_jid)
def on_add_to_roster_activate(self, widget, jid):
AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text):
xml = get_builder('chat_context_menu.ui')
menu = xml.get_object('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/jid 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
text = text.lower()
if text.startswith('xmpp:'):
text = text[5:]
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]
if self.account and app.connections[self.account].\
roster_supported:
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
if kind == 'xmpp':
id_ = childs[0].connect('activate', self.on_copy_link_activate,
'xmpp:' + text)
self.handlers[id_] = childs[0]
childs[0].set_label(_('Copy JID'))
childs[2].hide() # copy mail/jid address
childs[3].hide() # open mail composer
childs[4].hide() # jid section separator
elif kind == 'mail':
childs[2].set_label(_('Copy Email Address'))
childs[4].hide() # jid section separator
childs[5].hide() # start chat
childs[6].hide() # join group chat
childs[7].hide() # add to roster
if kind != 'xmpp':
childs[0].hide() # copy link location
childs[1].hide() # open link in browser
menu.attach_to_widget(self.tv, None)
menu.popup(None, None, None, None, event.button.button, event.time)
def hyperlink_handler(self, texttag, widget, event, iter_, kind):
if event.type == Gdk.EventType.BUTTON_PRESS:
begin_iter = iter_.copy()
# we get the beginning 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()
# Detect XHTML-IM link
word = getattr(texttag, 'href', None)
if word:
if word.startswith('xmpp'):
kind = 'xmpp'
elif word.startswith('mailto:'):
kind = 'mail'
elif app.interface.sth_at_sth_dot_sth_re.match(word):
# it's a JID or mail
kind = 'sth_at_sth'
else:
word = self.tv.get_buffer().get_text(begin_iter, end_iter, True)
if event.button.button == 3: # right click
self.make_link_menu(event, kind, word)
return True
self.plugin_modified = False
app.plugin_manager.extension_point(
'hyperlink_handler', word, kind, self,
self.tv.get_toplevel())
if self.plugin_modified:
return
# we launch the correct application
if kind == 'xmpp':
word = word[5:]
if '?' in word:
(jid, action) = word.split('?')
if action == 'join':
app.interface.join_gc_minimal(self.account, jid)
else:
self.on_start_chat_activate(None, jid)
else:
self.on_start_chat_activate(None, word)
# handle geo:-URIs
elif word[:4] == 'geo:':
location = word[4:]
lat, _, lon = location.partition(',')
if lon == '':
return
uri = 'https://www.openstreetmap.org/?' \
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % \
{'lat': lat, 'lon': lon}
helpers.launch_browser_mailer(kind, uri)
# other URIs
else:
helpers.launch_browser_mailer(kind, word)
def detect_and_print_special_text(self, otext, other_tags, graphics=True,
iter_=None, additional_data=None):
"""

View File

@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkMenu" id="chat_context_menu">
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="copy_link_location_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Copy Link Location</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="open_link_in_browser_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Open Link in Browser</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="copy_email_address_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Copy JID/Email Address</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="open_email_composer_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Open Email Composer</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="jid_section_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="start_chat_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Start Chat</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="join_group_chat_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Join _Group Chat</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_join_group_chat_menuitem_activate" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="add_to_roster_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Add to Roster...</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
</interface>

View File

@ -675,7 +675,7 @@ def get_bookmarks_menu(account, rebuild=False):
# Build Join Groupchat
action = 'app.{}-join-groupchat'.format(account)
menuitem = Gio.MenuItem.new(_('Join Group Chat'), action)
variant = GLib.Variant('s', account)
variant = GLib.Variant('as', [account, ''])
menuitem.set_action_and_target_value(action, variant)
menu.append_item(menuitem)
@ -749,7 +749,10 @@ def get_account_menu(account):
continue
action = 'app.{}{}'.format(account, action)
menuitem = Gio.MenuItem.new(label, action)
variant = GLib.Variant('s', account)
if 'add_contact' in action:
variant = GLib.Variant('as', [account, ''])
else:
variant = GLib.Variant('s', account)
menuitem.set_action_and_target_value(action, variant)
menu.append_item(menuitem)
else:
@ -840,6 +843,75 @@ def get_encryption_menu(control_id, type_id, zeroconf=False):
return menu
def get_conv_context_menu(account, kind, text):
if kind == 'xmpp':
if '?join' in text:
context_menu = [
('copy-link', _('Copy JID')),
('-join-groupchat', _('Join Groupchat')),
]
else:
context_menu = [
('copy-link', _('Copy JID')),
('-start-chat', _('Start Chat')),
('-add-contact', _('Add to Roster…')),
]
elif kind == 'url':
context_menu = [
('copy-link', _('Copy Link Location')),
('open-link', _('Open Link in Browser')),
]
elif kind == 'mail':
context_menu = [
('copy-link', _('Copy Email Address')),
('open-link', _('Open Email Composer')),
]
elif kind == 'sth_at_sth':
context_menu = [
('copy-link', _('Copy JID/Email')),
('open-link', _('Open Email Composer')),
('-start-chat', _('Start Chat')),
('-join-groupchat', _('Join Groupchat')),
('-add-contact', _('Add to Roster…')),
]
else:
return
menu = Gtk.Menu()
for item in context_menu:
action, label = item
menuitem = Gtk.MenuItem()
menuitem.set_label(label)
if action.startswith('-'):
action = 'app.%s%s' % (account, action)
else:
action = 'app.%s' % action
menuitem.set_action_name(action)
if 'join-groupchat' in action:
text = text.replace('xmpp:', '')
text = text.split('?')[0]
if 'add-contact' in action:
text = text.replace('xmpp:', '')
text = text.split('?')[0]
if action == 'app.open-link':
value = GLib.Variant.new_strv([kind, text])
elif action == 'app.copy-link':
value = GLib.Variant.new_string(text)
else:
value = GLib.Variant.new_strv([account, text])
menuitem.set_action_target_value(value)
menuitem.show()
menu.append(menuitem)
return menu
def escape_mnemonic(label):
if label is None:
return

View File

@ -43,16 +43,16 @@ from gi.repository import Pango
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import GLib
from gajim.common import app
from gajim.common import helpers
from gajim.common.i18n import _
from gajim.common.const import StyleAttr
from gajim.gtk import JoinGroupchatWindow
from gajim.gtk import AddNewContactWindow
from gajim.gtk.util import load_icon
from gajim.gtk.util import get_cursor
from gajim.gtk.util import get_builder
from gajim.gui_menu_builder import get_conv_context_menu
log = logging.getLogger('gajim.htmlview')
@ -825,7 +825,7 @@ class HtmlHandler(xml.sax.handler.ContentHandler):
class HtmlTextView(Gtk.TextView):
def __init__(self):
def __init__(self, account=None):
Gtk.TextView.__init__(self)
self.set_wrap_mode(Gtk.WrapMode.CHAR)
self.set_editable(False)
@ -835,11 +835,12 @@ class HtmlTextView(Gtk.TextView):
self.connect('unrealize', self.on_html_text_view_unrealized)
self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
self.id_ = self.connect('button-release-event',
self.on_left_mouse_button_release)
self.on_left_mouse_button_release)
self.get_buffer().eol_tag = self.get_buffer().create_tag('eol')
self.config = app.config
self.interface = app.interface
# end big hack
self.account = account
self.plugin_modified = False
def connect_tooltip(self, func=None):
self.connect('query-tooltip', func or self.__query_tooltip)
@ -892,73 +893,21 @@ class HtmlTextView(Gtk.TextView):
self._changed_cursor = False
return False
def on_open_link_activate(self, widget, kind, text):
helpers.launch_browser_mailer(kind, text)
def show_context_menu(self, _event, kind, text):
menu = get_conv_context_menu(self.account, kind, text)
if menu is None:
return
def on_copy_link_activate(self, widget, text):
clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clip.set_text(text, -1)
def destroy(menu, pspec):
visible = menu.get_property('visible')
if not visible:
GLib.idle_add(menu.destroy)
# def on_start_chat_activate(self, widget, jid):
# app.interface.new_chat_from_jid(self.account, jid)
menu.attach_to_widget(self, None)
menu.connect('notify::visible', destroy)
menu.popup_at_pointer()
def on_join_group_chat_menuitem_activate(self, widget, room_jid):
JoinGroupchatWindow(None, room_jid)
def on_add_to_roster_activate(self, widget, jid):
AddNewContactWindow(self.account, jid)
def make_link_menu(self, event, kind, text):
ui = get_builder('chat_context_menu.ui')
menu = ui.get_object('chat_context_menu')
childs = menu.get_children()
if kind == 'url':
childs[0].connect('activate', self.on_copy_link_activate, text)
childs[1].connect('activate', self.on_open_link_activate, kind,
text)
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
text = text.lower()
if text.startswith('xmpp:'):
text = text[5:]
childs[2].connect('activate', self.on_copy_link_activate, text)
childs[3].connect('activate', self.on_open_link_activate, kind,
text)
# childs[5].connect('activate', self.on_start_chat_activate, text)
childs[6].connect('activate',
self.on_join_group_chat_menuitem_activate, text)
# if self.account and app.connections[self.account].\
# roster_supported:
# childs[7].connect('activate',
# self.on_add_to_roster_activate, text)
# childs[7].show() # show add to roster menuitem
# else:
# childs[7].hide() # hide add to roster menuitem
if kind == 'xmpp':
childs[0].connect('activate', self.on_copy_link_activate,
'xmpp:' + text)
childs[2].hide() # copy mail address
childs[3].hide() # open mail composer
elif kind == 'mail':
childs[6].hide() # join group chat
if kind != 'xmpp':
childs[0].hide() # copy link location
childs[1].hide() # open link in browser
childs[4].hide() # jid section separator
childs[5].hide() # start chat
childs[7].hide() # add to roster
menu.popup(None, None, None, event.button, event.time)
def hyperlink_handler(self, texttag, widget, event, iter_, kind):
def hyperlink_handler(self, texttag, _widget, event, iter_, kind):
if event.type == Gdk.EventType.BUTTON_PRESS:
begin_iter = iter_.copy()
# we get the beginning of the tag
@ -980,31 +929,45 @@ class HtmlTextView(Gtk.TextView):
# it's a JID or mail
kind = 'sth_at_sth'
else:
word = self.textview.get_buffer().get_text(begin_iter,
end_iter)
word = self.get_buffer().get_text(begin_iter, end_iter, True)
if event.button == 3: # right click
self.make_link_menu(event, kind, word)
if event.button.button == 3: # right click
self.show_context_menu(event, kind, word)
return True
self.plugin_modified = False
app.plugin_manager.extension_point(
'hyperlink_handler', word, kind, self,
self.get_toplevel())
if self.plugin_modified:
return
# we launch the correct application
if kind == 'xmpp':
word = word[5:]
if '?' in word:
(jid, action) = word.split('?')
if action == 'join':
self.on_join_group_chat_menuitem_activate(None, jid)
app.interface.join_gc_minimal(self.account, jid)
else:
self.on_start_chat_activate(None, jid)
app.interface.new_chat_from_jid(self.account, jid)
else:
self.on_start_chat_activate(None, word)
app.interface.new_chat_from_jid(self.account, word)
# handle geo:-URIs
elif word[:4] == 'geo:':
location = word[4:]
lat, _, lon = location.partition(',')
if lon == '':
return
uri = 'https://www.openstreetmap.org/?' \
'mlat=%(lat)s&mlon=%(lon)s&zoom=16' % \
{'lat': lat, 'lon': lon}
helpers.launch_browser_mailer(kind, uri)
# other URIs
else:
helpers.launch_browser_mailer(kind, word)
def _hyperlink_handler(self, texttag, widget, event, iter_, kind):
# self.hyperlink_handler can be overwritten, so call it when needed
self.hyperlink_handler(texttag, widget, event, iter_, kind)
def display_html(self, html, textview, conv_textview, iter_=None):
buffer_ = self.get_buffer()
if iter_:

View File

@ -3679,7 +3679,7 @@ class RosterWindow:
When the join gc menuitem is clicked, show the join gc window
"""
app.app.activate_action('%s-join-groupchat' % account,
GLib.Variant('s', account))
GLib.Variant('as', [account, '']))
def on_show_transports_action(self, action, param):
app.config.set('show_transports_group', param.get_boolean())