novelWriter 2.4.3__py3-none-any.whl → 2.5b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/METADATA +4 -5
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/RECORD +109 -101
- novelwriter/__init__.py +33 -39
- novelwriter/assets/i18n/project_en_GB.json +1 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -0
- novelwriter/assets/icons/typicons_dark/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_quote.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -0
- novelwriter/assets/icons/typicons_light/nw_font.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_quote.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/cyberpunk_night.conf +5 -3
- novelwriter/assets/syntax/default_dark.conf +32 -18
- novelwriter/assets/syntax/default_light.conf +24 -10
- novelwriter/assets/syntax/dracula.conf +44 -0
- novelwriter/assets/syntax/grey_dark.conf +5 -4
- novelwriter/assets/syntax/grey_light.conf +5 -4
- novelwriter/assets/syntax/light_owl.conf +7 -6
- novelwriter/assets/syntax/night_owl.conf +7 -6
- novelwriter/assets/syntax/snazzy.conf +42 -0
- novelwriter/assets/syntax/solarized_dark.conf +4 -3
- novelwriter/assets/syntax/solarized_light.conf +4 -3
- novelwriter/assets/syntax/tango.conf +27 -11
- novelwriter/assets/syntax/tomorrow.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night.conf +7 -6
- novelwriter/assets/syntax/tomorrow_night_blue.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_bright.conf +6 -5
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +6 -5
- novelwriter/assets/text/credits_en.htm +4 -1
- novelwriter/assets/themes/cyberpunk_night.conf +2 -0
- novelwriter/assets/themes/default_dark.conf +1 -0
- novelwriter/assets/themes/default_light.conf +1 -0
- novelwriter/assets/themes/dracula.conf +47 -0
- novelwriter/assets/themes/solarized_dark.conf +1 -0
- novelwriter/assets/themes/solarized_light.conf +1 -0
- novelwriter/common.py +31 -9
- novelwriter/config.py +118 -84
- novelwriter/constants.py +40 -26
- novelwriter/core/buildsettings.py +63 -66
- novelwriter/core/coretools.py +2 -22
- novelwriter/core/docbuild.py +51 -40
- novelwriter/core/document.py +3 -5
- novelwriter/core/index.py +115 -45
- novelwriter/core/item.py +8 -19
- novelwriter/core/options.py +2 -4
- novelwriter/core/project.py +23 -57
- novelwriter/core/projectdata.py +1 -3
- novelwriter/core/projectxml.py +12 -15
- novelwriter/core/sessions.py +3 -5
- novelwriter/core/spellcheck.py +4 -9
- novelwriter/core/status.py +211 -164
- novelwriter/core/storage.py +0 -8
- novelwriter/core/tohtml.py +94 -100
- novelwriter/core/tokenizer.py +199 -112
- novelwriter/core/{tomd.py → tomarkdown.py} +97 -78
- novelwriter/core/toodt.py +212 -148
- novelwriter/core/toqdoc.py +403 -0
- novelwriter/core/tree.py +5 -7
- novelwriter/dialogs/about.py +3 -5
- novelwriter/dialogs/docmerge.py +1 -3
- novelwriter/dialogs/docsplit.py +1 -3
- novelwriter/dialogs/editlabel.py +0 -2
- novelwriter/dialogs/preferences.py +111 -88
- novelwriter/dialogs/projectsettings.py +216 -180
- novelwriter/dialogs/quotes.py +3 -4
- novelwriter/dialogs/wordlist.py +3 -9
- novelwriter/enum.py +31 -25
- novelwriter/error.py +8 -15
- novelwriter/extensions/circularprogress.py +5 -6
- novelwriter/extensions/configlayout.py +18 -18
- novelwriter/extensions/eventfilters.py +1 -5
- novelwriter/extensions/modified.py +50 -13
- novelwriter/extensions/novelselector.py +1 -3
- novelwriter/extensions/pagedsidebar.py +9 -12
- novelwriter/extensions/simpleprogress.py +1 -3
- novelwriter/extensions/statusled.py +1 -3
- novelwriter/extensions/switch.py +4 -6
- novelwriter/extensions/switchbox.py +7 -6
- novelwriter/extensions/versioninfo.py +3 -9
- novelwriter/gui/doceditor.py +98 -126
- novelwriter/gui/dochighlight.py +237 -183
- novelwriter/gui/docviewer.py +46 -94
- novelwriter/gui/docviewerpanel.py +3 -10
- novelwriter/gui/editordocument.py +1 -3
- novelwriter/gui/itemdetails.py +7 -11
- novelwriter/gui/mainmenu.py +11 -7
- novelwriter/gui/noveltree.py +11 -24
- novelwriter/gui/outline.py +11 -23
- novelwriter/gui/projtree.py +26 -43
- novelwriter/gui/search.py +1 -3
- novelwriter/gui/sidebar.py +2 -6
- novelwriter/gui/statusbar.py +6 -10
- novelwriter/gui/theme.py +23 -48
- novelwriter/guimain.py +50 -71
- novelwriter/shared.py +30 -15
- novelwriter/tools/dictionaries.py +8 -12
- novelwriter/tools/lipsum.py +2 -4
- novelwriter/tools/manusbuild.py +1 -3
- novelwriter/tools/manuscript.py +66 -145
- novelwriter/tools/manussettings.py +67 -73
- novelwriter/tools/noveldetails.py +6 -11
- novelwriter/tools/welcome.py +2 -16
- novelwriter/tools/writingstats.py +6 -9
- novelwriter/types.py +45 -3
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4.3.dist-info → novelWriter-2.5b1.dist-info}/top_level.txt +0 -0
@@ -27,20 +27,26 @@ from __future__ import annotations
|
|
27
27
|
import logging
|
28
28
|
|
29
29
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
30
|
-
from PyQt5.QtGui import QCloseEvent, QColor
|
30
|
+
from PyQt5.QtGui import QCloseEvent, QColor
|
31
31
|
from PyQt5.QtWidgets import (
|
32
|
-
QApplication, QColorDialog, QDialog, QDialogButtonBox,
|
33
|
-
QLineEdit,
|
34
|
-
QVBoxLayout, QWidget
|
32
|
+
QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox,
|
33
|
+
QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
|
34
|
+
QTreeWidgetItem, QVBoxLayout, QWidget
|
35
35
|
)
|
36
36
|
|
37
37
|
from novelwriter import CONFIG, SHARED
|
38
38
|
from novelwriter.common import simplified
|
39
|
+
from novelwriter.constants import nwLabels, trConst
|
40
|
+
from novelwriter.core.status import NWStatus, StatusEntry
|
41
|
+
from novelwriter.enum import nwStatusShape
|
39
42
|
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
|
40
43
|
from novelwriter.extensions.modified import NComboBox, NIconToolButton
|
41
44
|
from novelwriter.extensions.pagedsidebar import NPagedSideBar
|
42
45
|
from novelwriter.extensions.switch import NSwitch
|
43
|
-
from novelwriter.types import
|
46
|
+
from novelwriter.types import (
|
47
|
+
QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding,
|
48
|
+
QtUserRole
|
49
|
+
)
|
44
50
|
|
45
51
|
logger = logging.getLogger(__name__)
|
46
52
|
|
@@ -179,19 +185,19 @@ class GuiProjectSettings(QDialog):
|
|
179
185
|
|
180
186
|
rebuildTrees = False
|
181
187
|
|
182
|
-
if self.statusPage.
|
183
|
-
|
184
|
-
project.
|
188
|
+
if self.statusPage.changed:
|
189
|
+
logger.debug("Updating status labels")
|
190
|
+
project.data.itemStatus.update(self.statusPage.getNewList())
|
185
191
|
rebuildTrees = True
|
186
192
|
|
187
|
-
if self.importPage.
|
188
|
-
|
189
|
-
project.
|
193
|
+
if self.importPage.changed:
|
194
|
+
logger.debug("Updating importance labels")
|
195
|
+
project.data.itemImport.update(self.importPage.getNewList())
|
190
196
|
rebuildTrees = True
|
191
197
|
|
192
|
-
if self.replacePage.
|
193
|
-
|
194
|
-
project.data.setAutoReplace(
|
198
|
+
if self.replacePage.changed:
|
199
|
+
logger.debug("Updating auto-replace settings")
|
200
|
+
project.data.setAutoReplace(self.replacePage.getNewList())
|
195
201
|
|
196
202
|
self.newProjectSettingsReady.emit(rebuildTrees)
|
197
203
|
QApplication.processEvents()
|
@@ -221,8 +227,6 @@ class GuiProjectSettings(QDialog):
|
|
221
227
|
|
222
228
|
return
|
223
229
|
|
224
|
-
# END Class GuiProjectSettings
|
225
|
-
|
226
230
|
|
227
231
|
class _SettingsPage(NScrollableForm):
|
228
232
|
|
@@ -296,17 +300,15 @@ class _SettingsPage(NScrollableForm):
|
|
296
300
|
|
297
301
|
return
|
298
302
|
|
299
|
-
# END Class _SettingsPage
|
300
|
-
|
301
303
|
|
302
304
|
class _StatusPage(NFixedPage):
|
303
305
|
|
304
|
-
|
305
|
-
|
306
|
+
C_DATA = 0
|
307
|
+
C_LABEL = 0
|
308
|
+
C_USAGE = 1
|
306
309
|
|
307
|
-
|
308
|
-
|
309
|
-
NUM_ROLE = QtUserRole + 2
|
310
|
+
D_KEY = QtUserRole
|
311
|
+
D_ENTRY = QtUserRole + 1
|
310
312
|
|
311
313
|
def __init__(self, parent: QWidget, isStatus: bool) -> None:
|
312
314
|
super().__init__(parent=parent)
|
@@ -325,12 +327,21 @@ class _StatusPage(NFixedPage):
|
|
325
327
|
)
|
326
328
|
|
327
329
|
self._changed = False
|
328
|
-
self.
|
329
|
-
self.
|
330
|
+
self._color = QColor(100, 100, 100)
|
331
|
+
self._shape = nwStatusShape.SQUARE
|
332
|
+
self._icons = {}
|
330
333
|
|
331
|
-
self.
|
334
|
+
self._iPx = SHARED.theme.baseIconHeight
|
332
335
|
iSz = SHARED.theme.baseIconSize
|
333
|
-
|
336
|
+
bPd = CONFIG.pxInt(4)
|
337
|
+
|
338
|
+
iColor = self.palette().text().color()
|
339
|
+
|
340
|
+
# Labels
|
341
|
+
self.trCountNone = self.tr("Not in use")
|
342
|
+
self.trCountOne = self.tr("Used once")
|
343
|
+
self.trCountMore = self.tr("Used by {0} items")
|
344
|
+
self.trSelColor = self.tr("Select Colour")
|
334
345
|
|
335
346
|
# Title
|
336
347
|
self.pageTitle = NColourLabel(
|
@@ -341,12 +352,14 @@ class _StatusPage(NFixedPage):
|
|
341
352
|
# List Box
|
342
353
|
self.listBox = QTreeWidget(self)
|
343
354
|
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
|
344
|
-
self.listBox.
|
345
|
-
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
355
|
+
self.listBox.setColumnWidth(self.C_LABEL, wCol0)
|
346
356
|
self.listBox.setIndentation(0)
|
357
|
+
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
358
|
+
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
359
|
+
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
|
347
360
|
|
348
|
-
for key, entry in status.
|
349
|
-
self._addItem(key, entry
|
361
|
+
for key, entry in status.iterItems():
|
362
|
+
self._addItem(key, StatusEntry.duplicate(entry))
|
350
363
|
|
351
364
|
# List Controls
|
352
365
|
self.addButton = NIconToolButton(self, iSz, "add")
|
@@ -367,16 +380,43 @@ class _StatusPage(NFixedPage):
|
|
367
380
|
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
368
381
|
self.editName.setEnabled(False)
|
369
382
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
self.colButton.setEnabled(False)
|
375
|
-
self.colButton.clicked.connect(self._selectColour)
|
383
|
+
buttonStyle = (
|
384
|
+
f"QToolButton {{padding: 0 {bPd}px;}} "
|
385
|
+
"QToolButton::menu-indicator {image: none;}"
|
386
|
+
)
|
376
387
|
|
377
|
-
self.
|
378
|
-
self.
|
379
|
-
self.
|
388
|
+
self.colorButton = NIconToolButton(self, iSz)
|
389
|
+
self.colorButton.setToolTip(self.tr("Colour"))
|
390
|
+
self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
391
|
+
self.colorButton.setStyleSheet(buttonStyle)
|
392
|
+
self.colorButton.setEnabled(False)
|
393
|
+
self.colorButton.clicked.connect(self._selectColour)
|
394
|
+
|
395
|
+
def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None:
|
396
|
+
for shape, label in items.items():
|
397
|
+
icon = NWStatus.createIcon(self._iPx, iColor, shape)
|
398
|
+
action = menu.addAction(icon, trConst(label))
|
399
|
+
action.triggered.connect(lambda _, shape=shape: self._selectShape(shape))
|
400
|
+
self._icons[shape] = icon
|
401
|
+
|
402
|
+
self.shapeMenu = QMenu(self)
|
403
|
+
buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN)
|
404
|
+
buildMenu(self.shapeMenu.addMenu(self.tr("Circles ...")), nwLabels.SHAPES_CIRCLE)
|
405
|
+
buildMenu(self.shapeMenu.addMenu(self.tr("Bars ...")), nwLabels.SHAPES_BARS)
|
406
|
+
buildMenu(self.shapeMenu.addMenu(self.tr("Blocks ...")), nwLabels.SHAPES_BLOCKS)
|
407
|
+
|
408
|
+
self.shapeButton = NIconToolButton(self, iSz)
|
409
|
+
self.shapeButton.setMenu(self.shapeMenu)
|
410
|
+
self.shapeButton.setToolTip(self.tr("Shape"))
|
411
|
+
self.shapeButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
412
|
+
self.shapeButton.setStyleSheet(buttonStyle)
|
413
|
+
self.shapeButton.setEnabled(False)
|
414
|
+
|
415
|
+
self.applyButton = QToolButton(self)
|
416
|
+
self.applyButton.setText(self.tr("Apply"))
|
417
|
+
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
418
|
+
self.applyButton.setEnabled(False)
|
419
|
+
self.applyButton.clicked.connect(self._applyChanges)
|
380
420
|
|
381
421
|
# Assemble
|
382
422
|
self.listControls = QVBoxLayout()
|
@@ -387,28 +427,30 @@ class _StatusPage(NFixedPage):
|
|
387
427
|
self.listControls.addStretch(1)
|
388
428
|
|
389
429
|
self.editBox = QHBoxLayout()
|
390
|
-
self.editBox.addWidget(self.editName)
|
391
|
-
self.editBox.addWidget(self.
|
392
|
-
self.editBox.addWidget(self.
|
430
|
+
self.editBox.addWidget(self.editName, 1)
|
431
|
+
self.editBox.addWidget(self.colorButton, 0)
|
432
|
+
self.editBox.addWidget(self.shapeButton, 0)
|
433
|
+
self.editBox.addWidget(self.applyButton, 0)
|
393
434
|
|
394
435
|
self.mainBox = QVBoxLayout()
|
395
|
-
self.mainBox.addWidget(self.listBox)
|
396
|
-
self.mainBox.addLayout(self.editBox)
|
436
|
+
self.mainBox.addWidget(self.listBox, 1)
|
437
|
+
self.mainBox.addLayout(self.editBox, 0)
|
397
438
|
|
398
439
|
self.innerBox = QHBoxLayout()
|
399
|
-
self.innerBox.addLayout(self.mainBox)
|
400
|
-
self.innerBox.addLayout(self.listControls)
|
440
|
+
self.innerBox.addLayout(self.mainBox, 1)
|
441
|
+
self.innerBox.addLayout(self.listControls, 0)
|
401
442
|
|
402
443
|
self.outerBox = QVBoxLayout()
|
403
|
-
self.outerBox.addWidget(self.pageTitle)
|
404
|
-
self.outerBox.addLayout(self.innerBox)
|
444
|
+
self.outerBox.addWidget(self.pageTitle, 0)
|
445
|
+
self.outerBox.addLayout(self.innerBox, 1)
|
405
446
|
|
406
447
|
self.setCentralLayout(self.outerBox)
|
448
|
+
self._setButtonIcons()
|
407
449
|
|
408
450
|
return
|
409
451
|
|
410
452
|
@property
|
411
|
-
def
|
453
|
+
def changed(self) -> bool:
|
412
454
|
"""The user changed these settings."""
|
413
455
|
return self._changed
|
414
456
|
|
@@ -416,20 +458,17 @@ class _StatusPage(NFixedPage):
|
|
416
458
|
# Methods
|
417
459
|
##
|
418
460
|
|
419
|
-
def getNewList(self) -> tuple[
|
461
|
+
def getNewList(self) -> list[tuple[str | None, StatusEntry]]:
|
420
462
|
"""Return list of entries."""
|
421
463
|
if self._changed:
|
422
|
-
|
464
|
+
update = []
|
423
465
|
for n in range(self.listBox.topLevelItemCount()):
|
424
|
-
item
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
})
|
431
|
-
return newList, self._colDeleted
|
432
|
-
return [], []
|
466
|
+
if item := self.listBox.topLevelItem(n):
|
467
|
+
key = item.data(self.C_DATA, self.D_KEY)
|
468
|
+
entry = item.data(self.C_DATA, self.D_ENTRY)
|
469
|
+
update.append((key, entry))
|
470
|
+
return update
|
471
|
+
return []
|
433
472
|
|
434
473
|
def columnWidth(self) -> int:
|
435
474
|
"""Return the size of the header column."""
|
@@ -442,124 +481,119 @@ class _StatusPage(NFixedPage):
|
|
442
481
|
@pyqtSlot()
|
443
482
|
def _selectColour(self) -> None:
|
444
483
|
"""Open a dialog to select the status icon colour."""
|
445
|
-
if self.
|
446
|
-
|
447
|
-
|
448
|
-
)
|
449
|
-
if newCol.isValid():
|
450
|
-
self._selColour = newCol
|
451
|
-
pixmap = QPixmap(self.iPx, self.iPx)
|
452
|
-
pixmap.fill(newCol)
|
453
|
-
self.colButton.setIcon(QIcon(pixmap))
|
454
|
-
self.colButton.setIconSize(pixmap.rect().size())
|
484
|
+
if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
|
485
|
+
self._color = color
|
486
|
+
self._setButtonIcons()
|
455
487
|
return
|
456
488
|
|
457
489
|
@pyqtSlot()
|
458
490
|
def _newItem(self) -> None:
|
459
491
|
"""Create a new status item."""
|
460
|
-
|
492
|
+
color = QColor(100, 100, 100)
|
493
|
+
shape = nwStatusShape.SQUARE
|
494
|
+
icon = NWStatus.createIcon(self._iPx, color, shape)
|
495
|
+
self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
|
461
496
|
self._changed = True
|
462
497
|
return
|
463
498
|
|
464
499
|
@pyqtSlot()
|
465
500
|
def _delItem(self) -> None:
|
466
501
|
"""Delete a status item."""
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
if
|
502
|
+
if item := self._getSelectedItem():
|
503
|
+
iRow = self.listBox.indexOfTopLevelItem(item)
|
504
|
+
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
|
505
|
+
if entry.count > 0:
|
471
506
|
SHARED.error(self.tr("Cannot delete a status item that is in use."))
|
472
507
|
else:
|
473
508
|
self.listBox.takeTopLevelItem(iRow)
|
474
|
-
self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
475
509
|
self._changed = True
|
476
510
|
return
|
477
511
|
|
478
512
|
@pyqtSlot()
|
479
|
-
def
|
513
|
+
def _applyChanges(self) -> None:
|
480
514
|
"""Save changes made to a status item."""
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
515
|
+
if item := self._getSelectedItem():
|
516
|
+
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
|
517
|
+
|
518
|
+
name = simplified(self.editName.text())
|
519
|
+
icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
|
520
|
+
|
521
|
+
entry.name = name
|
522
|
+
entry.color = self._color
|
523
|
+
entry.shape = self._shape
|
524
|
+
entry.icon = icon
|
525
|
+
|
526
|
+
item.setText(self.C_LABEL, name)
|
527
|
+
item.setIcon(self.C_LABEL, icon)
|
528
|
+
|
488
529
|
self._changed = True
|
530
|
+
|
489
531
|
return
|
490
532
|
|
491
533
|
@pyqtSlot()
|
492
|
-
def
|
534
|
+
def _selectionChanged(self) -> None:
|
493
535
|
"""Extract the info of a selected item and populate the settings
|
494
536
|
boxes and button. If no item is selected, clear the form.
|
495
537
|
"""
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
self.
|
503
|
-
self.editName.setText(name)
|
504
|
-
self.colButton.setIcon(QIcon(pixmap))
|
538
|
+
if item := self._getSelectedItem():
|
539
|
+
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
|
540
|
+
self._color = entry.color
|
541
|
+
self._shape = entry.shape
|
542
|
+
self._setButtonIcons()
|
543
|
+
|
544
|
+
self.editName.setText(entry.name)
|
505
545
|
self.editName.selectAll()
|
506
546
|
self.editName.setFocus()
|
547
|
+
|
507
548
|
self.editName.setEnabled(True)
|
508
|
-
self.
|
509
|
-
self.
|
549
|
+
self.colorButton.setEnabled(True)
|
550
|
+
self.shapeButton.setEnabled(True)
|
551
|
+
self.applyButton.setEnabled(True)
|
552
|
+
|
510
553
|
else:
|
511
|
-
|
512
|
-
|
513
|
-
self.
|
554
|
+
self._color = QColor(100, 100, 100)
|
555
|
+
self._shape = nwStatusShape.SQUARE
|
556
|
+
self._setButtonIcons()
|
514
557
|
self.editName.setText("")
|
515
|
-
|
558
|
+
|
516
559
|
self.editName.setEnabled(False)
|
517
|
-
self.
|
518
|
-
self.
|
560
|
+
self.colorButton.setEnabled(False)
|
561
|
+
self.shapeButton.setEnabled(False)
|
562
|
+
self.applyButton.setEnabled(False)
|
519
563
|
return
|
520
564
|
|
521
565
|
##
|
522
566
|
# Internal Functions
|
523
567
|
##
|
524
568
|
|
525
|
-
def
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
569
|
+
def _selectShape(self, shape: nwStatusShape) -> None:
|
570
|
+
"""Set the current shape."""
|
571
|
+
self._shape = shape
|
572
|
+
self._setButtonIcons()
|
573
|
+
return
|
530
574
|
|
575
|
+
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
|
576
|
+
"""Add a status item to the list."""
|
531
577
|
item = QTreeWidgetItem()
|
532
|
-
item.setText(self.
|
533
|
-
item.setIcon(self.
|
534
|
-
item.
|
535
|
-
item.setData(self.
|
536
|
-
item.setData(self.
|
537
|
-
item.setText(self.COL_USAGE, self._usageString(count))
|
538
|
-
|
578
|
+
item.setText(self.C_LABEL, entry.name)
|
579
|
+
item.setIcon(self.C_LABEL, entry.icon)
|
580
|
+
item.setText(self.C_USAGE, self._usageString(entry.count))
|
581
|
+
item.setData(self.C_DATA, self.D_KEY, key)
|
582
|
+
item.setData(self.C_DATA, self.D_ENTRY, entry)
|
539
583
|
self.listBox.addTopLevelItem(item)
|
540
|
-
|
541
584
|
return
|
542
585
|
|
543
586
|
def _moveItem(self, step: int) -> None:
|
544
587
|
"""Move and item up or down step."""
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
cItem = self.listBox.takeTopLevelItem(tIndex)
|
556
|
-
self.listBox.insertTopLevelItem(nIndex, cItem)
|
557
|
-
self.listBox.clearSelection()
|
558
|
-
|
559
|
-
if cItem is not None:
|
560
|
-
cItem.setSelected(True)
|
561
|
-
self._changed = True
|
562
|
-
|
588
|
+
if item := self._getSelectedItem():
|
589
|
+
tIdx = self.listBox.indexOfTopLevelItem(item)
|
590
|
+
nItm = self.listBox.topLevelItemCount()
|
591
|
+
nIdx = tIdx + step
|
592
|
+
if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)):
|
593
|
+
self.listBox.insertTopLevelItem(nIdx, cItem)
|
594
|
+
self.listBox.clearSelection()
|
595
|
+
cItem.setSelected(True)
|
596
|
+
self._changed = True
|
563
597
|
return
|
564
598
|
|
565
599
|
def _getSelectedItem(self) -> QTreeWidgetItem | None:
|
@@ -571,19 +605,24 @@ class _StatusPage(NFixedPage):
|
|
571
605
|
def _usageString(self, count: int) -> str:
|
572
606
|
"""Generate usage string."""
|
573
607
|
if count == 0:
|
574
|
-
return self.
|
608
|
+
return self.trCountNone
|
575
609
|
elif count == 1:
|
576
|
-
return self.
|
610
|
+
return self.trCountOne
|
577
611
|
else:
|
578
|
-
return self.
|
612
|
+
return self.trCountMore.format(count)
|
579
613
|
|
580
|
-
|
614
|
+
def _setButtonIcons(self) -> None:
|
615
|
+
"""Set the colour of the colour button."""
|
616
|
+
icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
|
617
|
+
self.colorButton.setIcon(icon)
|
618
|
+
self.shapeButton.setIcon(self._icons[self._shape])
|
619
|
+
return
|
581
620
|
|
582
621
|
|
583
622
|
class _ReplacePage(NFixedPage):
|
584
623
|
|
585
|
-
|
586
|
-
|
624
|
+
C_KEY = 0
|
625
|
+
C_REPL = 1
|
587
626
|
|
588
627
|
def __init__(self, parent: QWidget) -> None:
|
589
628
|
super().__init__(parent=parent)
|
@@ -605,15 +644,17 @@ class _ReplacePage(NFixedPage):
|
|
605
644
|
# List Box
|
606
645
|
self.listBox = QTreeWidget(self)
|
607
646
|
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
|
608
|
-
self.listBox.setColumnWidth(self.
|
647
|
+
self.listBox.setColumnWidth(self.C_KEY, wCol0)
|
609
648
|
self.listBox.setIndentation(0)
|
610
|
-
self.listBox.
|
649
|
+
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
650
|
+
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
651
|
+
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
|
611
652
|
|
612
653
|
for aKey, aVal in SHARED.project.data.autoReplace.items():
|
613
654
|
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
|
614
655
|
self.listBox.addTopLevelItem(newItem)
|
615
656
|
|
616
|
-
self.listBox.sortByColumn(self.
|
657
|
+
self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
|
617
658
|
self.listBox.setSortingEnabled(True)
|
618
659
|
|
619
660
|
# List Controls
|
@@ -633,8 +674,10 @@ class _ReplacePage(NFixedPage):
|
|
633
674
|
self.editValue.setEnabled(False)
|
634
675
|
self.editValue.setMaxLength(80)
|
635
676
|
|
636
|
-
self.
|
637
|
-
self.
|
677
|
+
self.applyButton = QToolButton(self)
|
678
|
+
self.applyButton.setText(self.tr("Apply"))
|
679
|
+
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
|
680
|
+
self.applyButton.clicked.connect(self._applyChanges)
|
638
681
|
|
639
682
|
# Assemble
|
640
683
|
self.listControls = QVBoxLayout()
|
@@ -645,11 +688,11 @@ class _ReplacePage(NFixedPage):
|
|
645
688
|
self.editBox = QHBoxLayout()
|
646
689
|
self.editBox.addWidget(self.editKey, 4)
|
647
690
|
self.editBox.addWidget(self.editValue, 5)
|
648
|
-
self.editBox.addWidget(self.
|
691
|
+
self.editBox.addWidget(self.applyButton, 0)
|
649
692
|
|
650
693
|
self.mainBox = QVBoxLayout()
|
651
|
-
self.mainBox.addWidget(self.listBox)
|
652
|
-
self.mainBox.addLayout(self.editBox)
|
694
|
+
self.mainBox.addWidget(self.listBox, 1)
|
695
|
+
self.mainBox.addLayout(self.editBox, 0)
|
653
696
|
|
654
697
|
self.innerBox = QHBoxLayout()
|
655
698
|
self.innerBox.addLayout(self.mainBox)
|
@@ -664,7 +707,7 @@ class _ReplacePage(NFixedPage):
|
|
664
707
|
return
|
665
708
|
|
666
709
|
@property
|
667
|
-
def
|
710
|
+
def changed(self) -> bool:
|
668
711
|
"""The user changed these settings."""
|
669
712
|
return self._changed
|
670
713
|
|
@@ -672,15 +715,13 @@ class _ReplacePage(NFixedPage):
|
|
672
715
|
# Methods
|
673
716
|
##
|
674
717
|
|
675
|
-
def getNewList(self) -> dict:
|
718
|
+
def getNewList(self) -> dict[str, str]:
|
676
719
|
"""Extract the list from the widget."""
|
677
720
|
new = {}
|
678
721
|
for n in range(self.listBox.topLevelItemCount()):
|
679
|
-
if
|
680
|
-
|
681
|
-
|
682
|
-
if len(aKey) > 0:
|
683
|
-
new[aKey] = aVal
|
722
|
+
if item := self.listBox.topLevelItem(n):
|
723
|
+
if key := self._stripNotAllowed(item.text(self.C_KEY)):
|
724
|
+
new[key] = item.text(self.C_REPL)
|
684
725
|
return new
|
685
726
|
|
686
727
|
def columnWidth(self) -> int:
|
@@ -692,51 +733,48 @@ class _ReplacePage(NFixedPage):
|
|
692
733
|
##
|
693
734
|
|
694
735
|
@pyqtSlot()
|
695
|
-
def
|
736
|
+
def _selectionChanged(self) -> None:
|
696
737
|
"""Extract the details from the selected item and populate the
|
697
738
|
edit form.
|
698
739
|
"""
|
699
|
-
if
|
700
|
-
editKey
|
701
|
-
|
702
|
-
self.editKey.setText(editKey)
|
703
|
-
self.editValue.setText(editVal)
|
740
|
+
if item := self._getSelectedItem():
|
741
|
+
self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
|
742
|
+
self.editValue.setText(item.text(self.C_REPL))
|
704
743
|
self.editKey.setEnabled(True)
|
705
744
|
self.editValue.setEnabled(True)
|
706
745
|
self.editKey.selectAll()
|
707
746
|
self.editKey.setFocus()
|
747
|
+
else:
|
748
|
+
self.editKey.setText("")
|
749
|
+
self.editValue.setText("")
|
750
|
+
self.editKey.setEnabled(False)
|
751
|
+
self.editValue.setEnabled(False)
|
708
752
|
return
|
709
753
|
|
710
754
|
@pyqtSlot()
|
711
|
-
def
|
755
|
+
def _applyChanges(self) -> None:
|
712
756
|
"""Save the form data into the list widget."""
|
713
|
-
if
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
selItem.setText(self.COL_REPL, newVal)
|
720
|
-
self.editKey.clear()
|
721
|
-
self.editValue.clear()
|
722
|
-
self.editKey.setEnabled(False)
|
723
|
-
self.editValue.setEnabled(False)
|
724
|
-
self.listBox.clearSelection()
|
757
|
+
if item := self._getSelectedItem():
|
758
|
+
key = self._stripNotAllowed(self.editKey.text())
|
759
|
+
value = self.editValue.text()
|
760
|
+
if key and value:
|
761
|
+
item.setText(self.C_KEY, f"<{key}>")
|
762
|
+
item.setText(self.C_REPL, value)
|
725
763
|
self._changed = True
|
726
764
|
return
|
727
765
|
|
728
766
|
@pyqtSlot()
|
729
767
|
def _addEntry(self) -> None:
|
730
768
|
"""Add a new list entry."""
|
731
|
-
|
732
|
-
self.listBox.addTopLevelItem(QTreeWidgetItem([
|
769
|
+
key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
|
770
|
+
self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
|
733
771
|
return
|
734
772
|
|
735
773
|
@pyqtSlot()
|
736
774
|
def _delEntry(self) -> None:
|
737
775
|
"""Delete the selected entry."""
|
738
|
-
if
|
739
|
-
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(
|
776
|
+
if item := self._getSelectedItem():
|
777
|
+
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
|
740
778
|
self._changed = True
|
741
779
|
return
|
742
780
|
|
@@ -753,5 +791,3 @@ class _ReplacePage(NFixedPage):
|
|
753
791
|
def _stripNotAllowed(self, key: str) -> str:
|
754
792
|
"""Clean up the replace key string."""
|
755
793
|
return "".join(c for c in key if c.isalnum())
|
756
|
-
|
757
|
-
# END Class _ReplacePage
|
novelwriter/dialogs/quotes.py
CHANGED
@@ -25,15 +25,15 @@ from __future__ import annotations
|
|
25
25
|
|
26
26
|
import logging
|
27
27
|
|
28
|
-
from PyQt5.QtGui import QFontMetrics
|
29
28
|
from PyQt5.QtCore import QSize, pyqtSlot
|
29
|
+
from PyQt5.QtGui import QFontMetrics
|
30
30
|
from PyQt5.QtWidgets import (
|
31
31
|
QDialog, QDialogButtonBox, QFrame, QHBoxLayout, QLabel, QListWidget,
|
32
32
|
QListWidgetItem, QVBoxLayout, QWidget
|
33
33
|
)
|
34
34
|
|
35
35
|
from novelwriter import CONFIG
|
36
|
-
from novelwriter.constants import
|
36
|
+
from novelwriter.constants import nwQuotes, trConst
|
37
37
|
from novelwriter.types import QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, QtUserRole
|
38
38
|
|
39
39
|
logger = logging.getLogger(__name__)
|
@@ -50,6 +50,7 @@ class GuiQuoteSelect(QDialog):
|
|
50
50
|
|
51
51
|
logger.debug("Create: GuiQuoteSelect")
|
52
52
|
self.setObjectName("GuiQuoteSelect")
|
53
|
+
self.setWindowTitle(self.tr("Select Quote Style"))
|
53
54
|
|
54
55
|
self.outerBox = QVBoxLayout()
|
55
56
|
self.innerBox = QHBoxLayout()
|
@@ -141,5 +142,3 @@ class GuiQuoteSelect(QDialog):
|
|
141
142
|
self.previewLabel.setText(quote)
|
142
143
|
self._selected = quote
|
143
144
|
return
|
144
|
-
|
145
|
-
# END Class GuiQuoteSelect
|