forked from sanyaade-machine-learning/Transana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRichTextEditCtrl_RTC.py
2465 lines (2196 loc) · 119 KB
/
RichTextEditCtrl_RTC.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (C) 2010-2014 The Board of Regents of the University of Wisconsin System
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
""" This module implements the RichTextEditCtrl class, a Rich Text editor based on wxRichTextCtrl. """
__author__ = 'David Woods <[email protected]>'
DEBUG = False
if DEBUG:
print "RichTextEditCtrl_RTC DEBUG is ON."
# import wxPython
import wx
# import wxPython's RichTextCtrl
import wx.richtext as richtext
SHOWHIDDEN = False
if SHOWHIDDEN:
print "RichTextEditCtrl_RTC SHOWHIDDEN is ON."
HIDDENSIZE = 8
HIDDENCOLOR = wx.Colour(0, 255, 0)
else:
HIDDENSIZE = 1
HIDDENCOLOR = wx.Colour(255, 255, 255)
# import Transana's Constants
import TransanaConstants
# import Transana's Globals
import TransanaGlobal
# import Transana's Dialogs for the ErrorDialog
import Dialogs
# import Transana's Snapshot object
import Snapshot
# import the TranscriptionUI module for parent comparisons
import TranscriptionUI_RTC
# import the TranscriptEditor object
import TranscriptEditor_RTC
# Import the Python Rich Text Parser I wrote
import PyRTFParser
# import Python modules sys, os, string, and re (regular expressions)
import sys, os, string, re
# import Python Exceptions
import exceptions
# the pickle module enables us to fast-save
import pickle
# import Python's cStringIO for faster string processing
import cStringIO
# Import Python's traceback module for error reporting in DEBUG mode
import traceback
# import python's webbrowser module
import webbrowser
# Define the Time Code Character
TIMECODE_CHAR = unicode('\xc2\xa4', 'utf-8')
### On Windows, there is a problem with the wxWidgets' wxRichTextCtrl. It uses up Windows GDI Resources and Transana will crash
### if the GDI Resource Usage exceeds 10,000. This happens primarily during report generation and bulk formatting. See wxWidgets
### ticket #13381.
###
### NOTE: True for wxPython 2.8.12.x, but not for wxPython 2.9.4.0
###
### The following Windows-only function determines the number of GDI Resources in use by Transana.
##
### If we're on Windows ...
##if 'wxMSW' in wx.PlatformInfo:
##
## # import the win32 methods and constants we need to determine the number of GDI Resources in use.
## # This is based on code found in wxWidgets ticket #4451
## from win32api import GetCurrentProcess
## from win32con import GR_GDIOBJECTS
## from win32process import GetGuiResources
##
## def GDIReport():
## """ Report the number of GDI Resources in use by Transana """
## # Get the number of GDI Resources in use from the Operating System
## numGDI = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS)
## # Return the number of GDI Resources in use
## return numGDI
class RichTextEditCtrl(richtext.RichTextCtrl):
""" This class is a Rich Text edit control implemented for Transana """
def __init__(self, parent, id=-1, pos = None, suppressGDIWarning = False):
"""Initialize a RichTextCtrl object."""
# Initialize a RichTextCtrl object
richtext.RichTextCtrl.__init__(self, parent, id, pos = pos, style=wx.VSCROLL | wx.HSCROLL | wx.WANTS_CHARS)
## Unfortunately, SetScale is still pretty broken. It changes the size of the transcript display,
## but event.GetPosition() isn't Scale sensitive and GetScaleX() and GetScaleY() don't return the
## correct values (on OSX for sure, I didn't check Windows.) I tried adjusting GetPosition for a
## KNOWN scale of 1.25, and the results were just not right. As a result, Drag-and-Drop in the
## Transcript stopped working, so I couldn't make a selection and create a Clip or an inVivo code.
## # Get the Transcript Scale from the Configuration Data
## scaleFactor = TransanaGlobal.configData.transcriptScale
## # ... Set the Scale so text looks bigger
## self.SetScale(scaleFactor, scaleFactor)
# Bind key press handlers
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_CHAR, self.OnKey)
self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
# Bind mouse click handlers
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
# Bind a hyperlink event handler
self.Bind(wx.EVT_TEXT_URL, self.OnURL)
# The wx.richtext.RichTextCtrl does some things that aren't Transana-friendly with default behaviors.
# This section, and the accompanying methods, clean that up by replacing the standard Cut, Copy, Paste,
# Undo, and Redo methods
# Replace the Accelerator Table for the RichTextCtrl.
# This removes the Ctrl-A Select All accelerator completely
# As of wxPython 2.9.5, this is AUTOMATICALLY converted to CMD for Mac
accelList = [(wx.ACCEL_CTRL, ord('C'), wx.ID_COPY),
(wx.ACCEL_CTRL, ord('V'), wx.ID_PASTE),
(wx.ACCEL_CTRL, ord('X'), wx.ID_CUT),
(wx.ACCEL_CTRL, ord('Y'), wx.ID_REDO),
(wx.ACCEL_CTRL, ord('Z'), wx.ID_UNDO)]
aTable = wx.AcceleratorTable(accelList)
# Assign the modified accelerator table to the control
self.SetAcceleratorTable(aTable)
# We need to define our own Cut, Copy, and Paste methods rather than using the default ones from the RichTextCtrl.
self.Bind(wx.EVT_MENU, self.OnCutCopy, id=wx.ID_CUT)
self.Bind(wx.EVT_MENU, self.OnCutCopy, id=wx.ID_COPY)
self.Bind(wx.EVT_MENU, self.OnPaste, id=wx.ID_PASTE)
# However, we can leave the Undo and Redo commands alone and use the default ones from the RichTextCtrl.
# Initialize current style to None
self.txtAttr = None
# Define the Default Text style
self.txtOriginalAttr = richtext.RichTextAttr()
self.txtOriginalAttr.SetTextColour(wx.Colour(0,0,0))
self.txtOriginalAttr.SetBackgroundColour(wx.Colour(255, 255, 255))
self.txtOriginalAttr.SetFontFaceName(TransanaGlobal.configData.defaultFontFace)
self.txtOriginalAttr.SetFontSize(TransanaGlobal.configData.defaultFontSize)
self.txtOriginalAttr.SetFontWeight(wx.FONTWEIGHT_NORMAL)
self.txtOriginalAttr.SetFontStyle(wx.FONTSTYLE_NORMAL)
self.txtOriginalAttr.SetFontUnderlined(False)
# Define the style for Time Codes
self.txtTimeCodeAttr = richtext.RichTextAttr()
self.txtTimeCodeAttr.SetTextColour(wx.Colour(255,0,0))
self.txtTimeCodeAttr.SetBackgroundColour(wx.Colour(255, 255, 255))
self.txtTimeCodeAttr.SetFontFaceName('Courier New')
self.txtTimeCodeAttr.SetFontSize(14)
self.txtTimeCodeAttr.SetFontWeight(wx.FONTWEIGHT_NORMAL)
self.txtTimeCodeAttr.SetFontStyle(wx.FONTSTYLE_NORMAL)
self.txtTimeCodeAttr.SetFontUnderlined(False)
self.STYLE_TIMECODE = 0
# Define the style for Hidden Text (used for Time Code Data)
self.txtHiddenAttr = richtext.RichTextAttr()
self.txtHiddenAttr.SetTextColour(HIDDENCOLOR)
self.txtHiddenAttr.SetBackgroundColour(wx.Colour(255, 255, 255))
self.txtHiddenAttr.SetFontFaceName('Times New Roman')
self.txtHiddenAttr.SetFontSize(HIDDENSIZE)
self.txtHiddenAttr.SetFontWeight(wx.FONTWEIGHT_NORMAL)
self.txtHiddenAttr.SetFontStyle(wx.FONTSTYLE_NORMAL)
self.txtHiddenAttr.SetFontUnderlined(False)
self.STYLE_HIDDEN = 0
# Define the style for Human Readable Time Code data
self.txtTimeCodeHRFAttr = richtext.RichTextAttr()
self.txtTimeCodeHRFAttr.SetTextColour(wx.Colour(0,0,255))
self.txtTimeCodeHRFAttr.SetBackgroundColour(wx.Colour(255, 255, 255))
self.txtTimeCodeHRFAttr.SetFontFaceName('Courier New')
self.txtTimeCodeHRFAttr.SetFontSize(10)
self.txtTimeCodeHRFAttr.SetFontWeight(wx.FONTWEIGHT_NORMAL)
self.txtTimeCodeHRFAttr.SetFontStyle(wx.FONTSTYLE_NORMAL)
self.txtTimeCodeHRFAttr.SetFontUnderlined(False)
# If set to point to a wxProgressDialog, this dialog will be updated as a document is loaded.
# Initialize to None.
self.ProgressDlg = None
self.lineNumberWidth = 0
# Initialize the String Selection used in keystroke processing
self.keyStringSelection = ''
# We occasionally need to adjust styles based on proximity to a time code. We need a flag to indicate that.
self.timeCodeFormatAdjustment = False
# This document should only display the GDI Warning once. Create a flag.
if 'wxMSW' in wx.PlatformInfo:
# The GDIWarningShown flag should be False initially, unless this segment is supposed to suppress the GDI warning
self.GDIWarningShown = suppressGDIWarning
def SetReadOnly(self, state):
""" Change Read Only status, implemented for wxSTC compatibility """
# RTC uses SetEditable(), the opposite of STC's SetReadOnly()
self.SetEditable(not state)
def GetReadOnly(self):
""" Report Read Only status, implemented for wxSTC compatibility """
# RTC uses IsEditable(), the opposite of STC's GetReadOnly()
return not self.IsEditable()
def GetModify(self):
""" Report Modified Status, implemented for wxSTC compatibility """
return self.IsModified()
## def PutEditedSelectionInClipboard(self):
## """ Put the TEXT for the current selection into the Clipboard """
## tempTxt = self.GetStringSelection()
## # Initialize an empty string for the modified data
## newSt = ''
## # Created a TextDataObject to hold the text data from the clipboard
## tempDataObject = wx.TextDataObject()
## # Track whether we're skipping characters or not. Start out NOT skipping
## skipChars = False
## # Now let's iterate through the characters in the text
## for ch in tempTxt:
## # Detect the time code character
## if ch == TransanaConstants.TIMECODE_CHAR:
## # If Time Code, start skipping characters
## skipChars = True
## # if we're skipping characters and we hit the ">" end of time code data symbol ...
## elif (ch == '>') and skipChars:
## # ... we can stop skipping characters.
## skipChars = False
## # If we're not skipping characters ...
## elif not skipChars:
## # ... add the character to the new string.
## newSt += ch
## # Save the new string in the Text Data Object
## tempDataObject.SetText(newSt)
## # Place the Text Data Object in the Clipboard
## wx.TheClipboard.SetData(tempDataObject)
##
def GetFormattedSelection(self, format, selectionOnly=False, stripTimeCodes=False):
""" Return a string with the formatted contents of the RichText control, or just the
current selection if specified. The format parameter can either be 'XML' or 'RTF'. """
# If a valid format is NOT passed ...
if not format in ['XML', 'RTF']:
# ... return a blank string
return ''
# NOTE: The wx.RichTextCtrl doesn't provide an easy way to get a formatted selection that I can figure out.
# This is WAY harder than I expected. This is a hack, but it works.
# If we are getting a selection or are stripping time codes ...
if selectionOnly or stripTimeCodes:
# Remember the Edited Status of the RTC
isModified = self.IsModified()
# Recursively get the current contents of the entire buffer (as it is NOW, potentially modified above)
originalText = self.GetFormattedSelection('XML')
# If we're just getting a selection ...
if selectionOnly:
# Freeze the control so things will work faster
self.Freeze()
# Get the start and end of the current selection
sel = self.GetSelection()
# Our last paragraph loses formatting! Let's fix that! First, grab the style info
tmpStyle = self.GetStyleAt(sel[1] - 1)
# Delete everything AFTER the end of the current selection
self.Delete((sel[1], self.GetLastPosition()))
# Delete everything BEFORE the start of the current selection
self.Delete((0, sel[0]))
# This leaves us with JUST the selection in the control!
# Now apply the formatting to the last paragraph
self.SetSelection(self.GetLastPosition() - 1, self.GetLastPosition())
self.SetStyleEx(self.GetSelectionRange(), tmpStyle, richtext.RICHTEXT_SETSTYLE_PARAGRAPHS_ONLY)
# Now reset the selection
self.SelectAll()
# If we want to strip time codes ...
if stripTimeCodes:
# Recursively get the contents of the entire buffer (as it is NOW, potentially modified above)
text = self.GetFormattedSelection('XML')
# Strip the Time Codes from the buffer contents
text = self.StripTimeCodes(text)
# Now put the altered contents of the buffer back into the control!
try:
stream = cStringIO.StringIO(text)
# Create an XML Handler
handler = richtext.RichTextXMLHandler()
# Load the XML text via the XML Handler.
# Note that for XML, the BUFFER is passed.
handler.LoadStream(self.GetBuffer(), stream)
# exception handling
except:
print "XML Handler Load failed"
print
print sys.exc_info()[0], sys.exc_info()[1]
print traceback.print_exc()
print
pass
# Now get the remaining contents of the form, and translate them to the desired format.
# If XML format is requested ...
if format == 'XML':
# Create a Stream
stream = cStringIO.StringIO()
# Get an XML Handler
handler = richtext.RichTextXMLHandler()
# Save the contents of the control to the stream
handler.SaveStream(self.GetBuffer(), stream)
# Convert the stream to a usable string
tmpBuffer = stream.getvalue()
# If RTF format is requested ....
elif format == 'RTF':
# Get an RTF Handler
handler = PyRTFParser.PyRichTextRTFHandler()
# Get the string representation by leaving off the filename parameter
tmpBuffer = handler.SaveFile(self.GetBuffer())
# If we are getting a selection or are stripping time codes ...
if selectionOnly or stripTimeCodes:
# Now put the ORIGINAL contents of the buffer back into the control!
try:
stream = cStringIO.StringIO(originalText)
# Create an XML Handler
handler = richtext.RichTextXMLHandler()
# Load the XML text via the XML Handler.
# Note that for XML, the BUFFER is passed.
handler.LoadStream(self.GetBuffer(), stream)
# exception handling
except:
print "XML Handler Load failed"
print
print sys.exc_info()[0], sys.exc_info()[1]
print traceback.print_exc()
print
pass
# If the transcript had been modified ...
if isModified:
# ... then mark it as modified
self.MarkDirty()
# If the transcript had NOT been modified ...
else:
# ... mark the transcript as unchanged. (Yeah, one of these is probably not necessary.)
self.DiscardEdits()
# If we're just getting a selection ...
if selectionOnly:
# Restore the original selection
self.SetSelection(sel[0], sel[1])
# Now thaw the control so that updates will be displayed again
self.Thaw()
# Return the buffer's XML string
return tmpBuffer
def OnCutCopy(self, event):
""" Handle Cut and Copy events, over-riding the RichTextCtrl versions.
This implementation supports Rich Text Formatted text, and at least on Windows and OS X
can share formatted text with other programs. """
# Note the original selection in the text
origSelection = self.GetSelection()
# Get the current selection in RTF format
rtfSelection = self.GetFormattedSelection('RTF', selectionOnly=True, stripTimeCodes=True)
# Create a Composite Data Object for the Clipboard
compositeDataObject = wx.DataObjectComposite()
# If we're on OS X ...
if 'wxMac' in wx.PlatformInfo:
# ... then RTF Format is called "public.rtf"
rtfFormat = wx.CustomDataFormat('public.rtf')
# If we're on Windows ...
else:
# ... then RTF Format is called "Rich Text Format"
rtfFormat = wx.CustomDataFormat('Rich Text Format')
# Create a Custom Data Object for the RTF format
rtfDataObject = wx.CustomDataObject(rtfFormat)
# Save the RTF version of the control selection to the RTF Custom Data Object
rtfDataObject.SetData(rtfSelection)
# Add the RTF Custom Data Object to the Composite Data Object
compositeDataObject.Add(rtfDataObject)
# Get the current selection in Plain Text
txtSelection = self.GetStringSelection()
# Create a Text Data Object
txtDataObject = wx.TextDataObject()
# Save the Plain Text version of the control selection to the Text Data Object
txtDataObject.SetText(txtSelection)
# Add the Plain Text Data Object to the Composite Data object
compositeDataObject.Add(txtDataObject)
# Open the Clipboard
wx.TheClipboard.Open()
# Place the Composite Data Object (with RTF and Plain Text) on the Clipboard
wx.TheClipboard.SetData(compositeDataObject)
# Close the Clipboard
wx.TheClipboard.Close()
# If we are CUTting (rather than COPYing) ...
# (NOTE: On OS X, the event object isn't what we expect, it's a MENU, so we have to get the menu item
# text and do a comparison!!!)
if self.IsEditable() and \
((event.GetId() == wx.ID_CUT) or \
((sys.platform == 'darwin') and \
(event.GetEventObject().GetLabel(event.GetId()) == _("Cu&t\tCtrl-X").decode('utf8')))):
# Reset the selection, which was mangled by the GetFormattedSelection call
self.SetSelection(origSelection[0], origSelection[1])
# ... delete the selection from the Rich Text Ctrl.
self.DeleteSelection()
def OnPaste(self, event=None):
""" Handle Paste events, over-riding the RichTextCtrl version.
This implementation supports Rich Text Formatted text, and at least on Windows can
share formatted text with other programs. """
# If the Clipboard isn't Open ...
if not wx.TheClipboard.IsOpened():
# ... open it!
wx.TheClipboard.Open()
# If the transcript is in EDIT mode ...
if self.IsEditable():
# Start a Batch Undo
self.BeginBatchUndo('Paste')
# If there's a selection ...
if self.GetSelection() != (-2, -2):
# ... delete it.
self.DeleteSelection()
# If we're on OS X ...
if 'wxMac' in wx.PlatformInfo:
# ... then RTF Format is called "public.rtf"
rtfFormat = wx.CustomDataFormat('public.rtf')
# if we're on Windows ...
else:
# ... then RTF Format is called "Rich Text Format"
rtfFormat = wx.CustomDataFormat('Rich Text Format')
# See if the RTF Format is supported by the current clipboard data object
if wx.TheClipboard.IsSupported(rtfFormat):
# Specify that the data object accepts data in RTF format
customDataObject = wx.CustomDataObject(rtfFormat)
# Try to get data from the Clipboard
success = wx.TheClipboard.GetData(customDataObject)
# If the data in the clipboard is in an appropriate format ...
if success:
# ... get the data from the clipboard
formattedText = customDataObject.GetData()
if DEBUG:
print
print "RTF data:"
print formattedText
print
# If the RTF Text ends with a Carriage Return (and it always does!) ...
if formattedText[-6:] == '\\par\n}':
# ... then remove that carriage return!
formattedText = formattedText[:-6] + formattedText[-1]
# Prepare the control for data
self.Freeze()
# Start exception handling
try:
# Use the custom RTF Handler
handler = PyRTFParser.PyRichTextRTFHandler()
# Load the RTF data into the Rich Text Ctrl via the RTF Handler.
# Note that for RTF, the wxRichTextCtrl CONTROL is passed with the RTF string.
handler.LoadString(self, formattedText, insertionPoint=self.GetInsertionPoint(), displayProgress=False)
# exception handling
except:
print "Custom RTF Handler Load failed"
print
print sys.exc_info()[0], sys.exc_info()[1]
print traceback.print_exc()
print
pass
# Signal the end of changing the control
self.Thaw()
# If there's not RTF data, see if there's Plain Text data
elif wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
# Create a Text Data Object
textDataObject = wx.TextDataObject()
# Get the Data from the Clipboard
wx.TheClipboard.GetData(textDataObject)
# Write the plain text into the Rich Text Ctrl
self.WriteText(textDataObject.GetText())
# End the Batch Undo
self.EndBatchUndo()
# Close the Clipboard
wx.TheClipboard.Close()
## def SetSavePoint(self):
## if DEBUG or True:
## print "RichTextEditCtrl.SetSavePoint() -- Which does NOTHING!!!!"
def GetSelectionStart(self):
""" Get the starting point of the current selection, implemented for wxSTC compatibility """
# If there is NO current selection ...
if self.GetSelection() == (-2, -2):
# ... return the current Insertion Point
return self.GetInsertionPoint()
# If there IS a selection ...
else:
# ... return the Selection's starting point
return self.GetSelection()[0]
def GetSelectionEnd(self):
""" Get the ending point of the current selection, implemented for wxSTC compatibility """
# If there is NO current selection ...
if self.GetSelection() == (-2, -2):
# ... return the current Insertion Point
return self.GetInsertionPoint()
# If there IS a selection ...
else:
# ... return the Selection's ending point
return self.GetSelection()[1]
def ShowCurrentSelection(self):
""" Ensure that ALL of the current selection is displayed on screen, if possible """
# Get the current selection
newSel = self.GetSelection()
# If there is NO selection ...
if newSel == (-2, -2):
# ... then we can exit!
return
# Show the END of the selection
self.ShowPosition(newSel[1])
# Show the START of the selection
self.ShowPosition(newSel[0])
# Don't you wish it were that simple? If even a little bit of the selection's row is visible, wxPython
# claims that the selection is visible. We want to do some additional correction of positioning.
# Get the position for the First Visible Line on the control
vlffl = self.GetVisibleLineForCaretPosition(self.GetFirstVisiblePosition())
# If this value returns None, there's nothing loaded in the control ...
if vlffl == None:
# ... so we can exit!
return
# Now get the position for the current Caret Position, which should be the start of the selection
vlfcp = self.GetVisibleLineForCaretPosition(self.GetCaretPosition())
# Check to see if the selection is ABOVE the TOP of the control, and there's room to scroll down
if (vlffl.GetAbsolutePosition()[1] >= vlfcp.GetAbsolutePosition()[1]) and (vlffl.GetAbsolutePosition()[1] > 22):
while (vlffl.GetAbsolutePosition()[1] >= vlfcp.GetAbsolutePosition()[1]) and (vlffl.GetAbsolutePosition()[1] > 22):
# note the initial position
before = vlffl.GetAbsolutePosition()[1]
# Scroll DOWN a bit
self.ScrollLines(-5)
# Get the position for the First Visible Line on the control
vlffl = self.GetVisibleLineForCaretPosition(self.GetFirstVisiblePosition())
# Note the corrected position
after = vlffl.GetAbsolutePosition()[1]
# If the position is unchanged, (this was causing problems because ScrollLines(-5) wasn't always
# having an affect) ...
if before == after:
# ... then exit the WHILE loop because it's not changing anything
break
# Check to see if the selection is BELOW THE BOTTOM OF THE CONTROL, adjusted a bit to make it work
elif vlfcp.GetAbsolutePosition()[1] - vlffl.GetAbsolutePosition()[1] > self.GetSize()[1] - 45:
# If so, scroll UP a bit
self.ScrollLines(5)
def GotoPos(self, pos):
""" Go to the specified position in the transcript, implemented for wxSTC compatibility """
# Move the Insertion Point to the desired position
self.SetInsertionPoint(pos)
def GetCharAt(self, pos):
""" Get the character at the specified position, implemented for wxSTC compatibility """
# If there IS a character at the requested position ...
if len(self.GetRange(pos, pos + 1)) == 1:
# ... return that character
return ord(self.GetRange(pos, pos + 1))
# If there is no valid character at the requested position ...
else:
# ... return -1 to signal a failure.
return -1
def GetText(self):
""" Get the text from the control, implemented for wxSTC compatibility """
# Return the control's contents, obtained from its GetValue() method
return self.GetValue()
def SetCurrentPos(self, pos):
""" Set the control's Current position, implemented for wxSTC compatibility """
# Set the Insertion Point
self.SetInsertionPoint(pos)
def GetCurrentPos(self):
""" Get the control's Current position, implemented for wxSTC compatibility """
# The Insertion Point is the Current Position
return self.GetInsertionPoint()
def SetAnchor(self, pos):
""" Mimic the wxSTC's SetAnchor() method, which makes a selection from the current cursor position to the
specified point """
# If a position is passed ...
if pos >= 0:
# ... select from the current cursor position to the specified position
self.SetSelection(self.GetCurrentPos(), pos)
# If a negative position is passed ...
else:
# ... just set the insertion point to 0
self.SetInsertionPoint(0)
def GetLength(self):
""" Report the length of the current document, implemented for wxSTC compatibility """
# RTC's GetLastPosition() method provides the information
return self.GetLastPosition()
def GetTextLength(self):
""" Report the length of the current document, implemented for wxSTC compatibility """
# RTC's GetLastPosition() method provides the information
return self.GetLastPosition()
def FindText(self, startPos, endPos, text):
""" Locate the specified text in the RTC """
# Okay, here's the problem. RTC doesn't provide a good, FAST text find capacity. And finding time codes is proving
# TOO SLOW, especially as transcripts start to get very large. This attempts to speed that process up.
# Remember the original selection in the control
(originalSelStart, originalSelEnd) = self.GetSelection()
# First, find the text in the STRING representation of the control's data using Python's string.find(),
# which is very fast.
textPos = self.GetValue().find(text, startPos, endPos)
# textPos represents the position of the desired text in the STRING representation.
# If there are IMAGES in the RTC control, this isn't the correct position, but it's pretty close.
# Each image only alters the position by 1 place! So here, we adjust slightly if there are images.
# If the text was found in the STRING representation ...
if (textPos > -1):
# ... search, starting at that location, to the end of the area to be searched
for pos in range(textPos, endPos - len(text)):
# Set the Selection based on the for loop variable and the length of the text
self.SetSelection(pos, pos + len(text))
# If this is our desired text ...
if self.GetStringSelection() == text:
# ... then this is the TRUE position of the search text.
textPos = pos
# We can stop looping
break
# If there was a selection highlight before we started mucking about with the control selection ...
if originalSelStart != originalSelEnd:
# ... then reset the control selection
self.SetSelection(originalSelStart, originalSelEnd)
# If we only had a cursor position, not a selection ...
else:
# ... reset the cursor position
self.SetInsertionPoint(originalSelStart)
# Return the position of the search text
return textPos
def StripTimeCodes(self, XMLText):
""" This method will take the contents of an RTC buffer in XML format and remove the Time Codes and
Time Code Data """
# This deletes based on FORMAT, deleting everything in TIME CODE FORMAT and HIDDEN FORMAT.
# First, let's create the XML represenation of TIME CODE FORMATTING
st = '<text textcolor="#FF0000" bgcolor="#FFFFFF" fontsize="14" fontstyle="90" fontweight="90" fontunderlined="0" fontface="Courier New">'
# While there is TIME CODE FORMATTING in the text ...
while st in XMLText:
# ... identify the starting position of the TIME CODE FORMATTING
startPos = XMLText.find(st)
# ... identify the ending position of the TIME CODE FORMATTING
endPos = XMLText.find('</text>', startPos)
# Remove the time code with all formatting
XMLText = XMLText[ : startPos] + XMLText[endPos + 7 : ]
# First, let's create the XML represenation of TIME CODE FORMATTING FROM RTF, which has different formatting.
# I've found two variations so far.
stList = ['<text textcolor="#FF0000" bgcolor="#FFFFFF" fontsize="11" fontstyle="90" fontweight="90" fontunderlined="0" fontface="Arial">',
'<text textcolor="#FF0000" bgcolor="#FFFFFF" fontsize="11" fontstyle="90" fontweight="90" fontunderlined="0" fontface="Courier New">']
# For each XML variation ...
for st in stList:
# While there is TIME CODE FORMATTING in the text ...
while st in XMLText:
# ... identify the starting position of the TIME CODE FORMATTING
startPos = XMLText.find(st)
# ... identify the ending position of the TIME CODE FORMATTING
endPos = XMLText.find('</text>', startPos)
# Remove the time code with all formatting
XMLText = XMLText[ : startPos] + XMLText[endPos + 7 : ]
# ... identify the starting position of the TIME CODE FORMATTING
startPos = XMLText.find('<', startPos)
# ... identify the ending position of the TIME CODE FORMATTING
endPos = XMLText.find('>', startPos)
# If time code data was found ...
if (startPos > -1) and (endPos > -1):
# ... remove the time code with all formatting
XMLText = XMLText[ : startPos] + XMLText[endPos + 4 : ]
# First, let's create the XML represenation of HIDDEN FORMATTING
st = '<text textcolor="#FFFFFF" bgcolor="#FFFFFF" fontsize="1" fontstyle="90" fontweight="90" fontunderlined="0" fontface="Times New Roman">'
# While there is HIDDEN FORMATTING in the text ...
while st in XMLText:
# ... identify the starting position of the HIDDEN FORMATTING
startPos = XMLText.find(st)
# ... identify the ending position of the HIDDEN FORMATTING
endPos = XMLText.find('</text>', startPos)
# Remove the hidden time code data with all formatting
XMLText = XMLText[ : startPos] + XMLText[endPos + 7 : ]
# Some RTF transcripts won't have the formatting right on the time codes, so they won't be found by the code above.
# This will try to find and remove these additional time codes.
# First, let's create the XML represenation of A TIME CODE
st = '¤'
# While there are TIME CODES in the text ...
while st in XMLText:
# ... identify the starting position of the TIME CODE
startPos = XMLText.find(st)
# ... identify the ending position of the TIME CODE DATA
endPos = XMLText.find('>', startPos + 4)
# Remove the hidden time code data
XMLText = XMLText[ : startPos] + XMLText[endPos + 4 : ]
return XMLText
def SetDefaultStyle(self, tmpStyle):
""" Over-ride the RichTextEditCtrl's SetDefaultStyle() to fix problems with setting the style at the
end of a line / paragraph. The font change doesn't always stick. """
# Attempt to apply the specified font to the document
richtext.RichTextCtrl.SetDefaultStyle(self, tmpStyle)
# Get the current default style
currentStyle = self.GetDefaultStyle()
# If the font isn't OK, the formatting didn't take!
if not currentStyle.GetFont().IsOk():
# In this case, let's add a SPACE character
self.WriteText(' ')
# Now select that space character, so it will be over-written by the next typing the user does
self.SetSelection(self.GetInsertionPoint() - 1, self.GetInsertionPoint())
# Now FORMAT that space character.
richtext.RichTextCtrl.SetDefaultStyle(self, tmpStyle)
def SetTxtStyle(self, fontColor = None, fontBgColor = None, fontFace = None, fontSize = None,
fontBold = None, fontItalic = None, fontUnderline = None,
parAlign = None, parLeftIndent = None, parRightIndent = None,
parTabs = None, parLineSpacing = None, parSpacingBefore = None, parSpacingAfter = None,
overrideGDIWarning = False):
""" I find some of the RichTextCtrl method names to be misleading. Some character styles are stacked in the RichTextCtrl,
and they are removed in the reverse order from how they are added, regardless of the method called.
For example, starting with plain text, BeginBold() makes it bold, and BeginItalic() makes it bold-italic. EndBold()
should make it italic but instead makes it bold. EndItalic() takes us back to plain text by removing the bold.
According to Julian, this functions "as expected" because of the way the RichTextCtrl is written.
The SetTxtStyle() method handles overlapping styles in a way that avoids this problem. """
# We need to determine if font conditions are being altered.
# Remember, (setting is None) is different than (setting == False) for Bold, Italic, and Underline!
if fontColor or fontBgColor or fontFace or fontSize or \
(fontBold is not None) or (fontItalic is not None) or (fontUnderline is not None):
formattingFont = True
else:
formattingFont = False
# If we are formatting ANY paragraph characteristic ...
if parAlign or parLeftIndent or parRightIndent or parTabs or parLineSpacing or parSpacingBefore or parSpacingAfter:
# ... note that we are doing paragraph formatting
formattingParagraph = True
# If we're NOT doing paragraph formatting ...
else:
# ... note that we're NOT doing paragraph formatting
formattingParagraph = False
## # If we're on Windows, have reached the GDI threshhold, and are not over-riding the GDI limit ...
## if ('wxMSW' in wx.PlatformInfo) and (GDIReport() > 8800) and (not overrideGDIWarning):
## # If we haven't shown the GDI Warning yet ...
## if not self.GDIWarningShown:
## # ... create and display the GDI warning
## prompt = _("This report has reached the upper limit for Windows GDI Resources.") + '\n' + \
## _("This report can display formatting in only part of the document.")
## dlg = Dialogs.InfoDialog(self, prompt)
## dlg.ShowModal()
## dlg.Destroy()
## # Signal that we HAVE shown the GDI Warning
## self.GDIWarningShown = True
##
## # Update the current text to use the Original (default) text for the rest of the document
## self.txtAttr = self.txtOriginalAttr
## # Set the Basic Style.
## self.SetBasicStyle(self.txtOriginalAttr)
##
## # If we don't have to worry about Windows GDI issues ...
## else:
# If there's no SELECTION in the text, paragraph formatting changes get lost. They don't happen.
# This code corrects for that.
# We do Paragraph Changes before font changes. In theory, this can help prevent the situation where
# changing both font and paragraph characteristics causes the font changes to be applied to the
# whole paragraph instead of just the insertion point or selection intended.
# If we have paragraph formatting ...
if formattingParagraph:
# If Paragraph Alignment is specified, set the alignment
if parAlign != None:
self.txtAttr.SetAlignment(parAlign)
# If Left Indent is specified, set the left indent
if parLeftIndent != None:
# Left Indent can be an integer for left margin only, or a 2-element tuple for left indent and left subindent.
if type(parLeftIndent) == int:
self.txtAttr.SetLeftIndent(parLeftIndent)
elif (type(parLeftIndent) == tuple) and (len(parLeftIndent) > 1):
self.txtAttr.SetLeftIndent(parLeftIndent[0], parLeftIndent[1])
# If Right Indent is specified, set the right indent
if parRightIndent != None:
self.txtAttr.SetRightIndent(parRightIndent)
# If Tabs are specified, set the tabs
if parTabs != None:
self.txtAttr.SetTabs(parTabs)
# If Line Spacing is specified, set Line Spacing
if parLineSpacing != None:
self.txtAttr.SetLineSpacing(parLineSpacing)
# If Paragraph Spacing Before is set, set spacing before
if parSpacingBefore != None:
self.txtAttr.SetParagraphSpacingBefore(parSpacingBefore)
# If Paragraph Spacing After is set, set spacing after
if parSpacingAfter != None:
self.txtAttr.SetParagraphSpacingAfter(parSpacingAfter)
# Get the current range
tmpRange = self.GetSelectionRange()
# Apply the paragraph formatting change
self.SetStyle(tmpRange, self.txtAttr)
# Apply the modified paragraph formatting to the document
self.SetDefaultStyle(self.txtAttr)
# Set the Basic Style too.
self.SetBasicStyle(self.txtAttr)
if formattingFont:
# Create a Font object
tmpFont = self.txtAttr.GetFont()
# If the font face (font name) is specified, set the font face
if fontFace:
# If we have a valid font and need to change the name ...
if not tmpFont.IsOk() or (tmpFont.GetFaceName() != fontFace):
# ... then change the name
self.txtAttr.SetFontFaceName(fontFace)
# If the font size is specified, set the font size
if fontSize:
# If we have a valid font and need to change the size ...
if not tmpFont.IsOk() or (tmpFont.GetPointSize() != fontSize):
# ... then change the size
self.txtAttr.SetFontSize(fontSize)
# If a color is specified, set text color
if fontColor:
# If we have a valid font and need to change the color ...
if self.txtAttr.GetTextColour() != fontColor:
# ... then change the color
self.txtAttr.SetTextColour(fontColor)
# If a background color is specified, set the background color
if fontBgColor:
# If we have a valid font and need to change the background color ...
if self.txtAttr.GetBackgroundColour() != fontBgColor:
# ... then change the background color
self.txtAttr.SetBackgroundColour(fontBgColor)
# If bold is specified, set or remove bold as requested
if fontBold != None:
# If Bold is being turned on ...
if fontBold:
# If we have a valid font and need to change to bold ...
if not tmpFont.IsOk() or (tmpFont.GetWeight() != wx.FONTWEIGHT_BOLD):
# ... turn on bold
self.txtAttr.SetFontWeight(wx.FONTWEIGHT_BOLD)
# if Bold is being turned off ...
else:
# If we have a valid font and need to change to not-bold ...
if not tmpFont.IsOk() or (tmpFont.GetWeight() != wx.FONTWEIGHT_NORMAL):
# ... turn off bold
self.txtAttr.SetFontWeight(wx.FONTWEIGHT_NORMAL)
# If italics is specified, set or remove italics as requested
if fontItalic != None:
# If Italics are being turned on ...
if fontItalic:
# If we have a valid font and need to change to italics ...
if not tmpFont.IsOk() or (tmpFont.GetStyle() != wx.FONTSTYLE_ITALIC):
# ... turn on italics
self.txtAttr.SetFontStyle(wx.FONTSTYLE_ITALIC)
# if Italics are being turned off ...
else:
# If we have a valid font and need to change to not-italics ...
if not tmpFont.IsOk() or (tmpFont.GetStyle() != wx.FONTSTYLE_NORMAL):
# ... turn off italics
self.txtAttr.SetFontStyle(wx.FONTSTYLE_NORMAL)
# If underline is specified, set or remove underline as requested
if fontUnderline != None:
# If Underline is being turned on ...
if fontUnderline:
# If we have a valid font and need to change to underline ...
if not tmpFont.IsOk() or (not tmpFont.GetUnderlined()):
# ... turn on underline
self.txtAttr.SetFontUnderlined(True)
# if Underline is being turned off ...
else:
# If we have a valid font and need to change to not-underline ...
if not tmpFont.IsOk() or (tmpFont.GetUnderlined()):
# ... turn off underline
self.txtAttr.SetFontUnderlined(False)
# Apply the modified font to the document
self.SetDefaultStyle(self.txtAttr)
# If we're in Read Only Mode ...
if self.GetReadOnly():
# ... then we DON'T want the control marked as changed
self.DiscardEdits()
def SetBold(self, setting):
""" Change the BOLD attribute """
# This doesn't properly ignore Time Codes and Hidden Text!!
# Apply bold to the current selection, or toggle bold if no selection
# self.ApplyBoldToSelection()
# If we have a text selection, not an insertion point, ...
if self.GetSelection() != (-2, -2):
# Begin an Undo Batch for formatting
self.BeginBatchUndo('Format')
## # For each character in the current selection ...
## for selPos in range(self.GetSelection()[0], self.GetSelection()[1]):
## # Get the Font Attributes of the current Character
## tmpStyle = self.GetStyleAt(selPos)
## # We don't want to update the formatting of Time Codes or of hidden Time Code Data.
## if (not self.CompareFormatting(tmpStyle, self.txtTimeCodeAttr, fullCompare=False)) and (not self.IsStyleHiddenAt(selPos)):
## if setting:
## tmpStyle.SetFontWeight(wx.FONTWEIGHT_BOLD)
## else:
## tmpStyle.SetFontWeight(wx.FONTWEIGHT_NORMAL)
## # ... format the selected text
## tmpRange = richtext.RichTextRange(selPos, selPos + 1)
## self.SetStyle(tmpRange, tmpStyle)
##
## print "Applying Bold to ", selPos, selPos + 1
# Create a blank RichTextAttributes object
tmpAttr = richtext.RichTextAttr()
# If we're adding Bold ...
if setting:
# ... set the Font Weight to Bold
tmpAttr.SetFontWeight(wx.FONTWEIGHT_BOLD)
# If we're removing Bold ...
else:
# ... set the Font Weight to Normal
tmpAttr.SetFontWeight(wx.FONTWEIGHT_NORMAL)
# Apply the style to the selected block
self.SetStyle(self.GetSelection(), tmpAttr)
# End the Formatting Undo Batch
self.EndBatchUndo()
else:
# Set the Style
self.SetTxtStyle(fontBold = setting)
def GetBold(self):
""" Determine the current value of the BOLD attribute """
# If there is a selection ...
if self.GetSelection() != (-2, -2):
# ... get the style of the selection
tmpStyle = self.GetStyleAt(self.GetSelection()[0])
# If there is NO selection ...
else: