novelWriter 2.6b2__py3-none-any.whl → 2.6.1__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.
Files changed (107) hide show
  1. {novelWriter-2.6b2.dist-info → novelWriter-2.6.1.dist-info}/METADATA +2 -2
  2. {novelWriter-2.6b2.dist-info → novelWriter-2.6.1.dist-info}/RECORD +107 -106
  3. {novelWriter-2.6b2.dist-info → novelWriter-2.6.1.dist-info}/WHEEL +1 -1
  4. novelwriter/__init__.py +4 -4
  5. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  6. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  7. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  8. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  9. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  10. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  11. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  12. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  13. novelwriter/assets/i18n/nw_pl_PL.qm +0 -0
  14. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  15. novelwriter/assets/i18n/nw_ru_RU.qm +0 -0
  16. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  17. novelwriter/assets/i18n/project_de_DE.json +2 -0
  18. novelwriter/assets/i18n/project_en_US.json +2 -0
  19. novelwriter/assets/i18n/project_es_419.json +2 -0
  20. novelwriter/assets/i18n/project_fr_FR.json +3 -1
  21. novelwriter/assets/i18n/project_it_IT.json +2 -0
  22. novelwriter/assets/i18n/project_ja_JP.json +2 -0
  23. novelwriter/assets/i18n/project_nb_NO.json +2 -0
  24. novelwriter/assets/i18n/project_nl_NL.json +2 -0
  25. novelwriter/assets/i18n/project_pl_PL.json +2 -0
  26. novelwriter/assets/i18n/project_pt_BR.json +2 -0
  27. novelwriter/assets/i18n/project_zh_CN.json +2 -0
  28. novelwriter/assets/manual.pdf +0 -0
  29. novelwriter/assets/manual_fr_FR.pdf +0 -0
  30. novelwriter/assets/sample.zip +0 -0
  31. novelwriter/common.py +1 -1
  32. novelwriter/config.py +45 -20
  33. novelwriter/constants.py +40 -36
  34. novelwriter/core/buildsettings.py +8 -1
  35. novelwriter/core/coretools.py +1 -1
  36. novelwriter/core/docbuild.py +1 -1
  37. novelwriter/core/document.py +1 -1
  38. novelwriter/core/index.py +1 -1
  39. novelwriter/core/item.py +1 -1
  40. novelwriter/core/itemmodel.py +1 -1
  41. novelwriter/core/options.py +1 -1
  42. novelwriter/core/project.py +3 -7
  43. novelwriter/core/projectdata.py +8 -2
  44. novelwriter/core/projectxml.py +1 -1
  45. novelwriter/core/sessions.py +1 -1
  46. novelwriter/core/spellcheck.py +1 -1
  47. novelwriter/core/status.py +17 -3
  48. novelwriter/core/storage.py +1 -1
  49. novelwriter/core/tree.py +1 -1
  50. novelwriter/dialogs/about.py +1 -1
  51. novelwriter/dialogs/docmerge.py +1 -1
  52. novelwriter/dialogs/docsplit.py +1 -1
  53. novelwriter/dialogs/editlabel.py +1 -1
  54. novelwriter/dialogs/preferences.py +16 -7
  55. novelwriter/dialogs/projectsettings.py +146 -96
  56. novelwriter/dialogs/quotes.py +1 -1
  57. novelwriter/dialogs/wordlist.py +11 -10
  58. novelwriter/enum.py +1 -8
  59. novelwriter/error.py +2 -2
  60. novelwriter/extensions/configlayout.py +1 -1
  61. novelwriter/extensions/eventfilters.py +1 -1
  62. novelwriter/extensions/modified.py +17 -5
  63. novelwriter/extensions/novelselector.py +4 -3
  64. novelwriter/extensions/pagedsidebar.py +4 -4
  65. novelwriter/extensions/progressbars.py +4 -4
  66. novelwriter/extensions/statusled.py +8 -9
  67. novelwriter/extensions/switch.py +3 -3
  68. novelwriter/extensions/switchbox.py +1 -1
  69. novelwriter/extensions/versioninfo.py +1 -1
  70. novelwriter/formats/shared.py +13 -12
  71. novelwriter/formats/todocx.py +2 -2
  72. novelwriter/formats/tohtml.py +31 -28
  73. novelwriter/formats/tokenizer.py +19 -13
  74. novelwriter/formats/tomarkdown.py +4 -3
  75. novelwriter/formats/toodt.py +2 -2
  76. novelwriter/formats/toqdoc.py +3 -1
  77. novelwriter/formats/toraw.py +1 -1
  78. novelwriter/gui/doceditor.py +39 -21
  79. novelwriter/gui/dochighlight.py +1 -1
  80. novelwriter/gui/docviewer.py +2 -2
  81. novelwriter/gui/docviewerpanel.py +1 -1
  82. novelwriter/gui/editordocument.py +1 -1
  83. novelwriter/gui/itemdetails.py +4 -3
  84. novelwriter/gui/mainmenu.py +1 -1
  85. novelwriter/gui/noveltree.py +1 -1
  86. novelwriter/gui/outline.py +8 -9
  87. novelwriter/gui/projtree.py +20 -25
  88. novelwriter/gui/search.py +2 -9
  89. novelwriter/gui/sidebar.py +1 -1
  90. novelwriter/gui/statusbar.py +26 -10
  91. novelwriter/gui/theme.py +1 -1
  92. novelwriter/guimain.py +2 -6
  93. novelwriter/shared.py +1 -1
  94. novelwriter/text/counting.py +1 -1
  95. novelwriter/text/patterns.py +1 -1
  96. novelwriter/tools/dictionaries.py +3 -3
  97. novelwriter/tools/lipsum.py +1 -1
  98. novelwriter/tools/manusbuild.py +1 -1
  99. novelwriter/tools/manuscript.py +7 -7
  100. novelwriter/tools/manussettings.py +9 -9
  101. novelwriter/tools/noveldetails.py +1 -1
  102. novelwriter/tools/welcome.py +2 -2
  103. novelwriter/tools/writingstats.py +1 -1
  104. novelwriter/types.py +2 -2
  105. {novelWriter-2.6b2.dist-info → novelWriter-2.6.1.dist-info}/LICENSE.md +0 -0
  106. {novelWriter-2.6b2.dist-info → novelWriter-2.6.1.dist-info}/entry_points.txt +0 -0
  107. {novelWriter-2.6b2.dist-info → novelWriter-2.6.1.dist-info}/top_level.txt +0 -0
@@ -7,7 +7,7 @@ Created: 2019-06-10 [0.1.5] GuiPreferences
7
7
  Rewritten: 2024-01-08 [2.3b1] GuiPreferences
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2019 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -271,10 +271,10 @@ class GuiPreferences(NDialog):
271
271
  self.tr("Include project notes in status bar word count"), self.incNotesWCount
272
272
  )
273
273
 
274
- # Auto Save
274
+ # Behaviour
275
275
  # =========
276
276
 
277
- title = self.tr("Auto Save")
277
+ title = self.tr("Behaviour")
278
278
  section += 1
279
279
  self.sidebar.addButton(title, section)
280
280
  self.mainForm.addGroupLabel(title, section)
@@ -301,6 +301,14 @@ class GuiPreferences(NDialog):
301
301
  self.tr("How often the project is automatically saved."), unit=self.tr("seconds")
302
302
  )
303
303
 
304
+ # Ask before exiting novelWriter
305
+ self.askBeforeExit = NSwitch(self)
306
+ self.askBeforeExit.setChecked(CONFIG.askBeforeExit)
307
+ self.mainForm.addRow(
308
+ self.tr("Ask before exiting novelWriter"), self.askBeforeExit,
309
+ self.tr("Only applies when a project is open.")
310
+ )
311
+
304
312
  # Project Backup
305
313
  # ==============
306
314
 
@@ -584,7 +592,7 @@ class GuiPreferences(NDialog):
584
592
  self.narratorBreak.setText(CONFIG.narratorBreak)
585
593
  self.mainForm.addRow(
586
594
  self.tr("Narrator break symbol"), self.narratorBreak,
587
- self.tr("Symbol to indicate a narrator break in dialogue")
595
+ self.tr("Symbol to indicate a narrator break in dialogue.")
588
596
  )
589
597
 
590
598
  self.narratorDialog = QLineEdit(self)
@@ -927,9 +935,10 @@ class GuiPreferences(NDialog):
927
935
  CONFIG.incNotesWCount = self.incNotesWCount.isChecked()
928
936
  CONFIG.setTextFont(self._textFont)
929
937
 
930
- # Auto Save
931
- CONFIG.autoSaveDoc = self.autoSaveDoc.value()
932
- CONFIG.autoSaveProj = self.autoSaveProj.value()
938
+ # Behaviour
939
+ CONFIG.autoSaveDoc = self.autoSaveDoc.value()
940
+ CONFIG.autoSaveProj = self.autoSaveProj.value()
941
+ CONFIG.askBeforeExit = self.askBeforeExit.isChecked()
933
942
 
934
943
  # Project Backup
935
944
  CONFIG.setBackupPath(self.backupPath)
@@ -7,7 +7,7 @@ Created: 2018-09-29 [0.0.1] GuiProjectSettings
7
7
  Rewritten: 2024-01-26 [2.3b1] GuiProjectSettings
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -24,18 +24,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
24
24
  """
25
25
  from __future__ import annotations
26
26
 
27
+ import csv
27
28
  import logging
28
29
 
30
+ from pathlib import Path
31
+
29
32
  from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
30
33
  from PyQt5.QtGui import QCloseEvent, QColor
31
34
  from PyQt5.QtWidgets import (
32
35
  QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox,
33
- QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
34
- QTreeWidgetItem, QVBoxLayout, QWidget
36
+ QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget,
37
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
35
38
  )
36
39
 
37
40
  from novelwriter import CONFIG, SHARED
38
- from novelwriter.common import qtLambda, simplified
41
+ from novelwriter.common import formatFileFilter, qtLambda, simplified
39
42
  from novelwriter.constants import nwLabels, trConst
40
43
  from novelwriter.core.status import NWStatus, StatusEntry
41
44
  from novelwriter.enum import nwStatusShape
@@ -310,11 +313,13 @@ class _StatusPage(NFixedPage):
310
313
  super().__init__(parent=parent)
311
314
 
312
315
  if isStatus:
313
- status = SHARED.project.data.itemStatus
316
+ self._kind = self.tr("Status")
317
+ self._store = SHARED.project.data.itemStatus
314
318
  pageLabel = self.tr("Novel Document Status Levels")
315
319
  colSetting = "statusColW"
316
320
  else:
317
- status = SHARED.project.data.itemImport
321
+ self._kind = self.tr("Importance")
322
+ self._store = SHARED.project.data.itemImport
318
323
  pageLabel = self.tr("Project Note Importance Levels")
319
324
  colSetting = "importColW"
320
325
 
@@ -352,29 +357,42 @@ class _StatusPage(NFixedPage):
352
357
  self.listBox.setIndentation(0)
353
358
  self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
354
359
  self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
355
- self.listBox.itemSelectionChanged.connect(self._selectionChanged)
360
+ self.listBox.itemSelectionChanged.connect(self._onSelectionChanged)
356
361
 
357
- for key, entry in status.iterItems():
362
+ for key, entry in self._store.iterItems():
358
363
  self._addItem(key, StatusEntry.duplicate(entry))
359
364
 
360
365
  # List Controls
361
366
  self.addButton = NIconToolButton(self, iSz, "add")
362
- self.addButton.clicked.connect(self._newItem)
367
+ self.addButton.setToolTip(self.tr("Add Label"))
368
+ self.addButton.clicked.connect(self._onItemCreate)
363
369
 
364
370
  self.delButton = NIconToolButton(self, iSz, "remove")
365
- self.delButton.clicked.connect(self._delItem)
371
+ self.delButton.setToolTip(self.tr("Delete Label"))
372
+ self.delButton.clicked.connect(self._onItemDelete)
366
373
 
367
374
  self.upButton = NIconToolButton(self, iSz, "up")
375
+ self.upButton.setToolTip(self.tr("Move Up"))
368
376
  self.upButton.clicked.connect(qtLambda(self._moveItem, -1))
369
377
 
370
- self.dnButton = NIconToolButton(self, iSz, "down")
371
- self.dnButton.clicked.connect(qtLambda(self._moveItem, 1))
378
+ self.downButton = NIconToolButton(self, iSz, "down")
379
+ self.downButton.setToolTip(self.tr("Move Down"))
380
+ self.downButton.clicked.connect(qtLambda(self._moveItem, 1))
381
+
382
+ self.importButton = NIconToolButton(self, iSz, "import")
383
+ self.importButton.setToolTip(self.tr("Import Labels"))
384
+ self.importButton.clicked.connect(self._importLabels)
385
+
386
+ self.exportButton = NIconToolButton(self, iSz, "export")
387
+ self.exportButton.setToolTip(self.tr("Export Labels"))
388
+ self.exportButton.clicked.connect(self._exportLabels)
372
389
 
373
390
  # Edit Form
374
- self.editName = QLineEdit(self)
375
- self.editName.setMaxLength(40)
376
- self.editName.setPlaceholderText(self.tr("Select item to edit"))
377
- self.editName.setEnabled(False)
391
+ self.labelText = QLineEdit(self)
392
+ self.labelText.setMaxLength(40)
393
+ self.labelText.setPlaceholderText(self.tr("Select item to edit"))
394
+ self.labelText.setEnabled(False)
395
+ self.labelText.textEdited.connect(self._onNameEdit)
378
396
 
379
397
  buttonStyle = (
380
398
  f"QToolButton {{padding: 0 {bPd}px;}} "
@@ -386,13 +404,13 @@ class _StatusPage(NFixedPage):
386
404
  self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
387
405
  self.colorButton.setStyleSheet(buttonStyle)
388
406
  self.colorButton.setEnabled(False)
389
- self.colorButton.clicked.connect(self._selectColour)
407
+ self.colorButton.clicked.connect(self._onColourSelect)
390
408
 
391
409
  def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None:
392
410
  for shape, label in items.items():
393
411
  icon = NWStatus.createIcon(self._iPx, iColor, shape)
394
412
  action = menu.addAction(icon, trConst(label))
395
- action.triggered.connect(lambda _, shape=shape: self._selectShape(shape))
413
+ action.triggered.connect(qtLambda(self._selectShape, shape))
396
414
  self._icons[shape] = icon
397
415
 
398
416
  self.shapeMenu = QMenu(self)
@@ -408,33 +426,27 @@ class _StatusPage(NFixedPage):
408
426
  self.shapeButton.setStyleSheet(buttonStyle)
409
427
  self.shapeButton.setEnabled(False)
410
428
 
411
- self.applyButton = QToolButton(self)
412
- self.applyButton.setText(self.tr("Apply"))
413
- self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
414
- self.applyButton.setEnabled(False)
415
- self.applyButton.clicked.connect(self._applyChanges)
416
-
417
429
  # Assemble
418
430
  self.listControls = QVBoxLayout()
419
431
  self.listControls.addWidget(self.addButton)
420
432
  self.listControls.addWidget(self.delButton)
421
433
  self.listControls.addWidget(self.upButton)
422
- self.listControls.addWidget(self.dnButton)
434
+ self.listControls.addWidget(self.downButton)
423
435
  self.listControls.addStretch(1)
436
+ self.listControls.addWidget(self.importButton)
437
+ self.listControls.addWidget(self.exportButton)
424
438
 
425
439
  self.editBox = QHBoxLayout()
426
- self.editBox.addWidget(self.editName, 1)
440
+ self.editBox.addWidget(self.labelText, 1)
427
441
  self.editBox.addWidget(self.colorButton, 0)
428
442
  self.editBox.addWidget(self.shapeButton, 0)
429
- self.editBox.addWidget(self.applyButton, 0)
430
-
431
- self.mainBox = QVBoxLayout()
432
- self.mainBox.addWidget(self.listBox, 1)
433
- self.mainBox.addLayout(self.editBox, 0)
434
443
 
435
- self.innerBox = QHBoxLayout()
436
- self.innerBox.addLayout(self.mainBox, 1)
437
- self.innerBox.addLayout(self.listControls, 0)
444
+ self.innerBox = QGridLayout()
445
+ self.innerBox.addWidget(self.listBox, 0, 0)
446
+ self.innerBox.addLayout(self.listControls, 0, 1)
447
+ self.innerBox.addLayout(self.editBox, 1, 0)
448
+ self.innerBox.setRowStretch(0, 1)
449
+ self.innerBox.setColumnStretch(0, 1)
438
450
 
439
451
  self.outerBox = QVBoxLayout()
440
452
  self.outerBox.addWidget(self.pageTitle, 0)
@@ -474,16 +486,28 @@ class _StatusPage(NFixedPage):
474
486
  # Private Slots
475
487
  ##
476
488
 
489
+ @pyqtSlot(str)
490
+ def _onNameEdit(self, text: str) -> None:
491
+ """Update the status label text."""
492
+ if item := self._getSelectedItem():
493
+ name = simplified(text)
494
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
495
+ entry.name = name
496
+ item.setText(self.C_LABEL, name)
497
+ self._changed = True
498
+ return
499
+
477
500
  @pyqtSlot()
478
- def _selectColour(self) -> None:
501
+ def _onColourSelect(self) -> None:
479
502
  """Open a dialog to select the status icon colour."""
480
503
  if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
481
504
  self._color = color
482
505
  self._setButtonIcons()
506
+ self._updateIcon()
483
507
  return
484
508
 
485
509
  @pyqtSlot()
486
- def _newItem(self) -> None:
510
+ def _onItemCreate(self) -> None:
487
511
  """Create a new status item."""
488
512
  color = QColor(100, 100, 100)
489
513
  shape = nwStatusShape.SQUARE
@@ -493,7 +517,7 @@ class _StatusPage(NFixedPage):
493
517
  return
494
518
 
495
519
  @pyqtSlot()
496
- def _delItem(self) -> None:
520
+ def _onItemDelete(self) -> None:
497
521
  """Delete a status item."""
498
522
  if item := self._getSelectedItem():
499
523
  iRow = self.listBox.indexOfTopLevelItem(item)
@@ -506,28 +530,7 @@ class _StatusPage(NFixedPage):
506
530
  return
507
531
 
508
532
  @pyqtSlot()
509
- def _applyChanges(self) -> None:
510
- """Save changes made to a status item."""
511
- if item := self._getSelectedItem():
512
- entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
513
-
514
- name = simplified(self.editName.text())
515
- icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
516
-
517
- entry.name = name
518
- entry.color = self._color
519
- entry.shape = self._shape
520
- entry.icon = icon
521
-
522
- item.setText(self.C_LABEL, name)
523
- item.setIcon(self.C_LABEL, icon)
524
-
525
- self._changed = True
526
-
527
- return
528
-
529
- @pyqtSlot()
530
- def _selectionChanged(self) -> None:
533
+ def _onSelectionChanged(self) -> None:
531
534
  """Extract the info of a selected item and populate the settings
532
535
  boxes and button. If no item is selected, clear the form.
533
536
  """
@@ -537,25 +540,59 @@ class _StatusPage(NFixedPage):
537
540
  self._shape = entry.shape
538
541
  self._setButtonIcons()
539
542
 
540
- self.editName.setText(entry.name)
541
- self.editName.selectAll()
542
- self.editName.setFocus()
543
+ self.labelText.setText(entry.name)
544
+ self.labelText.selectAll()
545
+ self.labelText.setFocus()
543
546
 
544
- self.editName.setEnabled(True)
547
+ self.labelText.setEnabled(True)
545
548
  self.colorButton.setEnabled(True)
546
549
  self.shapeButton.setEnabled(True)
547
- self.applyButton.setEnabled(True)
548
-
549
550
  else:
550
551
  self._color = QColor(100, 100, 100)
551
552
  self._shape = nwStatusShape.SQUARE
552
553
  self._setButtonIcons()
553
- self.editName.setText("")
554
+ self.labelText.setText("")
554
555
 
555
- self.editName.setEnabled(False)
556
+ self.labelText.setEnabled(False)
556
557
  self.colorButton.setEnabled(False)
557
558
  self.shapeButton.setEnabled(False)
558
- self.applyButton.setEnabled(False)
559
+ return
560
+
561
+ @pyqtSlot()
562
+ def _importLabels(self) -> None:
563
+ """Import labels from file."""
564
+ if path := QFileDialog.getOpenFileName(
565
+ self, self.tr("Import File"),
566
+ str(CONFIG.homePath()), filter=formatFileFilter(["*.csv", "*"]),
567
+ )[0]:
568
+ try:
569
+ with open(path, mode="r", encoding="utf-8") as fo:
570
+ for row in csv.reader(fo):
571
+ if entry := self._store.fromRaw(row):
572
+ self._addItem(None, entry)
573
+ self._changed = True
574
+ except Exception as exc:
575
+ SHARED.error("Could not read file.", exc=exc)
576
+ return
577
+ return
578
+
579
+ @pyqtSlot()
580
+ def _exportLabels(self) -> None:
581
+ """Export labels to file."""
582
+ name = f"{SHARED.project.data.fileSafeName} - {self._kind}.csv"
583
+ if path := QFileDialog.getSaveFileName(
584
+ self, self.tr("Export File"), str(CONFIG.homePath() / name),
585
+ )[0]:
586
+ try:
587
+ path = Path(path).with_suffix(".csv")
588
+ with open(path, mode="w", encoding="utf-8") as fo:
589
+ writer = csv.writer(fo)
590
+ for n in range(self.listBox.topLevelItemCount()):
591
+ if item := self.listBox.topLevelItem(n):
592
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
593
+ writer.writerow([entry.shape.name, entry.color.name(), entry.name])
594
+ except Exception as exc:
595
+ SHARED.error("Could not write file.", exc=exc)
559
596
  return
560
597
 
561
598
  ##
@@ -566,6 +603,19 @@ class _StatusPage(NFixedPage):
566
603
  """Set the current shape."""
567
604
  self._shape = shape
568
605
  self._setButtonIcons()
606
+ self._updateIcon()
607
+ return
608
+
609
+ def _updateIcon(self) -> None:
610
+ """Apply changes made to a status icon."""
611
+ if item := self._getSelectedItem():
612
+ icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
613
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
614
+ entry.color = self._color
615
+ entry.shape = self._shape
616
+ entry.icon = icon
617
+ item.setIcon(self.C_LABEL, icon)
618
+ self._changed = True
569
619
  return
570
620
 
571
621
  def _addItem(self, key: str | None, entry: StatusEntry) -> None:
@@ -644,7 +694,7 @@ class _ReplacePage(NFixedPage):
644
694
  self.listBox.setIndentation(0)
645
695
  self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
646
696
  self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
647
- self.listBox.itemSelectionChanged.connect(self._selectionChanged)
697
+ self.listBox.itemSelectionChanged.connect(self._onSelectionChanged)
648
698
 
649
699
  for aKey, aVal in SHARED.project.data.autoReplace.items():
650
700
  newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
@@ -655,25 +705,22 @@ class _ReplacePage(NFixedPage):
655
705
 
656
706
  # List Controls
657
707
  self.addButton = NIconToolButton(self, iSz, "add")
658
- self.addButton.clicked.connect(self._addEntry)
708
+ self.addButton.clicked.connect(self._onEntryCreated)
659
709
 
660
710
  self.delButton = NIconToolButton(self, iSz, "remove")
661
- self.delButton.clicked.connect(self._delEntry)
711
+ self.delButton.clicked.connect(self._onEntryDeleted)
662
712
 
663
713
  # Edit Form
664
714
  self.editKey = QLineEdit(self)
665
715
  self.editKey.setPlaceholderText(self.tr("Select item to edit"))
666
716
  self.editKey.setEnabled(False)
667
717
  self.editKey.setMaxLength(40)
718
+ self.editKey.textEdited.connect(self._onKeyEdit)
668
719
 
669
720
  self.editValue = QLineEdit(self)
670
721
  self.editValue.setEnabled(False)
671
- self.editValue.setMaxLength(80)
672
-
673
- self.applyButton = QToolButton(self)
674
- self.applyButton.setText(self.tr("Apply"))
675
- self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
676
- self.applyButton.clicked.connect(self._applyChanges)
722
+ self.editValue.setMaxLength(250)
723
+ self.editValue.textEdited.connect(self._onValueEdit)
677
724
 
678
725
  # Assemble
679
726
  self.listControls = QVBoxLayout()
@@ -684,7 +731,6 @@ class _ReplacePage(NFixedPage):
684
731
  self.editBox = QHBoxLayout()
685
732
  self.editBox.addWidget(self.editKey, 4)
686
733
  self.editBox.addWidget(self.editValue, 5)
687
- self.editBox.addWidget(self.applyButton, 0)
688
734
 
689
735
  self.mainBox = QVBoxLayout()
690
736
  self.mainBox.addWidget(self.listBox, 1)
@@ -716,7 +762,7 @@ class _ReplacePage(NFixedPage):
716
762
  new = {}
717
763
  for n in range(self.listBox.topLevelItemCount()):
718
764
  if item := self.listBox.topLevelItem(n):
719
- if key := self._stripNotAllowed(item.text(self.C_KEY)):
765
+ if key := self._stripKey(item.text(self.C_KEY)):
720
766
  new[key] = item.text(self.C_REPL)
721
767
  return new
722
768
 
@@ -728,13 +774,29 @@ class _ReplacePage(NFixedPage):
728
774
  # Private Slots
729
775
  ##
730
776
 
777
+ @pyqtSlot(str)
778
+ def _onKeyEdit(self, text: str) -> None:
779
+ """Update the key text."""
780
+ if (item := self._getSelectedItem()) and (key := self._stripKey(text)):
781
+ item.setText(self.C_KEY, f"<{key}>")
782
+ self._changed = True
783
+ return
784
+
785
+ @pyqtSlot(str)
786
+ def _onValueEdit(self, text: str) -> None:
787
+ """Update the value text."""
788
+ if item := self._getSelectedItem():
789
+ item.setText(self.C_REPL, text)
790
+ self._changed = True
791
+ return
792
+
731
793
  @pyqtSlot()
732
- def _selectionChanged(self) -> None:
794
+ def _onSelectionChanged(self) -> None:
733
795
  """Extract the details from the selected item and populate the
734
796
  edit form.
735
797
  """
736
798
  if item := self._getSelectedItem():
737
- self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
799
+ self.editKey.setText(self._stripKey(item.text(self.C_KEY)))
738
800
  self.editValue.setText(item.text(self.C_REPL))
739
801
  self.editKey.setEnabled(True)
740
802
  self.editValue.setEnabled(True)
@@ -748,26 +810,14 @@ class _ReplacePage(NFixedPage):
748
810
  return
749
811
 
750
812
  @pyqtSlot()
751
- def _applyChanges(self) -> None:
752
- """Save the form data into the list widget."""
753
- if item := self._getSelectedItem():
754
- key = self._stripNotAllowed(self.editKey.text())
755
- value = self.editValue.text()
756
- if key and value:
757
- item.setText(self.C_KEY, f"<{key}>")
758
- item.setText(self.C_REPL, value)
759
- self._changed = True
760
- return
761
-
762
- @pyqtSlot()
763
- def _addEntry(self) -> None:
813
+ def _onEntryCreated(self) -> None:
764
814
  """Add a new list entry."""
765
815
  key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
766
816
  self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
767
817
  return
768
818
 
769
819
  @pyqtSlot()
770
- def _delEntry(self) -> None:
820
+ def _onEntryDeleted(self) -> None:
771
821
  """Delete the selected entry."""
772
822
  if item := self._getSelectedItem():
773
823
  self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
@@ -784,6 +834,6 @@ class _ReplacePage(NFixedPage):
784
834
  return items[0]
785
835
  return None
786
836
 
787
- def _stripNotAllowed(self, key: str) -> str:
837
+ def _stripKey(self, key: str) -> str:
788
838
  """Clean up the replace key string."""
789
839
  return "".join(c for c in key if c.isalnum())
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2020-06-18 [0.9.0] GuiQuoteSelect
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2021 Veronica Berglyd Olsen and novelWriter contributors
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2021-02-12 [1.2rc1] GuiWordList
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2021 Veronica Berglyd Olsen and novelWriter contributors
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -96,9 +96,11 @@ class GuiWordList(NDialog):
96
96
  self.newEntry = QLineEdit(self)
97
97
 
98
98
  self.addButton = NIconToolButton(self, iSz, "add")
99
+ self.addButton.setToolTip(self.tr("Add Word"))
99
100
  self.addButton.clicked.connect(self._doAdd)
100
101
 
101
102
  self.delButton = NIconToolButton(self, iSz, "remove")
103
+ self.delButton.setToolTip(self.tr("Remove Word"))
102
104
  self.delButton.clicked.connect(self._doDelete)
103
105
 
104
106
  self.editBox = QHBoxLayout()
@@ -184,11 +186,10 @@ class GuiWordList(NDialog):
184
186
  SHARED.info(self.tr(
185
187
  "Note: The import file must be a plain text file with UTF-8 or ASCII encoding."
186
188
  ))
187
- ffilter = formatFileFilter(["*.txt", "*"])
188
- path, _ = QFileDialog.getOpenFileName(
189
- self, self.tr("Import File"), str(CONFIG.homePath()), filter=ffilter
190
- )
191
- if path:
189
+ if path := QFileDialog.getOpenFileName(
190
+ self, self.tr("Import File"), str(CONFIG.homePath()),
191
+ filter=formatFileFilter(["*.txt", "*"]),
192
+ )[0]:
192
193
  try:
193
194
  with open(path, mode="r", encoding="utf-8") as fo:
194
195
  words = set(w.strip() for w in fo.read().split())
@@ -202,10 +203,10 @@ class GuiWordList(NDialog):
202
203
  @pyqtSlot()
203
204
  def _exportWords(self) -> None:
204
205
  """Export words to file."""
205
- path, _ = QFileDialog.getSaveFileName(
206
- self, self.tr("Export File"), str(CONFIG.homePath())
207
- )
208
- if path:
206
+ name = f"{SHARED.project.data.fileSafeName} - {self.windowTitle()}.txt"
207
+ if path := QFileDialog.getSaveFileName(
208
+ self, self.tr("Export File"), str(CONFIG.homePath() / name),
209
+ )[0]:
209
210
  try:
210
211
  path = Path(path).with_suffix(".txt")
211
212
  with open(path, mode="w", encoding="utf-8") as fo:
novelwriter/enum.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2018-11-02 [0.0.1]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -68,13 +68,6 @@ class nwComment(Enum):
68
68
  STORY = 7
69
69
 
70
70
 
71
- class nwTrinary(Enum):
72
-
73
- NEGATIVE = -1
74
- NEUTRAL = 0
75
- POSITIVE = 1
76
-
77
-
78
71
  class nwChange(Enum):
79
72
 
80
73
  CREATE = 0
novelwriter/error.py CHANGED
@@ -6,7 +6,7 @@ File History:
6
6
  Created: 2020-08-02 [0.10.2]
7
7
 
8
8
  This file is a part of novelWriter
9
- Copyright 2018–2024, Veronica Berglyd Olsen
9
+ Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors
10
10
 
11
11
  This program is free software: you can redistribute it and/or modify
12
12
  it under the terms of the GNU General Public License as published by
@@ -76,7 +76,7 @@ class NWErrorMessage(QDialog):
76
76
 
77
77
  font = QFont()
78
78
  font.setPointSize(round(0.9*self.font().pointSize()))
79
- font.setFamily(QFontDatabase.systemFont(QFontDatabase.FixedFont).family())
79
+ font.setFamily(QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont).family())
80
80
 
81
81
  self.msgBody = QPlainTextEdit()
82
82
  self.msgBody.setFont(font)
@@ -10,7 +10,7 @@ Created: 2024-01-26 [2.3b1] NFixedPage
10
10
  Created: 2024-03-12 [2.4b1] NWrappedWidgetBox
11
11
 
12
12
  This file is a part of novelWriter
13
- Copyright 2018–2024, Veronica Berglyd Olsen
13
+ Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors
14
14
 
15
15
  This program is free software: you can redistribute it and/or modify
16
16
  it under the terms of the GNU General Public License as published by
@@ -7,7 +7,7 @@ Created: 2023-08-31 [2.1rc1] WheelEventFilter
7
7
  Created: 2023-11-28 [2.2] StatusTipFilter
8
8
 
9
9
  This file is a part of novelWriter
10
- Copyright 2018–2024, Veronica Berglyd Olsen
10
+ Copyright (C) 2023 Veronica Berglyd Olsen and novelWriter contributors
11
11
 
12
12
  This program is free software: you can redistribute it and/or modify
13
13
  it under the terms of the GNU General Public License as published by
@@ -10,7 +10,7 @@ Created: 2024-05-01 [2.5b1] NToolDialog
10
10
  Created: 2024-05-01 [2.5b1] NNonBlockingDialog
11
11
 
12
12
  This file is a part of novelWriter
13
- Copyright 2018–2024, Veronica Berglyd Olsen
13
+ Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
14
14
 
15
15
  This program is free software: you can redistribute it and/or modify
16
16
  it under the terms of the GNU General Public License as published by
@@ -30,14 +30,15 @@ from __future__ import annotations
30
30
  from enum import Enum
31
31
  from typing import TYPE_CHECKING
32
32
 
33
- from PyQt5.QtCore import QSize, Qt, pyqtSlot
34
- from PyQt5.QtGui import QWheelEvent
33
+ from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
34
+ from PyQt5.QtGui import QMouseEvent, QWheelEvent
35
35
  from PyQt5.QtWidgets import (
36
- QApplication, QComboBox, QDialog, QDoubleSpinBox, QSpinBox, QToolButton,
37
- QWidget
36
+ QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox,
37
+ QToolButton, QWidget
38
38
  )
39
39
 
40
40
  from novelwriter import CONFIG, SHARED
41
+ from novelwriter.types import QtMouseLeft
41
42
 
42
43
  if TYPE_CHECKING: # pragma: no cover
43
44
  from novelwriter.guimain import GuiMain
@@ -196,3 +197,14 @@ class NIconToggleButton(QToolButton):
196
197
  iconSize = self.iconSize()
197
198
  self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height())))
198
199
  return
200
+
201
+
202
+ class NClickableLabel(QLabel):
203
+
204
+ mouseClicked = pyqtSignal()
205
+
206
+ def mousePressEvent(self, event: QMouseEvent) -> None:
207
+ """Capture a left mouse click and emit its signal."""
208
+ if event.button() == QtMouseLeft:
209
+ self.mouseClicked.emit()
210
+ return super().mousePressEvent(event)