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:
authorDaniel Brötzmann <mailtrash@posteo.de>2020-03-08 14:44:33 +0300
committerlovetox <philipp@hoerist.com>2020-05-06 23:50:28 +0300
commitc073d5dc2343f98b638eeeffd63c7d8906b39384 (patch)
tree1a2f4e14575b13c111fbdc3a3012c21f8360d0ff
parenta875bee6d81508dfdb78fb6efe13443b0fc8daea (diff)
[preview] Add preview widget
-rw-r--r--url_image_preview/context_menu.ui10
-rw-r--r--url_image_preview/mime_types.py103
-rw-r--r--url_image_preview/preview.css11
-rw-r--r--url_image_preview/preview.ui166
-rw-r--r--url_image_preview/url_image_preview.py312
-rw-r--r--url_image_preview/utils.py31
6 files changed, 533 insertions, 100 deletions
diff --git a/url_image_preview/context_menu.ui b/url_image_preview/context_menu.ui
index d472025..4967289 100644
--- a/url_image_preview/context_menu.ui
+++ b/url_image_preview/context_menu.ui
@@ -1,10 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
+<!-- Generated with glade 3.22.2 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="context_menu">
<property name="can_focus">False</property>
<child>
+ <object class="GtkMenuItem" id="download">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Download</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
<object class="GtkMenuItem" id="open">
<property name="visible">True</property>
<property name="can_focus">False</property>
diff --git a/url_image_preview/mime_types.py b/url_image_preview/mime_types.py
new file mode 100644
index 0000000..828d6f9
--- /dev/null
+++ b/url_image_preview/mime_types.py
@@ -0,0 +1,103 @@
+# This is an excerpt of Media Types from
+# https://www.iana.org/assignments/media-types/media-types.xhtml
+# plus some additions
+MIME_TYPES = (
+ # application/
+ 'application/calendar+json',
+ 'application/calendar+xml',
+ 'application/epub+zip',
+ 'application/json',
+ 'application/mp4',
+ 'application/msword',
+ 'application/octet-stream',
+ 'application/ogg',
+ 'application/pdf',
+ 'application/pgp-encrypted',
+ 'application/pgp-signature',
+ 'application/postscript',
+ 'application/rtf',
+ 'application/vcard+json',
+ 'application/vcard+xml',
+ 'application/vnd.amazon.mobi8-ebook',
+ 'application/vnd.google-earth.kml+xml',
+ 'application/vnd.google-earth.kmz',
+ # Start office
+ 'application/vnd.ms-access',
+ 'application/vnd.ms-excel',
+ 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 'application/vnd.ms-excel.template.macroEnabled.12',
+ 'application/vnd.ms-powerpoint',
+ 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+ 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+ 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+ 'application/vnd.ms-word.document.macroEnabled.12',
+ 'application/vnd.ms-word.template.macroEnabled.12',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'application/vnd.openxmlformats-officedocument.vmlDrawing',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ # End office
+ 'application/vnd.sqlite3',
+ 'application/zip',
+ # audio/*
+ 'audio/aac',
+ 'audio/ac3',
+ 'audio/flac',
+ 'audio/mp4',
+ 'audio/mpeg',
+ 'audio/ogg',
+ 'audio/opus',
+ 'audio/wav',
+ 'audio/x-flac',
+ 'audio/x-matroska',
+ # font/*
+ 'font/ttf',
+ 'font/woff',
+ 'font/woff2',
+ # image/*
+ 'image/bmp',
+ 'image/x-bmp',
+ 'image/x-ms-bmp',
+ 'image/gif',
+ 'image/heic',
+ 'image/heif',
+ 'image/jpeg',
+ 'image/png',
+ 'image/svg+xml',
+ 'image/tiff',
+ 'image/vnd.adobe.photoshop',
+ 'image/vnd.dwg',
+ 'image/vnd.dxf',
+ 'image/vnd.microsoft.icon',
+ 'image/x-icon',
+ 'image/x-xcf',
+ # model/*
+ 'model/mtl',
+ 'model/obj',
+ 'model/stl',
+ # text/*
+ 'text/calendar',
+ 'text/csv',
+ 'text/markdown',
+ 'text/rtf',
+ 'text/vcard',
+ # video/*
+ 'video/H264',
+ 'video/H265',
+ 'video/mp4',
+ 'video/mpeg4-generic',
+ 'video/ogg',
+ 'video/quicktime',
+ 'video/vc1',
+ 'video/VP8',
+ 'video/webm',
+ 'video/x-matroska',
+ 'video/x-msvideo',
+)
diff --git a/url_image_preview/preview.css b/url_image_preview/preview.css
new file mode 100644
index 0000000..01bed75
--- /dev/null
+++ b/url_image_preview/preview.css
@@ -0,0 +1,11 @@
+.preview-box {
+ border: 1px solid;
+ border-color: @borders;
+ border-radius: 5px;
+ padding: 10px;
+ margin: 5px;
+ background-color: @theme_unfocused_base_color;
+}
+.preview-button {
+ border: 1px solid @borders;
+}
diff --git a/url_image_preview/preview.ui b/url_image_preview/preview.ui
new file mode 100644
index 0000000..677349e
--- /dev/null
+++ b/url_image_preview/preview.ui
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <object class="GtkBox" id="preview_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkEventBox" id="event_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="button_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="file_name">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ <property name="single_line_mode">True</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="file_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="single_line_mode">True</property>
+ <property name="xalign">0</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="save_as_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Save as...</property>
+ <property name="valign">end</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-as-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="preview-button"/>
+ <class name="flat"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="open_folder_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Open folder</property>
+ <property name="valign">end</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="preview-button"/>
+ <class name="flat"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="download_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Download</property>
+ <property name="valign">end</property>
+ <property name="relief">none</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-download-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="preview-button"/>
+ <class name="flat"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="preview-box"/>
+ </style>
+ </object>
+</interface>
diff --git a/url_image_preview/url_image_preview.py b/url_image_preview/url_image_preview.py
index c7fc23d..a44de2b 100644
--- a/url_image_preview/url_image_preview.py
+++ b/url_image_preview/url_image_preview.py
@@ -17,6 +17,7 @@
import os
import logging
import shutil
+import mimetypes
from pathlib import Path
from functools import partial
from urllib.parse import urlparse
@@ -24,9 +25,10 @@ from urllib.parse import unquote
from gi.repository import Gtk
from gi.repository import Gdk
+from gi.repository import GdkPixbuf
+from gi.repository import Gio
from gi.repository import GLib
from gi.repository import Soup
-from gi.repository import GdkPixbuf
from gajim.common import app
from gajim.common import configpaths
@@ -38,15 +40,16 @@ from gajim.common.helpers import get_tls_error_phrase
from gajim.common.helpers import get_user_proxy
from gajim.gtk.dialogs import ErrorDialog
from gajim.gtk.filechoosers import FileSaveDialog
-from gajim.gtk.util import load_icon
+from gajim.gtk.util import get_cursor
from gajim.gtk.util import get_monitor_scale_factor
+from gajim.gtk.util import load_icon
from gajim.plugins import GajimPlugin
from gajim.plugins.helpers import get_builder
from gajim.plugins.plugins_i18n import _
from url_image_preview.config_dialog import UrlImagePreviewConfigDialog
-
+from url_image_preview.mime_types import MIME_TYPES
log = logging.getLogger('gajim.p.preview')
@@ -72,26 +75,29 @@ if ERROR_MSG is None:
from url_image_preview.utils import parse_fragment
from url_image_preview.utils import create_thumbnail
from url_image_preview.utils import pixbuf_from_data
- from url_image_preview.utils import create_clickable_image
from url_image_preview.utils import filename_from_uri
# pylint: enable=ungrouped-imports
-def get_accepted_mime_types():
- accepted_mime_types = set()
+
+def get_previewable_mime_types():
+ previewable_mime_types = set()
for fmt in GdkPixbuf.Pixbuf.get_formats():
for mime_type in fmt.get_mime_types():
- accepted_mime_types.add(mime_type.lower())
+ previewable_mime_types.add(mime_type.lower())
if Image is not None:
Image.init()
for mime_type in Image.MIME.values():
- accepted_mime_types.add(mime_type.lower())
+ previewable_mime_types.add(mime_type.lower())
return tuple(filter(
lambda mime_type: mime_type.startswith('image'),
- accepted_mime_types
+ previewable_mime_types
))
-ACCEPTED_MIME_TYPES = get_accepted_mime_types()
+PREVIEWABLE_MIME_TYPES = get_previewable_mime_types()
+mime_types = set(MIME_TYPES)
+# Merge both: if it’s a previewable image, it should be allowed
+ALLOWED_MIME_TYPES = mime_types.union(PREVIEWABLE_MIME_TYPES)
class UrlImagePreviewPlugin(GajimPlugin):
def init(self):
@@ -109,15 +115,17 @@ class UrlImagePreviewPlugin(GajimPlugin):
self._on_disconnect_chat_control_base),
'history_window': (self._on_connect_history_window,
self._on_disconnect_history_window),
- 'print_real_text': (self._print_real_text, None), }
+ 'print_real_text': (self._print_real_text, None),
+ }
self.config_default_values = {
'PREVIEW_SIZE': (150, 'Preview size (100-1000)'),
- 'MAX_FILE_SIZE': (5242880, 'Max file size for image preview'),
+ 'MAX_FILE_SIZE': ('10485760', 'Max file size for image preview'),
'ALLOW_ALL_IMAGES': (False, ''),
'LEFTCLICK_ACTION': ('open_menuitem', 'Open'),
'ANONYMOUS_MUC': (False, ''),
- 'VERIFY': (True, ''),}
+ 'VERIFY': (True, ''),
+ }
self._textviews = {}
self._sessions = {}
@@ -131,13 +139,37 @@ class UrlImagePreviewPlugin(GajimPlugin):
if GLib.mkdir_with_parents(str(self._thumb_dir), 0o700) != 0:
log.error('Failed to create: %s', self._thumb_dir)
+ if app.config.get('use_kib_mib'):
+ self._units = GLib.FormatSizeFlags.IEC_UNITS
+ else:
+ self._units = GLib.FormatSizeFlags.DEFAULT
+
self._migrate_config()
+ self._load_css()
def _migrate_config(self):
action = self.config['LEFTCLICK_ACTION']
if action.endswith('_menuitem'):
self.config['LEFTCLICK_ACTION'] = action[:-9]
+ @staticmethod
+ def _load_css():
+ path = Path(__file__).parent / 'preview.css'
+ try:
+ with path.open('r') as file:
+ css = file.read()
+ except Exception as exc:
+ log.error('Error loading css: %s', exc)
+ return
+
+ try:
+ provider = Gtk.CssProvider()
+ provider.load_from_data(bytes(css.encode('utf-8')))
+ Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
+ provider, 610)
+ except Exception:
+ log.exception('Error loading application css')
+
def _on_connect_chat_control_base(self, chat_control):
account = chat_control.account
if account not in self._sessions:
@@ -161,7 +193,8 @@ class UrlImagePreviewPlugin(GajimPlugin):
if textview == textview_:
return control_id
- def _create_session(self, account):
+ @staticmethod
+ def _create_session(account):
session = Soup.Session()
session.add_feature_by_type(Soup.ContentSniffer)
session.props.https_aliases = ['aesgcm']
@@ -211,7 +244,7 @@ class UrlImagePreviewPlugin(GajimPlugin):
size=preview.size,
scale=get_monitor_scale_factor(),
pixbuf=True)
- self._update_textview(pixbuf, preview)
+ self._update_textview(preview, pixbuf)
return
preview = self._process_web_uri(uri,
@@ -287,9 +320,9 @@ class UrlImagePreviewPlugin(GajimPlugin):
account):
try:
split_geo_uri(uri)
- except Exception as error:
+ except Exception as err:
log.error(uri)
- log.error(error)
+ log.error(err)
return
return Preview(uri,
@@ -331,11 +364,16 @@ class UrlImagePreviewPlugin(GajimPlugin):
log.error('%s: %s', preview.orig_path.name, error)
return
- if preview.create_thumbnail(data):
- write_file_async(preview.thumb_path,
- preview.thumbnail,
- self._on_thumb_write_finished,
- preview)
+ preview.mime_type = self._guess_mime_type(preview.orig_path)
+ preview.file_size = os.path.getsize(preview.orig_path)
+ if preview.is_previewable:
+ if preview.create_thumbnail(data):
+ write_file_async(preview.thumb_path,
+ preview.thumbnail,
+ self._on_thumb_write_finished,
+ preview)
+ else:
+ self._update_textview(preview, None)
def _on_thumb_load_finished(self, data, error, preview):
if data is None:
@@ -343,6 +381,8 @@ class UrlImagePreviewPlugin(GajimPlugin):
return
preview.thumbnail = data
+ preview.mime_type = self._guess_mime_type(preview.orig_path)
+ preview.file_size = os.path.getsize(preview.orig_path)
try:
pixbuf = pixbuf_from_data(preview.thumbnail)
@@ -351,9 +391,9 @@ class UrlImagePreviewPlugin(GajimPlugin):
preview.thumb_path.name,
err)
return
- self._update_textview(pixbuf, preview)
+ self._update_textview(preview, pixbuf)
- def _download_content(self, preview):
+ def _download_content(self, preview, force=False):
if preview.account is None:
# History Window can be opened without account context
# This means we can not apply proxy settings
@@ -361,7 +401,8 @@ class UrlImagePreviewPlugin(GajimPlugin):
log.info('Start downloading: %s', preview.request_uri)
message = Soup.Message.new('GET', preview.request_uri)
message.connect('starting', self._check_certificate, preview)
- message.connect('content-sniffed', self._on_content_sniffed, preview)
+ message.connect(
+ 'content-sniffed', self._on_content_sniffed, preview, force)
session = self._get_session(preview.account)
session.queue_message(message, self._on_finished, preview)
@@ -379,20 +420,25 @@ class UrlImagePreviewPlugin(GajimPlugin):
session.cancel_message(message, Soup.Status.CANCELLED)
return
- def _on_content_sniffed(self, message, type_, _params, preview):
- size = message.props.response_headers.get_content_length()
+ def _on_content_sniffed(self, message, type_, _params, preview, force):
+ file_size = message.props.response_headers.get_content_length()
uri = message.props.uri.to_string(False)
session = self._get_session(preview.account)
- if type_ not in ACCEPTED_MIME_TYPES:
- log.info('Not allowed content type: %s, %s', type_, uri)
+ preview.mime_type = type_
+ preview.file_size = file_size
+
+ if type_ not in ALLOWED_MIME_TYPES:
+ log.info('Not an allowed content type: %s, %s', type_, uri)
session.cancel_message(message, Soup.Status.CANCELLED)
return
- if size == 0 or size > int(self.config['MAX_FILE_SIZE']):
+ if file_size == 0 or file_size > int(self.config['MAX_FILE_SIZE']):
log.info('File size (%s) too big or unknown (zero) for URL: \'%s\'',
- size, uri)
- session.cancel_message(message, Soup.Status.CANCELLED)
- return
+ file_size, uri)
+ if not force:
+ session.cancel_message(message, Soup.Status.CANCELLED)
+
+ self._update_textview(preview, None)
def _on_finished(self, _session, message, preview):
if message.status_code != Soup.Status.OK:
@@ -412,11 +458,14 @@ class UrlImagePreviewPlugin(GajimPlugin):
self._on_orig_write_finished,
preview)
- if preview.create_thumbnail(data):
- write_file_async(preview.thumb_path,
- preview.thumbnail,
- self._on_thumb_write_finished,
- preview)
+ if preview.is_previewable:
+ if preview.create_thumbnail(data):
+ write_file_async(preview.thumb_path,
+ preview.thumbnail,
+ self._on_thumb_write_finished,
+ preview)
+ else:
+ self._update_textview(preview, None)
@staticmethod
def _on_orig_write_finished(_result, error, preview):
@@ -425,6 +474,7 @@ class UrlImagePreviewPlugin(GajimPlugin):
return
log.info('File stored: %s', preview.orig_path.name)
+ preview.file_size = os.path.getsize(preview.orig_path)
def _on_thumb_write_finished(self, _result, error, preview):
if error is not None:
@@ -435,14 +485,29 @@ class UrlImagePreviewPlugin(GajimPlugin):
try:
pixbuf = pixbuf_from_data(preview.thumbnail)
- except Exception as err:
+ except Exception as error:
log.error('Unable to load: %s, %s',
preview.thumb_path.name,
- err)
+ error)
return
- self._update_textview(pixbuf, preview)
+ self._update_textview(preview, pixbuf)
+
+ @staticmethod
+ def _guess_mime_type(data):
+ mime_type, _ = mimetypes.MimeTypes().guess_type(data)
+ if mime_type is None:
+ # Try to guess MIME type by file name
+ mime_type, _ = Gio.content_type_guess(str(data), None)
+ log.debug('Guessed MIME type: %s', str(mime_type))
+ return mime_type
+
+ @staticmethod
+ def _get_icon_for_mime_type(mime_type):
+ if mime_type is None:
+ return Gio.Icon.new_for_string('mail-attachment')
+ return Gio.content_type_get_icon(mime_type)
- def _update_textview(self, pixbuf, preview):
+ def _update_textview(self, preview, data):
textview = self._textviews.get(preview.control_id)
if textview is None:
# Control closed
@@ -454,30 +519,108 @@ class UrlImagePreviewPlugin(GajimPlugin):
anchor = buffer_.create_child_anchor(iter_)
anchor.plaintext = preview.uri
- image = create_clickable_image(pixbuf, preview)
+ preview_widget = self._create_preview_widget(preview, data)
- textview.tv.add_child_at_anchor(image, anchor)
+ textview.tv.add_child_at_anchor(preview_widget, anchor)
buffer_.delete(iter_,
buffer_.get_iter_at_mark(preview.end_mark))
- image.connect('button-press-event',
- self._on_button_press_event,
- preview)
-
if textview.autoscroll:
textview.scroll_to_end()
- def _get_context_menu(self, preview):
- path = self.local_file_path('context_menu.ui')
+ def _create_preview_widget(self, preview, data):
+ if isinstance(data, GdkPixbuf.PixbufAnimation):
+ image = Gtk.Image.new_from_animation(data)
+ elif isinstance(data, GdkPixbuf.Pixbuf):
+ image = Gtk.Image.new_from_pixbuf(data)
+ else:
+ icon = self._get_icon_for_mime_type(preview.mime_type)
+ image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.DIALOG)
+
+ def _on_realize(box):
+ box.get_window().set_cursor(get_cursor('pointer'))
+
+ def _on_enter_leave(button, event):
+ if event.type == Gdk.EventType.ENTER_NOTIFY:
+ button.get_window().set_cursor(get_cursor('default'))
+ else:
+ button.get_window().set_cursor(get_cursor('text'))
+
+ path = self.local_file_path('preview.ui')
ui = get_builder(path)
- if preview.is_aes_encrypted:
- ui.open_link_in_browser.hide()
+
+ ui.download_button.set_no_show_all(True)
+ ui.download_button.connect('enter-notify-event', _on_enter_leave)
+ ui.download_button.connect('leave-notify-event', _on_enter_leave)
+ ui.download_button.connect('clicked', self._on_download, preview)
+
+ ui.save_as_button.set_no_show_all(True)
+ ui.save_as_button.connect('enter-notify-event', _on_enter_leave)
+ ui.save_as_button.connect('leave-notify-event', _on_enter_leave)
+ ui.save_as_button.connect('clicked', self._on_save_as, preview)
+
+ ui.open_folder_button.set_no_show_all(True)
+ ui.open_folder_button.connect('enter-notify-event', _on_enter_leave)
+ ui.open_folder_button.connect('leave-notify-event', _on_enter_leave)
+ ui.open_folder_button.connect('clicked', self._on_open_folder, preview)
+
+ ui.event_box.set_tooltip_text(preview.filename)
+ ui.event_box.add(image)
+ ui.event_box.connect('realize', _on_realize)
+ ui.event_box.connect('button-press-event',
+ self._on_button_press_event,
+ preview)
+
+ ui.preview_box.show_all()
if preview.is_geo_uri:
- ui.open_link_in_browser.hide()
- ui.save_as.hide()
- ui.open_folder.hide()
+ ui.file_name.set_text(_('Click to view location'))
+ ui.save_as_button.hide()
+ ui.open_folder_button.hide()
+ ui.download_button.hide()
+ location = split_geo_uri(preview.uri)
+ ui.file_size.set_text(_('Lat: %s Lon: %s') % (
+ location.lat, location.lon))
+ ui.event_box.set_tooltip_text(_('Location at Lat: %s Lon: %s') % (
+ location.lat, location.lon))
+ ui.event_box.set_halign(Gtk.Align.CENTER)
+ ui.preview_box.set_size_request(160, -1)
+ return ui.preview_box
+
+ if preview.is_previewable and preview.orig_exists():
+ ui.event_box.set_halign(Gtk.Align.CENTER)
+ else:
+ image.set_property('pixel-size', 64)
+
+ if preview.orig_exists():
+ ui.download_button.hide()
+ else:
+ ui.save_as_button.hide()
+ ui.open_folder_button.hide()
+ file_size_string = _('File size unknown')
+ if preview.file_size != 0:
+ file_size_string = GLib.format_size_full(
+ preview.file_size, self._units)
+ ui.file_size.set_text(file_size_string)
+
+ ui.preview_box.set_size_request(300, -1)
+ ui.file_name.set_text(preview.filename)
+ ui.file_name.set_tooltip_text(preview.filename)
+
+ return ui.preview_box
+
+ def _get_context_menu(self, preview):
+ def destroy(menu, _pspec):
+ visible = menu.get_property('visible')
+ if not visible:
+ GLib.idle_add(menu.destroy)
+
+ path = self.local_file_path('context_menu.ui')
+ ui = get_builder(path)
+
+ ui.download.connect(
+ 'activate', self._on_download, preview)
ui.open.connect(
'activate', self._on_open, preview)
ui.save_as.connect(
@@ -488,24 +631,43 @@ class UrlImagePreviewPlugin(GajimPlugin):
'activate', self._on_open_link_in_browser, preview)
ui.copy_link_location.connect(
'activate', self._on_copy_link_location, preview)
+ ui.context_menu.connect('notify::visible', destroy)
- def destroy(menu, _pspec):
- visible = menu.get_property('visible')
- if not visible:
- GLib.idle_add(menu.destroy)
+ if preview.is_aes_encrypted:
+ ui.open_link_in_browser.hide()
+
+ if preview.is_geo_uri:
+ ui.download.hide()
+ ui.open_link_in_browser.hide()
+ ui.save_as.hide()
+ ui.open_folder.hide()
+ return ui.context_menu
+
+ if preview.orig_exists():
+ ui.download.hide()
+ else:
+ ui.open.hide()
+ ui.save_as.hide()
+ ui.open_folder.hide()
- ui.context_menu.connect('notify::visible', destroy)
return ui.context_menu
- @staticmethod
- def _on_open(_menu, preview):
+ def _on_download(self, _menu, preview):
+ if not preview.orig_exists():
+ self._download_content(preview, force=True)
+
+ def _on_open(self, _menu, preview):
if preview.is_geo_uri:
open_uri(preview.uri)
return
+
+ if not preview.orig_exists():
+ self._download_content(preview, force=True)
+ return
+
open_file(preview.orig_path)
- @staticmethod
- def _on_save_as(_menu, preview):
+ def _on_save_as(self, _menu, preview):
def on_ok(target_path):
dirname = Path(target_path).parent
if not os.access(dirname, os.W_OK):
@@ -517,13 +679,19 @@ class UrlImagePreviewPlugin(GajimPlugin):
return
shutil.copyfile(str(preview.orig_path), target_path)
+ if not preview.orig_exists():
+ self._download_content(preview, force=True)
+ return
+
FileSaveDialog(on_ok,
path=app.config.get('last_save_dir'),
file_name=preview.filename,
transient_for=app.app.get_active_window())
- @staticmethod
- def _on_open_folder(_menu, preview):
+ def _on_open_folder(self, _menu, preview):
+ if not preview.orig_exists():
+ self._download_content(preview, force=True)
+ return
open_file(preview.orig_path.parent)
@staticmethod
@@ -560,15 +728,19 @@ class Preview:
self._uri = uri
self._urlparts = urlparts
self._filename = filename_from_uri(self._uri)
+
self.size = size
self.control_id = control_id
self.orig_path = orig_path
self.thumb_path = thumb_path
self.start_mark = start_mark
self.end_mark = end_mark
- self.thumbnail = None
self.account = account
+ self.thumbnail = None
+ self.mime_type = None
+ self.file_size = 0
+
self.key, self.iv = None, None
if self.is_aes_encrypted:
self.key, self.iv = parse_fragment(urlparts.fragment)
@@ -582,6 +754,10 @@ class Preview:
return not self.is_geo_uri
@property
+ def is_previewable(self):
+ return self.mime_type in PREVIEWABLE_MIME_TYPES
+
+ @property
def uri(self):
return self._uri
@@ -612,6 +788,6 @@ class Preview:
def create_thumbnail(self, data):
self.thumbnail = create_thumbnail(data, self.size)
if self.thumbnail is None:
- log.warning('creating thumbnail failed for: %s', self.orig_path)
+ log.warning('Creating thumbnail failed for: %s', self.orig_path)
return False
return True
diff --git a/url_image_preview/utils.py b/url_image_preview/utils.py
index a6aba99..3ee682d 100644
--- a/url_image_preview/utils.py
+++ b/url_image_preview/utils.py
@@ -26,7 +26,6 @@ from urllib.parse import unquote
from gi.repository import GdkPixbuf
from gi.repository import GLib
-from gi.repository import Gtk
from PIL import Image
@@ -35,7 +34,6 @@ from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import algorithms
from cryptography.hazmat.primitives.ciphers.modes import GCM
-from gajim.gtk.util import get_cursor
log = logging.getLogger('gajim.p.preview.utils')
@@ -237,35 +235,6 @@ def pixbuf_from_data(data):
return loader.get_pixbuf()
-def create_clickable_image(pixbuf, preview):
- if isinstance(pixbuf, GdkPixbuf.PixbufAnimation):
- image = Gtk.Image.new_from_animation(pixbuf)
- else:
- image = Gtk.Image.new_from_pixbuf(pixbuf)
-
- css = '''#Preview {
- box-shadow: 0px 0px 3px 0px alpha(@theme_text_color, 0.2);
- margin: 5px 10px 5px 10px; }'''
-
- provider = Gtk.CssProvider()
- provider.load_from_data(bytes(css.encode()))
- context = image.get_style_context()
- context.add_provider(provider,
- Gtk.STYLE_PROVIDER_PRIORITY_USER)
-
- image.set_name('Preview')
-
- def _on_realize(box):
- box.get_window().set_cursor(get_cursor('pointer'))
-
- event_box = Gtk.EventBox()
- event_box.connect('realize', _on_realize)
- event_box.set_tooltip_text(preview.uri)
- event_box.add(image)
- event_box.show_all()
- return event_box
-
-
def parse_fragment(fragment):
if not fragment:
raise ValueError('Invalid fragment')