Philipp Hörist pushed to branch master at gajim / gajim
Commits: 5a906c14 by Philipp Hörist at 2018-09-21T13:46:52Z Move HtmlTextView test into test folder
- - - - - 3f2f51c1 by Philipp Hörist at 2018-09-21T13:46:52Z Add license header to emoji_data.py
- - - - - 4d4d0023 by Philipp Hörist at 2018-09-21T13:46:52Z Extend stubs for emoji_data
- - - - - f7a2ac9d by Philipp Hörist at 2018-09-21T13:46:52Z Add annotations
- Add a gtk/types modules - Add a gtk/const module to prevent circular imports
- - - - - 74b66837 by Philipp Hörist at 2018-09-21T13:46:52Z Add annotations
- Remove some unused vars from app
- - - - -
16 changed files:
- gajim/accounts_window.py - gajim/common/app.py - gajim/common/types.py - gajim/config.py - gajim/gtk/account_wizard.py - + gajim/gtk/const.py - gajim/gtk/emoji_data.py - gajim/gtk/emoji_data.pyi - gajim/gtk/filechoosers.py - + gajim/gtk/types.py - gajim/gtkexcepthook.py - gajim/gui_interface.py - gajim/htmltextview.py - gajim/plugins/gajimplugin.py - + test/gtk/htmltextview.py - test/lib/gajim_mocks.py
Changes:
===================================== gajim/accounts_window.py ===================================== @@ -258,10 +258,8 @@ class AccountsWindow(Gtk.ApplicationWindow): 'accounts', account, 'name') app.block_signed_in_notifications[account] = True app.sleeper_state[account] = 'off' - app.encrypted_chats[account] = [] app.last_message_time[account] = {} app.status_before_autoaway[account] = '' - app.transport_avatar[account] = {} app.gajim_optional_features[account] = [] app.caps_hash[account] = '' helpers.update_optional_features(account) @@ -292,10 +290,8 @@ class AccountsWindow(Gtk.ApplicationWindow): del app.to_be_removed[account] del app.newly_added[account] del app.sleeper_state[account] - del app.encrypted_chats[account] del app.last_message_time[account] del app.status_before_autoaway[account] - del app.transport_avatar[account] del app.gajim_optional_features[account] del app.caps_hash[account] if len(app.connections) >= 2:
===================================== gajim/common/app.py ===================================== @@ -24,6 +24,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 typing import Dict # pylint: disable=unused-import +from typing import List # pylint: disable=unused-import + import os import sys import logging @@ -68,53 +71,66 @@ css_config = None
os_info = None # used to cache os information
-transport_type = {} # list the type of transport +transport_type = {} # type: Dict[str, str]
-last_message_time = {} # list of time of the latest incoming message - # {acct1: {jid1: time1, jid2: time2}, } -encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..} +# dict of time of the latest incoming message per jid +# {acct1: {jid1: time1, jid2: time2}, } +last_message_time = {} # type: Dict[str, Dict[str, float]]
contacts = LegacyContactsAPI() -gc_connected = {} # tell if we are connected to the room or not - # {acct: {room_jid: True}} -gc_passwords = {} # list of the pass required to enter a room - # {room_jid: password} -automatic_rooms = {} # list of rooms that must be automaticaly configured - # and for which we have a list of invities - #{account: {room_jid: {'invities': []}}} - -groups = {} # list of groups -newly_added = {} # list of contacts that has just signed in -to_be_removed = {} # list of contacts that has just signed out + +# tell if we are connected to the room or not +# {acct: {room_jid: True}} +gc_connected = {} # type: Dict[str, Dict[str, bool]] + +# dict of the pass required to enter a room +# {room_jid: password} +gc_passwords = {} # type: Dict[str, str] + +# dict of rooms that must be automaticaly configured +# and for which we have a list of invities +# {account: {room_jid: {'invities': []}}} +automatic_rooms = {} # type: Dict[str, Dict[str, Dict[str, List[str]]]] + + # dict of groups, holds if they are expanded or not +groups = {} # type: Dict[str, Dict[str, Dict[str, bool]]] + +# list of contacts that has just signed in +newly_added = {} # type: Dict[str, List[str]] + +# list of contacts that has just signed out +to_be_removed = {} # type: Dict[str, List[str]]
events = Events()
notification = None
-nicks = {} # list of our nick names in each account +# list of our nick names in each account +nicks = {} # type: Dict[str, str] + # should we block 'contact signed in' notifications for this account? # this is only for the first 30 seconds after we change our show # to something else than offline # can also contain account/transport_jid to block notifications for contacts # from this transport block_signed_in_notifications = {} -con_types = {} # type of each connection (ssl, tls, tcp, ...)
-sleeper_state = {} # whether we pass auto away / xa or not + # type of each connection (ssl, tls, tcp, ...) +con_types = {} # type: Dict[str, Optional[str]] + +# whether we pass auto away / xa or not #'off': don't use sleeper for this account #'online': online and use sleeper #'autoaway': autoaway and use sleeper #'autoxa': autoxa and use sleeper -status_before_autoaway = {} +sleeper_state = {} # type: Dict[str, str]
-# jid of transport contacts for which we need to ask avatar when transport will -# be online -transport_avatar = {} # {transport_jid: [jid_list]} +status_before_autoaway = {} # type: Dict[str, str]
# Is Gnome configured to activate on single click ? single_click = False SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible', 'error'] + 'invisible', 'error']
# zeroconf account name ZEROCONF_ACC_NAME = 'Local'
===================================== gajim/common/types.py ===================================== @@ -32,6 +32,7 @@ if TYPE_CHECKING: from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf from gajim.common.contacts import Contact from gajim.common.contacts import GC_Contact + from gajim.common.nec import NetworkEvent
ConnectionT = Union['Connection', 'ConnectionZeroconf'] ContactT = Union['Contact', 'GC_Contact'] @@ -44,3 +45,9 @@ PEPHandlersDict = Dict[str, List[PEPNotifyCallback]]
# Configpaths PathTuple = Tuple[Optional[PathLocation], str, Optional[PathType]] + +# Plugins +PluginExtensionPoints = Dict[str, Tuple[Optional[Callable[..., None]], + Optional[Callable[..., None]]]] +EventHandlersDict = Dict[str, Tuple[int, Callable[['NetworkEvent'], Optional[bool]]]] +PluginEvents = List['NetworkEvent']
===================================== gajim/config.py ===================================== @@ -674,10 +674,8 @@ class RemoveAccountWindow: del app.to_be_removed[self.account] del app.newly_added[self.account] del app.sleeper_state[self.account] - del app.encrypted_chats[self.account] del app.last_message_time[self.account] del app.status_before_autoaway[self.account] - del app.transport_avatar[self.account] del app.gajim_optional_features[self.account] del app.caps_hash[self.account] if len(app.connections) >= 2: # Do not merge accounts if only one exists
===================================== gajim/gtk/account_wizard.py ===================================== @@ -582,10 +582,8 @@ class AccountCreationWizard: app.nicks[self.account] = config['name'] app.block_signed_in_notifications[self.account] = True app.sleeper_state[self.account] = 'off' - app.encrypted_chats[self.account] = [] app.last_message_time[self.account] = {} app.status_before_autoaway[self.account] = '' - app.transport_avatar[self.account] = {} app.gajim_optional_features[self.account] = [] app.caps_hash[self.account] = '' helpers.update_optional_features(self.account)
===================================== gajim/gtk/const.py ===================================== @@ -0,0 +1,19 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY 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 Gajim. If not, see http://www.gnu.org/licenses/. + +# Constants for the gtk module + +from collections import namedtuple + +Filter = namedtuple('Filter', 'name pattern default')
===================================== gajim/gtk/emoji_data.py ===================================== @@ -1,3 +1,17 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY 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 Gajim. If not, see http://www.gnu.org/licenses/. + import re import weakref from enum import IntEnum
===================================== gajim/gtk/emoji_data.pyi ===================================== @@ -1 +1,39 @@ -# Needs to be here because mypy get stuck on emoji_data.py +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY 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 Gajim. If not, see http://www.gnu.org/licenses/. + +from enum import IntEnum +from collections import OrderedDict + +from gi.repository import GdkPixbuf + +class Emoji(IntEnum): + ... + +def is_emoji(codepoints: str) -> bool: ... +def get_emoji_pixbuf(codepoints: str) -> GdkPixbuf.Pixbuf: ... + +class EmojiData(OrderedDict): + ... + +class EmojiPixbufs(dict): + ... + +class EmojiAsciiData(dict): + ... + +emoji_pixbufs = EmojiPixbufs() + +emoji_ascii_data = EmojiAsciiData() + +emoji_data = EmojiData()
===================================== gajim/gtk/filechoosers.py ===================================== @@ -18,7 +18,6 @@ import os import sys from pathlib import Path -from collections import namedtuple
from gi.repository import Gtk from gi.repository import GdkPixbuf @@ -26,11 +25,11 @@ from gi.repository import GObject
from gajim.common import app from gajim.common.i18n import _ +from gajim.gtk.const import Filter +from gajim.gtk.types import FilterList # pylint: disable=unused-import
-Filter = namedtuple('Filter', 'name pattern default')
- -def _require_native(): +def _require_native() -> bool: if app.is_flatpak(): return True if sys.platform in ('win32', 'darwin'): @@ -85,7 +84,7 @@ class BaseFileChooser: class BaseFileOpenDialog:
_title = _('Choose File to Send…') - _filters = [Filter(_('All files'), '*', True)] + _filters = [Filter(_('All files'), '*', True)] # type: FilterList
class BaseAvatarChooserDialog: @@ -96,7 +95,7 @@ class BaseAvatarChooserDialog: if _require_native(): _filters = [Filter(_('PNG files'), '*.png', True), Filter(_('JPEG files'), '*.jp*g', False), - Filter(_('SVG files'), '*.svg', False)] + Filter(_('SVG files'), '*.svg', False)] # type: FilterList else: _filters = [Filter(_('Images'), ['image/png', 'image/jpeg', @@ -106,7 +105,7 @@ class BaseAvatarChooserDialog: class NativeFileChooserDialog(Gtk.FileChooserNative, BaseFileChooser):
_title = '' - _filters = [] + _filters = [] # type: FilterList _action = Gtk.FileChooserAction.OPEN
def __init__(self, accept_cb, cancel_cb=None, transient_for=None, @@ -161,7 +160,7 @@ class NativeAvatarChooserDialog(BaseAvatarChooserDialog, NativeFileChooserDialog class GtkFileChooserDialog(Gtk.FileChooserDialog, BaseFileChooser):
_title = '' - _filters = [] + _filters = [] # type: FilterList _action = Gtk.FileChooserAction.OPEN _preivew_size = (200, 200)
===================================== gajim/gtk/types.py ===================================== @@ -0,0 +1,22 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY 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 Gajim. If not, see http://www.gnu.org/licenses/. + +# Types for typechecking + +from typing import ClassVar +from typing import List + +from gajim.gtk.const import Filter + +FilterList = ClassVar[List[Filter]]
===================================== gajim/gtkexcepthook.py ===================================== @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see http://www.gnu.org/licenses/.
+from typing import cast + import sys import os import traceback @@ -40,7 +42,8 @@ if __name__ == '__main__': glade_file = os.path.join('data', 'gui', 'exception_dialog.ui') else: from gajim.common import configpaths - glade_file = os.path.join(configpaths.get('GUI'), 'exception_dialog.ui') + gui_path = cast(str, configpaths.get('GUI')) + glade_file = os.path.join(gui_path, 'exception_dialog.ui')
_exception_in_progress = threading.Lock()
===================================== gajim/gui_interface.py ===================================== @@ -2714,10 +2714,8 @@ class Interface: app.nicks[a] = app.config.get_per('accounts', a, 'name') app.block_signed_in_notifications[a] = True app.sleeper_state[a] = 0 - app.encrypted_chats[a] = [] app.last_message_time[a] = {} app.status_before_autoaway[a] = '' - app.transport_avatar[a] = {} app.gajim_optional_features[a] = [] app.caps_hash[a] = ''
===================================== gajim/htmltextview.py ===================================== @@ -44,10 +44,6 @@ from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GdkPixbuf
-if __name__ == '__main__': - from gajim.common import configpaths - configpaths.init() - from gajim.common import app from gajim.common import helpers from gajim.common.i18n import _ @@ -1086,185 +1082,3 @@ class HtmlTextView(Gtk.TextView): self.add_child_at_anchor(emoji, anchor) buffer_.delete_mark(start_mark) buffer_.delete_mark(end_mark) - -change_cursor = None - -if __name__ == '__main__': - from gajim.conversation_textview import ConversationTextview - from gajim.gui_interface import Interface - - Interface() - - # create fake app.plugin_manager.gui_extension_point method for tests - def extension_point(*args): - pass - def gui_extension_point(*args): - pass - app.plugin_manager = Interface() - app.plugin_manager.extension_point = extension_point - app.plugin_manager.gui_extension_point = gui_extension_point - - htmlview = ConversationTextview(None) - - def on_textview_motion_notify_event(widget, event): - """ - Change the cursor to a hand when we are over a mail or an url - """ - global change_cursor - w = htmlview.tv.get_window(Gtk.TextWindowType.TEXT) - device = w.get_display().get_device_manager().get_client_pointer() - pointer = w.get_device_position(device) - x = pointer[1] - y = pointer[2] - tags = htmlview.tv.get_iter_at_location(x, y)[1].get_tags() - if change_cursor: - w.set_cursor(get_cursor('XTERM')) - change_cursor = None - for tag in tags: - try: - if tag.is_anchor: - w.set_cursor(get_cursor('HAND2')) - change_cursor = tag - except Exception: - pass - - htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event) - - def handler(texttag, widget, event, iter_, kind): - if event.type == Gdk.EventType.BUTTON_PRESS: - pass - - htmlview.tv.hyperlink_handler = htmlview.hyperlink_handler - - htmlview.print_real_text(None, xhtml='<div>' - '<span style="color: red; text-decoration:underline">Hello</span><br/>\n' - ' <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n' - '<span style="font-size: 500%; font-family: serif">World</span>\n' - '</div>\n') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <p xmlns='http://www.w3.org/1999/xhtml'>a:b - <a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google - </a> - </p><br/> - </body>''') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <p style='font-size:large'> - <span style='font-style: italic'>O - <span style='font-size:larger'>M</span>G</span>, - I'm <span style='color:green'>green</span> - with <span style='font-weight: bold'>envy</span>! - </p> - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - http://test.com/ testing links autolinkifying - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <p>As Emerson said in his essay <span style=' - font-style: italic; background-color:cyan'>Self-Reliance</span>:</p> - <p style='margin-left: 5px; margin-right: 2%'> - "A foolish consistency is the hobgoblin of little minds." - </p> - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <p style='text-align:center'> - Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>? - </p> - <p style='text-align:right'> - <img src='http://www.xmpp.org/images/psa-license.jpg' - alt='A License to Jabber' width='50%' height='50%'/> - </p> - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <ul style='background-color:rgb(120,140,100)'> - <li> One </li> - <li> Two </li> - <li> Three </li> - </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n): -def faciter(n,acc): - if n==0: return acc - return faciter(n-1, acc*n) -if n<0: raise ValueError('Must be non-negative') -return faciter(n,1)</pre> - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <ol style='background-color:rgb(120,140,100)'> - <li> One </li> - <li> Two is nested: <ul style='background-color:rgb(200,200,100)'> - <li> One </li> - <li style='font-size:50%'> Two </li> - <li style='font-size:200%'> Three </li> - <li style='font-size:9999pt'> Four </li> - </ul></li> - <li> Three </li></ol> - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <p> - <strong> - <a href='xmpp:example@example.org'>xmpp link</a> - </strong>: </p> - <div xmlns='http://www.w3.org/1999/xhtml'> - <cite style='margin: 7px;' title='xmpp:examples@example.org'> - <p> - <strong>examples@example.org wrote:</strong> - </p> - <p>this cite - bla bla bla, smile- :-) …</p> - </cite> - <div> - <p>some text</p> - </div> - </div> - <p/> - <p>#232/1</p> - </body> - ''') - htmlview.print_real_text(None, xhtml='<hr />') - htmlview.print_real_text(None, xhtml=''' - <body xmlns='http://www.w3.org/1999/xhtml'> - <br/> -<img src='data:image/png;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw\ -AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz\ -ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp\ -a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl\ -ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis\ -F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH\ -hhx4dbgYKAAA7' alt='Larry'/> - </body> - ''') - htmlview.tv.show() - sw = Gtk.ScrolledWindow() - sw.set_property('hscrollbar-policy', Gtk.PolicyType.AUTOMATIC) - sw.set_property('vscrollbar-policy', Gtk.PolicyType.AUTOMATIC) - sw.set_property('border-width', 0) - sw.add(htmlview.tv) - sw.show() - frame = Gtk.Frame() - frame.set_shadow_type(Gtk.ShadowType.IN) - frame.show() - frame.add(sw) - win = Gtk.Window() - win.add(frame) - win.set_default_size(400, 300) - win.show_all() - win.connect('destroy', lambda win: Gtk.main_quit()) - Gtk.main()
===================================== gajim/plugins/gajimplugin.py ===================================== @@ -21,15 +21,25 @@ Base class for implementing plugin. :license: GPL '''
+from typing import List # pylint: disable=W0611 +from typing import Tuple # pylint: disable=W0611 +from typing import Dict # pylint: disable=W0611 +from typing import Any # pylint: disable=W0611 + import os import locale +import logging +import pickle
from gajim.common import configpaths +from gajim.common.types import PluginExtensionPoints # pylint: disable=W0611 +from gajim.common.types import EventHandlersDict # pylint: disable=W0611 +from gajim.common.types import PluginEvents # pylint: disable=W0611
from gajim.plugins.helpers import log_calls, log from gajim.plugins.gui import GajimPluginConfigDialog
-import logging + log = logging.getLogger('gajim.p.plugin')
@@ -86,7 +96,7 @@ class GajimPlugin:
:todo: should be allow rich text here (like HTML or reStructuredText)? ''' - authors = [] + authors = [] # type: List[str] ''' Plugin authors.
@@ -104,7 +114,7 @@ class GajimPlugin: :todo: should we check whether provided string is valid URI? (Maybe using 'property') ''' - gui_extension_points = {} + gui_extension_points = {} # type: PluginExtensionPoints ''' Extension points that plugin wants to connect with and handlers to be used.
@@ -118,7 +128,7 @@ class GajimPlugin: or when extpoint is destroyed and plugin is activate (eg. chat window closed). ''' - config_default_values = {} + config_default_values = {} # type: Dict[str, Tuple[Any, str]] ''' Default values for keys that should be stored in plug-in config.
@@ -132,7 +142,7 @@ class GajimPlugin:
:type: {} of 2-element tuples ''' - events_handlers = {} + events_handlers = {} # type: EventHandlersDict ''' Dictionary with events handlers.
@@ -143,7 +153,7 @@ class GajimPlugin:
:type: {} with 2-element tuples ''' - events = [] + events = [] # type: PluginEvents ''' New network event classes to be registered in Network Events Controller.
@@ -152,7 +162,7 @@ class GajimPlugin: '''
@log_calls('GajimPlugin') - def __init__(self): + def __init__(self) -> None: self.config = GajimPluginConfig(self) ''' Plug-in configuration dictionary. @@ -168,11 +178,11 @@ class GajimPlugin: self.init()
@log_calls('GajimPlugin') - def save_config(self): + def save_config(self) -> None: self.config.save()
@log_calls('GajimPlugin') - def load_config(self): + def load_config(self) -> None: self.config.load()
def __eq__(self, plugin): @@ -203,7 +213,6 @@ class GajimPlugin: def deactivate(self): pass
-import pickle
class GajimPluginConfig(): @log_calls('GajimPluginConfig')
===================================== test/gtk/htmltextview.py ===================================== @@ -0,0 +1,188 @@ +import sys +import os +from unittest.mock import MagicMock + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) + +from gi.repository import Gtk, Gdk + +from gajim.common import app +from gajim.common import configpaths +configpaths.init() +from gajim.common import caps_cache + +from gajim.gtk.util import get_cursor +from gajim.conversation_textview import ConversationTextview +from gajim.gui_interface import Interface + + +caps_cache.capscache = MagicMock() +app.plugin_manager = MagicMock() +app.logger = MagicMock() +app.interface = Interface() + +change_cursor = None +htmlview = ConversationTextview(None) + +def on_textview_motion_notify_event(widget, event): + """ + Change the cursor to a hand when we are over a mail or an url + """ + global change_cursor + w = htmlview.tv.get_window(Gtk.TextWindowType.TEXT) + device = w.get_display().get_device_manager().get_client_pointer() + pointer = w.get_device_position(device) + x = pointer[1] + y = pointer[2] + tags = htmlview.tv.get_iter_at_location(x, y)[1].get_tags() + if change_cursor: + w.set_cursor(get_cursor('XTERM')) + change_cursor = None + for tag in tags: + try: + if tag.is_anchor: + w.set_cursor(get_cursor('HAND2')) + change_cursor = tag + except Exception: + pass + +htmlview.tv.connect('motion_notify_event', on_textview_motion_notify_event) + +def handler(texttag, widget, event, iter_, kind): + if event.type == Gdk.EventType.BUTTON_PRESS: + pass + +htmlview.tv.hyperlink_handler = htmlview.hyperlink_handler + +htmlview.print_real_text(None, xhtml='<div>' +'<span style="color: red; text-decoration:underline">Hello</span><br/>\n' + ' <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n' +'<span style="font-size: 500%; font-family: serif">World</span>\n' + '</div>\n') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> + <p xmlns='http://www.w3.org/1999/xhtml'>a:b + <a href='http://google.com/' xmlns='http://www.w3.org/1999/xhtml'>Google + </a> + </p><br/> +</body>''') +htmlview.print_real_text(None, xhtml=''' + <body xmlns='http://www.w3.org/1999/xhtml'> + <p style='font-size:large'> + <span style='font-style: italic'>O + <span style='font-size:larger'>M</span>G</span>, + I'm <span style='color:green'>green</span> + with <span style='font-weight: bold'>envy</span>! + </p> + </body> + ''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> + http://test.com/ testing links autolinkifying +</body> + ''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> + <p>As Emerson said in his essay <span style=' + font-style: italic; background-color:cyan'>Self-Reliance</span>:</p> + <p style='margin-left: 5px; margin-right: 2%'> + "A foolish consistency is the hobgoblin of little minds." + </p> +</body> + ''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> + <p style='text-align:center'> + Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>? + </p> + <p style='text-align:right'> + <img src='http://www.xmpp.org/images/psa-license.jpg' + alt='A License to Jabber' width='50%' height='50%'/> + </p> +</body> + ''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> + <ul style='background-color:rgb(120,140,100)'> + <li> One </li> + <li> Two </li> + <li> Three </li> + </ul><hr /><pre style="background-color:rgb(120,120,120)">def fac(n): +def faciter(n,acc): +if n==0: return acc +return faciter(n-1, acc*n) +if n<0: raise ValueError('Must be non-negative') +return faciter(n,1)</pre> +</body> + ''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> + <ol style='background-color:rgb(120,140,100)'> + <li> One </li> + <li> Two is nested: <ul style='background-color:rgb(200,200,100)'> + <li> One </li> + <li style='font-size:50%'> Two </li> + <li style='font-size:200%'> Three </li> + <li style='font-size:9999pt'> Four </li> + </ul></li> + <li> Three </li></ol> +</body> + ''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> +<p> + <strong> + <a href='xmpp:example@example.org'>xmpp link</a> + </strong>: </p> +<div xmlns='http://www.w3.org/1999/xhtml'> + <cite style='margin: 7px;' title='xmpp:examples@example.org'> + <p> + <strong>examples@example.org wrote:</strong> + </p> + <p>this cite - bla bla bla, smile- :-) …</p> + </cite> + <div> + <p>some text</p> + </div> +</div> +<p/> +<p>#232/1</p> +</body> +''') +htmlview.print_real_text(None, xhtml='<hr />') +htmlview.print_real_text(None, xhtml=''' +<body xmlns='http://www.w3.org/1999/xhtml'> +<br/> +<img src='data:image/png;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAw\ +AAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFz\ +ByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSp\ +a/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJl\ +ZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uis\ +F81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PH\ +hhx4dbgYKAAA7' alt='Larry'/> +</body> +''') +htmlview.tv.show() +sw = Gtk.ScrolledWindow() +sw.set_property('hscrollbar-policy', Gtk.PolicyType.AUTOMATIC) +sw.set_property('vscrollbar-policy', Gtk.PolicyType.AUTOMATIC) +sw.set_property('border-width', 0) +sw.add(htmlview.tv) +sw.show() +frame = Gtk.Frame() +frame.set_shadow_type(Gtk.ShadowType.IN) +frame.show() +frame.add(sw) +win = Gtk.Window() +win.add(frame) +win.set_default_size(400, 300) +win.show_all() +win.connect('destroy', lambda win: Gtk.main_quit()) +Gtk.main()
===================================== test/lib/gajim_mocks.py ===================================== @@ -35,10 +35,8 @@ class MockConnection(Mock, ConnectionHandlers): app.nicks[account] = app.config.get_per('accounts', account, 'name') app.block_signed_in_notifications[account] = True app.sleeper_state[account] = 0 - app.encrypted_chats[account] = [] app.last_message_time[account] = {} app.status_before_autoaway[account] = '' - app.transport_avatar[account] = {} app.gajim_optional_features[account] = [] app.caps_hash[account] = ''
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/efca9cd9c7296b4be1b2496c1390836f65...