Allow drag and drop to open documents

This commit is contained in:
Veronica Berglyd Olsen
2024-11-20 02:12:28 +01:00
parent 72f1bc2d77
commit 5dcfe5c874
6 changed files with 74 additions and 85 deletions
+6 -1
View File
@@ -37,7 +37,7 @@ from typing import TYPE_CHECKING, Any, Literal, TypeVar
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtCore import QCoreApplication, QMimeData, QUrl
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
@@ -441,6 +441,11 @@ def qtLambda(func: Callable, *args: Any, **kwargs: Any) -> Callable:
return wrapper
def decodeMimeHandles(mimeData: QMimeData) -> list[str]:
"""Decode and split a mime data object with handles."""
return mimeData.data(nwConst.MIME_HANDLE).data().decode().split("|")
##
# Encoder Functions
##
+3 -2
View File
@@ -32,7 +32,7 @@ from PyQt5.QtCore import QAbstractItemModel, QMimeData, QModelIndex, Qt
from PyQt5.QtGui import QIcon
from novelwriter import SHARED
from novelwriter.common import minmax
from novelwriter.common import decodeMimeHandles, minmax
from novelwriter.constants import nwConst
from novelwriter.core.item import NWItem
from novelwriter.enum import nwItemClass
@@ -191,6 +191,7 @@ class ProjectNode:
if 0 <= pos < len(self._children):
node = self._children.pop(pos)
self._refreshChildrenPos()
self.updateCount()
return node
return None
@@ -330,7 +331,7 @@ class ProjectModel(QAbstractItemModel):
"""Process mime data drop."""
if self.canDropMimeData(data, action, row, column, parent):
items = []
for handle in data.data(nwConst.MIME_HANDLE).data().decode().split("|"):
for handle in decodeMimeHandles(data):
if (index := self.indexFromHandle(handle)).isValid():
items.append(index)
self.multiMove(items, parent, row)
+32 -3
View File
@@ -43,8 +43,9 @@ from PyQt5.QtCore import (
pyqtSlot
)
from PyQt5.QtGui import (
QColor, QCursor, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap,
QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption
QColor, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent,
QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QTextBlock,
QTextCursor, QTextDocument, QTextOption
)
from PyQt5.QtWidgets import (
QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
@@ -52,7 +53,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, qtLambda, transferCase
from novelwriter.common import decodeMimeHandles, minmax, qtLambda, transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
from novelwriter.core.document import NWDocument
from novelwriter.enum import (
@@ -116,6 +117,7 @@ class GuiDocEditor(QPlainTextEdit):
loadDocumentTagRequest = pyqtSignal(str, Enum)
novelItemMetaChanged = pyqtSignal(str)
novelStructureChanged = pyqtSignal()
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
requestNewNoteCreation = pyqtSignal(str, nwItemClass)
requestNextDocument = pyqtSignal(str, bool)
requestProjectItemRenamed = pyqtSignal(str, str)
@@ -191,6 +193,7 @@ class GuiDocEditor(QPlainTextEdit):
self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True)
self.setFrameStyle(QFrame.Shape.NoFrame)
self.setAcceptDrops(True)
# Custom Shortcuts
self.keyContext = QShortcut(self)
@@ -997,6 +1000,32 @@ class GuiDocEditor(QPlainTextEdit):
return
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
"""Overload drag enter event to handle dragged items."""
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
event.acceptProposedAction()
else:
super().dragEnterEvent(event)
return
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
"""Overload drag move event to handle dragged items."""
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
event.acceptProposedAction()
else:
super().dragMoveEvent(event)
return
def dropEvent(self, event: QDropEvent) -> None:
"""Overload drop event to handle dragged items."""
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
if handles := decodeMimeHandles(event.mimeData()):
if SHARED.project.tree.checkType(handles[0], nwItemType.FILE):
self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True)
else:
super().dropEvent(event)
return
def focusNextPrevChild(self, next: bool) -> bool:
"""Capture the focus request from the tab key on the text
editor. If the editor has focus, we do not change focus and
+32 -3
View File
@@ -31,15 +31,18 @@ import logging
from enum import Enum
from PyQt5.QtCore import QPoint, Qt, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCursor, QDesktopServices, QMouseEvent, QPalette, QResizeEvent, QTextCursor
from PyQt5.QtGui import (
QCursor, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent,
QMouseEvent, QPalette, QResizeEvent, QTextCursor
)
from PyQt5.QtWidgets import (
QAction, QApplication, QFrame, QHBoxLayout, QMenu, QTextBrowser,
QToolButton, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
from novelwriter.constants import nwStyles, nwUnicode
from novelwriter.common import decodeMimeHandles, qtLambda
from novelwriter.constants import nwConst, nwStyles, nwUnicode
from novelwriter.enum import nwDocAction, nwDocMode, nwItemType
from novelwriter.error import logException
from novelwriter.extensions.configlayout import NColourLabel
@@ -445,6 +448,32 @@ class GuiDocViewer(QTextBrowser):
super().mouseReleaseEvent(event)
return
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
"""Overload drag enter event to handle dragged items."""
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
event.acceptProposedAction()
else:
super().dragEnterEvent(event)
return
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
"""Overload drag move event to handle dragged items."""
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
event.acceptProposedAction()
else:
super().dragMoveEvent(event)
return
def dropEvent(self, event: QDropEvent) -> None:
"""Overload drop event to handle dragged items."""
if event.mimeData().hasFormat(nwConst.MIME_HANDLE):
if handles := decodeMimeHandles(event.mimeData()):
if SHARED.project.tree.checkType(handles[0], nwItemType.FILE):
self.openDocumentRequest.emit(handles[0], nwDocMode.VIEW, "", True)
else:
super().dropEvent(event)
return
##
# Internal Functions
##
-76
View File
@@ -1111,82 +1111,6 @@ class GuiProjectTree(QTreeView):
# self._scrollTimer.stop()
return
##
# Events
##
# def startDrag(self, dropAction: Qt.DropAction) -> None:
# """Capture the drag and drop handling to pop alerts."""
# super().startDrag(dropAction)
# if self._popAlert:
# SHARED.error(self._popAlert)
# self._popAlert = None
# return
# def dragEnterEvent(self, event: QDragEnterEvent) -> None:
# """Check that we're only dragging items that are siblings, and
# not a root level item.
# """
# items = self.selectedItems()
# if items and (parent := items[0].parent()) and all(x.parent() is parent for x in items):
# super().dragEnterEvent(event)
# else:
# logger.warning("Drag action is not allowed and has been cancelled")
# self._popAlert = self.tr(
# "Drag and drop is only allowed for single items, non-root "
# "items, or multiple items with the same parent."
# )
# event.mimeData().clear()
# event.ignore()
# return
# def dragMoveEvent(self, event: QDragMoveEvent) -> None:
# """Capture the drag move event to enable edge auto scroll."""
# y = event.pos().y()
# if y < self._scrollMargin:
# if not self._scrollTimer.isActive():
# self._scrollDirection = -1
# self._scrollTimer.start()
# elif y > self.height() - self._scrollMargin:
# if not self._scrollTimer.isActive():
# self._scrollDirection = 1
# self._scrollTimer.start()
# super().dragMoveEvent(event)
# return
# def dropEvent(self, event: QDropEvent) -> None:
# """Overload the drop item event to ensure the drag and drop
# action is allowed, and update relevant data.
# """
# tItem = self.itemAt(event.pos())
# onItem = self.dropIndicatorPosition() == QAbstractItemView.DropIndicatorPosition.OnItem
# onView = self.dropIndicatorPosition() == QAbstractItemView.DropIndicatorPosition.OnViewport
# # Make sure nothing can be dropped on invisible root (see #1569)
# # Make sure nothing can be dropped on the viewport (see #2108)
# if tItem is None or (tItem.parent() is None and not onItem) or onView:
# logger.error("Invalid drop location")
# event.ignore()
# return
# mItems: dict[str, tuple[QTreeWidgetItem, bool]] = {}
# sItems = self.selectedItems()
# if sItems and (parent := sItems[0].parent()) and all(x.parent() is parent for x in sItems):
# for sItem in sItems:
# mHandle = str(sItem.data(self.C_DATA, self.D_HANDLE))
# mItems[mHandle] = (sItem, sItem.isExpanded())
# self.propagateCount(mHandle, 0)
# super().dropEvent(event)
# for mHandle, (sItem, isExpanded) in mItems.items():
# self._postItemMove(mHandle)
# sItem.setExpanded(isExpanded)
# self._alertTreeChange(mHandle, flush=False)
# self.saveTreeOrder()
# return
##
# Internal Functions
##
+1
View File
@@ -259,6 +259,7 @@ class GuiMain(QMainWindow):
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
self.docEditor.openDocumentRequest.connect(self._openDocument)
self.docEditor.requestNewNoteCreation.connect(self.projView.createNewNote)
self.docEditor.requestNextDocument.connect(self.openNextDocument)
self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)