mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
Progress Dialogs for loading and saving
This commit is contained in:
parent
c7e7dff3a6
commit
a8cdd79ab9
9 changed files with 119 additions and 36 deletions
|
|
@ -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!')
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue