Fit the page on startup, name the bundle, show the selection
Three things a first session trips over. The editor opened at an arbitrary zoom. show_page does fit the page, but it runs before the window has been laid out, when the viewport is still its default size; redo it once when the real size arrives. The bundle was named after the PDF, which is whatever the download was called. Take the song's title instead — unsafe characters dropped, then whitespace collapsed to dashes. Accented letters stay, since ä and ö are not a filesystem's problem, but a leading dot would hide the file. And the selected slice was drawn as an outline whose top and bottom edges run under the cut lines painted over them, leaving two thin verticals in the margins and no way to tell what was selected. Wash the slice, as the discard and engraved states already do, and keep the outline for the trim anomalies it exists to show.
This commit is contained in:
@@ -13,6 +13,7 @@ a jump source's `destination`.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
@@ -39,6 +40,19 @@ METADATA_FIELDS = (
|
||||
NUMERIC_FIELDS = frozenset({"tempo"})
|
||||
|
||||
|
||||
def filename(project: Project) -> str:
|
||||
"""The bundle's name, from the song's title.
|
||||
|
||||
Spaces become dashes and anything that is not a letter, digit, dash, dot or
|
||||
underscore goes. Letters keep their accents — ä and ö are not a filesystem's
|
||||
problem — but a leading dot would make the bundle invisible.
|
||||
"""
|
||||
# Drop the unsafe characters before collapsing whitespace, not after, or
|
||||
# "Sävel & Ääni" keeps the dash the ampersand left behind.
|
||||
title = re.sub(r"[^\w\s.-]", "", (project.metadata.get("title") or ""))
|
||||
return f"{re.sub(r'\s+', '-', title.strip()).lstrip('.-') or 'song'}.zip"
|
||||
|
||||
|
||||
def _engraving(project: Project, page: int, slot: int) -> dict | None:
|
||||
"""The notation behind a re-engraved slice, or None for a scanned one.
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ def _export(args: argparse.Namespace) -> int:
|
||||
elif project.source_changed():
|
||||
print("WARNING: the PDF has changed since these cuts were made")
|
||||
|
||||
out = Path(args.out) if args.out else source.path.with_suffix(".zip")
|
||||
out = Path(args.out) if args.out else source.path.with_name(bundle.filename(project))
|
||||
try:
|
||||
bundle.write(project, source, out)
|
||||
except ValueError as error:
|
||||
|
||||
@@ -75,6 +75,8 @@ _CUT = QColor(220, 40, 40)
|
||||
_CUT_ACTIVE = QColor(255, 120, 0)
|
||||
_VERTEX = QColor(255, 200, 0)
|
||||
_DISCARD = QColor(120, 120, 140, 90)
|
||||
_SELECT = QColor(0, 170, 0)
|
||||
_SELECT_WASH = QColor(0, 200, 60, 40)
|
||||
_RECT = QColor(40, 140, 220)
|
||||
_MARKER = QColor(150, 60, 190)
|
||||
_ENGRAVED = QColor(200, 120, 0)
|
||||
@@ -104,6 +106,7 @@ class PageView(QGraphicsView):
|
||||
self.selected_slice = 0
|
||||
self.picking = False
|
||||
self._drag: tuple[str, int, int] | None = None
|
||||
self._fitted = False
|
||||
|
||||
# -- state ------------------------------------------------------------
|
||||
|
||||
@@ -137,10 +140,15 @@ class PageView(QGraphicsView):
|
||||
self._slice_polygon(slot, w, h), QPen(Qt.NoPen), QBrush(_DISCARD)
|
||||
)
|
||||
|
||||
# The selected slice, outlined so trim anomalies are visible.
|
||||
pen = QPen(QColor(0, 170, 0), 2)
|
||||
# The selected slice. The outline alone is nearly invisible: its top and
|
||||
# bottom edges run under the cut lines drawn over them, leaving two thin
|
||||
# verticals at the page margins. A wash says which slice is selected at
|
||||
# a glance; the outline stays, because it is what shows trim anomalies.
|
||||
selected = self._slice_polygon(self.selected_slice, w, h)
|
||||
scene.addPolygon(selected, QPen(Qt.NoPen), QBrush(_SELECT_WASH))
|
||||
pen = QPen(_SELECT, 3)
|
||||
pen.setCosmetic(True)
|
||||
scene.addPolygon(self._slice_polygon(self.selected_slice, w, h), pen)
|
||||
scene.addPolygon(selected, pen)
|
||||
|
||||
x0, y0, x1, y1 = self.project.page_content_rect(self.page_index)
|
||||
pen = QPen(_RECT, 2, Qt.DashLine)
|
||||
@@ -361,6 +369,15 @@ class PageView(QGraphicsView):
|
||||
self.redraw()
|
||||
self.changed.emit()
|
||||
|
||||
def resizeEvent(self, event) -> None:
|
||||
super().resizeEvent(event)
|
||||
# The fit in show_page runs before the window has been laid out, when
|
||||
# the viewport is still its default size, so the first page opens at
|
||||
# some arbitrary zoom. Redo it once, when the real size arrives.
|
||||
if not self._fitted and self.pixmap is not None:
|
||||
self._fitted = True
|
||||
self.fitInView(self.scene().sceneRect(), Qt.KeepAspectRatio)
|
||||
|
||||
def wheelEvent(self, event) -> None:
|
||||
factor = 1.15 if event.angleDelta().y() > 0 else 1 / 1.15
|
||||
self.scale(factor, factor)
|
||||
@@ -844,7 +861,10 @@ class Editor(QMainWindow):
|
||||
self.metadata["title"].setFocus()
|
||||
return
|
||||
target, _ = QFileDialog.getSaveFileName(
|
||||
self, "Export bundle", str(self.source.path.with_suffix(".zip")), "Bundle (*.zip)"
|
||||
self,
|
||||
"Export bundle",
|
||||
str(self.source.path.with_name(bundle.filename(self.project))),
|
||||
"Bundle (*.zip)",
|
||||
)
|
||||
if not target:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user