diff --git a/CHANGES.md b/CHANGES.md
index daf3b76..c410ebb 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,31 @@
CHANGES
+ver 2.1.1
+
+This is a minor revision as most are under the hood changes
+
+- Change: hookup menu Undo/Redo items to stack, now items will display things like "Redo/Undo insert ..."
+
+- Change: remove unused actions from UI
+
+- Change: New qcommand `style_update` to update style changes, brings style changes into undo framework
+
+- Change: QUndoCommand text messages standardized
+
+- Change: Main editor and dialogs have new/condensed tooltips, docs updated.
+
+- Change: Toolbox Dock now uses tabs (holding scroll areas) rather than pages, less scrolling needed and more space for ui since page titles do not take up vertical space
+
+- Bug: word wrapped text did not return proper times for timestamps, added `split` method to elements
+
+- Bug: fixed RTF parser that did not respect spaces as delimiters vs text, now using new json format
+
+- Bug: fix #10 where editor falls back to default system font, should now fall back to first style font
+
+- Change: SRT export now word-wraps to 47 characters max automatically.
+
+- Change: gather UI updates into `update_gui` to update every cursor move
+
ver 2.1.0
This version focuses mainly on "things" to be inserted.
diff --git a/docs/reference/commands.md b/docs/reference/commands.md
index e150f1e..a706848 100644
--- a/docs/reference/commands.md
+++ b/docs/reference/commands.md
@@ -51,6 +51,12 @@ Attributes:
- `space_placement`: value from Plover config
- `add_space`: whether to add space upon merge, default is `True`
+## style_update
+
+- `styles`: reference to style dict in editor
+- `style_name`: name of style to be updated
+- `new_style_dict`: dict containing new style attributes
+- `old_style_dict`: dict with old style attributes
## set_par_style:
diff --git a/docs/reference/elements.md b/docs/reference/elements.md
index 3e553f9..b8e4d8c 100644
--- a/docs/reference/elements.md
+++ b/docs/reference/elements.md
@@ -28,6 +28,7 @@ It has the methods:
- `__getitem__`: returns new instance after deepcopy
- `__repr__`: representation as `dict`
- `length`: returns length of string, here as placeholder in order to keep consistency with other subclassed elements, the functional length
+- `split`: splits text string on whitespace (re from textwrapper), returns list of elements containing each text piece separately, but same otherwise as original
- `from_dict`: can populate class using a dict
- `to_display`: formatted string for display in GUI, should be a string for three lines, 1) icon letter, 2) element data, if any, 3) text
- `to_json`: returns dict of attributes
diff --git a/docs/reference/export.md b/docs/reference/export.md
index 3db3143..659ccef 100644
--- a/docs/reference/export.md
+++ b/docs/reference/export.md
@@ -58,4 +58,28 @@ The Open Document Format (`ODF`) is an open source standard for documents based
### RTF/CRE
-Plover2CAT offers exports to RTF/CRE, the commonly used data exchange format for transcripts. As a result, it is possible to import transcripts from Plover2CAT to commercial software such as CaseCatalyst. Plover2CAT exports a [subset](rtf_support.md) of the [RTF spec](https://web.archive.org/web/20201017075356/http://www.legalxml.org/workgroups/substantive/transcripts/cre-spec.htm).
\ No newline at end of file
+Plover2CAT offers exports to RTF/CRE, the commonly used data exchange format for transcripts. As a result, it is possible to import transcripts from Plover2CAT to commercial software such as CaseCatalyst. Plover2CAT exports a [subset](rtf_support.md) of the [RTF spec](https://web.archive.org/web/20201017075356/http://www.legalxml.org/workgroups/substantive/transcripts/cre-spec.htm).
+
+## Export from editor
+
+The editor offloads exporting to another thread through `documentWorker`.
+
+`documentWorker` takes:
+
+- `document`: copy of transcript in dict form (save transcript automatically to update)
+- `path`: export file path
+- `config`: transcript config
+- `styles`: dict of styles for transcript
+- `user_field_dict`: dict of fields
+- `home_dir`: transcript dir path
+
+`documentWorker` has two signals:
+
+- `progress`: sent after generating a paragraph with paragraph number, updates progress bar in editor
+- `finished`: sent after export file is created
+
+Each export format has its own method called from editor.
+
+### Wrappers and helpers
+
+Wrapping of paragraphs is done with `steno_wrap_*` after appropriate formatting with `format_*_text`, resulting in a `dict` containing `{line_num: line_data_dict}`.
\ No newline at end of file
diff --git a/docs/reference/main.md b/docs/reference/main.md
index 5ba155d..5e7301a 100644
--- a/docs/reference/main.md
+++ b/docs/reference/main.md
@@ -9,11 +9,14 @@ The main class in Plover2CAT is PloverCATWindow that subclasses `QMainWindow` an
- `recorder`: instance of `QAudioRecorder`
- `config`: dict holding transcript configuration
- `file_name`: path for transcript folder
+- `backup_document`: dict of transcript data, updated on save
- `styles`: dict holding styles
- `txt_formats`: dict holding "full" font formatting info (after recursion)
- `par_formats`: dict holding "full" paragraph formatting info (after recursion)
- `user_field_dict`: dict, holds user defined fields
- `auto_paragraph_affixes`: dict, holds affixes for styles
+- `index_dialog`: index dialog editor
+- `suggest_dialog`: suggestion dialog editor
- `styles_path`: path referencing style file
- `stroke_time`: text string timestamp of last stroke
- `audio_file`: path referencing file being played/recorded
@@ -22,6 +25,7 @@ The main class in Plover2CAT is PloverCATWindow that subclasses `QMainWindow` an
- `last_raw_steno`: string, raw steno of last stroke
- `last_string_sent`: string, text sent with last stroke
- `last_backspaces_sent`: integer, number of backspaces sent with last stroke
+- `track_lengths`: deque holding len of string_sent and backspaces_sent, used for tracking/comparing corrections if strokes need to be combined
- `autosave_time`: `QTimer` object for activating autosave
- `undo_stack`: holds `QUndoStack`
- `cutcopy_storage`: `element_collection` holding steno to paste
@@ -56,6 +60,7 @@ Methods that use manipulate the stroke data or use `QUndoCommands` are in *itali
### GUI
- `set_shortcuts`: reads `shortcuts.json` and makes menu shortcuts as needed
+- `edit_shortcuts`: opens shortcut dialog editor
- `about`: displays version
- `acknowledge`: displays acknowledgments
- `open_help`: sends user to help docs
@@ -77,17 +82,20 @@ Methods that use manipulate the stroke data or use `QUndoCommands` are in *itali
- `navigate_to`: function accepts block number, moves and sets editor cursor to beginning of block
- `update_gui`: collects other functions to be updated each time cursor changes
- `update_navigation`: updates Navigation pane, displays list of heading paragraphs
+- `update_index_menu`: generates sub-menu items for quick index entry insertion
+- `set_autosave_time`: set autosave time interval
### Transcript management
- `create_new`: creates new transcript project
- *`open_file`*: opens existing transcript project
- `save_file`: saves transcript project
-- `save_transcript`: extracts transcript data from editor
+- `save_transcript`: extracts transcript data from editor, only updates values if necessary, ie every par starting with first with `userState` == 1
- `dulwich_save`: commits transcript files to repo with commit message
- *`load_transcript`*: loads transcript data into editor and `userData` in blocks
- `revert_file`: reverts transcript back to selected commit from repo
- *`save_as_file`*: saves transcript data and tape into new location
+- `autosave`: saves present transcript to hidden file
- `close_file`: closes transcript project and cleans up editor
- `action_close`: quits editor window
- `recentfile_open`: opens a recent file through `action`
@@ -101,7 +109,7 @@ Methods that use manipulate the stroke data or use `QUndoCommands` are in *itali
- `remove_dict`: file selection dialog to remove custom dict from `dict/` and plover dictionary stack
- `set_dictionary_config`: takes list of dictionary paths, generate default dict if missing, backups present dictionary stack and loads transcript dictionaries
- `restore_dictionary_from_backup`: restore plover dictionary stack from backup file
-
+- `transcript_suggest`: trigger suggestion dialog
### Config management
- `load_config_file`: reads config file and sets editor UI variables
@@ -116,12 +124,13 @@ Methods that use manipulate the stroke data or use `QUndoCommands` are in *itali
- `gen_style_formats`: generates complete font and paragraph format dicts recursively for each style
- `select_style_file`: load style fil from user file selction
- `style_from_template`: reads ODF or RTF file, extracting only style information to write to new style file
-- `display_block_data`: triggered manually after text changes or split/merge, updates style and block properties display, triggers autocomplete dropdown if toggled
+- `display_block_data`: updates style and block properties display, triggers autocomplete dropdown if toggled
- `display_block_steno`: takes strokes, update Reveal Steno dock with strokes, called from `display_block_data`
- `refresh_steno_display`: updates Reveal Steno pane manually
- *`update_paragraph_style`*: updates style of present paragraph block
- `update_style_display`: updates UI elements to display present style
- `style_edit`: changes properties of current style to user selections
+- `check_undo_stack`: will trigger editor style refresh if undo/redo action is related to style changes
- `new_style`: create a new style based on current style
- *`refresh_editor_styles`*: complete refresh of all paragraph blocks based on present styles
- *`to_next_styles`*: sets current block style based on `nextstylename` attribute of previous block if exists
@@ -161,7 +170,10 @@ Methods that use manipulate the stroke data or use `QUndoCommands` are in *itali
- *`edit_fields`*: calls `fieldDialogWindow` to create and edit user fields, and refreshes existing field elements in text
- `add_begin_auto_affix`: checks and adds prefix set for `style`, copying `element` and returning `automatic_text` element
- `add_end_auto_affix`: checks and adds suffix set for `style`, copying `element` and returning `automatic_text` element
-
+- `insert_index_entry`: create index element and insert into transcript
+- `extract_indexes`: find all index entries in transcript
+- `update_indices`: check and updates transcript index entries according to present indices from indices dialog
+- `edit_indices`: opens indices editor dialog
### Search
- `search`: wrapper function for three types of searches
diff --git a/docs/reference/rtf_support.md b/docs/reference/rtf_support.md
index 5c57079..5d4992c 100644
--- a/docs/reference/rtf_support.md
+++ b/docs/reference/rtf_support.md
@@ -55,7 +55,6 @@ Recognized RTF/CRE flags
Timecode (\cxt)
Steno (\cxs)
Automatic Text (\cxa)
- Delete space (\cxds)
diff --git a/plover_cat/__version__.py b/plover_cat/__version__.py
index 0873e16..3ead992 100644
--- a/plover_cat/__version__.py
+++ b/plover_cat/__version__.py
@@ -1,2 +1,2 @@
-__version__ = "2.1.0"
+__version__ = "2.1.1"
diff --git a/plover_cat/affix_dialog.ui b/plover_cat/affix_dialog.ui
index 2ca93fe..41b1678 100644
--- a/plover_cat/affix_dialog.ui
+++ b/plover_cat/affix_dialog.ui
@@ -24,7 +24,11 @@
-
-
+
+
+ Style to set affixes for
+
+
-
@@ -42,6 +46,9 @@
-
+
+ String to add at start of paragraph
+
false
@@ -55,7 +62,11 @@
-
-
+
+
+ String to add to end of paragraph
+
+
@@ -66,6 +77,9 @@
Qt::NoFocus
+
+ Insert tab character into prefix/suffix string
+
Insert tab at cursor
@@ -73,6 +87,9 @@
-
+
+ Save changes for selected style
+
Save
diff --git a/plover_cat/affix_dialog_ui.py b/plover_cat/affix_dialog_ui.py
deleted file mode 100644
index dd98533..0000000
--- a/plover_cat/affix_dialog_ui.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'plover_cat\affix_dialog.ui'
-#
-# Created by: PyQt5 UI code generator 5.15.9
-#
-# WARNING: Any manual changes made to this file will be lost when pyuic5 is
-# run again. Do not edit this file unless you know what you are doing.
-
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-
-class Ui_affixDialog(object):
- def setupUi(self, affixDialog):
- affixDialog.setObjectName("affixDialog")
- affixDialog.resize(387, 173)
- self.verticalLayout = QtWidgets.QVBoxLayout(affixDialog)
- self.verticalLayout.setObjectName("verticalLayout")
- self.formLayout = QtWidgets.QFormLayout()
- self.formLayout.setObjectName("formLayout")
- self.label = QtWidgets.QLabel(affixDialog)
- self.label.setObjectName("label")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
- self.styleName = QtWidgets.QComboBox(affixDialog)
- self.styleName.setObjectName("styleName")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.styleName)
- self.line = QtWidgets.QFrame(affixDialog)
- self.line.setFrameShape(QtWidgets.QFrame.HLine)
- self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
- self.line.setObjectName("line")
- self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.line)
- self.label_3 = QtWidgets.QLabel(affixDialog)
- self.label_3.setObjectName("label_3")
- self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3)
- self.prefixString = QtWidgets.QLineEdit(affixDialog)
- self.prefixString.setClearButtonEnabled(False)
- self.prefixString.setObjectName("prefixString")
- self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.prefixString)
- self.label_2 = QtWidgets.QLabel(affixDialog)
- self.label_2.setObjectName("label_2")
- self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_2)
- self.suffixString = QtWidgets.QLineEdit(affixDialog)
- self.suffixString.setObjectName("suffixString")
- self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.suffixString)
- self.verticalLayout.addLayout(self.formLayout)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.insertTab = QtWidgets.QPushButton(affixDialog)
- self.insertTab.setFocusPolicy(QtCore.Qt.NoFocus)
- self.insertTab.setObjectName("insertTab")
- self.horizontalLayout.addWidget(self.insertTab)
- self.saveAffixes = QtWidgets.QPushButton(affixDialog)
- self.saveAffixes.setObjectName("saveAffixes")
- self.horizontalLayout.addWidget(self.saveAffixes)
- self.verticalLayout.addLayout(self.horizontalLayout)
- self.affixButtonBox = QtWidgets.QDialogButtonBox(affixDialog)
- self.affixButtonBox.setOrientation(QtCore.Qt.Horizontal)
- self.affixButtonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
- self.affixButtonBox.setObjectName("affixButtonBox")
- self.verticalLayout.addWidget(self.affixButtonBox)
-
- self.retranslateUi(affixDialog)
- self.affixButtonBox.accepted.connect(affixDialog.accept) # type: ignore
- self.affixButtonBox.rejected.connect(affixDialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(affixDialog)
-
- def retranslateUi(self, affixDialog):
- _translate = QtCore.QCoreApplication.translate
- affixDialog.setWindowTitle(_translate("affixDialog", "Dialog"))
- self.label.setText(_translate("affixDialog", "Style:"))
- self.label_3.setText(_translate("affixDialog", "Prefix string:"))
- self.label_2.setText(_translate("affixDialog", "Suffix string:"))
- self.insertTab.setText(_translate("affixDialog", "Insert tab at cursor"))
- self.saveAffixes.setText(_translate("affixDialog", "Save"))
diff --git a/plover_cat/documentWorker.py b/plover_cat/documentWorker.py
index 0458c44..24588b9 100644
--- a/plover_cat/documentWorker.py
+++ b/plover_cat/documentWorker.py
@@ -8,7 +8,7 @@
from PyQt5.QtGui import QFontMetrics
from time import sleep
from plover import log
-from plover_cat.helpers import save_json, ms_to_hours, return_commits, inch_to_spaces
+from plover_cat.helpers import save_json, ms_to_hours, return_commits, inch_to_spaces, write_command
from plover_cat.steno_objects import *
from plover_cat.rtf_parsing import *
from plover_cat.export_helpers import *
@@ -485,8 +485,8 @@ def save_rtf(self):
info_string.append(write_command("cxnoflines", value = page_vspan))
# cxlinex and cxtimex is hardcoded as it is also harcoded in odf
# based on rtf spec, confusing whether left text margin, or left page margin
- info_string.append(write_command("creatim", value = create_string))
- info_string.append(write_command("buptim", value = backup_string))
+ info_string.append(write_command("creatim", value = create_string, group = True))
+ info_string.append(write_command("buptim", value = backup_string, group = True))
info_string.append(write_command("cxlinex", value = int(in_to_twip(-0.15))))
info_string.append(write_command("cxtimex", value = int(in_to_twip(-1.5))))
info_string.append(write_command("cxnofstrokes", value = stroke_count))
@@ -510,27 +510,20 @@ def save_srt(self):
line_num = 1
doc_lines = []
log.debug(f"Exporting in SRT to {self.path}")
- for block_num, block_data in self.document.items():
- doc_lines += [block_num]
- audiostarttime = block_data["audiostarttime"]
- # webvtt uses periods for ms separator
- audiostarttime = audiostarttime.replace(".", ",")
- if "audioendtime" in block_data:
- audioendtime = block_data["audioendtime"]
- elif int(block_num) == (len(self.document) - 1):
- log.debug(f"Block {block_num} does not have audioendtime. Last block in document. Setting 0 as timestamp.")
- audioendtime = ms_to_hours(0)
- else:
- log.debug(f"Block {block_num} does not have audioendtime. Attempting to use starttime from next block.")
- try:
- audioendtime = self.document[str(int(block_num) + 1)]["audiostarttime"]
- except (TypeError, KeyError) as err:
- audioendtime = ms_to_hours(0)
- audioendtime = audioendtime.replace(".", ",")
- doc_lines += [audiostarttime + " --> " + audioendtime]
- el_list = [ef.gen_element(element_dict = i, user_field_dict = self.user_field_dict) for i in block_data["strokes"]]
- doc_lines += ["".join([el.to_text() for el in el_list])]
- doc_lines += [""]
+ for block_num, block_data in self.document.items():
+ if "audioendtime" not in block_data:
+ if str(int(block_num) + 1) in self.document and "audiostarttime" in self.document[str(int(block_num) + 1)]:
+ block_data["audioendtime"] = self.document[str(int(block_num) + 1)]["audiostarttime"]
+ else:
+ block_data["audioendtime"] = None
+ el_list = element_collection([ef.gen_element(element_dict = i, user_field_dict = self.user_field_dict) for i in block_data["strokes"]])
+ par_dict = format_srt_text(el_list, line_num = line_num, audiostarttime = block_data["audiostarttime"], audioendtime = block_data["audioendtime"])
+ line_num += len(par_dict)
+ for k, v in par_dict.items():
+ doc_lines += [k]
+ doc_lines += [ms_to_hours(v["starttime"]).replace(".", ",") + " --> " + ms_to_hours(v["endtime"]).replace(".", ",")]
+ doc_lines += ["".join([el.to_text() for el in v["text"]])]
+ doc_lines += [""]
self.progress.emit(int(block_num))
file_path = pathlib.Path(self.path)
with open(file_path, "w", encoding="utf-8") as f:
diff --git a/plover_cat/export_helpers.py b/plover_cat/export_helpers.py
index 7b05d18..797de59 100644
--- a/plover_cat/export_helpers.py
+++ b/plover_cat/export_helpers.py
@@ -143,9 +143,26 @@ def format_text(block_data, style, max_char = 80, line_num = 0):
par_text[k]["text"] = par_text[k]["text"] + "\n" * line_spaces
return(par_text)
+def format_srt_text(block_data, line_num = 0, audiostarttime = "", audioendtime = ""):
+ par_text = steno_wrap_srt(block_data, max_char = 47, starting_line_num = line_num)
+ # line_times = []
+ # if audiostarttime and audioendtime:
+ # for line, data in par_txt.items():
+ # line_times.append(data["starttime"])
+ # line_times.append(data["endtime"])
+ # if all([t >= audiostarttime and t <= audioendtime for t in line_times]):
+ # return(par_text)
+ # else:
+ # audiostarttime = hours_to_ms(audiostarttime)
+ # audioendtime = hours_to_ms(audioendtime)
+ # # not complete
+ return(par_text)
+
def steno_wrap_plain(text, block_data, max_char = 80, tab_space = 4, first_line_indent = "",
- par_indent = "", timestamp = False, starting_line_num = 0):
+ par_indent = "", starting_line_num = 0):
+ """returns dict of dicts, {line_number: {line_text, line_timestamp}}"""
# the -1 in max char is because the rounding is not perfect, might have some lines that just tip over
+ # uses text string instead of block_data because text has pre-expanded tabs
wrapped = textwrap.wrap(text, width = max_char - 1, initial_indent= first_line_indent,
subsequent_indent= par_indent, expand_tabs = False, tabsize = tab_space, replace_whitespace=False)
begin_pos = 0
@@ -167,7 +184,8 @@ def steno_wrap_plain(text, block_data, max_char = 80, tab_space = 4, first_line_
return(par_dict)
def steno_wrap_odf(block_data, max_char = 80, tab_space = 4, first_line_indent = "",
- par_indent = "", timestamp = False, starting_line_num = 0):
+ par_indent = "", starting_line_num = 0):
+ """ returns dict of dicts {line_number: {line_text, line_timestamp}}"""
# the -1 in max char is because the rounding is not perfect, might have some lines that just tip over
wrapper = steno_wrapper(width = max_char - 1, initial_indent= first_line_indent,
subsequent_indent= par_indent, expand_tabs = False, tabsize = tab_space, replace_whitespace=False)
@@ -179,7 +197,23 @@ def steno_wrap_odf(block_data, max_char = 80, tab_space = 4, first_line_indent =
par_dict[starting_line_num + ind + 1] = {"text": wrapped[ind], "time": line_time}
return(par_dict)
+def steno_wrap_srt(block_data, max_char = 47, tab_space = 0, first_line_indent = "",
+ par_indent = "", starting_line_num = 0):
+ # change from other wrapper here, tabs are expanded into 0 spaces
+ wrapper = steno_wrapper(width = max_char - 1, initial_indent= first_line_indent,
+ subsequent_indent= par_indent, expand_tabs = True, tabsize = tab_space, replace_whitespace=False)
+ wrapped = wrapper.wrap(text = block_data)
+ begin_pos = 0
+ par_dict = {}
+ for ind, i in enumerate(wrapped):
+ ec = element_collection(i)
+ start_time = ec.audio_time()
+ end_time = ec.audio_time(reverse = True)
+ par_dict[starting_line_num + ind + 1] = {"text": wrapped[ind], "starttime": start_time, "endtime": end_time}
+ return(par_dict)
+
def load_odf_styles(path):
+ """extract styles from ODT file and convert supported to par + text style dicts"""
log.debug(f"Loading ODF style file from {str(path)}")
style_text = load(path)
json_styles = {}
@@ -209,6 +243,7 @@ def load_odf_styles(path):
return(json_styles)
def recursive_style_format(style_dict, style, prop = "paragraphproperties"):
+ """used to get full style par/text format dict if style inherits from another"""
if "parentstylename" in style_dict[style]:
parentstyle = recursive_style_format(style_dict, style_dict[style]["parentstylename"], prop = prop)
if prop in style_dict[style]:
@@ -221,6 +256,7 @@ def recursive_style_format(style_dict, style, prop = "paragraphproperties"):
return({})
def parprop_to_blockformat(par_dict):
+ """take dict of paragraph attributes, returns QTextBlockFormat obj"""
par_format = QTextBlockFormat()
if "textalign" in par_dict:
if par_dict["textalign"] == "justify":
@@ -257,6 +293,7 @@ def parprop_to_blockformat(par_dict):
return(par_format)
def txtprop_to_textformat(txt_dict):
+ """takes dict of text attributes, returns QTextCharFormat obj"""
txt_format = QTextCharFormat()
if "fontfamily" in txt_dict:
potential_font = QFont(txt_dict["fontfamily"])
diff --git a/plover_cat/field_dialog.ui b/plover_cat/field_dialog.ui
index 5d7e832..4c7fdc6 100644
--- a/plover_cat/field_dialog.ui
+++ b/plover_cat/field_dialog.ui
@@ -27,7 +27,11 @@
-
-
+
+
+ Identifier for field, best be ASCII characters
+
+
-
@@ -37,7 +41,11 @@
-
-
+
+
+ Text value for field, appears in transcript. If left blank, field name appears in text.
+
+
-
@@ -53,6 +61,9 @@
Qt::DefaultContextMenu
+
+ Double-click cells to edit field values.
+
QAbstractItemView::DoubleClicked
@@ -80,6 +91,12 @@
-
+
+ false
+
+
+ Removes selected field from table
+
Remove field
@@ -87,6 +104,9 @@
-
+
+
+
Qt::Horizontal
diff --git a/plover_cat/field_dialog_ui.py b/plover_cat/field_dialog_ui.py
deleted file mode 100644
index d9cfeb6..0000000
--- a/plover_cat/field_dialog_ui.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'plover_cat\field_dialog.ui'
-#
-# Created by: PyQt5 UI code generator 5.15.9
-#
-# WARNING: Any manual changes made to this file will be lost when pyuic5 is
-# run again. Do not edit this file unless you know what you are doing.
-
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-
-class Ui_fieldDialog(object):
- def setupUi(self, fieldDialog):
- fieldDialog.setObjectName("fieldDialog")
- fieldDialog.setWindowModality(QtCore.Qt.WindowModal)
- fieldDialog.resize(400, 300)
- self.verticalLayout = QtWidgets.QVBoxLayout(fieldDialog)
- self.verticalLayout.setObjectName("verticalLayout")
- self.formLayout = QtWidgets.QFormLayout()
- self.formLayout.setObjectName("formLayout")
- self.label = QtWidgets.QLabel(fieldDialog)
- self.label.setObjectName("label")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
- self.fieldName = QtWidgets.QLineEdit(fieldDialog)
- self.fieldName.setObjectName("fieldName")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.fieldName)
- self.label_2 = QtWidgets.QLabel(fieldDialog)
- self.label_2.setObjectName("label_2")
- self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
- self.fieldValue = QtWidgets.QLineEdit(fieldDialog)
- self.fieldValue.setObjectName("fieldValue")
- self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.fieldValue)
- self.addNewField = QtWidgets.QPushButton(fieldDialog)
- self.addNewField.setObjectName("addNewField")
- self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.addNewField)
- self.verticalLayout.addLayout(self.formLayout)
- self.userDictTable = QtWidgets.QTableWidget(fieldDialog)
- self.userDictTable.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
- self.userDictTable.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked)
- self.userDictTable.setTabKeyNavigation(False)
- self.userDictTable.setProperty("showDropIndicator", False)
- self.userDictTable.setDragDropOverwriteMode(False)
- self.userDictTable.setAlternatingRowColors(True)
- self.userDictTable.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
- self.userDictTable.setObjectName("userDictTable")
- self.userDictTable.setColumnCount(0)
- self.userDictTable.setRowCount(0)
- self.verticalLayout.addWidget(self.userDictTable)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.removeField = QtWidgets.QPushButton(fieldDialog)
- self.removeField.setObjectName("removeField")
- self.horizontalLayout.addWidget(self.removeField)
- self.buttonBox = QtWidgets.QDialogButtonBox(fieldDialog)
- self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
- self.buttonBox.setObjectName("buttonBox")
- self.horizontalLayout.addWidget(self.buttonBox)
- self.verticalLayout.addLayout(self.horizontalLayout)
-
- self.retranslateUi(fieldDialog)
- self.buttonBox.accepted.connect(fieldDialog.accept) # type: ignore
- self.buttonBox.rejected.connect(fieldDialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(fieldDialog)
-
- def retranslateUi(self, fieldDialog):
- _translate = QtCore.QCoreApplication.translate
- fieldDialog.setWindowTitle(_translate("fieldDialog", "Field Editor"))
- self.label.setText(_translate("fieldDialog", "Field name:"))
- self.label_2.setText(_translate("fieldDialog", "Field value:"))
- self.addNewField.setText(_translate("fieldDialog", "Add new field"))
- self.userDictTable.setSortingEnabled(True)
- self.removeField.setText(_translate("fieldDialog", "Remove field"))
diff --git a/plover_cat/helpers.py b/plover_cat/helpers.py
index c253d3f..0259a7e 100644
--- a/plover_cat/helpers.py
+++ b/plover_cat/helpers.py
@@ -6,7 +6,20 @@
from plover import log
from dulwich.porcelain import open_repo_closing
+def write_command(control, text = None, value = None, visible = True, group = False):
+ command = "\\" + control
+ if value is not None:
+ command = command + str(value)
+ if text:
+ command = command + " " + text
+ if not visible:
+ command = "\\*" + command
+ if group:
+ command = "{" + command + "}"
+ return(command)
+
def return_commits(repo, max_entries = 100):
+ """ Adapted from dulwich, returns commit info"""
with open_repo_closing(repo) as r:
walker = r.get_walker(max_entries = max_entries, paths=None, reverse=False)
commit_strs = []
@@ -24,6 +37,13 @@ def ms_to_hours(millis):
hours, minutes = divmod(minutes, 60)
return ("%02d:%02d:%02d.%03d" % (hours, minutes, seconds, milliseconds))
+def hours_to_ms(hour_str):
+ """Converts formatted hour:min:sec.milli to milliseconds"""
+ hours, minutes, sec_ms = hour_str.split(":")
+ seconds, milliseconds = sec_md.split(".")
+ total_ms = milliseconds + seconds * 1000 + minutes * 60000 + hours * 3600000
+ return(total_ms)
+
def in_to_pt(inch):
inch = float(inch)
return(inch * 72)
@@ -90,6 +110,7 @@ def backup_dictionary_stack(dictionaries, path):
pass
def remove_empty_from_dict(d):
+ """removes dict key:value if value is None-like"""
if type(d) is dict:
return dict((k, remove_empty_from_dict(v)) for k, v in d.items() if v and remove_empty_from_dict(v))
elif type(d) is list:
@@ -98,8 +119,9 @@ def remove_empty_from_dict(d):
return d
def hide_file(filename):
+ """helper for windows os to hide autosave file"""
import ctypes
FILE_ATTRIBUTE_HIDDEN = 0x02
ret = ctypes.windll.kernel32.SetFileAttributesW(filename, FILE_ATTRIBUTE_HIDDEN)
- if not ret: # There was an error.
+ if not ret:
raise ctypes.WinError()
diff --git a/plover_cat/index_dialog.ui b/plover_cat/index_dialog.ui
index 0d247ba..0636dde 100644
--- a/plover_cat/index_dialog.ui
+++ b/plover_cat/index_dialog.ui
@@ -28,10 +28,17 @@
-
-
-
+
+
+ Indices are numbered starting at 0
+
+
-
+
+ Add another index
+
Add New Index
@@ -49,13 +56,20 @@
-
-
-
+
+
+ Prefix for index entry
+
+
-
true
+
+ Do not show entry description in transcript
+
Hide entry descriptions
@@ -101,7 +115,11 @@
-
-
+
+
+ Text for the index entry
+
+
-
@@ -124,6 +142,9 @@
false
+
+ Save changes to present index and insert selected entry
+
Save && Insert
@@ -131,6 +152,9 @@
-
+
+ Save changes to selected index
+
Save
diff --git a/plover_cat/index_dialog_ui.py b/plover_cat/index_dialog_ui.py
deleted file mode 100644
index ed68bda..0000000
--- a/plover_cat/index_dialog_ui.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'plover_cat\index_dialog.ui'
-#
-# Created by: PyQt5 UI code generator 5.15.9
-#
-# WARNING: Any manual changes made to this file will be lost when pyuic5 is
-# run again. Do not edit this file unless you know what you are doing.
-
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-
-class Ui_indexDialog(object):
- def setupUi(self, indexDialog):
- indexDialog.setObjectName("indexDialog")
- indexDialog.resize(448, 305)
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout(indexDialog)
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
- self.verticalLayout = QtWidgets.QVBoxLayout()
- self.verticalLayout.setObjectName("verticalLayout")
- self.formLayout = QtWidgets.QFormLayout()
- self.formLayout.setObjectName("formLayout")
- self.label = QtWidgets.QLabel(indexDialog)
- self.label.setObjectName("label")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
- self.indexChoice = QtWidgets.QComboBox(indexDialog)
- self.indexChoice.setObjectName("indexChoice")
- self.horizontalLayout_3.addWidget(self.indexChoice)
- self.addNewIndex = QtWidgets.QPushButton(indexDialog)
- self.addNewIndex.setObjectName("addNewIndex")
- self.horizontalLayout_3.addWidget(self.addNewIndex)
- self.formLayout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_3)
- self.label_2 = QtWidgets.QLabel(indexDialog)
- self.label_2.setObjectName("label_2")
- self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
- self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_4.setObjectName("horizontalLayout_4")
- self.indexPrefix = QtWidgets.QLineEdit(indexDialog)
- self.indexPrefix.setObjectName("indexPrefix")
- self.horizontalLayout_4.addWidget(self.indexPrefix)
- self.hideDescript = QtWidgets.QCheckBox(indexDialog)
- self.hideDescript.setEnabled(True)
- self.hideDescript.setChecked(True)
- self.hideDescript.setTristate(False)
- self.hideDescript.setObjectName("hideDescript")
- self.horizontalLayout_4.addWidget(self.hideDescript)
- self.formLayout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_4)
- self.verticalLayout.addLayout(self.formLayout)
- self.label_3 = QtWidgets.QLabel(indexDialog)
- self.label_3.setObjectName("label_3")
- self.verticalLayout.addWidget(self.label_3)
- self.displayEntries = QtWidgets.QTableWidget(indexDialog)
- self.displayEntries.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked)
- self.displayEntries.setObjectName("displayEntries")
- self.displayEntries.setColumnCount(0)
- self.displayEntries.setRowCount(0)
- self.verticalLayout.addWidget(self.displayEntries)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.label_4 = QtWidgets.QLabel(indexDialog)
- self.label_4.setObjectName("label_4")
- self.horizontalLayout.addWidget(self.label_4)
- self.entryText = QtWidgets.QLineEdit(indexDialog)
- self.entryText.setObjectName("entryText")
- self.horizontalLayout.addWidget(self.entryText)
- self.entryAdd = QtWidgets.QPushButton(indexDialog)
- self.entryAdd.setEnabled(False)
- self.entryAdd.setObjectName("entryAdd")
- self.horizontalLayout.addWidget(self.entryAdd)
- self.verticalLayout.addLayout(self.horizontalLayout)
- self.horizontalLayout_2.addLayout(self.verticalLayout)
- self.verticalLayout_2 = QtWidgets.QVBoxLayout()
- self.verticalLayout_2.setObjectName("verticalLayout_2")
- self.saveAndInsert = QtWidgets.QPushButton(indexDialog)
- self.saveAndInsert.setEnabled(False)
- self.saveAndInsert.setObjectName("saveAndInsert")
- self.verticalLayout_2.addWidget(self.saveAndInsert)
- self.saveIndex = QtWidgets.QPushButton(indexDialog)
- self.saveIndex.setObjectName("saveIndex")
- self.verticalLayout_2.addWidget(self.saveIndex)
- self.buttonBox = QtWidgets.QDialogButtonBox(indexDialog)
- self.buttonBox.setOrientation(QtCore.Qt.Vertical)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
- self.buttonBox.setObjectName("buttonBox")
- self.verticalLayout_2.addWidget(self.buttonBox)
- self.horizontalLayout_2.addLayout(self.verticalLayout_2)
-
- self.retranslateUi(indexDialog)
- self.buttonBox.rejected.connect(indexDialog.hide) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(indexDialog)
-
- def retranslateUi(self, indexDialog):
- _translate = QtCore.QCoreApplication.translate
- indexDialog.setWindowTitle(_translate("indexDialog", "Dialog"))
- self.label.setText(_translate("indexDialog", "Index:"))
- self.addNewIndex.setText(_translate("indexDialog", "Add New Index"))
- self.label_2.setText(_translate("indexDialog", "Prefix:"))
- self.hideDescript.setText(_translate("indexDialog", "Hide entry descriptions"))
- self.label_3.setText(_translate("indexDialog", "Entries for index:"))
- self.displayEntries.setToolTip(_translate("indexDialog", "Double-click to edit index entry descriptions."))
- self.displayEntries.setSortingEnabled(True)
- self.label_4.setText(_translate("indexDialog", "Index entry text:"))
- self.entryAdd.setText(_translate("indexDialog", "Add new entry"))
- self.saveAndInsert.setText(_translate("indexDialog", "Save && Insert"))
- self.saveIndex.setText(_translate("indexDialog", "Save"))
diff --git a/plover_cat/main_window.py b/plover_cat/main_window.py
index 65ace6a..7765185 100644
--- a/plover_cat/main_window.py
+++ b/plover_cat/main_window.py
@@ -134,6 +134,25 @@ def __init__(self, engine):
self.track_lengths = deque(maxlen = 10)
self.autosave_time = QTimer()
self.undo_stack = QUndoStack(self)
+ self.actionUndo = self.undo_stack.createUndoAction(self)
+ undo_icon = QtGui.QIcon()
+ undo_icon.addFile(":/arrow-curve-180.png", QtCore.QSize(), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.actionUndo.setIcon(undo_icon)
+ self.actionUndo.setShortcutContext(QtCore.Qt.WindowShortcut)
+ self.actionUndo.setToolTip("Undo writing or other action")
+ self.actionUndo.setShortcut("Ctrl+Z")
+ self.actionUndo.setObjectName("actionUndo")
+ self.actionRedo = self.undo_stack.createRedoAction(self)
+ redo_icon = QtGui.QIcon()
+ redo_icon.addFile(":/arrow-curve.png", QtCore.QSize(), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.actionRedo.setIcon(redo_icon)
+ self.actionRedo.setShortcutContext(QtCore.Qt.WindowShortcut)
+ self.actionRedo.setToolTip("Redo writing or other action")
+ self.actionRedo.setShortcut("Ctrl+Y")
+ self.actionRedo.setObjectName("actionRedo")
+ self.menuEdit.addSeparator()
+ self.menuEdit.addAction(self.actionUndo)
+ self.menuEdit.addAction(self.actionRedo)
self.undoView.setStack(self.undo_stack)
self.cutcopy_storage = {}
self.repo = None
@@ -188,8 +207,9 @@ def __init__(self, engine):
self.actionCopy.triggered.connect(lambda: self.copy_steno())
self.actionCut.triggered.connect(lambda: self.cut_steno())
self.actionPaste.triggered.connect(lambda: self.paste_steno())
- self.actionRedo.triggered.connect(self.undo_stack.redo)
- self.actionUndo.triggered.connect(self.undo_stack.undo)
+ self.undo_stack.indexChanged.connect(self.check_undo_stack)
+ # self.actionRedo.triggered.connect(self.undo_stack.redo)
+ # self.actionUndo.triggered.connect(self.undo_stack.undo)
self.actionFindReplacePane.triggered.connect(lambda: self.show_find_replace())
self.actionJumpToParagraph.triggered.connect(self.jump_par)
self.navigationList.itemDoubleClicked.connect(self.heading_navigation)
@@ -510,7 +530,9 @@ def jump_par(self):
def show_find_replace(self):
if self.textEdit.textCursor().hasSelection() and self.search_text.isChecked():
self.search_term.setText(self.textEdit.textCursor().selectedText())
- self.toolBox.setCurrentWidget(self.find_replace_pane)
+ if not self.dockProp.isVisible():
+ self.dockProp.setVisible(True)
+ self.tabWidget.setCurrentWidget(self.find_replace_pane)
log.debug("User set find pane visible.")
def heading_navigation(self, item):
@@ -532,6 +554,8 @@ def update_gui(self):
current_cursor = self.textEdit.textCursor()
if current_cursor.block().userData():
self.display_block_steno(current_cursor.block().userData()["strokes"])
+ self.update_style_display(current_cursor.block().userData()["style"])
+ self.display_block_data()
# skip if still on same block
if self.cursor_block == self.textEdit.textCursor().blockNumber():
log.debug("Cursor in same paragraph as previous.")
@@ -655,8 +679,11 @@ def create_new(self):
self.update_paragraph_style()
document_cursor = self.textEdit.textCursor()
document_cursor.setBlockFormat(self.par_formats[self.style_selector.currentText()])
- document_cursor.setCharFormat(self.txt_formats[self.style_selector.currentText()])
- self.textEdit.setTextCursor(document_cursor)
+ document_cursor.setCharFormat(self.txt_formats[self.style_selector.currentText()])
+ self.textEdit.setCurrentCharFormat(self.txt_formats[self.style_selector.currentText()])
+ self.textEdit.setTextCursor(document_cursor)
+ self.textEdit.document().setDefaultFont(self.txt_formats[self.style_selector.currentText()].font())
+ self.undo_stack.clear()
def open_file(self, file_path = ""):
self.mainTabs.setCurrentIndex(1)
@@ -743,7 +770,8 @@ def open_file(self, file_path = ""):
document_cursor = self.textEdit.textCursor()
document_cursor.setBlockFormat(self.par_formats[self.style_selector.currentText()])
document_cursor.setCharFormat(self.txt_formats[self.style_selector.currentText()])
- self.textEdit.setTextCursor(document_cursor)
+ self.textEdit.document().setDefaultFont(self.txt_formats[self.style_selector.currentText()].font())
+ self.undo_stack.clear()
def save_file(self):
if not self.file_name:
@@ -773,7 +801,7 @@ def save_transcript(self, path):
if status == 1:
block_dict = deepcopy(block.userData().return_all())
block_num = block.blockNumber()
- print(f"Paragraph {block_num} changed.")
+ # print(f"Paragraph {block_num} changed.")
block_dict["strokes"] = block_dict["strokes"].to_json()
json_document[str(block_num)] = block_dict
block.setUserState(-1)
@@ -1033,7 +1061,7 @@ def recentfile_store(self, path):
except ValueError:
pass
recent_file_paths.insert(0, path)
- print(recent_file_paths)
+ # print(recent_file_paths)
# only remember last ten
del recent_file_paths[10:]
settings.setValue("recentfiles", recent_file_paths)
@@ -1370,7 +1398,7 @@ def display_block_data(self):
block_data = current_cursor.block().userData()
log.debug(f"Update GUI to display block {block_number} data")
if not block_data:
- block_data = BlockUserData()
+ return
self.editorParagraphLabel.setText(str(block_number))
if block_data["creationtime"]:
self.editorCreationTime.setDateTime(QDateTime.fromString(block_data["creationtime"], "yyyy-MM-ddTHH:mm:ss.zzz"))
@@ -1492,6 +1520,9 @@ def style_edit(self):
# this is important so a style is not based on itself
if self.blockParentStyle.currentText() != style_name:
new_style_dict["parentstylename"] = self.blockParentStyle.currentText()
+ else:
+ QMessageBox.warning(self, "Edit style", "Style cannot be parent of itself.")
+ return
if self.blockNextStyle.currentIndex() != -1:
new_style_dict["nextstylename"] = self.blockNextStyle.currentText()
if self.blockHeadingLevel.currentText() != "":
@@ -1524,18 +1555,30 @@ def style_edit(self):
# do not set if tabstop = 0, weird things might happen
if tab_pos:
new_par_dict["tabstop"] = "%.2fin" % tab_pos
- original_style_txt.update(new_txt_dict)
- original_style_txt = remove_empty_from_dict(original_style_txt)
- original_style_par.update(new_par_dict)
- log.debug("New paragraph properties: %s" % original_style_par)
- log.debug("New text properties: %s" % original_style_txt)
- new_style_dict["paragraphproperties"] = original_style_par
- new_style_dict["textproperties"] = original_style_txt
- log_dict = {"action": "edit_style", "style_dict": new_style_dict}
- log.info(f"Style: {log_dict}")
- self.styles[style_name] = new_style_dict
- self.gen_style_formats()
- self.refresh_editor_styles()
+ min_txt_style = {}
+ for k, v in new_txt_dict.items():
+ if k in original_style_txt and v == original_style_txt[k]:
+ continue
+ else:
+ min_txt_style[k] = v
+ min_par_style = {}
+ for k, v in new_par_dict.items():
+ if k in original_style_par and v == original_style_par[k]:
+ continue
+ else:
+ min_par_style[k] = v
+ # original_style_txt.update(new_txt_dict)
+ # original_style_txt = remove_empty_from_dict(original_style_txt)
+ # original_style_par.update(new_par_dict)
+ new_style_dict["paragraphproperties"] = min_par_style
+ new_style_dict["textproperties"] = min_txt_style
+ style_cmd = style_update(self.styles, style_name, new_style_dict)
+ self.undo_stack.push(style_cmd)
+ # self.refresh_editor_styles()
+
+ def check_undo_stack(self, index):
+ if self.undo_stack.undoText().startswith("Style:") or self.undo_stack.redoText().startswith("Style:"):
+ self.refresh_editor_styles()
def new_style(self):
log.debug("User create new style")
@@ -1558,25 +1601,37 @@ def refresh_editor_styles(self):
user_choice = QMessageBox.question(self, "Refresh styles", f"There are {self.textEdit.document().blockCount()} paragraphs. Style refreshing may take some time. Continue?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if user_choice == QMessageBox.No:
return
+ self.gen_style_formats()
block = self.textEdit.document().begin()
+ current_cursor = self.textEdit.textCursor()
self.progressBar = QProgressBar(self)
self.progressBar.setMaximum(self.textEdit.document().blockCount())
self.progressBar.setFormat("Re-style paragraph %v")
self.statusBar.addWidget(self.progressBar)
- self.undo_stack.beginMacro("Refresh editor.")
+ self.progressBar.show()
while True:
try:
block_style = block.userData()["style"]
except TypeError:
- block_style = ""
- style_cmd = set_par_style(block.blockNumber(), block_style, self.textEdit, self.par_formats, self.txt_formats)
- self.undo_stack.push(style_cmd)
+ # block_style = ""
+ continue
+ current_cursor.setPosition(block.position())
+ current_cursor.movePosition(QTextCursor.StartOfBlock)
+ current_cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
+ current_cursor.setBlockFormat(self.par_formats[block_style])
+ it = block.begin()
+ while not it.atEnd():
+ frag = it.fragment()
+ if frag.isValid() and not frag.charFormat().isImageFormat():
+ current_cursor.setPosition(frag.position())
+ current_cursor.setPosition(frag.position() + frag.length(), QTextCursor.KeepAnchor)
+ current_cursor.setCharFormat(self.txt_formats[block_style])
+ it += 1
self.progressBar.setValue(block.blockNumber())
QApplication.processEvents()
if block == self.textEdit.document().lastBlock():
break
block = block.next()
- self.undo_stack.endMacro()
self.statusBar.removeWidget(self.progressBar)
def to_next_style(self):
@@ -1922,7 +1977,7 @@ def on_stroke(self, stroke_pressed):
current_strokes = current_cursor.block().userData()["strokes"]
start_pos = backtrack_coord(current_cursor.positionInBlock(), backspaces_sent,
current_strokes.lens(), current_strokes.lengths())
- self.undo_stack.beginMacro(f"Remove {backspaces_sent} backspaces(s).")
+ self.undo_stack.beginMacro(f"Remove: {backspaces_sent} backspaces(s).")
if start_pos < 0:
log.debug(f"{start_pos} backspaces than exists on current paragraph.")
while start_pos < 0:
@@ -1955,7 +2010,7 @@ def on_stroke(self, stroke_pressed):
if "\n" in string_sent and self.last_string_sent != "\n":
list_segments = string_sent.splitlines(True)
self.track_lengths.append(len(self.last_string_sent))
- self.undo_stack.beginMacro("Insert Group")
+ self.undo_stack.beginMacro(f"Insert: {string_sent}")
for i, segment in enumerate(list_segments):
stroke = stroke_text(time = self.stroke_time, stroke = self.last_raw_steno, text = segment.rstrip("\n"))
# because this is all occurring in one stroke, only first segment gets the stroke
@@ -1996,7 +2051,6 @@ def on_stroke(self, stroke_pressed):
stroke)
self.undo_stack.push(insert_cmd)
self.last_string_sent = ""
- self.display_block_data()
self.textEdit.document().setModified(True)
self.statusBar.clearMessage()
@@ -2068,7 +2122,7 @@ def cut_steno(self, store = True):
self.cutcopy_storage = block_data["strokes"].extract_steno(start_pos, stop_pos)
log.debug("Data stored for pasting")
self.statusBar.showMessage("Cut from paragraph {par_num}, from {start} to {end}".format(par_num = current_block_num, start = start_pos, end = stop_pos))
- self.undo_stack.beginMacro("Cut")
+ self.undo_stack.beginMacro(f"Cut: {selected_text}")
remove_cmd = steno_remove(self.textEdit, current_block_num,
start_pos, len(selected_text))
self.undo_stack.push(remove_cmd)
@@ -2087,7 +2141,7 @@ def paste_steno(self):
current_block_num = current_cursor.blockNumber()
current_block = self.textEdit.document().findBlockByNumber(current_block_num)
start_pos = min(current_cursor.position(), current_cursor.anchor()) - current_block.position()
- self.undo_stack.beginMacro("Paste")
+ self.undo_stack.beginMacro(f"Paste: {store_data.to_text()}")
self.textEdit.blockSignals(True)
for el in store_data.data:
current_block = self.textEdit.textCursor().blockNumber()
@@ -2280,7 +2334,7 @@ def insert_text(self):
current_block_num = current_cursor.blockNumber()
current_block = self.textEdit.document().findBlockByNumber(current_block_num)
start_pos = min(current_cursor.position(), current_cursor.anchor()) - current_block.position()
- self.undo_stack.beginMacro("Insert normal text")
+ self.undo_stack.beginMacro(f"Insert: {text}")
fake_steno = text_element(text = text)
insert_cmd = steno_insert(self.textEdit, current_block_num, start_pos, fake_steno)
self.undo_stack.push(insert_cmd)
@@ -2313,10 +2367,8 @@ def edit_fields(self):
new_field_dict = self.field_dialog.user_field_dict
# after user_field_dict is set, then refresh
current_cursor = self.textEdit.textCursor()
- self.undo_stack.beginMacro("Refresh fields.")
update_cmd = update_field(self.textEdit, current_cursor.blockNumber(), current_cursor.positionInBlock(), self.user_field_dict, new_field_dict)
self.undo_stack.push(update_cmd)
- self.undo_stack.endMacro()
def add_begin_auto_affix(self, element, style):
if style not in self.auto_paragraph_affixes:
@@ -2352,7 +2404,7 @@ def insert_index_entry(self, el = None, action = None):
el = index_text(prefix = index_prefix, indexname = index_name, hidden = index_hidden, text = txt)
start_pos = current_cursor.positionInBlock()
current_block = current_cursor.blockNumber()
- self.undo_stack.beginMacro("Insert index")
+ self.undo_stack.beginMacro("Insert: index entry")
if current_cursor.hasSelection() and el == None:
self.cut_steno(store = False)
self.textEdit.setTextCursor(current_cursor)
@@ -2616,7 +2668,7 @@ def replace(self, to_next = True, steno = "", replace_term = None):
replace_term = self.replace_term.text()
if self.textEdit.textCursor().hasSelection():
log.debug("Replace %s with %s", self.textEdit.textCursor().selectedText(), replace_term)
- self.undo_stack.beginMacro("Replace")
+ self.undo_stack.beginMacro(f"Replace: {self.textEdit.textCursor().selectedText()} with {replace_term}")
current_cursor = self.textEdit.textCursor()
current_block = current_cursor.block()
start_pos = min(current_cursor.position(), current_cursor.anchor()) - current_block.position()
@@ -3083,6 +3135,7 @@ def import_rtf(self):
self.statusBar.showMessage("Parsing RTF.")
self.progressBar = QProgressBar(self)
self.statusBar.addWidget(self.progressBar)
+ # self.progressBar.show()
parse_results = rtf_steno(selected_file[0], self.progressBar)
QApplication.setOverrideCursor(Qt.WaitCursor)
parse_results.parse_document()
@@ -3090,7 +3143,7 @@ def import_rtf(self):
style_dict, renamed_indiv_style = load_rtf_styles(parse_results)
rtf_paragraphs = parse_results.paragraphs
for ind, par in rtf_paragraphs.items():
- par["data"]["style"] = renamed_indiv_style[int(ind)]
+ par["style"] = renamed_indiv_style[int(ind)]
file_path = pathlib.Path(pathlib.Path(selected_file[0]).name).with_suffix(".transcript")
file_path = self.file_name / file_path
save_json(rtf_paragraphs, file_path)
diff --git a/plover_cat/plover_cat.ui b/plover_cat/plover_cat.ui
index 1be4701..e0b1809 100644
--- a/plover_cat/plover_cat.ui
+++ b/plover_cat/plover_cat.ui
@@ -6,7 +6,7 @@
0
0
- 956
+ 955
743
@@ -71,7 +71,7 @@
true
-
+
-
@@ -147,7 +147,8 @@
- 8
+ Courier New
+ 12
@@ -187,7 +188,7 @@
0
0
- 956
+ 955
19
@@ -244,9 +245,6 @@
-
-
-
@@ -331,6 +329,9 @@
Steno Actions
+
+ true
+
@@ -348,6 +349,9 @@
Styling
+
+ true
+
@@ -360,6 +364,9 @@
Insert
+
+ true
+
+ -
+
+
+ Page right margin
+
+
+ in.
+
+
+ 4
+
+
+ 0.379900000000000
+
+
+
+ -
+
- 5
+ Bottom Margin
-
- -
+
+
+ -
+
+
+ Page bottom margin
+
+
+ in.
+
+
+ 4
+
+
+ 0.787400000000000
+
+
+
+ -
+
- 6
+ Max Char per Line
-
- -
+
+
+ -
+
+
+ Each line can contain at most n char, excluding line number and timestamps
+
+
+ Automatic
+
+
+
+ -
+
- 7
+ Max Lines per Page
-
- -
+
+
+ -
+
+
+ Automatic
+
+
+
+ -
+
- 8
+ Line Numbering
+
+
+
+ -
+
+
+ Enable line number in applicable formats
-
- -
- 9
+
-
- -
+
+
+ -
+
- 10
+ Frequency
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Modify Current Style
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
- 0
- 0
- 410
- 415
-
-
-
- Set page format for ODF export
-
-
-
- :/document-resize-actual.png:/document-resize-actual.png
-
-
- Page Format
-
-
- -
-
-
-
-
-
- Page Width
-
-
-
- -
-
-
- Paper width
-
-
- in.
-
-
- 8.500000000000000
-
-
-
- -
-
-
- Page Height
-
-
-
- -
-
-
- Page height
-
-
- in.
-
-
- 11.000000000000000
-
-
-
- -
-
-
- Left Margin
-
-
-
- -
-
-
- Page left margin
-
-
- in.
-
-
- 4
-
-
- 1.750000000000000
-
-
-
- -
-
-
- Top Margin
-
-
-
- -
-
-
- Page top margin
-
-
- in.
-
-
- 4
-
-
- 0.787400000000000
-
-
-
- -
-
-
- Right Margin
-
-
-
- -
-
-
- Page right margin
-
-
- in.
-
-
- 4
-
-
- 0.379900000000000
-
-
-
- -
-
-
- Bottom Margin
-
-
-
- -
-
-
- Page bottom margin
-
-
- in.
-
-
- 4
-
-
- 0.787400000000000
-
-
-
- -
-
-
- Max Char per Line
-
-
-
- -
-
-
- Each line can contain at most n char,
-excluding line number and timestamps
-
-
- Automatic
-
-
-
- -
-
-
- Max Lines per Page
-
-
-
- -
-
-
- Automatic
-
-
-
- -
-
-
- Line Numbering
-
-
-
- -
-
-
- Enable line number in applicable formats
-
-
-
-
-
-
- -
-
-
- Frequency
-
-
-
- -
-
-
- Show line number every nth line for ODF
-
-
- 1
-
-
-
- -
-
-
- Line Timestamp
-
-
-
- -
-
-
-
-
-
-
-
-
- -
-
-
- Header:
-
-
-
- -
-
-
-
-
-
- Qt::ClickFocus
-
-
- Header text to be aligned left.
-Use %p for page number.
-
-
- Left
-
-
-
- -
-
-
- Qt::ClickFocus
-
-
- Header text to be centered.
-Use %p for page number.
-
-
- Center
-
-
-
- -
-
-
- Qt::ClickFocus
-
-
- Header text to be aligned right.
-Use %p for page number.
-
-
- Right
-
-
-
-
-
- -
-
-
- Footer:
-
-
-
- -
-
-
-
-
-
- Qt::ClickFocus
-
-
- Footer text to be aligned left.
-Use %p for page number.
-
-
- Left
-
-
-
- -
-
-
- Qt::ClickFocus
-
-
- Footer text to be aligned center.
-Use %p for page number.
-
-
- Center
-
-
-
- -
-
-
- Qt::ClickFocus
-
-
- Footer text to be aligned right.
-Use %p for page number.
-
-
- Right
-
-
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Confirm and save changes to page and margin parameters
-
-
- Change Page Layout
-
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
-
-
-
-
- 0
- 0
- 300
- 217
-
-
-
- Find and replace
-
-
-
- :/magnifier.png:/magnifier.png
-
-
- Find and Replace
-
-
- -
-
-
-
-
-
- Search Types
-
-
-
-
-
-
- Search visible text for match
-
-
- Text
-
-
- true
-
-
-
- -
-
-
- Search strokes, find text has to match stroke completely
-
-
- Underlying Steno
-
-
-
- -
-
-
- Search what appears to be untranslated chords in visible text
-
-
- Untrans
-
-
-
-
-
-
- -
-
-
-
-
-
- Text to search for
-
-
- Find
-
-
-
- -
-
-
- Perform selected search forwards
-
-
- Next
-
-
+
+
+ -
+
+
+ Show line number every nth line for ODF
+
+
+ 1
+
+
+
+ -
+
+
+ Line Timestamp
+
+
+
+ -
+
+
+
+
+
+
+
-
-
-
- Perform selected search backwards
-
+
- Previous
+ Header:
-
-
- -
-
-
-
-
- The text to replace the match found in search
-
-
- Replace
-
-
+
+
-
+
+
+ Qt::ClickFocus
+
+
+ Header text to be aligned left. Use %p for page number.
+
+
+ Left
+
+
+
+ -
+
+
+ Qt::ClickFocus
+
+
+ Header text to be centered. Use %p for page number.
+
+
+ Center
+
+
+
+ -
+
+
+ Qt::ClickFocus
+
+
+ Header text to be aligned right. Use %p for page number.
+
+
+ Right
+
+
+
+
-
-
-
- Replace the found match with the text in the replace textbox
-
+
- Once
+ Footer:
-
-
-
- Replace all matches with text in replace textbox
-
-
- All
-
-
-
-
-
- -
-
-
-
-
-
- Case sensitive search
-
-
- Match Case
-
-
+
+
-
+
+
+ Qt::ClickFocus
+
+
+ Footer text to be aligned left. Use %p for page number.
+
+
+ Left
+
+
+
+ -
+
+
+ Qt::ClickFocus
+
+
+ Footer text to be aligned center. Use %p for page number.
+
+
+ Center
+
+
+
+ -
+
+
+ Qt::ClickFocus
+
+
+ Footer text to be aligned right. Use %p for page number.
+
+
+ Right
+
+
+
+
-
-
-
- Text in find has to match text as a whole word/stroke
-
-
- Whole Word/Stroke
+
+
+ Qt::NoFocus
-
-
- -
-
- Search will continue from top/bottom if a match is not found in forward/back search
+ Confirm and save changes to page and margin parameters
- Wrap
+ Change Page Layout
-
- -
-
-
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 40
-
-
-
-
-
+
+
-
-
-
- 0
- 0
- 251
- 215
-
-
-
- Edit paragraph properties
-
+
- :/edit-pilcrow.png:/edit-pilcrow.png
+ :/magnifier.png:/magnifier.png
-
- Paragraph Properties Editor
+
+ Find and Replace
-
+
+ Find and replace
+
+
-
-
-
- Qt::NoFocus
-
-
- Unlock to edit paragraph properties
-
-
- Lock
-
-
+
+
true
-
-
- -
-
-
-
-
-
- Paragraph
-
-
-
- -
-
-
- Paragraph number
-
-
- 0
-
-
-
- -
-
-
- Creation Time
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Modifies creation time of paragraph
-
-
- yyyy-MM-dd hh:mm:ss.zzz
-
-
-
- -
-
-
- Edit Time
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Read only. Displays edit time
-of paragraph. Will change upon
-any cursor movement.
-
-
- true
-
-
- yyyy-MM-dd hh:mm:ss.zzz
-
-
-
- -
-
-
- Audio Start Time
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Modifies audio start time of
-paragraph. By default, the
-audio end time of the previous
-paragraph is set by this
-unless previous paragraph has
-audio end time set.
-
-
- QDateTimeEdit::HourSection
-
-
- hh:mm:ss.zzz
-
-
-
- -
-
-
- Audio End Time
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Sets audio end time for this
-paragraph. If not set, audio
-end time is the audio start
-time from next paragraph.
-
-
- hh:mm:ss.zzz
-
-
-
- -
-
-
- Notes
-
-
-
- -
-
-
- Qt::NoFocus
-
-
- Custom notes attached to paragraph
-
-
-
-
-
- -
-
-
- false
-
-
- Qt::NoFocus
-
-
- Enable with checkbox in order to submit edits
-
-
- Edit Paragraph Properties
-
-
-
-
-
-
-
-
- 0
- 0
- 239
- 279
-
-
-
-
- :/microphone.png:/microphone.png
-
-
- Audio Recording
-
-
- -
-
-
-
-
-
-
-
-
- Input Device:
-
-
-
- -
-
-
- Audio Codec:
-
-
-
- -
-
-
- File Container:
-
-
-
- -
-
-
- Sample Rate
-
-
-
- -
-
-
- Select where to receive audio input
+
+
+
+ 0
+ 0
+ 405
+ 240
+
+
+
+
-
+
+
+ Search Types
+
+
-
+
+
+ Search visible text for match
+
+
+ Text
+
+
+ true
+
+
+
+ -
+
+
+ Search strokes, find text has to match stroke completely
+
+
+ Underlying Steno
+
+
+
+ -
+
+
+ Search what appears to be untranslated chords in visible text
+
+
+ Untrans
+
+
+
+
- -
-
-
- Select the audio codec to record with, depends on system codecs
-
-
+
-
+
+
-
+
+
+ Text to search for
+
+
+ Find
+
+
+
+ -
+
+
+ Perform selected search forwards
+
+
+ Next
+
+
+
+ -
+
+
+ Perform selected search backwards
+
+
+ Previous
+
+
+
+
- -
-
-
- Select audio file type, depends on system
-
-
+
-
+
+
-
+
+
+ The text to replace the match found in search
+
+
+ Replace
+
+
+
+ -
+
+
+ Replace the found match with the text in the replace textbox
+
+
+ Once
+
+
+
+ -
+
+
+ Replace all matches with text in replace textbox
+
+
+ All
+
+
+
+
- -
-
-
- Select the sample rate for the recorded audio
-
-
- QComboBox::InsertAtTop
-
-
+
-
+
+
-
+
+
+ Case sensitive search
+
+
+ Match Case
+
+
+
+ -
+
+
+ Text in find has to match text as a whole word/stroke
+
+
+ Whole Word/Stroke
+
+
+
+ -
+
+
+ Search will continue from top/bottom if a match is not found in forward/back search
+
+
+ Wrap
+
+
+
+
- -
-
-
- Select number of channels to record from
+
-
+
+
+ Qt::Vertical
-
-
- -
-
-
- Channels
+
+
+ 20
+ 40
+
-
+
-
- -
-
-
- Encoding Mode
-
-
-
-
-
-
- Constant quality means varying bitrate for audio file
-
-
- Constant Quality
-
-
- true
-
-
-
- -
-
-
- Quality of audio recording, from very bad to very good
-
-
- 4
-
-
- 2
-
-
- Qt::Horizontal
-
-
-
- -
-
-
- Constant bitrate means quality will vary
-
-
- Constant Bitrate
-
-
-
- -
-
-
- Select bitrate for recording
-
-
-
-
-
-
-
+
+
-
-
-
- 0
- 0
- 361
- 164
-
-
+
- :/spell-check.png:/spell-check.png
+ :/edit-pilcrow.png:/edit-pilcrow.png
-
- Spellcheck
+
+ Paragraph
+
+
+ Edit paragraph properties
-
+
-
-
-
-
-
-
-
-
-
-
-
- en-US
-
-
-
-
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 405
+ 240
+
+
+
-
-
-
- Search
+
+
+ Qt::NoFocus
+
+
+ Unlock to edit paragraph properties
-
-
- -
-
- Skip
+ Lock
+
+
+ true
-
-
-
- Ignore All
-
-
+
+
-
+
+
+ Paragraph
+
+
+
+ -
+
+
+ Paragraph number
+
+
+ 0
+
+
+
+ -
+
+
+ Creation Time
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Modifies creation time of paragraph
+
+
+ yyyy-MM-dd hh:mm:ss.zzz
+
+
+
+ -
+
+
+ Edit Time
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Read only. Displays edit time of paragraph. Will change upon any cursor movement.
+
+
+ true
+
+
+ yyyy-MM-dd hh:mm:ss.zzz
+
+
+
+ -
+
+
+ Audio Start Time
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Modifies audio start time of paragraph.
+
+
+ QDateTimeEdit::HourSection
+
+
+ hh:mm:ss.zzz
+
+
+
+ -
+
+
+ Audio End Time
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Sets audio end time for this paragraph.
+
+
+ hh:mm:ss.zzz
+
+
+
+ -
+
+
+ Notes
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Custom notes attached to paragraph
+
+
+
+
-
-
+
+
+ false
+
+
+ Qt::NoFocus
+
+
+ Enable with checkbox in order to submit edits
+
- Change
+ Edit Paragraph Properties
-
-
+
Qt::Vertical
@@ -2366,45 +2161,314 @@ time from next paragraph.
-
- -
-
+
+
+
+
+
+
+
+
+ :/microphone.png:/microphone.png
+
+
+ Audio Recording
+
+
+ Set audio recording parameters
+
+
+ -
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 388
+ 283
+
+
+
-
-
-
-
-
+
+
-
+
- Detected:
+ Input Device:
- -
-
+
-
+
+
+ Audio Codec:
+
+
+
+ -
+
+
+ File Container:
+
+
+
+ -
+
+
+ Sample Rate
+
+
+
+ -
+
+
+ Select where to receive audio input
+
+
+
+ -
+
+
+ Select the audio codec to record with, depends on system codecs
+
+
+
+ -
+
+
+ Select audio file type, depends on system
+
+
+
+ -
+
+
+ Select the sample rate for the recorded audio
+
+
+ QComboBox::InsertAtTop
+
+
+
+ -
+
+
+ Select number of channels to record from
+
+
+
+ -
+
+
+ Channels
+
+
-
-
-
- Qt::NoFocus
-
-
- Qt::NoContextMenu
-
-
- Double click on choice to replace
-
-
- QAbstractItemView::DoubleClicked
-
-
- QAbstractItemView::SelectColumns
+
+
+ Encoding Mode
+
+
-
+
+
+ Constant quality means varying bitrate for audio file
+
+
+ Constant Quality
+
+
+ true
+
+
+
+ -
+
+
+ Quality of audio recording, from very bad to very good
+
+
+ 4
+
+
+ 2
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Constant bitrate means quality will vary
+
+
+ Constant Bitrate
+
+
+
+ -
+
+
+ Select bitrate for recording
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+ :/spell-check.png:/spell-check.png
+
+
+ Spellcheck
+
+
+ Spellcheck editor transcript
+
+
+ -
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 405
+ 240
+
+
+
+
-
+
+
-
+
+
-
+
+
-
+
+ en-US
+
+
+
+
+ -
+
+
+ Search
+
+
+
+ -
+
+
+ Skip
+
+
+
+ -
+
+
+ Ignore All
+
+
+
+ -
+
+
+ Change
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+ -
+
+
-
+
+
-
+
+
+ Detected:
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ Qt::NoContextMenu
+
+
+ Double click on choice to replace
+
+
+ QAbstractItemView::DoubleClicked
+
+
+ QAbstractItemView::SelectColumns
+
+
+
+
+
+
+
+
+
+
@@ -2453,7 +2517,7 @@ time from next paragraph.
- 97
+ 100
210
@@ -2488,7 +2552,7 @@ time from next paragraph.
Qt::NoContextMenu
- Placeholder for new development
+ Session actions for undo/redo
@@ -2500,12 +2564,19 @@ time from next paragraph.
-
-
+
+
+ Previous versions to revert to
+
+
-
-
+
+ Confirm revert transcript to selected revision
+
Revert
@@ -2521,7 +2592,7 @@ time from next paragraph.
- 174
+ 127
113
@@ -2620,7 +2691,7 @@ time from next paragraph.
- 91
+ 100
113
@@ -2804,63 +2875,6 @@ time from next paragraph.
Ctrl+V
-
-
-
- :/magnifier-zoom-in.png:/magnifier-zoom-in.png
-
-
- Zoom In
-
-
- Ctrl+=
-
-
-
-
-
- :/magnifier-zoom-out.png:/magnifier-zoom-out.png
-
-
- Zoom Out
-
-
- Ctrl+-
-
-
-
-
-
- :/arrow-curve-180.png:/arrow-curve-180.png
-
-
- Undo
-
-
- Undo writing or other action
-
-
- Ctrl+Z
-
-
- Qt::WindowShortcut
-
-
-
-
-
- :/arrow-curve.png:/arrow-curve.png
-
-
- Redo
-
-
- Redo writing or other action
-
-
- Ctrl+Y
-
-
@@ -2967,9 +2981,7 @@ time from next paragraph.
Merge Paragraphs
- Merge two paragraphs by selecting across
-two paragraphs, or place cursor in second of
-paragraphs to merge
+ Merge two paragraphs by selecting across two paragraphs, or place cursor in second of paragraphs to merge
@@ -2993,7 +3005,7 @@ paragraphs to merge
Window Font
- Set font and size
+ Set font and size for window
@@ -3111,7 +3123,7 @@ paragraphs to merge
Lock Cursor At End
- All writing will be appended to end of document
+ Keep cursor at end of document for writing
@@ -3122,7 +3134,7 @@ paragraphs to merge
Capture All Steno Output
- Steno output will be recorded even if window is not in focus
+ Record steno output even if window is not in focus
@@ -3219,6 +3231,9 @@ paragraphs to merge
Reset Paragraph
+
+ Reset paragraph by clearing all data and text
+
@@ -3258,6 +3273,9 @@ paragraphs to merge
About
+
+ Show version information
+
F1
@@ -3281,6 +3299,9 @@ paragraphs to merge
Paper Tape Font
+
+ Set font for Paper Tape dock
+
@@ -3298,19 +3319,24 @@ paragraphs to merge
Generate Style File From Template
- Select template file (ODT) and generate style json
-with supported formatting only.
+ Select template file (ODT) and generate style json with supported formatting only.
Create New Style
+
+ Create new style based on existing style
+
Refresh Editor
+
+ Refresh formatting for entire editor
+
F5
@@ -3324,8 +3350,7 @@ with supported formatting only.
Normal Text
- Insert normal text through popup
-dialog box at cursor location
+ Insert normal text through dialog
Ins
@@ -3342,6 +3367,9 @@ dialog box at cursor location
Show All Characters
+
+ Show all characters, including invisible ones
+
@@ -3378,11 +3406,17 @@ dialog box at cursor location
Jump to Paragraph ...
+
+ Jump to paragraph by paragraph number
+
Translate Tape
+
+ Import tape file to translate to editor
+
@@ -3397,6 +3431,9 @@ dialog box at cursor location
Image
+
+ Insert image object
+
@@ -3407,7 +3444,7 @@ dialog box at cursor location
Background Color
- Set background color
+ Set background color of editor window
@@ -3425,6 +3462,9 @@ dialog box at cursor location
Delete Last Untrans
+
+ Scan to find last untranslate, and delete it
+
@@ -3444,6 +3484,9 @@ dialog box at cursor location
Edit Fields
+
+ Edit fields values
+
@@ -3456,7 +3499,7 @@ dialog box at cursor location
Autosave
- Enable autosave to backup file every 5 minutes.
+ Enable autosave to backup file at defined time intervals
@@ -3467,28 +3510,40 @@ dialog box at cursor location
Edit Paragraph Affixes
- Open dialog to edit paragraph affixes
+ Add and edit paragraph affixes for styles
Set Autosave Time
+
+ Set time intervals for autosave
+
Edit Menu Shortcuts
+
+ Customize shortcuts for menu items
+
Edit Indices
+
+ Add and edit index and index entries
+
Transcript Suggestions
+
+ Analyze transcript for common words/n-grams to add to dictionary
+
diff --git a/plover_cat/qcommands.py b/plover_cat/qcommands.py
index 11b0bcc..bcd67e3 100644
--- a/plover_cat/qcommands.py
+++ b/plover_cat/qcommands.py
@@ -127,7 +127,7 @@ def redo(self):
self.document.setTextCursor(current_cursor)
log_dict = {"action": "remove", "block": self.block, "position_in_block": self.position_in_block, "end": self.position_in_block + self.length}
log.info(f"Remove: {log_dict}")
- self.setText("Remove: %d chars" % len(self.steno))
+ self.setText("Remove: %d backspace(s)" % len(self.steno))
def undo(self):
current_cursor = self.document.textCursor()
current_block = self.document.document().findBlockByNumber(self.block)
@@ -177,7 +177,7 @@ def redo(self):
log.info(f"Insert: {log_dict}")
current_cursor.insertImage(imageFormat)
current_block.setUserState(1)
- self.setText("Insert image")
+ self.setText("Insert: image object")
self.document.setTextCursor(current_cursor)
def undo(self):
current_cursor = self.document.textCursor()
@@ -253,7 +253,7 @@ def redo(self):
current_block.setUserData(first_data)
new_block.setUserData(second_data)
new_block.setUserState(1)
- self.setText("Split: %d,%d" % (self.block, self.position_in_block))
+ self.setText("Split: paragraph %d at %d" % (self.block, self.position_in_block))
log_dict = {"action": "split", "block": self.block, "position_in_block": self.position_in_block}
log.info(f"Split: {log_dict}")
self.block_state = current_block.userState()
@@ -334,7 +334,7 @@ def redo(self):
log_dict = {"action": "merge", "block": self.block}
log.info(f"Merge: {log_dict}")
self.document.setTextCursor(current_cursor)
- self.setText("Merge: %d & %d" % (first_block_num, second_block_num))
+ self.setText("Merge: paragraphs %d & %d" % (first_block_num, second_block_num))
def undo(self):
current_cursor = self.document.textCursor()
first_block_num = self.block
@@ -389,7 +389,7 @@ def redo(self):
self.style = list(self.par_formats.keys())[0]
block_data = update_user_data(block_data, "style", self.style)
current_block.setUserData(block_data)
- self.setText("Style: Par. %d set style %s" % (self.block, self.style))
+ self.setText(f"Format: set paragraph {self.block} style to {self.style}")
current_cursor.movePosition(QTextCursor.StartOfBlock)
current_cursor.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor)
current_cursor.setBlockFormat(self.par_formats[self.style])
@@ -435,6 +435,26 @@ def undo(self):
log_dict = {"action": "set_style", "block": self.block, "style": self.old_style}
log.info(f"Style: {log_dict}")
+class style_update(QUndoCommand):
+ def __init__(self, styles, style_name, new_style_dict):
+ super().__init__()
+ self.styles = styles
+ self.style_name = style_name
+ self.new_style_dict = deepcopy(new_style_dict)
+ self.old_style_dict = {}
+ def redo(self):
+ if self.style_name in self.styles:
+ self.old_style_dict = deepcopy(self.styles[self.style_name])
+ self.styles[self.style_name] = self.new_style_dict
+ log_dict = {"action": "edit_style", "style_dict": self.new_style_dict}
+ log.info(f"Style: {log_dict}")
+ self.setText(f"Style: update style attributes for style {self.style_name}")
+ def undo(self):
+ if self.old_style_dict:
+ self.styles[self.style_name] = self.old_style_dict
+ log_dict = {"action": "edit_style", "style_dict": self.old_style_dict}
+ log.info(f"Style undo: {log_dict}")
+
class update_field(QUndoCommand):
def __init__(self, document, block, position, old_dict, new_dict):
super().__init__()
@@ -470,7 +490,7 @@ def redo(self):
break
block = block.next()
current_cursor.setPosition(current_block.position() + self.position_in_block)
- self.setText("Update fields")
+ self.setText("Fields: update fields")
log_dict = {"action": "field", "field": self.new_dict}
log.info(f"Field: {log_dict}")
def undo(self):
@@ -530,7 +550,7 @@ def redo(self):
break
block = block.next()
current_cursor.setPosition(current_block.position() + self.position_in_block)
- self.setText("Update indexes.")
+ self.setText("Indices: update indices.")
log_dict = {"action": "index", "index": self.new_dict}
log.info(f"Index: {log_dict}")
def undo(self):
diff --git a/plover_cat/rtf_parsing.py b/plover_cat/rtf_parsing.py
index 8ee3373..1ab3f36 100644
--- a/plover_cat/rtf_parsing.py
+++ b/plover_cat/rtf_parsing.py
@@ -28,14 +28,16 @@
from plover import log
+from plover_cat.steno_objects import *
# control chars \ { }
LBRACE, RBRACE, BKS = map(Suppress, "{}\\")
-text_whitespace = Word(printables, exclude_chars="\\{}") + Opt(White(" ", max=1))
+text_whitespace = Opt(White(" \t")) + Word(printables, exclude_chars="\\{}") + Opt(White(" \t"))
text_whitespace.set_name("text")
+text_whitespace.leave_whitespace()
# special chars that exist as control words, should be replaced by actual unicode equivalents,
tab_char = Literal("\\tab").set_parse_action(replace_with("\N{CHARACTER TABULATION}")) # "\t" or "\N{CHARACTER TABULATION}"
@@ -97,6 +99,7 @@ def control_parse(s, l, t):
Combine(
Word(alphas)("control") +
Opt(Word(nums + '-').set_parse_action(common.convert_to_integer))("num") +
+ Opt(White(" ", max=1)) +
Opt(Literal(";"))("ending")
)
)
@@ -226,34 +229,35 @@ def parse_cxa(self, element):
cxa_dict = collapse_dict(element)
self.steno = ""
self.text = cxa_dict["text"]
- self.append_stroke()
+ timestamp = "%sT%s:%s:%s.%s" % (self.date, self.timecode["hour"], self.timecode["min"], self.timecode["sec"], self.timecode["milli"])
+ ael = automatic_text(prefix = cxa_dict["text"], time = timestamp)
+ self.par.append(ael.to_json())
def parse_steno(self, element):
try:
stroke = element[1]["value"]
except:
stroke = ""
self.steno = stroke
- # self.steno = stroke_element
def parse_text(self, element):
self.text = element["value"]
def append_stroke(self):
timestamp = "%sT%s:%s:%s.%s" % (self.date, self.timecode["hour"], self.timecode["min"], self.timecode["sec"], self.timecode["milli"])
- stroke = [timestamp, self.steno, self.text]
- self.par.append(stroke)
+ ael = stroke_text(time = timestamp, stroke = self.steno, text = self.text)
+ self.par.append(ael.to_json())
def convert_framerate_milli(self, frames):
milli = 1000 * frames / self.framerate
return(milli)
def set_new_paragraph(self):
- strokes = self.par
par_num = str(len(self.paragraphs))
- par_text = "".join([stroke[2] for stroke in self.par])
+ # par_text = "".join([stroke[2] for stroke in self.par])
# this last stroke should capture the stroke emitting \par
timestamp = "%sT%s:%s:%s.%s" % (self.date, self.timecode["hour"], self.timecode["min"], self.timecode["sec"], self.timecode["milli"])
- last_stroke = [timestamp, self.steno, "\n"]
- self.par.append(last_stroke)
+ last_stroke = stroke_text(time = timestamp, stroke = self.steno, text = "\n")
+ self.par.append(last_stroke.to_json())
+ strokes = self.par
par_dict = {}
- par_dict["text"] = par_text
- par_dict["data"] = {"strokes": strokes, "creationtime": strokes[0][0]}
+ par_dict["strokes"] = strokes
+ par_dict["creationtime"] = strokes[0]["time"]
par_dict["style"] = self.par_style
self.paragraphs[par_num] = par_dict
self.par = []
@@ -306,7 +310,7 @@ def scan_par_styles(self):
style_list = []
for i in style_string.scanString(data):
style_list.append(i[0].asList())
- print(len(style_list))
+ # print(len(style_list))
par_style_index = 0
for ind, el in enumerate(style_list):
new_style_dict = collapse_dict(el)
@@ -360,9 +364,6 @@ def modify_styleindex_to_name(styles, style_names):
styles[key] = i
return(styles)
-def modify_fontindex_to_name(font,font_name):
- pass
-
def extract_par_style(style_dict):
one_style_dict = {}
one_part_dict = {}
@@ -477,19 +478,3 @@ def load_rtf_styles(parse_results):
renamed_indiv_style.append(new_style_name)
return(style_dict, renamed_indiv_style)
-def generate_stroke_rtf(stroke):
- time_string = datetime.strptime(stroke[0], "%Y-%m-%dT%H:%M:%S.%f").strftime('%H:%M:%S')
- string = write_command("cxt", time_string + ":00", visible = False, group = True) + write_command("cxs", stroke[1], visible = False, group = True) + stroke[2]
- return(string)
-
-def write_command(control, text = None, value = None, visible = True, group = False):
- command = "\\" + control
- if value is not None:
- command = command + str(value)
- if text:
- command = command + " " + text
- if not visible:
- command = "\\*" + command
- if group:
- command = "{" + command + "}"
- return(command)
\ No newline at end of file
diff --git a/plover_cat/shortcut_dialog.ui b/plover_cat/shortcut_dialog.ui
index 84ac6b0..2eb5580 100644
--- a/plover_cat/shortcut_dialog.ui
+++ b/plover_cat/shortcut_dialog.ui
@@ -24,7 +24,11 @@
-
-
+
+
+ Name of menu item
+
+
-
@@ -34,7 +38,11 @@
-
-
+
+
+ Click and then press keys for shortcut
+
+
@@ -45,6 +53,9 @@
Qt::NoContextMenu
+
+ Check if shortcuts conflict and save
+
Validate and save
diff --git a/plover_cat/shortcut_dialog_ui.py b/plover_cat/shortcut_dialog_ui.py
deleted file mode 100644
index eddd4a9..0000000
--- a/plover_cat/shortcut_dialog_ui.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Form implementation generated from reading ui file 'plover_cat\shortcut_dialog.ui'
-#
-# Created by: PyQt5 UI code generator 5.15.9
-#
-# WARNING: Any manual changes made to this file will be lost when pyuic5 is
-# run again. Do not edit this file unless you know what you are doing.
-
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-
-class Ui_shortcutDialog(object):
- def setupUi(self, shortcutDialog):
- shortcutDialog.setObjectName("shortcutDialog")
- shortcutDialog.resize(391, 129)
- self.verticalLayout = QtWidgets.QVBoxLayout(shortcutDialog)
- self.verticalLayout.setObjectName("verticalLayout")
- self.formLayout = QtWidgets.QFormLayout()
- self.formLayout.setObjectName("formLayout")
- self.label = QtWidgets.QLabel(shortcutDialog)
- self.label.setObjectName("label")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
- self.text_name = QtWidgets.QComboBox(shortcutDialog)
- self.text_name.setObjectName("text_name")
- self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.text_name)
- self.label_2 = QtWidgets.QLabel(shortcutDialog)
- self.label_2.setObjectName("label_2")
- self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
- self.shortcut = QtWidgets.QKeySequenceEdit(shortcutDialog)
- self.shortcut.setObjectName("shortcut")
- self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.shortcut)
- self.verticalLayout.addLayout(self.formLayout)
- self.horizontalLayout = QtWidgets.QHBoxLayout()
- self.horizontalLayout.setObjectName("horizontalLayout")
- self.validate = QtWidgets.QPushButton(shortcutDialog)
- self.validate.setContextMenuPolicy(QtCore.Qt.NoContextMenu)
- self.validate.setObjectName("validate")
- self.horizontalLayout.addWidget(self.validate)
- self.buttonBox = QtWidgets.QDialogButtonBox(shortcutDialog)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth())
- self.buttonBox.setSizePolicy(sizePolicy)
- self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
- self.buttonBox.setObjectName("buttonBox")
- self.horizontalLayout.addWidget(self.buttonBox)
- self.verticalLayout.addLayout(self.horizontalLayout)
-
- self.retranslateUi(shortcutDialog)
- self.buttonBox.accepted.connect(shortcutDialog.accept) # type: ignore
- self.buttonBox.rejected.connect(shortcutDialog.reject) # type: ignore
- QtCore.QMetaObject.connectSlotsByName(shortcutDialog)
-
- def retranslateUi(self, shortcutDialog):
- _translate = QtCore.QCoreApplication.translate
- shortcutDialog.setWindowTitle(_translate("shortcutDialog", "Dialog"))
- self.label.setText(_translate("shortcutDialog", "Menu Item"))
- self.label_2.setText(_translate("shortcutDialog", "Shortcut"))
- self.validate.setText(_translate("shortcutDialog", "Validate and save"))
diff --git a/plover_cat/steno_objects.py b/plover_cat/steno_objects.py
index de0996d..e66ad38 100644
--- a/plover_cat/steno_objects.py
+++ b/plover_cat/steno_objects.py
@@ -5,8 +5,7 @@
from copy import deepcopy
from itertools import accumulate
from bisect import bisect_left, bisect
-from plover_cat.rtf_parsing import write_command
-from plover_cat.helpers import pixel_to_in
+from plover_cat.helpers import pixel_to_in, write_command
from plover_cat.constants import user_field_dict
from PyQt5.QtCore import QByteArray, QBuffer, QIODevice
from PyQt5.QtGui import QImage, QImageReader
@@ -14,6 +13,10 @@
from odf.text import P, UserFieldDecls, UserFieldDecl, UserFieldGet, UserIndexMarkStart, UserIndexMarkEnd
from odf.draw import Frame, TextBox, Image
+_whitespace = '\t\n\x0b\x0c\r '
+whitespace = r'[%s]' % re.escape(_whitespace)
+wordsep_simple_re = re.compile(r'(%s+)' % whitespace)
+
# display letters: s for stroke, t for text, i for image,
# sa for auto, c for conflict, f for field
# x for "index"/references, e for exhibit
@@ -28,6 +31,22 @@ def __init__(self, text = "", time = None):
self.time = time or datetime.now().isoformat("T", "milliseconds")
def __len__(self):
return(len(self.data))
+ def split(self):
+ if self.length() > 1:
+ chunks = wordsep_simple_re.split(self.data)
+ list_chunks = []
+ for c in chunks:
+ class_dict = deepcopy(self.__dict__)
+ class_dict["data"] = c
+ new_element = self.__class__()
+ new_element.from_dict(class_dict)
+ list_chunks.append(new_element)
+ return(list_chunks)
+ else:
+ class_dict = deepcopy(self.__dict__)
+ new_element = self.__class__()
+ new_element.from_dict(class_dict)
+ return([new_element])
def __getitem__(self, key):
class_dict = deepcopy(self.__dict__)
class_dict["data"] = self.data[key]
@@ -170,9 +189,7 @@ def to_odt(self, paragraph, document):
paragraph.addElement(user_field)
class automatic_text(stroke_text):
- """
- use for text such as Q\t, ie set directly for question style
- """
+ """use for text such as Q\t, ie set directly for question style"""
def __init__(self, prefix = "", suffix = "", **kargs):
super().__init__(**kargs)
self.element = "automatic"
@@ -533,6 +550,12 @@ def search_text(self, query):
def collection_time(self, reverse = False):
times = [el.time for el in self.data]
return(sorted(times, reverse = reverse)[0])
+ def audio_time(self, reverse = False):
+ times = [el.audiotime for el in self.data if el.element == "stroke" and el.audiotime != ""]
+ if times:
+ return(sorted(times, reverse = reverse)[0])
+ else:
+ return None
def replace_initial_tab(self, tab_replace = " "):
track_len = 3
for el in self.data:
@@ -580,25 +603,10 @@ def __init__(self, **kargs):
def _split(self, text):
# override
- # split text/stroke elements, return as chunked text elements
- # other elements just keep together
text.remove_end()
chunks = []
- txt = ""
for el in text.data:
- if el.element in ["stroke", "text"]:
- # keep adding to string as long as it is still "text"
- txt = txt + el.to_text()
- else:
- # add txt string and then append element, so list is ["text", el, "text"]
- txt_chunks = self.wordsep_simple_re.split(txt)
- chunks.extend([text_element(i) for i in txt_chunks if i])
- txt = ""
- chunks.append(el)
- if txt:
- # if there is remaining text, append
- txt_chunks = self.wordsep_simple_re.split(txt)
- chunks.extend([text_element(i) for i in txt_chunks if i])
+ chunks.extend(el.split())
return chunks
def _wrap_chunks(self, chunks):
diff --git a/plover_cat/suggest_dialog.ui b/plover_cat/suggest_dialog.ui
index 95a3dbc..765d334 100644
--- a/plover_cat/suggest_dialog.ui
+++ b/plover_cat/suggest_dialog.ui
@@ -46,6 +46,9 @@
-
+
+ Filter for words with size greater than selected in SCOWL. If blank, only filters common stopwords
+
0
@@ -110,6 +113,9 @@
-
+
+ Word/n-gram must occur at least this many times
+
1
@@ -127,6 +133,9 @@
-
+
+ N-gram must be this many words long
+
2
@@ -141,6 +150,9 @@
-
+
+ N-gram cannot be longer than this value
+
2
@@ -155,6 +167,9 @@
-
+
+ Perform selected search
+
Detect
@@ -169,6 +184,9 @@
-
+
+ Send selected translation and outline to dictionary
+
To Dictionary