Notes editor: Allow specifying image sizes

This commit is contained in:
Kovid Goyal 2023-10-27 18:44:17 +05:30
parent 3d7d07ed78
commit c1ca6616bd
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 104 additions and 15 deletions

View File

@ -4,6 +4,7 @@
import os
import re
import sys
import weakref
from collections import defaultdict
from contextlib import contextmanager
@ -14,13 +15,13 @@ from qt.core import (
QAction, QApplication, QBrush, QByteArray, QCheckBox, QColor, QColorDialog, QDialog,
QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout, QHBoxLayout, QIcon,
QKeySequence, QLabel, QLineEdit, QMenu, QPalette, QPlainTextEdit, QPointF,
QPushButton, QSize, QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat,
QPushButton, QSize, QSpinBox, QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat,
QTextCharFormat, QTextCursor, QTextDocument, QTextEdit, QTextFormat,
QTextFrameFormat, QTextListFormat, QTimer, QToolButton, QUrl, QVBoxLayout, QWidget,
pyqtSignal, pyqtSlot,
QTextFrameFormat, QTextImageFormat, QTextListFormat, QTimer, QToolButton, QUrl,
QVBoxLayout, QWidget, pyqtSignal, pyqtSlot,
)
from calibre import xml_replace_entities
from calibre import fit_image, xml_replace_entities
from calibre.db.constants import DATA_DIR_NAME
from calibre.ebooks.chardet import xml_to_unicode
from calibre.gui2 import (
@ -914,6 +915,56 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{
if fmt.isImageFormat():
c.deletePreviousChar()
def resize_image_at(self, cursor_pos):
c = self.textCursor()
c.clearSelection()
c.setPosition(cursor_pos)
c.movePosition(QTextCursor.MoveOperation.PreviousCharacter, QTextCursor.MoveMode.KeepAnchor)
fmt = c.charFormat()
if not fmt.isImageFormat():
return
fmt = fmt.toImageFormat()
from calibre.utils.img import image_from_data
img = image_from_data(self.loadResource(QTextDocument.ResourceType.ImageResource, QUrl(fmt.name())))
w, h = int(fmt.width()), int(fmt.height())
d = QDialog(self)
l = QVBoxLayout(d)
la = QLabel(_('Shrink image to fit within:'))
h = QHBoxLayout()
l.addLayout(h)
la = QLabel(_('&Width:'))
h.addWidget(la)
d.width = w = QSpinBox(self)
w.setRange(0, 10000), w.setSuffix(' px')
w.setValue(int(fmt.width()))
h.addWidget(w), la.setBuddy(w)
w.setSpecialValueText(' ')
la = QLabel(_('&Height:'))
h.addWidget(la)
d.height = w = QSpinBox(self)
w.setRange(0, 10000), w.setSuffix(' px')
w.setValue(int(fmt.height()))
h.addWidget(w), la.setBuddy(w)
w.setSpecialValueText(' ')
h.addStretch(10)
bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
bb.accepted.connect(d.accept), bb.rejected.connect(d.reject)
d.setWindowTitle(_('Enter new size for image'))
l.addWidget(bb)
d.resize(d.sizeHint())
if d.exec() == QDialog.DialogCode.Accepted:
page_width, page_height = (d.width.value() or sys.maxsize), (d.height.value() or sys.maxsize)
w, h = int(img.width()), int(img.height())
resized, nw, nh = fit_image(w, h, page_width, page_height)
if resized:
fmt.setWidth(nw), fmt.setHeight(nh)
else:
f = QTextImageFormat()
f.setName(fmt.name())
fmt = f
c.setCharFormat(fmt)
def align_image_at(self, cursor_pos, alignment):
c = self.textCursor()
c.clearSelection()
@ -971,6 +1022,9 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{
ac.triggered.connect(partial(self.align_image_at, c.position(), epos))
if pos == epos:
ac.setChecked(True)
cs = align_menu.addAction(QIcon.ic('resize.png'), _('Change size'))
cs.triggered.connect(partial(self.resize_image_at, c.position()))
align_menu.addSeparator()
a(_('Float to the left'), QTextFrameFormat.Position.FloatLeft)
a(_('Inline with text'), QTextFrameFormat.Position.InFlow)
a(_('Float to the right'), QTextFrameFormat.Position.FloatRight)

View File

@ -2,15 +2,16 @@
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from qt.core import (
QButtonGroup, QByteArray, QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout,
QIcon, QLabel, QLineEdit, QPixmap, QPushButton, QRadioButton, QSize, Qt,
QIcon, QLabel, QLineEdit, QPixmap, QPushButton, QRadioButton, QSize, QSpinBox, Qt,
QTextDocument, QTextFrameFormat, QTextImageFormat, QUrl, QVBoxLayout, QWidget,
pyqtSlot,
)
from typing import NamedTuple
from calibre import sanitize_file_name
from calibre import sanitize_file_name, fit_image
from calibre.db.constants import RESOURCE_URL_SCHEME
from calibre.db.notes.connect import hash_data
from calibre.db.notes.exim import export_note, import_note
@ -83,6 +84,7 @@ class AskImage(Dialog):
self.v = v = QVBoxLayout(self)
self.h = h = QHBoxLayout()
v.addLayout(h)
v.addWidget(self.bb)
self.image_preview = ip = ImageView(self, 'insert-image-for-notes-preview', True)
@ -100,6 +102,15 @@ class AskImage(Dialog):
ne.setPlaceholderText(_('Filename for the image'))
vr.addWidget(ne)
self.hb = hb = QHBoxLayout()
vr.addLayout(hb)
self.add_file_button = b = QPushButton(QIcon.ic('document_open.png'), _('Choose image &file'), self)
b.clicked.connect(self.add_file)
hb.addWidget(b)
self.paste_button = b = QPushButton(QIcon.ic('edit-paste.png'), _('&Paste from clipboard'), self)
b.clicked.connect(self.paste_image)
hb.addWidget(b)
self.la2 = la = QLabel(_('Place image:'))
vr.addWidget(la)
self.hr = hr = QHBoxLayout()
@ -113,14 +124,23 @@ class AskImage(Dialog):
bg.addButton(r), hr.addWidget(r)
self.inline.setChecked(True)
self.hb = hb = QHBoxLayout()
vr.addLayout(hb)
self.add_file_button = b = QPushButton(QIcon.ic('document_open.png'), _('Choose image &file'), self)
b.clicked.connect(self.add_file)
hb.addWidget(b)
self.paste_button = b = QPushButton(QIcon.ic('edit-paste.png'), _('&Paste from clipboard'), self)
b.clicked.connect(self.paste_image)
hb.addWidget(b)
self.la2 = la = QLabel(_('Shrink image to fit within:'))
vr.addWidget(la)
self.hr2 = h = QHBoxLayout()
vr.addLayout(h)
la = QLabel(_('&Width:'))
h.addWidget(la)
self.width = w = QSpinBox(self)
w.setRange(0, 10000), w.setSuffix(' px')
h.addWidget(w), la.setBuddy(w)
w.setSpecialValueText(' ')
la = QLabel(_('&Height:'))
h.addWidget(la)
self.height = w = QSpinBox(self)
w.setRange(0, 10000), w.setSuffix(' px')
h.addWidget(w), la.setBuddy(w)
w.setSpecialValueText(' ')
h.addStretch(10)
vr.addStretch(10)
self.add_file_button.setFocus(Qt.FocusReason.OtherFocusReason)
@ -154,7 +174,7 @@ class AskImage(Dialog):
def paste_image(self):
if not self.image_preview.paste_from_clipboard():
return error_dialog(self, _('Could not paste'), _(
'No image is present int he system clipboard'), show=True)
'No image is present in the system clipboard'), show=True)
@property
def image_layout(self) -> 'QTextFrameFormat.Position':
@ -164,6 +184,15 @@ class AskImage(Dialog):
if b is self.float_left:
return QTextFrameFormat.Position.FloatLeft
return QTextFrameFormat.Position.FloatRight
@property
def image_size(self) -> tuple[int, int]:
s = self.image_preview.pixmap().size()
return s.width(), s.height()
@property
def bounding_size(self) -> tuple[int, int]:
return (self.width.value() or sys.maxsize), (self.height.value() or sys.maxsize)
# }}}
@ -238,6 +267,12 @@ class NoteEditorWidget(EditorWidget):
fmt = QTextImageFormat()
alg, digest = ir.digest.split(':', 1)
fmt.setName(RESOURCE_URL_SCHEME + f'://{alg}/{digest}?placement={uuid4()}')
page_width, page_height = d.bounding_size
w, h = d.image_size
resized, nw, nh = fit_image(w, h, page_width, page_height)
if resized:
fmt.setWidth(nw)
fmt.setHeight(nh)
c.insertImage(fmt, d.image_layout)