Merge lp:~googol-deactivatedaccount/openlp/editor into lp:~m2j/openlp/editor

Proposed by Andreas Preikschat
Status: Work in progress
Proposed branch: lp:~googol-deactivatedaccount/openlp/editor
Merge into: lp:~m2j/openlp/editor
Diff against target: 356 lines (+128/-36)
6 files modified
core/formattededit.py (+120/-30)
core/highlighter.py (+3/-3)
core/spellchecker.py (+1/-1)
run_songedit.py (+1/-1)
sample_song2.txt (+2/-0)
songs/lyricsedit.py (+1/-1)
To merge this branch: bzr merge lp:~googol-deactivatedaccount/openlp/editor
Reviewer Review Type Date Requested Status
Meinert Jordan Pending
Review via email: mp+150586@code.launchpad.net

This proposal supersedes a proposal from 2013-02-25.

Description of the change

- Removed code (we require Qt 4.6)
- implemented undo stack

Current problems:
- The {br} tag is saved as ↵ in the stack when we are outside the line it appears (same applies for {p})

Thoughts:
- reimplement self.highlighter.rehighlight() and disable the undo_list from there.
- use blockSignals instead of the skip_undo attribute

To post a comment you must log in.
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

To see the stack is working (and we are not using the build in one) see this code: http://pastebin.com/YVHqCszt

Just type in something like "test test {r}asdf" and then press ctrl+z. You will see that the {r} is removed at once.

20. By Andreas Preikschat

started to implement a method to get the clean text from

21. By Andreas Preikschat

removed prints and commented code

Unmerged revisions

21. By Andreas Preikschat

removed prints and commented code

20. By Andreas Preikschat

started to implement a method to get the clean text from

19. By Andreas Preikschat

added example song without tags

18. By Andreas Preikschat

attempt to fix br/p tag insertion

17. By Andreas Preikschat

removed methods, removed parameter, fixed highlighting when editing a line

16. By Andreas Preikschat

more comments

15. By Andreas Preikschat

fixed cursor position

14. By Andreas Preikschat

fixed highlighting

13. By Andreas Preikschat

fixed highlighting

12. By Andreas Preikschat

reverted change

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'core/formattededit.py'
2--- core/formattededit.py 2012-12-12 23:18:59 +0000
3+++ core/formattededit.py 2013-02-27 16:24:22 +0000
4@@ -24,7 +24,6 @@
5 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
6 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
7 ###############################################################################
8-
9 import re
10
11 from PyQt4 import QtGui, QtCore, Qt
12@@ -38,26 +37,90 @@
13 from core.formattingtags import getAllTags, TAGREGEX, SPANTAGREGEX, TAGPAIRREGEX
14
15
16+class UndoCommand(QtGui.QUndoCommand):
17+ def __init__(self, parent):
18+ """
19+ Constructor
20+ """
21+ QtGui.QUndoCommand.__init__(self)
22+ self.plain_text_edit = parent
23+ old_command = self.plain_text_edit.undo_stack.command(self.plain_text_edit.undo_stack.count() - 1)
24+ self.new_string = self.plain_text_edit.toPlainTextClean()
25+ self.new_position = self.plain_text_edit.textCursor().position()
26+ # Get the old string from the previous command.
27+ self.old_string = old_command.new_string if old_command else u''
28+ self.old_positon = old_command.new_position if old_command else 0
29+
30+ def undo(self):
31+ """
32+ Action performed when this particular "undo command" is undon.
33+ """
34+ # Prevent onTextChanged signal being emitted.
35+ self.plain_text_edit.blockSignals(True)
36+ # Undo
37+ self.plain_text_edit.setPlainText(self.old_string)
38+ # Restore the cursor.
39+ cursor = self.plain_text_edit.textCursor()
40+ cursor.setPosition(self.old_positon)
41+ self.plain_text_edit.setTextCursor(cursor)
42+ # Allow signal again.
43+ self.plain_text_edit.blockSignals(False)
44+
45+ def redo(self):
46+ """
47+ Action performed when this particular "undo command" is done.
48+ """
49+ # Prevent onTextChanged signal being emitted.
50+ self.plain_text_edit.blockSignals(True)
51+ # Redo
52+ self.plain_text_edit.setPlainText(self.new_string)
53+ # Restore the cursor.
54+ cursor = self.plain_text_edit.textCursor()
55+ cursor.setPosition(self.new_position)
56+ self.plain_text_edit.setTextCursor(cursor)
57+ # Allow signal again.
58+ self.plain_text_edit.blockSignals(False)
59+
60+
61 class FormattedEdit(QtGui.QPlainTextEdit):
62 """
63 This is a text editor widget which works together with TagHighlighter.
64 """
65-
66+
67 def __init__(self, parent):
68 QtGui.QPlainTextEdit.__init__(self, parent)
69-
70+ self.undo_stack = QtGui.QUndoStack()
71+ self.skip_undo = False
72 self.hasMouseCursor = False
73 self.spellChecker = None
74 self.spellCheckEnabled = False
75 self.tagEditToolsEnabled = False
76 self.tagHighlightingEnabled = False
77-
78 self.setupHighlighter()
79 self.setupToolDock()
80
81 self.previousBlock = self.document().begin().previous()
82 self.onCursorPositionChanged()
83 QtCore.QObject.connect(self, QtCore.SIGNAL(u'cursorPositionChanged()'), self.onCursorPositionChanged)
84+ QtCore.QObject.connect(self, QtCore.SIGNAL(u'textChanged()'), self.onTextChanged)
85+
86+ def keyPressEvent(self, event):
87+ """
88+ Reimplement the ``keyPressEvent`` to act on undo/redo events.
89+
90+ ``event``
91+ The event
92+ """
93+ if event.key() == QtCore.Qt.Key_Z:
94+ # User wants to undo.
95+ if event.modifiers() == QtCore.Qt.ControlModifier:
96+ self.undo_stack.undo()
97+ return
98+ # User want to redo.
99+ if event.modifiers() == (QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier):
100+ self.undo_stack.redo()
101+ return
102+ QtGui.QPlainTextEdit.keyPressEvent(self, event)
103
104 def setupHighlighter(self, highlighter=None):
105 """
106@@ -79,7 +142,7 @@
107 self.toolDock.setOrientation(QtCore.Qt.Vertical)
108 self.toolDock.setFloating(True)
109 self.toolDock.adjustSize()
110- self.toolDock.hide()
111+ #self.toolDock.hide()
112 # TODO: basis for the tool dock
113 self.toolDockBackground = QtGui.QWidget(self)
114 self.toolDockBackground.hide()
115@@ -255,7 +318,9 @@
116 if enabled and not hasattr(self, u'replaceForthDict'):
117 self.setupReplacements({u'{br}': u'\u21B5\u2028', u'{p}': u'\u2028{p}\u204B', u'{/p}': u'\u00B6{/p}\u2028'})
118 self.highlighter.enableTagHighlighting(enabled)
119+ self.skip_undo = True
120 self.highlighter.rehighlight()
121+ self.skip_undo = False
122
123 def enableTagEditTools(self, enabled=True):
124 """
125@@ -457,10 +522,9 @@
126 offset -= len(match.group())
127 match = TAGPAIRREGEX.search(text, match.end())
128 cursor.endEditBlock()
129- if Qt.qVersion() < u'4.6':
130- self.highlighter.rehighlight()
131- else:
132- self.highlighter.rehighlightBlock(self.document().findBlock(start_pos))
133+ self.skip_undo = True
134+ self.highlighter.rehighlightBlock(self.document().findBlock(start_pos))
135+ self.skip_undo = False
136
137 def removeTag(self, tag=None):
138 """
139@@ -502,7 +566,7 @@
140 cursor.insertText(u'{/%s}' % s_tag)
141 for s_tag in reversed(start_tags[start_level:]):
142 if tag and s_tag != tag:
143- if unicode(self.toPlainText())[cursor.position(): \
144+ if unicode(self.toPlainText())[cursor.position():
145 cursor.position() + len(s_tag) + 3] == u'{/%s}' % s_tag:
146 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor,
147 len(s_tag) + 3)
148@@ -526,34 +590,43 @@
149 end_level = end_tags.index(tag) if tag else 0
150 for e_tag in reversed(end_tags[end_level:]):
151 if tag and e_tag != tag:
152- if unicode(self.toPlainText())[cursor.position() - len(e_tag) - 2:cursor.position()]
153- == u'{%s}' % e_tag:
154+ if unicode(self.toPlainText())[cursor.position() - len(e_tag) - 2:cursor.position()] == u'{%s}' % e_tag:
155 cursor.movePosition(QtGui.QTextCursor.PreviousCharacter, QtGui.QTextCursor.KeepAnchor,
156 len(e_tag) + 2)
157 cursor.removeSelectedText()
158 else:
159 cursor.insertText(u'{/%s}' % e_tag)
160 for end_tag in end_tags[end_level:]:
161- if unicode(self.toPlainText())[cursor.position(): cursor.position() + len(end_tag) + 3]
162- == u'{/%s}' % end_tag:
163+ if unicode(self.toPlainText())[cursor.position(): cursor.position() + len(end_tag) + 3] == u'{/%s}' % end_tag:
164 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, len(end_tag) + 3)
165 cursor.removeSelectedText()
166 else:
167 cursor.insertText(u'{%s}' % end_tag)
168- if Qt.qVersion() < u'4.6':
169- self.highlighter.rehighlight()
170- else:
171- self.highlighter.rehighlightBlock(
172- self.document().findBlock(start_pos))
173+ self.skip_undo = True
174+ self.highlighter.rehighlightBlock(self.document().findBlock(start_pos))
175+ self.skip_undo = False
176+
177+ def toPlainTextClean(self):
178+ """
179+ Returns the text with all tags. This is the inverse function to ``replaceTags()``.
180+ """
181+ # TODO: Not working! We need to use the regex to get the text with all tags.
182+ text = self.toPlainText()
183+ match = self.replaceBackRegExp.search(unicode(text))
184+ while match:
185+ offset = len(self.replaceBackDict[match.group()])
186+ match = self.replaceBackRegExp.search(unicode(block.text()), match.start() + offset)
187+ return self.toPlainText()
188
189 def replaceTags(self, block):
190 """
191- This function is replacing tags which can't be visualized with just syntax highlighting. For example {br} is
192- replaced by a down-left arrow and a newline character.
193+ This function is replacing tags whsetupReplacementsich can't be visualized with just syntax highlighting. For
194+ example {br} is replaced by a down-left arrow and a newline character.
195 """
196- # TODO: is there a way to skip the undo-list?
197 if not self.tagHighlightingEnabled:
198 return
199+ self.skip_undo = True
200+ self.blockSignals(True)
201 if block.userData() and block.userData().focus:
202 match = self.replaceBackRegExp.search(unicode(block.text()))
203 if match:
204@@ -576,6 +649,8 @@
205 cursor.insertText(self.replaceForthDict[match.group()])
206 offset = len(self.replaceForthDict[match.group()])
207 match = self.replaceForthRegExp.search(unicode(block.text()), match.start() + offset)
208+ self.skip_undo = False
209+ self.blockSignals(False)
210
211 def getSurroundingTags(self):
212 """
213@@ -603,6 +678,20 @@
214 end_tags[-1:] = []
215 return start_tags, end_tags
216
217+ def onTextChanged(self):
218+ """
219+ The text has changed. Save changes to the undo stack.
220+ """
221+ if self.skip_undo:
222+ return
223+ self.onCursorPositionChanged()
224+ position = self.textCursor().position()
225+ self.undo_stack.push(UndoCommand(self))
226+ cursor = self.textCursor()
227+ cursor.setPosition(position)
228+ self.setTextCursor(cursor)
229+ self.onCursorPositionChanged()
230+
231 def onCursorPositionChanged(self):
232 """
233 This function follows the cursor position and triggers adjustment of the highlighting.
234@@ -619,13 +708,12 @@
235 self.replaceTags(previous)
236 current.userData().focus = True
237 self.replaceTags(current)
238- if Qt.qVersion() < u'4.6':
239- self.highlighter.rehighlight()
240- else:
241- self.highlighter.rehighlightBlock(previous)
242- self.highlighter.rehighlightBlock(current)
243+ self.skip_undo = True
244+ self.highlighter.rehighlightBlock(previous)
245+ self.highlighter.rehighlightBlock(current)
246+ self.skip_undo = False
247 self.emit(QtCore.SIGNAL(u'cursorBlockChanged()'))
248-
249+
250 def getSpellCheckContextMenu(self, context_menu, event, language=None):
251 """
252 Prepend spell check items on the context menu.
253@@ -634,7 +722,8 @@
254 context_menu.insertMenu(context_menu.actions()[0], self.defaultDictMenu)
255 cursor = self.cursorForPosition(event.pos())
256 cursor.select(QtGui.QTextCursor.WordUnderCursor)
257- if cursor.hasSelection() and language is not False:
258+ if cursor.hasSelection() and language is not None:
259+ # FIXME: language is "de" but dict is calle "de_DE" or "de_AT" etc.
260 dictionary = self.spellChecker.get_dictionary(language)
261 text = unicode(cursor.selectedText())
262 if not dictionary.check(text):
263@@ -651,7 +740,7 @@
264 def updateToolDockVisibility(self):
265 # TODO: We have to delay this with a timer. Furthermore we should keep it visible if the pointer is above the
266 # tool dock or a menu.
267- self.toolDock.setVisible(self.hasFocus() or self.hasMouseCursor)
268+ self.toolDock.setVisible(self.hasFocus() or self.hasMouseCursor or True)
269
270 def contextMenuEvent(self, event):
271 """
272@@ -746,6 +835,7 @@
273 QtCore.QVariant()).toBool():
274 self.cleanUpFormattingTags(data[QtCore.QString(u'close')].toBool())
275
276+
277 class TextSignalAction(QtGui.QAction):
278 """
279 A special QAction that returns its text in a signal.
280
281=== modified file 'core/highlighter.py'
282--- core/highlighter.py 2012-12-12 23:18:59 +0000
283+++ core/highlighter.py 2013-02-27 16:24:22 +0000
284@@ -41,7 +41,7 @@
285 def __init__(self):
286 """
287 This class contains user data for text blocks
288-
289+
290 ``focus``
291 Holds whether cursor is on the block
292
293@@ -64,7 +64,7 @@
294 """
295 This is a syntax highlighter for OpenLPs formatting tags.
296 """
297-
298+
299 def __init__(self, document, edit):
300 QtGui.QSyntaxHighlighter.__init__(self, document)
301
302@@ -114,7 +114,7 @@
303 This function allows enabling and disabling of the formatting tag highlighting.
304 """
305 self.tagHighlightingEnabled = enabled
306-
307+
308
309 def highlightBlock(self, text):
310 """
311
312=== modified file 'core/spellchecker.py'
313--- core/spellchecker.py 2012-12-12 23:18:59 +0000
314+++ core/spellchecker.py 2013-02-27 16:24:22 +0000
315@@ -57,7 +57,7 @@
316
317 ``language``
318 A string for the dictionary key. If ``None`` the default dictionary will be returned.
319-
320+
321 Valid strings in can be found in self.available_languages. If the chosen dictionary is not available ``None``
322 will be returned.
323 """
324
325=== modified file 'run_songedit.py'
326--- run_songedit.py 2012-12-12 23:18:59 +0000
327+++ run_songedit.py 2013-02-27 16:24:22 +0000
328@@ -77,7 +77,7 @@
329 self.lyricsLabel.setText(translate('SongsPlugin.EditSongForm', '&Lyrics:'))
330 self.verseOrderLabel.setText(translate('SongsPlugin.EditSongForm', '&Verse order:'))
331
332- infile = open('sample_song.txt', 'r')
333+ infile = open('sample_song2.txt', 'r')
334 self.lyricsEdit.setPlainText(unicode(infile.read(), u'utf8'))
335 self.lyricsEdit.enableSpellCheck()
336 self.lyricsEdit.enableTagHighlighting()
337
338=== added file 'sample_song2.txt'
339--- sample_song2.txt 1970-01-01 00:00:00 +0000
340+++ sample_song2.txt 2013-02-27 16:24:22 +0000
341@@ -0,0 +1,2 @@
342+a b c d
343+asdfds
344
345=== modified file 'songs/lyricsedit.py'
346--- songs/lyricsedit.py 2012-12-12 23:18:59 +0000
347+++ songs/lyricsedit.py 2013-02-27 16:24:22 +0000
348@@ -188,7 +188,7 @@
349 def updateSideBarGeometry(self):
350 """
351 This method updates the visibility of the side bar and adjusts the editor viewport accordingly.
352- """
353+ """
354 top = self.verseSideBar.geometry().top()
355 right = self.verseSideBar.geometry().right()
356 if self.translationSideBar.headers():

Subscribers

People subscribed via source and target branches

to all changes: