mirror of
https://github.com/rbreu/beeref.git
synced 2026-03-11 08:54:28 +00:00
663 lines
23 KiB
Python
663 lines
23 KiB
Python
# 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
|
|
|
|
from PyQt6 import QtCore, QtGui, QtWidgets
|
|
from PyQt6.QtCore import Qt
|
|
|
|
from beeref.actions import ActionsMixin
|
|
from beeref import commands
|
|
from beeref.config import CommandlineArgs, BeeSettings
|
|
from beeref import constants
|
|
from beeref import fileio
|
|
from beeref import gui
|
|
from beeref.items import BeePixmapItem
|
|
from beeref.scene import BeeGraphicsScene
|
|
|
|
|
|
commandline_args = CommandlineArgs()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin):
|
|
|
|
def __init__(self, app, parent=None):
|
|
super().__init__(parent)
|
|
self.app = app
|
|
self.parent = parent
|
|
self.settings = BeeSettings()
|
|
self.welcome_overlay = gui.WelcomeOverlay(self)
|
|
|
|
self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(60, 60, 60)))
|
|
self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
|
|
self.setAcceptDrops(True)
|
|
|
|
self.undo_stack = QtGui.QUndoStack(self)
|
|
self.undo_stack.setUndoLimit(100)
|
|
self.undo_stack.canRedoChanged.connect(self.on_can_redo_changed)
|
|
self.undo_stack.canUndoChanged.connect(self.on_can_undo_changed)
|
|
self.undo_stack.cleanChanged.connect(self.on_undo_clean_changed)
|
|
|
|
self.filename = None
|
|
self.previous_transform = None
|
|
self.pan_active = False
|
|
self.zoom_active = False
|
|
self.movewin_active = False
|
|
|
|
self.scene = BeeGraphicsScene(self.undo_stack)
|
|
self.scene.changed.connect(self.on_scene_changed)
|
|
self.scene.selectionChanged.connect(self.on_selection_changed)
|
|
self.setScene(self.scene)
|
|
|
|
# Context menu and actions
|
|
self.build_menu_and_actions()
|
|
self.setContextMenuPolicy(
|
|
Qt.ContextMenuPolicy.CustomContextMenu)
|
|
self.customContextMenuRequested.connect(self.on_context_menu)
|
|
|
|
# Load file given via command line
|
|
if commandline_args.filename:
|
|
self.open_from_file(commandline_args.filename)
|
|
self.update_window_title()
|
|
|
|
@property
|
|
def filename(self):
|
|
return self._filename
|
|
|
|
@filename.setter
|
|
def filename(self, value):
|
|
self._filename = value
|
|
self.update_window_title()
|
|
if value:
|
|
self.settings.update_recent_files(value)
|
|
self.update_menu_and_actions()
|
|
|
|
def update_window_title(self):
|
|
clean = self.undo_stack.isClean()
|
|
if clean and not self.filename:
|
|
title = constants.APPNAME
|
|
else:
|
|
name = os.path.basename(self.filename or '[Untitled]')
|
|
clean = '' if clean else '*'
|
|
title = f'{name}{clean} - {constants.APPNAME}'
|
|
self.parent.setWindowTitle(title)
|
|
|
|
def on_scene_changed(self, region):
|
|
if not self.scene.items():
|
|
logger.debug('No items in scene')
|
|
self.setTransform(QtGui.QTransform())
|
|
self.welcome_overlay.show()
|
|
else:
|
|
self.welcome_overlay.hide()
|
|
self.recalc_scene_rect()
|
|
|
|
def on_can_redo_changed(self, can_redo):
|
|
self.actiongroup_set_enabled('active_when_can_redo', can_redo)
|
|
|
|
def on_can_undo_changed(self, can_undo):
|
|
self.actiongroup_set_enabled('active_when_can_undo', can_undo)
|
|
|
|
def on_undo_clean_changed(self, clean):
|
|
self.update_window_title()
|
|
|
|
def on_context_menu(self, point):
|
|
self.context_menu.exec(self.mapToGlobal(point))
|
|
|
|
def get_supported_image_formats(self, cls):
|
|
formats = map(lambda f: f'*.{f.data().decode()}',
|
|
cls.supportedImageFormats())
|
|
return ' '.join(formats)
|
|
|
|
def get_view_center(self):
|
|
return QtCore.QPoint(round(self.size().width() / 2),
|
|
round(self.size().height() / 2))
|
|
|
|
def clear_scene(self):
|
|
logging.debug('Clearing scene...')
|
|
self.scene.clear()
|
|
self.undo_stack.clear()
|
|
self.filename = None
|
|
self.setTransform(QtGui.QTransform())
|
|
|
|
def reset_previous_transform(self, toggle_item=None):
|
|
if (self.previous_transform
|
|
and self.previous_transform['toggle_item'] != toggle_item):
|
|
self.previous_transform = None
|
|
|
|
def fit_rect(self, rect, toggle_item=None):
|
|
if toggle_item and self.previous_transform:
|
|
logger.debug('Fit view: Reset to previous')
|
|
self.setTransform(self.previous_transform['transform'])
|
|
self.centerOn(self.previous_transform['center'])
|
|
self.previous_transform = None
|
|
return
|
|
if toggle_item:
|
|
self.previous_transform = {
|
|
'toggle_item': toggle_item,
|
|
'transform': QtGui.QTransform(self.transform()),
|
|
'center': self.mapToScene(self.get_view_center()),
|
|
}
|
|
else:
|
|
self.previous_transform = None
|
|
|
|
logger.debug(f'Fit view: {rect}')
|
|
self.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
|
|
self.recalc_scene_rect()
|
|
# It seems to be more reliable when we fit a second time
|
|
# Sometimes a changing scene rect can mess up the fitting
|
|
self.fitInView(rect, Qt.AspectRatioMode.KeepAspectRatio)
|
|
|
|
def on_action_fit_scene(self):
|
|
self.fit_rect(self.scene.itemsBoundingRect())
|
|
|
|
def on_action_fit_selection(self):
|
|
self.fit_rect(self.scene.itemsBoundingRect(selection_only=True))
|
|
|
|
def on_action_fullscreen(self, checked):
|
|
if checked:
|
|
self.parent.showFullScreen()
|
|
else:
|
|
self.parent.showNormal()
|
|
|
|
def on_action_always_on_top(self, checked):
|
|
self.parent.setWindowFlag(
|
|
Qt.WindowType.WindowStaysOnTopHint, on=checked)
|
|
self.parent.destroy()
|
|
self.parent.create()
|
|
self.parent.show()
|
|
|
|
def on_action_show_scrollbars(self, checked):
|
|
if checked:
|
|
self.setHorizontalScrollBarPolicy(
|
|
Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
self.setVerticalScrollBarPolicy(
|
|
Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
|
else:
|
|
self.setHorizontalScrollBarPolicy(
|
|
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
self.setVerticalScrollBarPolicy(
|
|
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
|
|
def on_action_show_menubar(self, checked):
|
|
if checked:
|
|
self.parent.setMenuBar(self.create_menubar())
|
|
else:
|
|
self.parent.setMenuBar(None)
|
|
|
|
def on_action_show_titlebar(self, checked):
|
|
self.parent.setWindowFlag(
|
|
Qt.WindowType.FramelessWindowHint, on=not checked)
|
|
self.parent.destroy()
|
|
self.parent.create()
|
|
self.parent.show()
|
|
|
|
def on_action_undo(self):
|
|
logger.debug('Undo: %s' % self.undo_stack.undoText())
|
|
self.undo_stack.undo()
|
|
|
|
def on_action_redo(self):
|
|
logger.debug('Redo: %s' % self.undo_stack.redoText())
|
|
self.undo_stack.redo()
|
|
|
|
def on_action_select_all(self):
|
|
self.scene.set_selected_all_items(True)
|
|
|
|
def on_action_deselect_all(self):
|
|
self.scene.set_selected_all_items(False)
|
|
|
|
def on_action_delete_items(self):
|
|
logger.debug('Deleting items...')
|
|
self.undo_stack.push(
|
|
commands.DeleteItems(
|
|
self.scene, self.scene.selectedItems(user_only=True)))
|
|
|
|
def on_action_cut(self):
|
|
logger.debug('Cutting items...')
|
|
self.on_action_copy()
|
|
self.undo_stack.push(
|
|
commands.DeleteItems(
|
|
self.scene, self.scene.selectedItems(user_only=True)))
|
|
|
|
def on_action_raise_to_top(self):
|
|
self.scene.raise_to_top()
|
|
|
|
def on_action_lower_to_bottom(self):
|
|
self.scene.lower_to_bottom()
|
|
|
|
def on_action_normalize_height(self):
|
|
self.scene.normalize_height()
|
|
|
|
def on_action_normalize_width(self):
|
|
self.scene.normalize_width()
|
|
|
|
def on_action_normalize_size(self):
|
|
self.scene.normalize_size()
|
|
|
|
def on_action_arrange_horizontal(self):
|
|
self.scene.arrange()
|
|
|
|
def on_action_arrange_vertical(self):
|
|
self.scene.arrange(vertical=True)
|
|
|
|
def on_action_arrange_optimal(self):
|
|
self.scene.arrange_optimal()
|
|
|
|
def on_action_flip_horizontally(self):
|
|
self.scene.flip_items(vertical=False)
|
|
|
|
def on_action_flip_vertically(self):
|
|
self.scene.flip_items(vertical=True)
|
|
|
|
def on_action_reset_scale(self):
|
|
self.undo_stack.push(commands.ResetScale(
|
|
self.scene.selectedItems(user_only=True)))
|
|
|
|
def on_action_reset_rotation(self):
|
|
self.undo_stack.push(commands.ResetRotation(
|
|
self.scene.selectedItems(user_only=True)))
|
|
|
|
def on_action_reset_flip(self):
|
|
self.undo_stack.push(commands.ResetFlip(
|
|
self.scene.selectedItems(user_only=True)))
|
|
|
|
def on_action_reset_transforms(self):
|
|
self.undo_stack.push(commands.ResetTransforms(
|
|
self.scene.selectedItems(user_only=True)))
|
|
|
|
def on_items_loaded(self, value):
|
|
logger.debug('On items loded: add queued images')
|
|
self.scene.add_queued_items()
|
|
|
|
def on_loading_finished(self, filename, errors):
|
|
if errors:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
'Problem loading file',
|
|
('<p>Problem loading file %s</p>'
|
|
'<p>Not accessible or not a proper bee file</p>') % filename)
|
|
else:
|
|
self.filename = filename
|
|
self.scene.add_queued_items()
|
|
self.on_action_fit_scene()
|
|
|
|
def open_from_file(self, filename):
|
|
logger.info(f'Opening file {filename}')
|
|
self.clear_scene()
|
|
self.worker = fileio.ThreadedIO(
|
|
fileio.load_bee, filename, self.scene)
|
|
self.worker.progress.connect(self.on_items_loaded)
|
|
self.worker.finished.connect(self.on_loading_finished)
|
|
self.progress = gui.BeeProgressDialog(
|
|
'Loading %s' % filename,
|
|
worker=self.worker,
|
|
parent=self)
|
|
self.worker.start()
|
|
|
|
def on_action_open(self):
|
|
filename, f = QtWidgets.QFileDialog.getOpenFileName(
|
|
parent=self,
|
|
caption='Open file',
|
|
filter=f'{constants.APPNAME} File (*.bee)')
|
|
if filename:
|
|
self.open_from_file(filename)
|
|
self.filename = filename
|
|
|
|
def on_saving_finished(self, filename, errors):
|
|
if errors:
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
'Problem saving file',
|
|
('<p>Problem saving file %s</p>'
|
|
'<p>File/directory not accessible</p>') % filename)
|
|
else:
|
|
self.filename = filename
|
|
self.undo_stack.setClean()
|
|
|
|
def do_save(self, filename, create_new):
|
|
if not filename.endswith('.bee'):
|
|
filename = f'{filename}.bee'
|
|
self.worker = fileio.ThreadedIO(
|
|
fileio.save_bee, filename, self.scene, create_new=create_new)
|
|
self.worker.finished.connect(self.on_saving_finished)
|
|
self.progress = gui.BeeProgressDialog(
|
|
'Saving %s' % filename,
|
|
worker=self.worker,
|
|
parent=self)
|
|
self.worker.start()
|
|
|
|
def on_action_save_as(self):
|
|
filename, f = QtWidgets.QFileDialog.getSaveFileName(
|
|
parent=self,
|
|
caption='Save file',
|
|
filter=f'{constants.APPNAME} File (*.bee)')
|
|
if filename:
|
|
self.do_save(filename, create_new=True)
|
|
|
|
def on_action_save(self):
|
|
if not self.filename:
|
|
self.on_action_save_as()
|
|
else:
|
|
self.do_save(self.filename, create_new=False)
|
|
|
|
def on_action_quit(self):
|
|
logger.info('User quit. Exiting...')
|
|
self.app.quit()
|
|
|
|
def on_action_help(self):
|
|
gui.HelpDialog(self)
|
|
|
|
def on_action_about(self):
|
|
QtWidgets.QMessageBox.about(
|
|
self,
|
|
f'About {constants.APPNAME}',
|
|
(f'<h2>{constants.APPNAME} {constants.VERSION}</h2>'
|
|
f'<p>{constants.APPNAME_FULL}</p>'
|
|
f'<p>{constants.COPYRIGHT}</p>'
|
|
f'<p><a href="{constants.WEBSITE}">'
|
|
f'Visit the {constants.APPNAME} website</a></p>'))
|
|
|
|
def on_action_debuglog(self):
|
|
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]
|
|
errornames = '<ul>%s</ul>' % '\n'.join(errornames)
|
|
msg = ('{errors} image(s) out of {total} '
|
|
'could not be opened:'.format(
|
|
errors=len(errors), total=len(errors)))
|
|
QtWidgets.QMessageBox.warning(
|
|
self,
|
|
'Problem loading images',
|
|
msg + errornames)
|
|
self.scene.add_queued_items()
|
|
self.scene.arrange_optimal()
|
|
self.undo_stack.endMacro()
|
|
|
|
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(pos),
|
|
self.scene)
|
|
self.worker.progress.connect(self.on_items_loaded)
|
|
self.worker.finished.connect(self.on_insert_images_finished)
|
|
self.progress = gui.BeeProgressDialog(
|
|
'Loading images',
|
|
worker=self.worker,
|
|
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_copy(self):
|
|
logger.debug('Copying to clipboard...')
|
|
clipboard = QtWidgets.QApplication.clipboard()
|
|
items = self.scene.selectedItems(user_only=True)
|
|
|
|
# At the moment, we can only copy one image to the global
|
|
# clipboard. (Later, we might create an image of the whole
|
|
# selection for external copying.)
|
|
clipboard.setPixmap(items[0].pixmap())
|
|
|
|
# However, we can copy all items to the internal clipboard:
|
|
self.scene.copy_selection_to_internal_clipboard()
|
|
|
|
# We set a marker for ourselves in the global clipboard so
|
|
# that we know to look up the internal clipboard when pasting:
|
|
clipboard.mimeData().setData(
|
|
'beeref/items', QtCore.QByteArray.number(len(items)))
|
|
|
|
def on_action_paste(self):
|
|
logger.debug('Pasting from clipboard...')
|
|
clipboard = QtWidgets.QApplication.clipboard()
|
|
pos = self.mapToScene(self.mapFromGlobal(self.cursor().pos()))
|
|
|
|
# See if we need to look up the internal clipboard:
|
|
data = clipboard.mimeData().data('beeref/items')
|
|
logger.debug(f'Custom data in clipboard: {data}')
|
|
if data:
|
|
self.scene.paste_from_internal_clipboard(pos)
|
|
return
|
|
|
|
img = clipboard.image()
|
|
if not img.isNull():
|
|
item = BeePixmapItem(img)
|
|
self.undo_stack.push(commands.InsertItems(self.scene, [item], pos))
|
|
return
|
|
logger.info('No image data in clipboard')
|
|
|
|
def on_selection_changed(self):
|
|
logger.debug('Currently selected items: %s',
|
|
len(self.scene.selectedItems(user_only=True)))
|
|
self.actiongroup_set_enabled('active_when_selection',
|
|
self.scene.has_selection())
|
|
self.viewport().repaint()
|
|
|
|
def recalc_scene_rect(self):
|
|
"""Resize the scene rectangle so that it is always one view width
|
|
wider than all items' bounding box at each side and one view
|
|
width higher on top and bottom. This gives the impression of
|
|
an infinite canvas."""
|
|
|
|
logger.debug('Recalculating scene rectangle...')
|
|
try:
|
|
topleft = self.mapFromScene(
|
|
self.scene.itemsBoundingRect().topLeft())
|
|
topleft = self.mapToScene(QtCore.QPoint(
|
|
topleft.x() - self.size().width(),
|
|
topleft.y() - self.size().height()))
|
|
bottomright = self.mapFromScene(
|
|
self.scene.itemsBoundingRect().bottomRight())
|
|
bottomright = self.mapToScene(QtCore.QPoint(
|
|
bottomright.x() + self.size().width(),
|
|
bottomright.y() + self.size().height()))
|
|
self.setSceneRect(QtCore.QRectF(topleft, bottomright))
|
|
except OverflowError:
|
|
logger.info('Maximum scene size reached')
|
|
logger.debug('Done recalculating scene rectangle')
|
|
|
|
def get_zoom_size(self, func):
|
|
"""Calculates the size of all items' bounding box in the view's
|
|
coordinates.
|
|
|
|
This helps ensure that we never zoom out too much (scene
|
|
becomes so tiny that items become invisible) or zoom in too
|
|
much (causing overflow errors).
|
|
|
|
:param func: Function which takes the width and height as
|
|
arguments and turns it into a number, for ex. ``min`` or ``max``.
|
|
"""
|
|
|
|
topleft = self.mapFromScene(
|
|
self.scene.itemsBoundingRect().topLeft())
|
|
bottomright = self.mapFromScene(
|
|
self.scene.itemsBoundingRect().bottomRight())
|
|
return func(bottomright.x() - topleft.x(),
|
|
bottomright.y() - topleft.y())
|
|
|
|
def scale(self, *args, **kwargs):
|
|
super().scale(*args, **kwargs)
|
|
self.scene.on_view_scale_change()
|
|
self.recalc_scene_rect()
|
|
|
|
def get_scale(self):
|
|
return self.transform().m11()
|
|
|
|
def pan(self, delta):
|
|
if not self.scene.items():
|
|
logger.debug('No items in scene; ignore pan')
|
|
return
|
|
|
|
hscroll = self.horizontalScrollBar()
|
|
hscroll.setValue(hscroll.value() + delta.x())
|
|
vscroll = self.verticalScrollBar()
|
|
vscroll.setValue(vscroll.value() + delta.y())
|
|
|
|
def zoom(self, delta, anchor):
|
|
if not self.scene.items():
|
|
logger.debug('No items in scene; ignore zoom')
|
|
return
|
|
|
|
# We caculate where the anchor is before and after the zoom
|
|
# and then move the view accordingly to keep the anchor fixed
|
|
# We can't use QGraphicsView's AnchorUnderMouse since it
|
|
# uses the current cursor position while we need the inital mouse
|
|
# press position for zooming with Ctrl + Middle Drag
|
|
anchor = QtCore.QPoint(round(anchor.x()),
|
|
round(anchor.y()))
|
|
ref_point = self.mapToScene(anchor)
|
|
if delta == 0:
|
|
return
|
|
factor = 1 + abs(delta / 1000)
|
|
if delta > 0:
|
|
if self.get_zoom_size(max) < 10000000:
|
|
self.scale(factor, factor)
|
|
else:
|
|
logger.debug('Maximum zoom size reached')
|
|
return
|
|
else:
|
|
if self.get_zoom_size(min) > 50:
|
|
self.scale(1/factor, 1/factor)
|
|
else:
|
|
logger.debug('Minimum zoom size reached')
|
|
return
|
|
|
|
self.pan(self.mapFromScene(ref_point) - anchor)
|
|
self.reset_previous_transform()
|
|
|
|
def wheelEvent(self, event):
|
|
self.zoom(event.angleDelta().y(), event.position())
|
|
event.accept()
|
|
|
|
def mousePressEvent(self, event):
|
|
if (event.button() == Qt.MouseButton.MiddleButton
|
|
and event.modifiers() == Qt.KeyboardModifier.ControlModifier):
|
|
self.zoom_active = True
|
|
self.event_start = event.position()
|
|
self.event_anchor = event.position()
|
|
event.accept()
|
|
return
|
|
|
|
if (event.button() == Qt.MouseButton.MiddleButton
|
|
or (event.button() == Qt.MouseButton.LeftButton
|
|
and event.modifiers() == Qt.KeyboardModifier.AltModifier)):
|
|
self.pan_active = True
|
|
self.event_start = event.position()
|
|
self.setCursor(Qt.CursorShape.ClosedHandCursor)
|
|
event.accept()
|
|
return
|
|
|
|
if (event.button() == Qt.MouseButton.LeftButton
|
|
and event.modifiers() == (Qt.KeyboardModifier.ControlModifier
|
|
| Qt.KeyboardModifier.AltModifier)):
|
|
self.movewin_active = True
|
|
self.event_start = self.mapToGlobal(event.position())
|
|
event.accept()
|
|
return
|
|
|
|
super().mousePressEvent(event)
|
|
|
|
def mouseMoveEvent(self, event):
|
|
if self.pan_active:
|
|
self.reset_previous_transform()
|
|
pos = event.position()
|
|
self.pan(self.event_start - pos)
|
|
self.event_start = pos
|
|
event.accept()
|
|
return
|
|
|
|
if self.zoom_active:
|
|
self.reset_previous_transform()
|
|
pos = event.position()
|
|
delta = (self.event_start - pos).y()
|
|
self.event_start = pos
|
|
self.zoom(delta * 20, self.event_anchor)
|
|
event.accept()
|
|
return
|
|
|
|
if self.movewin_active:
|
|
pos = self.mapToGlobal(event.position())
|
|
delta = pos - self.event_start
|
|
self.event_start = pos
|
|
self.parent.move(self.parent.x() + int(delta.x()),
|
|
self.parent.y() + int(delta.y()))
|
|
event.accept()
|
|
return
|
|
|
|
super().mouseMoveEvent(event)
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
if self.pan_active:
|
|
self.setCursor(Qt.CursorShape.ArrowCursor)
|
|
self.pan_active = False
|
|
event.accept()
|
|
return
|
|
if self.zoom_active:
|
|
self.zoom_active = False
|
|
event.accept()
|
|
return
|
|
if self.movewin_active:
|
|
self.movewin_active = False
|
|
event.accept()
|
|
return
|
|
|
|
super().mouseReleaseEvent(event)
|
|
|
|
def resizeEvent(self, event):
|
|
super().resizeEvent(event)
|
|
self.recalc_scene_rect()
|
|
self.welcome_overlay.resize(self.size())
|
|
|
|
def dragEnterEvent(self, event):
|
|
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')
|
|
|
|
def dragMoveEvent(self, event):
|
|
event.acceptProposedAction()
|
|
|
|
def dropEvent(self, event):
|
|
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)
|
|
self.undo_stack.push(commands.InsertItems(self.scene, [item], pos))
|
|
logger.info('Drop not an image')
|