mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Fix dropping images into BeeRef
This commit is contained in:
parent
6118acedc5
commit
d117790726
7 changed files with 199 additions and 24 deletions
|
|
@ -66,9 +66,10 @@ def handle_sigint(signum, frame):
|
|||
QtWidgets.QApplication.quit()
|
||||
|
||||
|
||||
def handle_uncaught_exception(exc_type, value, traceback):
|
||||
def handle_uncaught_exception(exc_type, exc, traceback):
|
||||
logger.critical('Unhandled exception',
|
||||
exc_info=(exc_type, value, traceback))
|
||||
exc_info=(exc_type, exc, traceback))
|
||||
QtWidgets.QApplication.quit()
|
||||
|
||||
|
||||
sys.excepthook = handle_uncaught_exception
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@
|
|||
|
||||
import logging
|
||||
|
||||
|
||||
from PyQt6 import QtCore, QtGui
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from beeref import commands
|
||||
from beeref.fileio.errors import BeeFileIOError
|
||||
from beeref.fileio.image import load_image
|
||||
from beeref.fileio.sql import SQLiteIO
|
||||
from beeref.items import BeePixmapItem
|
||||
|
||||
|
|
@ -59,9 +59,10 @@ def load_images(filenames, pos, scene, worker):
|
|||
worker.begin_processing.emit(len(filenames))
|
||||
for i, filename in enumerate(filenames):
|
||||
logger.info(f'Loading image from file {filename}')
|
||||
img = QtGui.QImage(filename)
|
||||
img, filename = load_image(filename)
|
||||
worker.progress.emit(i)
|
||||
if img.isNull():
|
||||
logger.info(f'Could not load file {filename}')
|
||||
errors.append(filename)
|
||||
continue
|
||||
item = BeePixmapItem(img, filename)
|
||||
|
|
|
|||
46
beeref/fileio/image.py
Normal file
46
beeref/fileio/image.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# This file is part of BeeRef.
|
||||
#
|
||||
# BeeRef 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, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# BeeRef 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 BeeRef. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os.path
|
||||
import tempfile
|
||||
from urllib.error import URLError
|
||||
from urllib import request
|
||||
|
||||
from PyQt6 import QtGui
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_image(path):
|
||||
if isinstance(path, str):
|
||||
return (QtGui.QImage(path), path)
|
||||
if path.isLocalFile():
|
||||
return (QtGui.QImage(path.path()), path.path())
|
||||
|
||||
img = QtGui.QImage()
|
||||
try:
|
||||
imgdata = request.urlopen(path.url()).read()
|
||||
except URLError as e:
|
||||
logger.debug(f'Downloading image failed: {e.reason}')
|
||||
else:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
fname = os.path.join(tmp, 'img')
|
||||
with open(fname, 'wb') as f:
|
||||
f.write(imgdata)
|
||||
logger.debug(f'Temporarily saved in: {fname}')
|
||||
img = QtGui.QImage(fname)
|
||||
return (img, path.url())
|
||||
|
|
@ -254,6 +254,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
self.scene.selectedItems(user_only=True)))
|
||||
|
||||
def on_items_loaded(self, value):
|
||||
logger.debug('On items loded: add queued images')
|
||||
self.scene.add_delayed_items()
|
||||
|
||||
def on_loading_finished(self, filename, errors):
|
||||
|
|
@ -266,6 +267,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
('<p>Problem loading file %s</p>'
|
||||
'<p>Not accessible or not a proper bee file</p>') % filename)
|
||||
else:
|
||||
self.scene.add_delayed_items()
|
||||
self.on_action_fit_scene()
|
||||
|
||||
def open_from_file(self, filename):
|
||||
|
|
@ -348,6 +350,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
gui.DebugLogDialog(self)
|
||||
|
||||
def on_insert_images_finished(self, filename, errors):
|
||||
logger.debug('Insert images finished')
|
||||
if errors:
|
||||
errornames = [
|
||||
f'<li>{fn}</li>' for fn in errors]
|
||||
|
|
@ -359,22 +362,19 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
self,
|
||||
'Problem loading images',
|
||||
msg + errornames)
|
||||
self.scene.add_delayed_items()
|
||||
self.scene.arrange_optimal()
|
||||
self.undo_stack.endMacro()
|
||||
|
||||
def on_action_insert_images(self):
|
||||
formats = self.get_supported_image_formats(QtGui.QImageReader)
|
||||
filenames, f = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
parent=self,
|
||||
caption='Select one ore more images to open',
|
||||
filter=f'Images ({formats})')
|
||||
|
||||
def do_insert_images(self, filenames, pos=None):
|
||||
if not pos:
|
||||
pos = self.get_view_center()
|
||||
self.scene.clearSelection()
|
||||
self.undo_stack.beginMacro('Insert Images')
|
||||
self.worker = fileio.ThreadedIO(
|
||||
fileio.load_images,
|
||||
filenames,
|
||||
self.mapToScene(self.get_view_center()),
|
||||
self.mapToScene(pos),
|
||||
self.scene)
|
||||
self.worker.progress.connect(self.on_items_loaded)
|
||||
self.worker.finished.connect(self.on_insert_images_finished)
|
||||
|
|
@ -384,6 +384,14 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
parent=self)
|
||||
self.worker.start()
|
||||
|
||||
def on_action_insert_images(self):
|
||||
formats = self.get_supported_image_formats(QtGui.QImageReader)
|
||||
filenames, f = QtWidgets.QFileDialog.getOpenFileNames(
|
||||
parent=self,
|
||||
caption='Select one ore more images to open',
|
||||
filter=f'Images ({formats})')
|
||||
self.do_insert_images(filenames)
|
||||
|
||||
def on_action_paste(self):
|
||||
logger.info('Pasting from clipboard...')
|
||||
clipboard = QtWidgets.QApplication.clipboard()
|
||||
|
|
@ -515,12 +523,11 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
self.welcome_overlay.resize(self.size())
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
logger.debug('Received drag enter event')
|
||||
|
||||
# tbd: always empty???
|
||||
print(event.mimeData().formats())
|
||||
print(dir(event))
|
||||
if event.mimeData().hasImage():
|
||||
mimedata = event.mimeData()
|
||||
logger.debug(f'Drag enter event: {mimedata.formats()}')
|
||||
if mimedata.hasUrls():
|
||||
event.acceptProposedAction()
|
||||
elif mimedata.hasImage():
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
logger.info('Attempted drop not an image')
|
||||
|
|
@ -529,6 +536,16 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|||
event.acceptProposedAction()
|
||||
|
||||
def dropEvent(self, event):
|
||||
logger.info('Handling file drop...')
|
||||
print(event.mimeData().formats())
|
||||
# tbd
|
||||
mimedata = event.mimeData()
|
||||
logger.debug(f'Handling file drop: {mimedata.formats()}')
|
||||
pos = QtCore.QPoint(round(event.position().x()),
|
||||
round(event.position().y()))
|
||||
if mimedata.hasUrls():
|
||||
self.do_insert_images(mimedata.urls(), pos)
|
||||
elif mimedata.hasImage():
|
||||
img = QtGui.QImage(mimedata.imageData())
|
||||
item = BeePixmapItem(img)
|
||||
pos = self.mapToScene(pos)
|
||||
item.set_pos_center(pos)
|
||||
self.undo_stack.push(commands.InsertItems(self.scene, [item]))
|
||||
logger.info('Drop not an image')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
flake8
|
||||
pytest
|
||||
coverage
|
||||
flake8
|
||||
httpretty
|
||||
pytest
|
||||
|
|
|
|||
49
tests/fileio/test_image.py
Normal file
49
tests/fileio/test_image.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import httpretty
|
||||
|
||||
from PyQt6 import QtCore
|
||||
|
||||
from beeref.fileio.image import load_image
|
||||
from ..base import BeeTestCase
|
||||
|
||||
|
||||
class LoadImageTestCase(BeeTestCase):
|
||||
|
||||
def test_loads_from_filename(self):
|
||||
img, filename = load_image(self.imgfilename3x3)
|
||||
assert img.isNull() is False
|
||||
assert filename == self.imgfilename3x3
|
||||
|
||||
def test_loads_from_nonexisting_filename(self):
|
||||
img, filename = load_image('foo.png')
|
||||
assert img.isNull() is True
|
||||
assert filename == 'foo.png'
|
||||
|
||||
def test_loads_from_existing_local_url(self):
|
||||
url = QtCore.QUrl.fromLocalFile(self.imgfilename3x3)
|
||||
img, filename = load_image(url)
|
||||
assert img.isNull() is False
|
||||
assert filename == self.imgfilename3x3
|
||||
|
||||
@httpretty.activate
|
||||
def test_loads_from_existing_web_url(self):
|
||||
url = 'http://example.com/foo.png'
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
url,
|
||||
body=self.imgdata3x3,
|
||||
)
|
||||
img, filename = load_image(QtCore.QUrl(url))
|
||||
assert img.isNull() is False
|
||||
assert filename == url
|
||||
|
||||
@httpretty.activate
|
||||
def test_loads_from_web_url_errors(self):
|
||||
url = 'http://example.com/foo.png'
|
||||
httpretty.register_uri(
|
||||
httpretty.GET,
|
||||
url,
|
||||
status=500,
|
||||
)
|
||||
img, filename = load_image(QtCore.QUrl(url))
|
||||
assert img.isNull() is True
|
||||
assert filename == url
|
||||
|
|
@ -364,3 +364,63 @@ class UpdateWindowTitleTestCase(ViewBaseTestCase):
|
|||
self.view.filename = 'test.bee'
|
||||
self.view.update_window_title()
|
||||
assert self.parent.windowTitle() == 'test.bee* - BeeRef'
|
||||
|
||||
|
||||
class DragDropTestCase(ViewBaseTestCase):
|
||||
|
||||
def test_drag_enter_when_url(self):
|
||||
url = QtCore.QUrl()
|
||||
url.fromLocalFile(self.imgfilename3x3)
|
||||
mimedata = QtCore.QMimeData()
|
||||
mimedata.setUrls([url])
|
||||
event = MagicMock()
|
||||
event.mimeData.return_value = mimedata
|
||||
|
||||
self.view.dragEnterEvent(event)
|
||||
event.acceptProposedAction.assert_called_once()
|
||||
|
||||
def test_drag_enter_when_img(self):
|
||||
mimedata = QtCore.QMimeData()
|
||||
mimedata.setImageData(QtGui.QImage(self.imgfilename3x3))
|
||||
event = MagicMock()
|
||||
event.mimeData.return_value = mimedata
|
||||
|
||||
self.view.dragEnterEvent(event)
|
||||
event.acceptProposedAction.assert_called_once()
|
||||
|
||||
def test_drag_enter_when_unsupported(self):
|
||||
mimedata = QtCore.QMimeData()
|
||||
event = MagicMock()
|
||||
event.mimeData.return_value = mimedata
|
||||
|
||||
self.view.dragEnterEvent(event)
|
||||
event.acceptProposedAction.assert_not_called()
|
||||
|
||||
def test_drag_move(self):
|
||||
event = MagicMock()
|
||||
self.view.dragMoveEvent(event)
|
||||
event.acceptProposedAction.assert_called_once()
|
||||
|
||||
@patch('beeref.view.BeeGraphicsView.do_insert_images')
|
||||
def test_drop_when_url(self, insert_mock):
|
||||
url = QtCore.QUrl()
|
||||
url.fromLocalFile(self.imgfilename3x3)
|
||||
mimedata = QtCore.QMimeData()
|
||||
mimedata.setUrls([url])
|
||||
event = MagicMock()
|
||||
event.mimeData.return_value = mimedata
|
||||
event.position.return_value = QtCore.QPointF(10, 20)
|
||||
|
||||
self.view.dropEvent(event)
|
||||
insert_mock.assert_called_once_with([url], QtCore.QPoint(10, 20))
|
||||
|
||||
def test_drop_when_img(self):
|
||||
mimedata = QtCore.QMimeData()
|
||||
mimedata.setImageData(QtGui.QImage(self.imgfilename3x3))
|
||||
event = MagicMock()
|
||||
event.mimeData.return_value = mimedata
|
||||
event.position.return_value = QtCore.QPointF(10, 20)
|
||||
|
||||
self.view.dropEvent(event)
|
||||
assert len(self.view.scene.items()) == 1
|
||||
assert self.view.scene.items()[0].isSelected() is True
|
||||
|
|
|
|||
Loading…
Reference in a new issue