Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Hörist <forenjunkie@chello.at>2017-11-01 02:00:40 +0300
committerPhilipp Hörist <forenjunkie@chello.at>2017-11-01 02:00:40 +0300
commit377c9fc9b83c710eaadbe880c0d153d376591ea6 (patch)
tree9b28510364d3a4d3b982c477e8ebf760a6b4b104
parenta4b62296ea43585c9d26c3d85eaec58d52953908 (diff)
parent371690176d6c8eb9b9e6b19b06c89f17824362a2 (diff)
Merge branch 'gtk3' into 'gtk3'
UrlImagePreview python3/gtk3 port See merge request gajim/gajim-plugins!46
-rw-r--r--url_image_preview/__init__.py1
-rw-r--r--url_image_preview/config_dialog.ui204
-rw-r--r--url_image_preview/context_menu.ui99
-rw-r--r--url_image_preview/http_functions.py222
-rw-r--r--url_image_preview/manifest.ini7
-rw-r--r--url_image_preview/url_image_preview.py672
6 files changed, 1094 insertions, 111 deletions
diff --git a/url_image_preview/__init__.py b/url_image_preview/__init__.py
index 58a9544..b2c8bee 100644
--- a/url_image_preview/__init__.py
+++ b/url_image_preview/__init__.py
@@ -1 +1,2 @@
+# simple redirect
from .url_image_preview import UrlImagePreviewPlugin
diff --git a/url_image_preview/config_dialog.ui b/url_image_preview/config_dialog.ui
index 438dd93..c43e685 100644
--- a/url_image_preview/config_dialog.ui
+++ b/url_image_preview/config_dialog.ui
@@ -1,55 +1,187 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
<interface>
- <requires lib="gtk+" version="2.16"/>
- <!-- interface-naming-policy toplevel-contextual -->
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name Text -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">256 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">512 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 MiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">5 MiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">10 MiB</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore2">
+ <columns>
+ <!-- column-name Text -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Open</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Save as</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Copy Link Location</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Open Link in Browser</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Open Downloaded File in Browser</col>
+ </row>
+ </data>
+ </object>
<object class="GtkWindow" id="window1">
+ <property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
- <property name="orientation">vertical</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">9</property>
<child>
- <object class="GtkHBox" id="hbox2">
+ <object class="GtkFrame" id="frame1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
<child>
- <object class="GtkLabel" id="preview_size_lebel">
- <property name="width_request">133</property>
- <property name="visible">True</property>
- <property name="xalign">0.029999999329447746</property>
- <property name="label" translatable="yes">Preview size</property>
- <property name="ellipsize">start</property>
- <property name="single_line_mode">True</property>
- <property name="track_visited_links">False</property>
- </object>
- <packing>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkSpinButton" id="preview_size">
+ <object class="GtkTable" id="table1">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="has_tooltip">True</property>
- <property name="tooltip_text" translatable="yes">Preview size(10-512)</property>
- <property name="invisible_char">&#x25CF;</property>
- <property name="width_chars">6</property>
- <property name="snap_to_ticks">True</property>
- <property name="numeric">True</property>
- <signal name="value_changed" handler="preview_size_value_changed"/>
+ <property name="can_focus">False</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkSpinButton" id="preview_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">&#x25CF;</property>
+ <property name="width_chars">6</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ <signal name="value-changed" handler="preview_size_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="max_size_combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore1</property>
+ <signal name="changed" handler="max_size_value_changed" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="leftclick_action_combobox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore2</property>
+ <signal name="changed" handler="leftclick_action_changed" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="max_size_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">13</property>
+ <property name="label" translatable="yes">Accept files smaller then</property>
+ <property name="track_visited_links">False</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="preview_size_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Preview size</property>
+ <property name="track_visited_links">False</property>
+ </object>
+ <packing>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="leftclick_action_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Left click action</property>
+ <property name="track_visited_links">False</property>
+ </object>
+ <packing>
+ <property name="y_options">GTK_EXPAND</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options">GTK_EXPAND</property>
+ </packing>
+ </child>
</object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">6</property>
<property name="position">0</property>
</packing>
</child>
- <child>
- <placeholder/>
- </child>
</object>
</child>
</object>
diff --git a/url_image_preview/context_menu.ui b/url_image_preview/context_menu.ui
new file mode 100644
index 0000000..cbb0390
--- /dev/null
+++ b/url_image_preview/context_menu.ui
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-open</property>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-save-as</property>
+ </object>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-copy</property>
+ </object>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-jump-to</property>
+ </object>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-jump-to</property>
+ </object>
+ <object class="GtkMenu" id="context_menu">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="open_menuitem">
+ <property name="label" translatable="yes">_Open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="save_as_menuitem">
+ <property name="label" translatable="yes">_Save as</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image2</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="encryption_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="copy_link_location_menuitem">
+ <property name="label" translatable="yes">_Copy Link Location</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image3</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="open_link_in_browser_menuitem">
+ <property name="label" translatable="yes">Open Link in _Browser</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image4</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="extras_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="open_file_in_browser_menuitem">
+ <property name="label" translatable="yes">Open _Downloaded File in Browser</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image5</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/url_image_preview/http_functions.py b/url_image_preview/http_functions.py
new file mode 100644
index 0000000..bd708b4
--- /dev/null
+++ b/url_image_preview/http_functions.py
@@ -0,0 +1,222 @@
+# -*- coding: utf-8 -*-
+##
+## 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 urllib.request as urllib2
+import socket
+import re
+
+from gajim.common import app
+from gajim.common import helpers
+import logging
+
+import os
+if os.name == 'nt':
+ import certifi
+
+if app.HAVE_PYCURL:
+ import pycurl
+ from io import StringIO
+
+
+log = logging.getLogger('gajim.plugin_system.url_image_preview.http_functions')
+
+def get_http_head(account, url):
+ # Check if proxy is used
+ proxy = helpers.get_proxy_info(account)
+ if proxy and proxy['type'] in ('http', 'socks5'):
+ return _get_http_head_proxy(url, proxy)
+ return _get_http_head_direct(url)
+
+def get_http_file(account, attrs):
+ # Check if proxy is used
+ proxy = helpers.get_proxy_info(account)
+ if proxy and proxy['type'] in ('http', 'socks5'):
+ return _get_http_proxy(attrs, proxy)
+ else:
+ return _get_http_direct(attrs)
+
+def _get_http_head_direct(url):
+ log.debug('Head request direct for URL: %s' % url)
+ try:
+ req = urllib2.Request(url)
+ req.get_method = lambda: 'HEAD'
+ req.add_header('User-Agent', 'Gajim %s' % app.version)
+ if os.name == 'nt':
+ f = urllib2.urlopen(req, cafile=certifi.where())
+ else:
+ f = urllib2.urlopen(req)
+ except Exception as ex:
+ log.debug('Could not get head response for URL: %s' % url)
+ log.debug("%s" % str(ex))
+ return ('', 0)
+ ctype = f.headers['Content-Type']
+ clen = f.headers['Content-Length']
+ try:
+ clen = int(clen)
+ except ValueError:
+ pass
+ return (ctype, clen)
+
+def _get_http_head_proxy(url, proxy):
+ log.debug('Head request with proxy for URL: %s' % url)
+ if not app.HAVE_PYCURL:
+ log.error('PYCURL not installed')
+ return ('', 0)
+
+ headers = ''
+ try:
+ b = StringIO()
+ c = pycurl.Curl()
+ c.setopt(pycurl.URL, url.encode('utf-8'))
+ c.setopt(pycurl.FOLLOWLOCATION, 1)
+ # Make a HEAD request:
+ c.setopt(pycurl.CUSTOMREQUEST, 'HEAD')
+ c.setopt(pycurl.NOBODY, 1)
+ c.setopt(pycurl.HEADER, 1)
+
+ c.setopt(pycurl.MAXFILESIZE, 2000000)
+ c.setopt(pycurl.WRITEFUNCTION, b.write)
+ c.setopt(pycurl.USERAGENT, 'Gajim ' + app.version)
+
+ # set proxy
+ c.setopt(pycurl.PROXY, proxy['host'].encode('utf-8'))
+ c.setopt(pycurl.PROXYPORT, proxy['port'])
+ if proxy['useauth']:
+ c.setopt(pycurl.PROXYUSERPWD, proxy['user'].encode('utf-8') +
+ ':' + proxy['pass'].encode('utf-8'))
+ c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
+ if proxy['type'] == 'http':
+ c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
+ elif proxy['type'] == 'socks5':
+ c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
+ x = c.perform()
+ c.close()
+ headers = b.getvalue()
+ except pycurl.error as ex:
+ log.debug('Could not get head response for URL: %s' % url)
+ log.debug("%s" % str(ex))
+ return ('', 0)
+
+ ctype = ''
+ searchObj = re.search(r'^Content-Type: (.*)$', headers, re.M | re.I)
+ if searchObj:
+ ctype = searchObj.group(1).strip()
+ clen = 0
+ searchObj = re.search(r'^Content-Length: (.*)$', headers, re.M | re.I)
+ if searchObj:
+ try:
+ clen = int(searchObj.group(1).strip())
+ except ValueError:
+ pass
+ return (ctype, clen)
+
+def _get_http_direct(attrs):
+ """
+ Download a file. This function should
+ be launched in a separated thread.
+ """
+ log.debug('Get request direct for URL: %s' % attrs['src'])
+ mem, alt, max_size = b'', '', 2 * 1024 * 1024
+ if 'max_size' in attrs:
+ max_size = attrs['max_size']
+ try:
+ req = urllib2.Request(attrs['src'])
+ req.add_header('User-Agent', 'Gajim ' + app.version)
+ if os.name == 'nt':
+ f = urllib2.urlopen(req, cafile=certifi.where())
+ else:
+ f = urllib2.urlopen(req)
+ except Exception as ex:
+ log.debug('Error loading file %s '
+ % attrs['src'] + str(ex))
+ pixbuf = None
+ alt = attrs.get('alt', 'Broken image')
+ else:
+ while True:
+ try:
+ temp = f.read(100)
+ except socket.timeout as ex:
+ log.debug('Timeout loading image %s '
+ % attrs['src'] + str(ex))
+ alt = attrs.get('alt', '')
+ if alt:
+ alt += '\n'
+ alt += _('Timeout loading image')
+ break
+ if temp:
+ mem += temp
+ else:
+ break
+ if len(mem) > max_size:
+ alt = attrs.get('alt', '')
+ if alt:
+ alt += '\n'
+ alt += _('Image is too big')
+ break
+ return (mem, alt)
+
+def _get_http_proxy(attrs, proxy):
+ """
+ Download an image through a proxy.
+ This function should be launched in a
+ separated thread.
+ """
+ log.debug('Get request with proxy for URL: %s' % attrs['src'])
+ if not app.HAVE_PYCURL:
+ log.error('PYCURL not installed')
+ return '', _('PyCURL is not installed')
+ mem, alt, max_size = '', '', 2 * 1024 * 1024
+ if 'max_size' in attrs:
+ max_size = attrs['max_size']
+ try:
+ b = StringIO()
+ c = pycurl.Curl()
+ c.setopt(pycurl.URL, attrs['src'].encode('utf-8'))
+ c.setopt(pycurl.FOLLOWLOCATION, 1)
+ c.setopt(pycurl.MAXFILESIZE, max_size)
+ c.setopt(pycurl.WRITEFUNCTION, b.write)
+ c.setopt(pycurl.USERAGENT, 'Gajim ' + app.version)
+ # set proxy
+ c.setopt(pycurl.PROXY, proxy['host'].encode('utf-8'))
+ c.setopt(pycurl.PROXYPORT, proxy['port'])
+ if proxy['useauth']:
+ c.setopt(pycurl.PROXYUSERPWD, proxy['user'].encode('utf-8') +
+ ':' + proxy['pass'].encode('utf-8'))
+ c.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
+ if proxy['type'] == 'http':
+ c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP)
+ elif proxy['type'] == 'socks5':
+ c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5)
+ x = c.perform()
+ c.close()
+ t = b.getvalue()
+ return (t, attrs.get('alt', ''))
+ except pycurl.error as ex:
+ alt = attrs.get('alt', '')
+ if alt:
+ alt += '\n'
+ if ex[0] == pycurl.E_FILESIZE_EXCEEDED:
+ alt += _('Image is too big')
+ elif ex[0] == pycurl.E_OPERATION_TIMEOUTED:
+ alt += _('Timeout loading image')
+ else:
+ alt += _('Error loading image')
+ except Exception as ex:
+ log.debug('Error loading file %s ' % attrs['src'] + str(ex))
+ pixbuf = None
+ alt = attrs.get('alt', 'Broken image')
+ return ('', alt)
diff --git a/url_image_preview/manifest.ini b/url_image_preview/manifest.ini
index da78a9b..16a352a 100644
--- a/url_image_preview/manifest.ini
+++ b/url_image_preview/manifest.ini
@@ -2,8 +2,11 @@
name: Url image preview
short_name: url_image_preview
version: 0.5.6
-description: Url image preview in chatbox.
+description: Displays a preview of links to images
authors = Denis Fomin <fominde@gmail.com>
Yann Leboulanger <asterix@lagaule.org>
-homepage = http://trac-plugins.gajim.org/wiki/UrlImagePreviewPlugin
+ Anders Sandblad <runeson@gmail.com>
+ Thilo Molitor <thilo@eightysoft.de>
+ Philipp Hoerist <philipp@hoerist.com>
+homepage = https://dev.gajim.org/gajim/gajim-plugins/wikis/UrlImagePreviewPlugin
min_gajim_version: 0.16.11
diff --git a/url_image_preview/url_image_preview.py b/url_image_preview/url_image_preview.py
index 5db72a6..1ff5c9a 100644
--- a/url_image_preview/url_image_preview.py
+++ b/url_image_preview/url_image_preview.py
@@ -1,116 +1,442 @@
# -*- coding: utf-8 -*-
+##
+## 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 gi.repository import Gtk
-from gi.repository import GdkPixbuf
-import re
+from gi.repository import Gtk, Gdk, GLib, GObject, GdkPixbuf, Gio
import os
+import hashlib
+import binascii
+from urllib.parse import urlparse
+from io import BytesIO
+import shutil
+import logging
+import nbxmpp
from gajim.common import app
+from gajim.common import ged
from gajim.common import helpers
+from gajim.common import configpaths
+from gajim import dialogs
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import log_calls
from gajim.plugins.gui import GajimPluginConfigDialog
from gajim.conversation_textview import TextViewImage
+from .http_functions import get_http_head, get_http_file
-EXTENSIONS = ('.png','.jpg','.jpeg','.gif','.raw','.svg')
+log = logging.getLogger('gajim.plugin_system.url_image_preview')
+
+try:
+ from PIL import Image
+except:
+ log.debug('Pillow not available')
+
+try:
+ if os.name == 'nt':
+ from cryptography.hazmat.backends.openssl import backend
+ else:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import Cipher
+ from cryptography.hazmat.primitives.ciphers import algorithms
+ from cryptography.hazmat.primitives.ciphers.modes import GCM
+ decryption_available = True
+except Exception as e:
+ DEP_MSG = 'For preview of encrypted images, ' \
+ 'please install python-cryptography!'
+ log.debug('Cryptography Import Error: ' + str(e))
+ log.info('Decryption/Encryption disabled due to errors')
+ decryption_available = False
+
+ACCEPTED_MIME_TYPES = ('image/png', 'image/jpeg', 'image/gif', 'image/raw',
+ 'image/svg+xml', 'image/x-ms-bmp')
class UrlImagePreviewPlugin(GajimPlugin):
@log_calls('UrlImagePreviewPlugin')
def init(self):
- self.description = _('Url image preview in chatbox.\n'
- 'Based on patch in ticket #5300:\n'
- 'http://trac.gajim.org/attachment/ticket/5300.')
+ if not decryption_available:
+ self.available_text = DEP_MSG
self.config_dialog = UrlImagePreviewPluginConfigDialog(self)
+ self.events_handlers = {}
+ self.events_handlers['message-received'] = (
+ ged.PRECORE, self.handle_message_received)
self.gui_extension_points = {
- 'chat_control_base': (self.connect_with_chat_control,
- self.disconnect_from_chat_control),
- 'print_special_text': (self.print_special_text,
- self.print_special_text1),}
+ 'chat_control_base': (self.connect_with_chat_control,
+ self.disconnect_from_chat_control),
+ 'print_special_text': (self.print_special_text, None), }
self.config_default_values = {
- 'PREVIEW_SIZE': (150, 'Preview size(10-512)'),}
- self.chat_control = None
- self.controls = []
+ 'PREVIEW_SIZE': (150, 'Preview size(10-512)'),
+ 'MAX_FILE_SIZE': (524288, 'Max file size for image preview'),
+ 'LEFTCLICK_ACTION': ('open_menuitem', 'Open')}
+ self.controls = {}
+
+ # remove oob tag if oob url == message text
+ def handle_message_received(self, event):
+ oob_node = event.stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
+ oob_url = None
+ oob_desc = None
+ if oob_node:
+ oob_url = oob_node.getTagData('url')
+ oob_desc = oob_node.getTagData('desc')
+ if oob_url and oob_url == event.msgtxt and \
+ (not oob_desc or oob_desc == ""):
+ log.debug("Detected oob tag containing same"
+ "url as the message text, deleting oob tag...")
+ event.stanza.delChild(oob_node)
@log_calls('UrlImagePreviewPlugin')
def connect_with_chat_control(self, chat_control):
-
- self.chat_control = chat_control
- control = Base(self, self.chat_control)
- self.controls.append(control)
+ account = chat_control.contact.account.name
+ jid = chat_control.contact.jid
+ if account not in self.controls:
+ self.controls[account] = {}
+ self.controls[account][jid] = Base(self, chat_control)
@log_calls('UrlImagePreviewPlugin')
def disconnect_from_chat_control(self, chat_control):
- for control in self.controls:
- control.disconnect_from_chat_control()
- self.controls = []
+ account = chat_control.contact.account.name
+ jid = chat_control.contact.jid
+ self.controls[account][jid].deinit()
+ del self.controls[account][jid]
def print_special_text(self, tv, special_text, other_tags, graphics=True,
- additional_data={}):
- for control in self.controls:
- if control.chat_control.conv_textview != tv:
+ additional_data=None, iter_=None):
+ account = tv.account
+ for jid in self.controls[account]:
+ if self.controls[account][jid].chat_control.conv_textview != tv:
continue
- control.print_special_text(special_text, other_tags, graphics=True)
+ self.controls[account][jid].print_special_text(
+ special_text, other_tags, graphics=graphics,
+ additional_data=additional_data, iter_=iter_)
+ return
- def print_special_text1(self, chat_control, special_text, other_tags=None,
- graphics=True, additional_data={}):
- for control in self.controls:
- if control.chat_control == chat_control:
- control.disconnect_from_chat_control()
- self.controls.remove(control)
class Base(object):
def __init__(self, plugin, chat_control):
self.plugin = plugin
self.chat_control = chat_control
self.textview = self.chat_control.conv_textview
+ self.handlers = {}
- def print_special_text(self, special_text, other_tags, graphics=True):
- if not app.interface.basic_pattern_re.match(special_text):
- return
+ self.directory = os.path.join(configpaths.gajimpaths['MY_DATA'],
+ 'downloads')
+ self.thumbpath = os.path.join(configpaths.gajimpaths['MY_CACHE'],
+ 'downloads.thumb')
+
+ try:
+ self._create_path(self.directory)
+ self._create_path(self.thumbpath)
+ except Exception as e:
+ log.error("Error creating download and/or thumbnail folder!")
+ raise
+
+ def deinit(self):
+ # remove all register handlers on wigets, created by self.xml
+ # to prevent circular references among objects
+ for i in list(self.handlers.keys()):
+ if self.handlers[i].handler_is_connected(i):
+ self.handlers[i].disconnect(i)
+ del self.handlers[i]
+
+ def print_special_text(self, special_text, other_tags, graphics=True,
+ additional_data=None, iter_=None):
# remove qip bbcode
special_text = special_text.rsplit('[/img]')[0]
- name, extension = os.path.splitext(special_text)
- if extension.lower() not in EXTENSIONS:
- return
- if not special_text.startswith('http://') and \
- special_text.startswith('www.'):
+ if special_text.startswith('www.'):
special_text = 'http://' + special_text
- if not special_text.startswith('ftp://') and \
- special_text.startswith('ftp.'):
+ if special_text.startswith('ftp.'):
special_text = 'ftp://' + special_text
- # show pics preview
+ urlparts = urlparse(special_text)
+ if urlparts.scheme not in ["https", "http", "ftp", "ftps", 'aesgcm'] or \
+ not urlparts.netloc:
+ log.info("Not accepting URL for image preview: %s" % special_text)
+ return
+
+ # Don't print the URL in the message window (in the calling function)
+ self.textview.plugin_modified = True
+
buffer_ = self.textview.tv.get_buffer()
- iter_ = buffer_.get_end_iter()
- mark = buffer_.create_mark(None, iter_, True)
- # start downloading image
- app.thread_interface(helpers.download_image, [
- self.textview.account, {'src': special_text}], self._update_img,
- [mark])
-
- def _update_img(self, mem_alt, mark):
- mem, alt = mem_alt
- if mem:
+ if not iter_:
+ iter_ = buffer_.get_end_iter()
+
+ # Show URL, until image is loaded (if ever)
+ ttt = buffer_.get_tag_table()
+ repl_start = buffer_.create_mark(None, iter_, True)
+ buffer_.insert_with_tags(iter_, special_text,
+ *[(ttt.lookup(t) if isinstance(t, str) else t) for t in ["url"]])
+ repl_end = buffer_.create_mark(None, iter_, True)
+
+ filename = os.path.basename(urlparts.path)
+ ext = os.path.splitext(filename)[1]
+ name = os.path.splitext(filename)[0]
+ namehash = hashlib.sha1(special_text.encode('utf-8')).hexdigest()
+ newfilename = name + '_' + namehash + ext
+ thumbfilename = name + '_' + namehash + '_thumb_' \
+ + str(self.plugin.config['PREVIEW_SIZE']) + ext
+
+ filepath = os.path.join(self.directory, newfilename)
+ thumbpath = os.path.join(self.thumbpath, thumbfilename)
+ filepaths = [filepath, thumbpath]
+
+ key = ''
+ iv = ''
+ encrypted = False
+ if urlparts.fragment:
+ fragment = binascii.unhexlify(urlparts.fragment)
+ key = fragment[16:]
+ iv = fragment[:16]
+ if len(key) == 32 and len(iv) == 16:
+ encrypted = True
+ if not encrypted:
+ key = fragment[12:]
+ iv = fragment[:12]
+ if len(key) == 32 and len(iv) == 12:
+ encrypted = True
+
+ # file exists but thumbnail got deleted
+ if os.path.exists(filepath) and not os.path.exists(thumbpath):
+ with open(filepath, 'rb') as f:
+ mem = f.read()
+ f.closed
+ app.thread_interface(
+ self._save_thumbnail, [thumbpath, (mem, '')],
+ self._update_img, [special_text, repl_start,
+ repl_end, filepath, encrypted])
+
+ # display thumbnail if already downloadeded
+ # (but only if file also exists)
+ elif os.path.exists(filepath) and os.path.exists(thumbpath):
+ app.thread_interface(
+ self._load_thumbnail, [thumbpath],
+ self._update_img, [special_text, repl_start,
+ repl_end, filepath, encrypted])
+
+ # or download file, calculate thumbnail and finally display it
+ else:
+ if encrypted and not decryption_available:
+ log.debug('Please install Crytography to decrypt pictures')
+ else:
+ # First get the http head request
+ # which does not fetch data, just headers
+ # then check the mime type and filesize
+ if urlparts.scheme == 'aesgcm':
+ special_text = 'https://' + special_text[9:]
+ app.thread_interface(
+ get_http_head, [self.textview.account, special_text],
+ self._check_mime_size, [special_text, repl_start, repl_end,
+ filepaths, key, iv, encrypted])
+
+ def _save_thumbnail(self, thumbpath, tuple_arg):
+ mem, alt = tuple_arg
+ size = self.plugin.config['PREVIEW_SIZE']
+ use_Gtk = False
+ output = None
+
+ try:
+ output = BytesIO()
+ im = Image.open(BytesIO(mem))
+ im.thumbnail((size, size), Image.ANTIALIAS)
+ im.save(output, "jpeg", quality=100, optimize=True)
+ mem = output.getvalue()
+ output.close()
+ except Exception as e:
+ if output:
+ output.close()
+ log.info("Failed to load image using pillow, "
+ "falling back to gdk pixbuf.")
+ log.debug(e)
+ use_Gtk = True
+
+ if use_Gtk:
+ log.info("Pillow not available or file corrupt, "
+ "trying to load using gdk pixbuf.")
try:
loader = GdkPixbuf.PixbufLoader()
loader.write(mem)
loader.close()
pixbuf = loader.get_pixbuf()
- pixbuf, w, h = self.get_pixbuf_of_size(pixbuf,
- self.plugin.config['PREVIEW_SIZE'])
- buffer_ = mark.get_buffer()
- end_iter = buffer_.get_iter_at_mark(mark)
- anchor = buffer_.create_child_anchor(end_iter)
- img = TextViewImage(anchor, alt)
- img.set_from_pixbuf(pixbuf)
- img.show()
- self.textview.tv.add_child_at_anchor(img, anchor)
+ pixbuf, w, h = self._get_pixbuf_of_size(pixbuf, size)
+
+ ok, mem = pixbuf.save_to_bufferv("jpeg", ["quality"], ["100"])
+ except Exception as e:
+ log.info("Failed to load image using gdk pixbuf, "
+ "ignoring image.")
+ log.debug(e)
+ return ('', '')
+
+ try:
+ self._create_path(os.path.dirname(thumbpath))
+ self._write_file(thumbpath, mem)
+ except Exception as e:
+ dialogs.ErrorDialog(
+ _('Could not save file'),
+ _('Exception raised while saving thumbnail '
+ 'for image file (see error log for more '
+ 'information)'),
+ transient_for=self.chat_control.parent_win.window)
+ log.error(str(e))
+ return (mem, alt)
+
+ def _load_thumbnail(self, thumbpath):
+ with open(thumbpath, 'rb') as f:
+ mem = f.read()
+ f.closed
+ return (mem, '')
+
+ def _write_file(self, path, data):
+ log.info("Writing '%s' of size %d..." % (path, len(data)))
+ try:
+ with open(path, "wb") as output_file:
+ output_file.write(data)
+ output_file.closed
+ except Exception as e:
+ log.error("Failed to write file '%s'!" % path)
+ raise
+
+ def _update_img(self, tuple_arg, url, repl_start, repl_end,
+ filepath, encrypted):
+ mem, alt = tuple_arg
+ if mem:
+ try:
+ urlparts = urlparse(url)
+ filename = os.path.basename(urlparts.path)
+ eb = Gtk.EventBox()
+ eb.connect('button-press-event', self.on_button_press_event,
+ filepath, filename, url, encrypted)
+ eb.connect('enter-notify-event', self.on_enter_event)
+ eb.connect('leave-notify-event', self.on_leave_event)
+
+ # this is threadsafe
+ # (Gtk textview is NOT threadsafe by itself!!)
+ def add_to_textview():
+ try: # textview closed in the meantime etc.
+ buffer_ = repl_start.get_buffer()
+ iter_ = buffer_.get_iter_at_mark(repl_start)
+ buffer_.insert(iter_, "\n")
+ anchor = buffer_.create_child_anchor(iter_)
+
+ # Use url as tooltip for image
+ img = TextViewImage(anchor, url)
+ loader = GdkPixbuf.PixbufLoader()
+ loader.write(mem)
+ loader.close()
+ pixbuf = loader.get_pixbuf()
+ img.set_from_pixbuf(pixbuf)
+
+ eb.add(img)
+ eb.show_all()
+ self.textview.tv.add_child_at_anchor(eb, anchor)
+ buffer_.delete(iter_,
+ buffer_.get_iter_at_mark(repl_end))
+ except Exception as ex:
+ log.warn("Exception while loading %s: %s" % (str(url), str(ex)))
+ return False
+ # add to mainloop --> make call threadsafe
+ GObject.idle_add(add_to_textview)
except Exception:
- pass
+ # URL is already displayed
+ log.error('Could not display image for URL: %s'
+ % url)
+ raise
+ else:
+ # If image could not be downloaded, URL is already displayed
+ log.error('Could not download image for URL: %s -- %s'
+ % (url, alt))
- def get_pixbuf_of_size(self, pixbuf, size):
+ def _check_mime_size(self, tuple_arg,
+ url, repl_start, repl_end, filepaths,
+ key, iv, encrypted):
+ file_mime, file_size = tuple_arg
+ # Check if mime type is acceptable
+ if file_mime == '' and file_size == 0:
+ log.info("Failed to load HEAD Request for URL: '%s'"
+ "(see debug log for more info)" % url)
+ # URL is already displayed
+ return
+ if file_mime.lower() not in ACCEPTED_MIME_TYPES:
+ log.info("Not accepted mime type '%s' for URL: '%s'"
+ % (file_mime.lower(), url))
+ # URL is already displayed
+ return
+ # Check if file size is acceptable
+ if file_size > self.plugin.config['MAX_FILE_SIZE'] or file_size == 0:
+ log.info("File size (%s) too big or unknown (zero) for URL: '%s'"
+ % (str(file_size), url))
+ # URL is already displayed
+ return
+
+ attributes = {'src': url,
+ 'max_size': self.plugin.config['MAX_FILE_SIZE'],
+ 'filepaths': filepaths,
+ 'key': key,
+ 'iv': iv}
+
+ app.thread_interface(
+ self._download_image, [self.textview.account,
+ attributes, encrypted],
+ self._update_img, [url, repl_start, repl_end,
+ filepaths[0], encrypted])
+
+ def _download_image(self, account, attributes, encrypted):
+ filepath = attributes['filepaths'][0]
+ thumbpath = attributes['filepaths'][1]
+ key = attributes['key']
+ iv = attributes['iv']
+ mem, alt = get_http_file(account, attributes)
+
+ # Decrypt file if necessary
+ if encrypted:
+ mem = self._aes_decrypt_fast(key, iv, mem)
+
+ try:
+ # Write file to harddisk
+ self._write_file(filepath, mem)
+ except Exception as e:
+ dialogs.ErrorDialog(
+ _('Could not save file'),
+ _('Exception raised while saving image file'
+ ' (see error log for more information)'),
+ transient_for=self.chat_control.parent_win.window)
+ log.error(str(e))
+
+ # Create thumbnail, write it to harddisk and return it
+ return self._save_thumbnail(thumbpath, (mem, alt))
+
+ def _create_path(self, folder):
+ if os.path.exists(folder):
+ return
+ log.debug("creating folder '%s'" % folder)
+ os.mkdir(folder, 0o700)
+
+ def _aes_decrypt_fast(self, key, iv, payload):
+ # Use AES128 GCM with the given key and iv to decrypt the payload.
+ if os.name == 'nt':
+ be = backend
+ else:
+ be = default_backend()
+ data = payload[:-16]
+ tag = payload[-16:]
+ decryptor = Cipher(
+ algorithms.AES(key),
+ GCM(iv, tag=tag),
+ backend=be).decryptor()
+ return decryptor.update(data) + decryptor.finalize()
+
+ def _get_pixbuf_of_size(self, pixbuf, size):
# Creates a pixbuf that fits in the specified square of sizexsize
# while preserving the aspect ratio
# Returns tuple: (scaled_pixbuf, actual_width, actual_height)
@@ -127,28 +453,198 @@ class Base(object):
image_height = int(size)
crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
- GdkPixbuf.InterpType.BILINEAR)
+ GdkPixbuf.InterpType.BILINEAR)
return (crop_pixbuf, image_width, image_height)
+ def make_rightclick_menu(self, event, data):
+ xml = Gtk.Builder()
+ xml.set_translation_domain('gajim_plugins')
+ xml.add_from_file(self.plugin.local_file_path('context_menu.ui'))
+ menu = xml.get_object('context_menu')
+
+ open_menuitem = xml.get_object('open_menuitem')
+ save_as_menuitem = xml.get_object('save_as_menuitem')
+ copy_link_location_menuitem = \
+ xml.get_object('copy_link_location_menuitem')
+ open_link_in_browser_menuitem = \
+ xml.get_object('open_link_in_browser_menuitem')
+ open_file_in_browser_menuitem = \
+ xml.get_object('open_file_in_browser_menuitem')
+ extras_separator = \
+ xml.get_object('extras_separator')
+
+ if data["encrypted"]:
+ open_link_in_browser_menuitem.hide()
+ if app.config.get('autodetect_browser_mailer') \
+ or app.config.get('custombrowser') == '':
+ extras_separator.hide()
+ open_file_in_browser_menuitem.hide()
+
+ id_ = open_menuitem.connect(
+ 'activate', self.on_open_menuitem_activate, data)
+ self.handlers[id_] = open_menuitem
+ id_ = save_as_menuitem.connect(
+ 'activate', self.on_save_as_menuitem_activate, data)
+ self.handlers[id_] = save_as_menuitem
+ id_ = copy_link_location_menuitem.connect(
+ 'activate', self.on_copy_link_location_menuitem_activate, data)
+ self.handlers[id_] = copy_link_location_menuitem
+ id_ = open_link_in_browser_menuitem.connect(
+ 'activate', self.on_open_link_in_browser_menuitem_activate, data)
+ self.handlers[id_] = open_link_in_browser_menuitem
+ id_ = open_file_in_browser_menuitem.connect(
+ 'activate', self.on_open_file_in_browser_menuitem_activate, data)
+ self.handlers[id_] = open_file_in_browser_menuitem
+
+ return menu
+
+ def on_open_menuitem_activate(self, menu, data):
+ filepath = data["filepath"]
+ helpers.launch_file_manager(filepath)
+
+ def on_save_as_menuitem_activate(self, menu, data):
+ filepath = data["filepath"]
+ original_filename = data["original_filename"]
+ def on_continue(response, target_path):
+ if response < 0:
+ return
+ shutil.copy(filepath, target_path)
+ dialog.destroy()
+
+ def on_ok(widget):
+ target_path = dialog.get_filename()
+ if os.path.exists(target_path):
+ # check if we have write permissions
+ if not os.access(target_path, os.W_OK):
+ file_name = os.path.basename(target_path)
+ dialogs.ErrorDialog(
+ _('Cannot overwrite existing file "%s"') % file_name,
+ _('A file with this name already exists and you do '
+ 'not have permission to overwrite it.'))
+ return
+ dialog2 = dialogs.FTOverwriteConfirmationDialog(
+ _('This file already exists'),
+ _('What do you want to do?'),
+ propose_resume=False,
+ on_response=(on_continue, target_path),
+ transient_for=dialog)
+ dialog2.set_destroy_with_parent(True)
+ else:
+ dirname = os.path.dirname(target_path)
+ if not os.access(dirname, os.W_OK):
+ dialogs.ErrorDialog(
+ _('Directory "%s" is not writable') % dirname,
+ _('You do not have permission to '
+ 'create files in this directory.'))
+ return
+ on_continue(0, target_path)
+
+ def on_cancel(widget):
+ dialog.destroy()
+
+ dialog = dialogs.FileChooserDialog(
+ title_text=_('Save Image as...'),
+ action=Gtk.FileChooserAction.SAVE,
+ buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
+ default_response=Gtk.ResponseType.OK,
+ current_folder=app.config.get('last_save_dir'),
+ on_response_ok=on_ok,
+ on_response_cancel=on_cancel)
+
+ dialog.set_current_name(original_filename)
+ dialog.connect('delete-event', lambda widget, event:
+ on_cancel(widget))
+
+ def on_copy_link_location_menuitem_activate(self, menu, data):
+ url = data["url"]
+ clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
+ clipboard.set_text(url, -1)
+ clipboard.store()
+
+ def on_open_link_in_browser_menuitem_activate(self, menu, data):
+ url = data["url"]
+ if data["encrypted"]:
+ dialogs.ErrorDialog(
+ _('Encrypted file'),
+ _('You cannot open encrypted files in your '
+ 'browser directly. Try "Open Downloaded File '
+ 'in Browser" instead.'),
+ transient_for=self.chat_control.parent_win.window)
+ else:
+ helpers.launch_browser_mailer('url', url)
+
+ def on_open_file_in_browser_menuitem_activate(self, menu, data):
+ if os.name == "nt":
+ filepath = "file://" + os.path.abspath(data["filepath"])
+ else:
+ filepath = "file://" + data["filepath"]
+ if app.config.get('autodetect_browser_mailer') \
+ or app.config.get('custombrowser') == '':
+ dialogs.ErrorDialog(
+ _('Cannot open downloaded file in browser'),
+ _('You have to set a custom browser executable '
+ 'in your gajim settings for this to work.'),
+ transient_for=self.chat_control.parent_win.window)
+ return
+ command = app.config.get('custombrowser')
+ command = helpers.build_command(command, filepath)
+ try:
+ helpers.exec_command(command)
+ except Exception:
+ pass
+
+ # Change mouse pointer to HAND2 when
+ # mouse enter the eventbox with the image
+ def on_enter_event(self, eb, event):
+ self.textview.tv.get_window(
+ Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
+
+ # Change mouse pointer to default when mouse leaves the eventbox
+ def on_leave_event(self, eb, event):
+ self.textview.tv.get_window(
+ Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor(Gdk.CursorType.XTERM))
+
+ def on_button_press_event(self, eb, event, filepath,
+ original_filename, url, encrypted):
+ data = {"filepath": filepath,
+ "original_filename": original_filename,
+ "url": url,
+ "encrypted": encrypted}
+ # left click
+ if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1:
+ method = getattr(self, "on_"
+ + self.plugin.config['LEFTCLICK_ACTION']
+ + "_activate")
+ method(event, data)
+ # right klick
+ elif event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
+ menu = self.make_rightclick_menu(event, data)
+ #menu.attach_to_widget(self.tv, None)
+ #menu.popup(None, None, None, event.button, event.time)
+ menu.popup_at_pointer(event)
+
def disconnect_from_chat_control(self):
pass
class UrlImagePreviewPluginConfigDialog(GajimPluginConfigDialog):
+ max_file_size = [262144, 524288, 1048576, 5242880, 10485760]
+ leftclick_action = ['open_menuitem', 'save_as_menuitem', 'copy_link_location_menuitem',
+ 'open_link_in_browser_menuitem', 'open_file_in_browser_menuitem']
+
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.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, ['vbox1'])
+ self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, [
+ 'vbox1', 'liststore1', 'liststore2'])
self.preview_size_spinbutton = self.xml.get_object('preview_size')
- adjustment = Gtk.Adjustment(value=20,
- lower=10,
- upper=512,
- step_increment=1,
- page_increment=10,
- page_size=0)
- self.preview_size_spinbutton.set_adjustment(adjustment)
+ self.preview_size_spinbutton.get_adjustment().configure(20, 10, 512, 1,
+ 10, 0)
+ self.max_size_combobox = self.xml.get_object('max_size_combobox')
+ self.leftclick_action_combobox = self.xml.get_object('leftclick_action_combobox')
vbox = self.xml.get_object('vbox1')
self.get_child().pack_start(vbox, True, True, 0)
@@ -157,7 +653,37 @@ class UrlImagePreviewPluginConfigDialog(GajimPluginConfigDialog):
def on_run(self):
self.preview_size_spinbutton.set_value(self.plugin.config[
'PREVIEW_SIZE'])
-
+ value = self.plugin.config['MAX_FILE_SIZE']
+ if value:
+ # this fails if we upgrade from an old version
+ # which has other file size values than we have now
+ try:
+ self.max_size_combobox.set_active(
+ self.max_file_size.index(value))
+ except:
+ pass
+ else:
+ self.max_size_combobox.set_active(-1)
+
+ value = self.plugin.config['LEFTCLICK_ACTION']
+ if value:
+ # this fails if we upgrade from an old version
+ # which has other file size values than we have now
+ try:
+ self.leftclick_action_combobox.set_active(
+ self.leftclick_action.index(value))
+ except:
+ pass
+ else:
+ self.leftclick_action_combobox.set_active(0)
def preview_size_value_changed(self, spinbutton):
self.plugin.config['PREVIEW_SIZE'] = spinbutton.get_value()
+
+ def max_size_value_changed(self, widget):
+ self.plugin.config['MAX_FILE_SIZE'] = self.max_file_size[
+ self.max_size_combobox.get_active()]
+
+ def leftclick_action_changed(self, widget):
+ self.plugin.config['LEFTCLICK_ACTION'] = self.leftclick_action[
+ self.leftclick_action_combobox.get_active()]