Progress Dialogs for loading and saving

This commit is contained in:
Rebecca Breu 2021-03-28 20:32:33 +02:00
parent c7e7dff3a6
commit a8cdd79ab9
9 changed files with 119 additions and 36 deletions

View file

@ -23,15 +23,15 @@ __all__ = ['load', 'save', 'BeeFileIOError']
logger = logging.getLogger('BeeRef')
def load(filename, scene):
def load(filename, scene, progress=None):
logger.info(f'Loading from file {filename}...')
io = SQLiteIO(filename, scene, readonly=True)
io = SQLiteIO(filename, scene, readonly=True, progress=progress)
return io.read()
def save(filename, scene, create_new=False):
def save(filename, scene, create_new=False, progress=None):
logger.info(f'Saving to file {filename}...')
logger.debug(f'Create new: {create_new}')
io = SQLiteIO(filename, scene, create_new)
io = SQLiteIO(filename, scene, create_new, progress=progress)
io.write()
logger.debug('Saved!')

View file

@ -52,11 +52,13 @@ class SQLiteIO:
USER_VERSION = 1
APPLICATION_ID = 2060242126
def __init__(self, filename, scene, create_new=False, readonly=False):
def __init__(self, filename, scene, create_new=False, readonly=False,
progress=None):
self.scene = scene
self.create_new = create_new
self.filename = filename
self.readonly = readonly
self.progress = progress
def __del__(self):
self._close_connection()
@ -74,6 +76,9 @@ class SQLiteIO:
and os.path.exists(self.filename)):
os.remove(self.filename)
if self.create_new:
self.scene.clear_save_ids()
if self.readonly:
self._connection = sqlite3.connect(
f'file:{self.filename}?mode=ro')
@ -123,13 +128,20 @@ class SQLiteIO:
'SELECT pos_x, pos_y, scale, filename, sqlar.data, items.id '
'FROM items '
'INNER JOIN sqlar on sqlar.item_id = items.id')
for row in rows:
if self.progress:
self.progress.setMaximum(len(rows))
for i, row in enumerate(rows):
item = BeePixmapItem(QtGui.QImage(), filename=row[3])
item.save_id = row[5]
item.pixmap_from_bytes(row[4])
item.setPos(row[0], row[1])
item.setScale(row[2])
self.scene.addItem(item)
if self.progress:
self.progress.setValue(i)
if self.progress.wasCanceled():
break
@handle_sqlite_errors
def write(self):
@ -149,12 +161,20 @@ class SQLiteIO:
def write_data(self):
to_delete = self.fetchall('SELECT id from ITEMS')
for item in self.scene.items_for_save():
if item.save_id and not self.create_new:
to_save = list(self.scene.items_for_save())
if self.progress:
self.progress.setMaximum(len(to_save))
for i, item in enumerate(to_save):
logger.debug(f'Saving {item} with id {item.save_id}')
if item.save_id:
self.update_item(item)
to_delete.remove((item.save_id,))
else:
self.insert_item(item)
if self.progress:
self.progress.setValue(i)
if self.progress.wasCanceled():
break
self.delete_items(to_delete)
self.connection.commit()

View file

@ -40,3 +40,14 @@ class WelcomeOverlay(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout()
layout.addWidget(label)
self.setLayout(layout)
class BeeProgressDialog(QtWidgets.QProgressDialog):
def __init__(self, label, maximum=100, parent=None):
super().__init__(label, 'Cancel', 0, maximum - 1, parent=parent)
self.setMinimumDuration(2)
self.setWindowModality(Qt.WindowModality.WindowModal)
def setMaximum(self, maximum):
super().setMaximum(maximum - 1)

View file

@ -32,17 +32,19 @@ class BeePixmapItem(QtWidgets.QGraphicsPixmapItem):
def __init__(self, image, filename=None):
super().__init__(QtGui.QPixmap.fromImage(image))
logger.debug(f'Initialized image "{filename}" with dimensions: '
f'{self.width} x {self.height} at index {self.zValue()}')
self.save_id = None
self.filename = filename
self.scale_factor = 1
logger.debug(f'Initialized {self}')
self.setFlags(
QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsMovable
| QtWidgets.QGraphicsItem.GraphicsItemFlags.ItemIsSelectable)
def __str__(self):
return (f'Image "{self.filename}" '
f'with dimensions {self.width} x {self.height}')
def setScale(self, factor):
if factor <= 0:
return

View file

@ -119,3 +119,7 @@ class BeeGraphicsScene(QtWidgets.QGraphicsScene):
"""
return filter(lambda i: hasattr(i, 'save_id'), self.items())
def clear_save_ids(self):
for item in self.items_for_save():
item.save_id = None

View file

@ -20,7 +20,7 @@ from PyQt6.QtCore import Qt
from beeref import commands
from beeref import fileio
from beeref.gui import WelcomeOverlay
from beeref.gui import BeeProgressDialog, WelcomeOverlay
from beeref.items import BeePixmapItem
from beeref.scene import BeeGraphicsScene
@ -235,22 +235,13 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
def on_action_normalize_size(self):
self.scene.normalize_size()
def add_images(self, images):
if isinstance(images, QtGui.QImage):
images = [images]
items = []
for img in images:
item = BeePixmapItem(img, getattr(img, 'filename', None))
item.set_pos_center(img.pos.x(), img.pos.y())
items.append(item)
self.undo_stack.push(commands.InsertItems(self.scene, items))
def open_from_file(self, filename):
logger.info(f'Opening file {filename}')
self.scene.clear()
self.undo_stack.clear()
try:
fileio.load(filename, self.scene)
progress = BeeProgressDialog('Save file...', parent=self)
fileio.load(filename, self.scene, progress)
self.filename = filename
except fileio.BeeFileIOError:
QtWidgets.QMessageBox.warning(
@ -277,7 +268,9 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
if not filename.endswith('.bee'):
filename = f'{filename}.bee'
try:
fileio.save(filename, self.scene, create_new=True)
progress = BeeProgressDialog('Save file...', parent=self)
fileio.save(filename, self.scene, create_new=True,
progress=progress)
self.filename = filename
except fileio.BeeFileIOError:
QtWidgets.QMessageBox.warning(
@ -290,7 +283,9 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
if not self.filename:
self.on_action_save_as()
else:
fileio.save(self.filename, self.scene, create_new=False)
progress = BeeProgressDialog('Save file...', parent=self)
fileio.save(self.filename, self.scene, create_new=False,
progress=progress)
def on_action_quit(self):
logger.info('User quit. Exiting...')
@ -305,19 +300,26 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
pos = self.mapToScene(self.get_view_center())
errors = []
images = []
for filename in filenames:
items = []
progress = BeeProgressDialog(
'Loading images...', len(filenames), parent=self)
for i, filename in enumerate(filenames):
logger.info(f'Loading image from file {filename}')
img = QtGui.QImage(filename)
if img.isNull():
errors.append(filename)
continue
img.pos = pos
img.filename = filename
images.append(img)
item = BeePixmapItem(img, filename)
item.set_pos_center(pos.x(), pos.y())
items.append(item)
pos.setX(pos.x() + 50)
pos.setY(pos.y() + 50)
self.add_images(images)
progress.setValue(i)
if progress.wasCanceled():
break
self.undo_stack.push(commands.InsertItems(self.scene, items))
if errors:
errornames = [
@ -339,9 +341,10 @@ class BeeGraphicsView(QtWidgets.QGraphicsView):
logger.info('No image data in clipboard')
else:
self.scene.clearSelection()
img.pos = self.mapToScene(
self.mapFromGlobal(self.cursor().pos()))
self.add_images(img)
item = BeePixmapItem(img)
pos = self.mapToScene(self.mapFromGlobal(self.cursor().pos()))
item.set_pos_center(pos.x(), pos.y())
self.undo_stack.push(commands.InsertItems(self.scene, [item]))
def on_selection_changed(self):
logger.debug('Currently selected items: %s',

View file

@ -15,7 +15,8 @@ from ..base import BeeTestCase
class SQLiteIOTestCase(BeeTestCase):
def setUp(self):
self.io = SQLiteIO(':memory:', None, create_new=True)
self.scene_mock = MagicMock()
self.io = SQLiteIO(':memory:', self.scene_mock, create_new=True)
def test_ẁrite_meta_application_id(self):
self.io.write_meta()
@ -38,6 +39,7 @@ class SQLiteIOTestCase(BeeTestCase):
'SELECT COUNT(*) FROM sqlite_master '
'WHERE type="table" AND name NOT LIKE "sqlite_%"')
assert result[0] == 2
self.scene_mock.clear_save_ids.assert_called_once()
def test_create_schema_on_new_when_not_create_new(self):
self.io.create_new = False
@ -46,6 +48,7 @@ class SQLiteIOTestCase(BeeTestCase):
'SELECT COUNT(*) FROM sqlite_master '
'WHERE type="table" AND name NOT LIKE "sqlite_%"')
assert result[0] == 0
self.scene_mock.clear_save_ids.assert_not_called()
def test_readonly_doesnt_allow_write(self):
scene = BeeGraphicsScene(None)
@ -172,6 +175,16 @@ class SQLiteIOWriteTestCase(BeeTestCase):
result = io.fetchone('SELECT COUNT(*) FROM items')
assert result[0] == 1
def test_updates_progress(self):
progress = MagicMock()
io = SQLiteIO(':memory:', self.scene, create_new=True,
progress=progress)
item = BeePixmapItem(QtGui.QImage())
self.scene.addItem(item)
io.write()
progress.setMaximum.assert_called_once_with(1)
progress.setValue.assert_called_once_with(0)
class SQLiteIOReadTestCase(BeeTestCase):
@ -203,6 +216,21 @@ class SQLiteIOReadTestCase(BeeTestCase):
assert item.width == 3
assert item.height == 3
def test_updates_progress(self):
progress = MagicMock()
io = SQLiteIO(':memory:', self.scene, create_new=True,
progress=progress)
io.create_schema_on_new()
io.ex('INSERT INTO items (type, pos_x, pos_y, scale, filename) '
'VALUES (?, ?, ?, ?, ?) ',
('pixmap', 0, 0, 1, 'bee.png'))
io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b''))
io.connection.commit()
io.read()
progress.setMaximum.assert_called_once_with(1)
progress.setValue.assert_called_once_with(0)
def test_raises_error_when_file_borked(self):
with tempfile.TemporaryDirectory() as dirname:
fname = os.path.join(dirname, 'test.bee')

View file

@ -11,6 +11,7 @@ class BeePixmapItemTestCase(BeeTestCase):
def test_init(self):
item = BeePixmapItem(
QtGui.QImage(self.imgfilename3x3), self.imgfilename3x3)
assert item.save_id is None
assert item.width == 3
assert item.height == 3
assert item.scale_factor == 1

View file

@ -1,8 +1,9 @@
import math
from unittest.mock import patch, MagicMock
from PyQt6 import QtGui
from PyQt6 import QtGui, QtWidgets
from beeref.items import BeePixmapItem
from beeref.scene import BeeGraphicsScene
from .base import BeeTestCase
@ -54,3 +55,16 @@ class BeeGraphicsSceneNormalizeTestCase(BeeTestCase):
def test_normalize_size_when_no_items(self):
self.scene.normalize_size()
def test_clear_save_ids(self):
item1 = BeePixmapItem(QtGui.QImage())
item1.save_id = 5
self.scene.addItem(item1)
item2 = BeePixmapItem(QtGui.QImage())
self.scene.addItem(item2)
item3 = QtWidgets.QGraphicsRectItem()
self.scene.clear_save_ids()
self.scene.addItem(item3)
assert item1.save_id is None
assert item2.save_id is None
assert hasattr(item3, 'save_id') is False