forked from sanyaade-machine-learning/Transana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KeywordMapClass.py
2852 lines (2666 loc) · 172 KB
/
KeywordMapClass.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) 2002-2015 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 the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#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 a map of keywords that have been applied to the selected Episode"""
__author__ = "David K. Woods <dwoods@wcer.wisc.edu>"
DEBUG = False
if DEBUG:
print "KeywordMapClass DEBUG is ON!!"
# import Python's os and sys modules
import os, sys
# import Python's platform module
import platform
# load wxPython for GUI
import wx
# load the GraphicsControl
import GraphicsControlClass
# Load the Printout Class
from KeywordMapPrintoutClass import MyPrintout
# Load the Collection object
import Collection
# Import Transana's Database Interface
import DBInterface
# Import Transana's Dialogs
import Dialogs
# Import Transana's Document object
import Document
# Import Transana's Episode object
import Episode
# Import Transana's Filter Dialog
import FilterDialog
# import Transana's Keyword Object
import KeywordObject
# import Transana Miscellaneous functions
import Misc
# import Transana's Quote object
import Quote
# import Transana's Constants
import TransanaConstants
# Import Transana's Exceptions
import TransanaExceptions
# import Transana's Globals
import TransanaGlobal
# Import Transana's Images
import TransanaImages
# Declare Control IDs
# Menu Item and Toolbar Item for File > Filter
M_FILE_FILTER = wx.NewId()
T_FILE_FILTER = wx.NewId()
# Menu Item and Toolbar Item for File > Save As
M_FILE_SAVEAS = wx.NewId()
T_FILE_SAVEAS = wx.NewId()
# Menu Item and Toolbar Item for File > Printer Setup
M_FILE_PRINTSETUP = wx.NewId()
T_FILE_PRINTSETUP = wx.NewId()
# Menu Item and Toolbar Item for File > Print Preview
M_FILE_PRINTPREVIEW = wx.NewId()
T_FILE_PRINTPREVIEW = wx.NewId()
# Menu Item and Toolbar Item for File > Print
M_FILE_PRINT = wx.NewId()
T_FILE_PRINT = wx.NewId()
# Menu Item and Toolbar Item for File > Exit
M_FILE_EXIT = wx.NewId()
T_FILE_EXIT = wx.NewId()
# Menu Item and Toolbar Item for Help > Help
M_HELP_HELP = wx.NewId()
T_HELP_HELP = wx.NewId()
# Series List Combo Box
ID_SERIESLIST = wx.NewId()
# Episode List Combo Box
ID_EPISODELIST = wx.NewId()
class KeywordMap(wx.Frame):
""" This is the main class for the Keyword Map application.
It can be instantiated as a free-standing report with a frame, or can be
called as an embedded graphic display for the Visualization window. """
def __init__(self, parent, ID=-1, title="", embedded=False, topOffset=0, controlObject=None):
# It's always important to remember your ancestors.
self.parent = parent
# Remember the title
self.title = title
# Initialize the Report Number
self.reportNumber = 0
# We do some things differently if we're a free-standing Keyword Map report
# or if we're an embedded Keyword Visualization.
self.embedded = embedded
# Remember the topOffset parameter value. This is used to specify a larger top margin for the keyword visualization,
# needed for the Hybrid Visualization.
self.topOffset = topOffset
# Let's remember the Control Object, if one is passed in
self.ControlObject = controlObject
# If a Control Object has been passed in ...
if self.ControlObject != None:
# ... register this report with the Control Object (which adds it to the Windows Menu)
self.ControlObject.AddReportWindow(self)
# Create a connection to the database
DBConn = DBInterface.get_db()
# Create a cursor and execute the appropriate query
self.DBCursor = DBConn.cursor()
# If we're NOT embedded, we need to create a full frame etc.
if not self.embedded:
# Determine the screen size for setting the initial dialog size
rect = wx.Display(TransanaGlobal.configData.primaryScreen).GetClientArea() # wx.ClientDisplayRect()
width = rect[2] * .80
height = rect[3] * .80
# Create the basic Frame structure with a white background
self.frame = wx.Frame.__init__(self, parent, ID, title, pos=(10, 10), size=wx.Size(width, height), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL | wx.NO_FULL_REPAINT_ON_RESIZE)
self.SetBackgroundColour(wx.WHITE)
# Set the icon
transanaIcon = wx.Icon(os.path.join(TransanaGlobal.programDir, "images", "Transana.ico"), wx.BITMAP_TYPE_ICO)
self.SetIcon(transanaIcon)
# The rest of the form creation is deferred to the Setup method. To set the form up,
# we need data from the database, which we cannot get until we have gotten the username
# and password information. However, to get that information, we need a Main Form to
# exist. Be careful not to confuse Setup() with SetupEmbedded().
# If we ARE embedded, we just use the parent object's graphics canvas for drawing the Keyword Map.
else:
# We need to unbind existing mouse motion event connections to avoid overlapping ToolTips and other problems.
self.parent.waveform.Unbind(wx.EVT_MOTION)
# point the local graphic object to the appropriate item in the parent, the Visualization Window's waveform object.
self.graphic = self.parent.waveform
# We need to assign the Keyword Map's Mouse Motion event to the existing graphic object.
self.graphic.Bind(wx.EVT_MOTION, self.OnMouseMotion)
# The rest of the information is deferred to the SetupEmbedded method. Be careful not to confuse
# SetupEmbedded() with Setup().
# Initialize DocumentNum and EpisodeNum
self.documentNum = None
self.episodeNum = None
# Initialize CollectionNum and the collection object
self.collectionNum = None
self.collection = None
# Initialize the Text (Document) Object
self.textObj = None
# Initialize Media File to nothing
self.MediaFile = ''
# Initialize Media Length and Character Length to 0
self.MediaLength = 0
self.CharacterLength = 0
# Initialize Keyword Lists to empty
self.unfilteredKeywordList = []
self.filteredKeywordList = []
# Intialize the Clip List to empty
self.clipList = []
self.clipFilterList = []
# Initialize the Snapshot List to empty
self.snapshotList = []
self.snapshotFilterList = []
# Initialize the Quote List to empty
self.quoteList = []
self.quoteFilterList = []
# To be able to show only parts of an Episode Time Line, we need variables for the time boundaries.
self.startTime = 0
self.endTime = 0
# Initialize the StartChar and EndChar values to -1 to indicate we're dealing with Media rather than Text
self.startChar = -1
self.endChar = -1
self.keywordClipList = {}
self.configName = ''
# Initialize variables required to avoid crashes when the visualization has been cleared
self.graphicindent = 0
self.Bounds = [1, 1, 1, 1]
# Create a dictionary of the colors for each keyword.
self.keywordColors = {'lastColor' : -1}
if not self.embedded:
# Get the Configuration values for the Keyword Map Options
self.barHeight = TransanaGlobal.configData.keywordMapBarHeight
self.whitespaceHeight = TransanaGlobal.configData.keywordMapWhitespace
self.hGridLines = TransanaGlobal.configData.keywordMapHorizontalGridLines
self.vGridLines = TransanaGlobal.configData.keywordMapVerticalGridLines
# We default to Color Output. When this was configurable, if a new Map was
# created in B & W, the colors never worked right afterwards.
self.colorOutput = True
# Get the colorAsKeyword value
self.colorAsKeywords = TransanaGlobal.configData.colorAsKeywords
else:
# Get the Configuration values for the Keyword Visualization Options
self.barHeight = TransanaGlobal.configData.keywordVisualizationBarHeight
self.whitespaceHeight = TransanaGlobal.configData.keywordVisualizationWhitespace
self.hGridLines = TransanaGlobal.configData.keywordVisualizationHorizontalGridLines
self.vGridLines = TransanaGlobal.configData.keywordVisualizationVerticalGridLines
self.colorOutput = True
def Setup(self, documentNum=None, episodeNum=None, collNum=None, seriesName='', documentName='', episodeName=''):
""" Complete initialization for the free-standing Keyword Map or Collection Keyword Map, not the embedded version. """
# Remember the appropriate Document information
self.documentNum = documentNum
self.documentName = documentName
# Remember the appropriate Episode information
self.episodeNum = episodeNum
self.episodeName = episodeName
# Remember the Series / Library information
self.seriesName = seriesName
# Remember the appropriate Collection information
self.collectionNum = collNum
# indicate that we're not working from a Quote. (The Keyword Map is never Quote-based.)
self.quoteNum = None
# indicate that we're not working from a Clip. (The Keyword Map is never Clip-based.)
self.clipNum = None
# Initialize Time and Character Positions
self.startTime = -1
self.endTime = -1
self.startChar = -1
self.endChar = -1
# You can't have a separate menu on the Mac, so we'll use a Toolbar
self.toolBar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_TEXT)
# Get the graphic for the Filter button
self.toolBar.AddTool(T_FILE_FILTER, TransanaImages.ArtProv_LISTVIEW.GetBitmap(), shortHelpString=_("Filter"))
self.toolBar.AddTool(T_FILE_SAVEAS, TransanaImages.SaveJPG16.GetBitmap(), shortHelpString=_('Save As'))
self.toolBar.AddTool(T_FILE_PRINTSETUP, TransanaImages.PrintSetup.GetBitmap(), shortHelpString=_('Set up Page'))
self.toolBar.AddTool(T_FILE_PRINTPREVIEW, TransanaImages.PrintPreview.GetBitmap(), shortHelpString=_('Print Preview'))
# Disable Print Preview on the PPC Mac and for Right-To-Left languages
if (platform.processor() == 'powerpc') or (TransanaGlobal.configData.LayoutDirection == wx.Layout_RightToLeft):
self.toolBar.EnableTool(T_FILE_PRINTPREVIEW, False)
self.toolBar.AddTool(T_FILE_PRINT, TransanaImages.Print.GetBitmap(), shortHelpString=_('Print'))
# create a bitmap button for the Move Down button
self.toolBar.AddTool(T_HELP_HELP, TransanaImages.ArtProv_HELP.GetBitmap(), shortHelpString=_("Help"))
self.toolBar.AddTool(T_FILE_EXIT, TransanaImages.Exit.GetBitmap(), shortHelpString=_('Exit'))
self.toolBar.Realize()
# Let's go ahead and keep the menu for non-Mac platforms
if not '__WXMAC__' in wx.PlatformInfo:
# Add a Menu Bar
menuBar = wx.MenuBar() # Create the Menu Bar
self.menuFile = wx.Menu() # Create the File Menu
self.menuFile.Append(M_FILE_FILTER, _("&Filter"), _("Filter report contents")) # Add "Filter" to File Menu
self.menuFile.Append(M_FILE_SAVEAS, _("Save &As"), _("Save image in JPEG format")) # Add "Save As" to File Menu
self.menuFile.Enable(M_FILE_SAVEAS, False)
self.menuFile.Append(M_FILE_PRINTSETUP, _("Page Setup"), _("Set up Page")) # Add "Page Setup" to the File Menu
self.menuFile.Append(M_FILE_PRINTPREVIEW, _("Print Preview"), _("Preview your printed output")) # Add "Print Preview" to the File Menu
self.menuFile.Enable(M_FILE_PRINTPREVIEW, False)
self.menuFile.Append(M_FILE_PRINT, _("&Print"), _("Send your output to the Printer")) # Add "Print" to the File Menu
self.menuFile.Enable(M_FILE_PRINT, False)
if self.collectionNum == None:
self.menuFile.Append(M_FILE_EXIT, _("E&xit"), _("Exit the Keyword Map program")) # Add "Exit" to the File Menu
else:
self.menuFile.Append(M_FILE_EXIT, _("E&xit"), _("Exit the Collection Keyword Map program")) # Add "Exit" to the File Menu
menuBar.Append(self.menuFile, _('&File')) # Add the File Menu to the Menu Bar
self.menuHelp = wx.Menu()
self.menuHelp.Append(M_HELP_HELP, _("&Help"), _("Help"))
menuBar.Append(self.menuHelp, _("&Help"))
self.SetMenuBar(menuBar) # Connect the Menu Bar to the Frame
# Link menu items and toolbar buttons to the appropriate methods
wx.EVT_MENU(self, M_FILE_FILTER, self.OnFilter) # Attach File > Filter to a method
wx.EVT_MENU(self, T_FILE_FILTER, self.OnFilter) # Attach Toolbar Filter to a method
wx.EVT_MENU(self, M_FILE_SAVEAS, self.OnSaveAs) # Attach File > Save As to a method
wx.EVT_MENU(self, T_FILE_SAVEAS, self.OnSaveAs) # Attach Toolbar Save As to a method
wx.EVT_MENU(self, M_FILE_PRINTSETUP, self.OnPrintSetup) # Attach File > Print Setup to a method
wx.EVT_MENU(self, T_FILE_PRINTSETUP, self.OnPrintSetup) # Attach Toolbar Print Setup to a method
wx.EVT_MENU(self, M_FILE_PRINTPREVIEW, self.OnPrintPreview) # Attach File > Print Preview to a method
wx.EVT_MENU(self, T_FILE_PRINTPREVIEW, self.OnPrintPreview) # Attach Toolbar Print Preview to a method
wx.EVT_MENU(self, M_FILE_PRINT, self.OnPrint) # Attach File > Print to a method
wx.EVT_MENU(self, T_FILE_PRINT, self.OnPrint) # Attach Toolbar Print to a method
wx.EVT_MENU(self, M_FILE_EXIT, self.CloseWindow) # Attach CloseWindow to File > Exit
wx.EVT_MENU(self, T_FILE_EXIT, self.CloseWindow) # Attach CloseWindow to Toolbar Exit
wx.EVT_MENU(self, M_HELP_HELP, self.OnHelp)
wx.EVT_MENU(self, T_HELP_HELP, self.OnHelp)
# Bind the form's EVT_CLOSE method
self.Bind(wx.EVT_CLOSE, self.OnClose)
# Determine the window boundaries
(w, h) = self.GetClientSizeTuple()
# If not embedded ...
if ((self.seriesName != '') and (self.episodeName != '')) or (self.collectionNum != None):
self.Bounds = (5, 5, w - 10, h - 25)
# If embedded ...
else:
self.Bounds = (5, 40, w - 10, h - 30)
# Create the Graphic Area using the GraphicControlClass
# NOTE: EVT_LEFT_DOWN, EVT_LEFT_UP, and EVT_RIGHT_UP are caught in GraphicsControlClass and are passed to this routine's
# OnLeftDown and OnLeftUp (for both left and right) methods because of the "passMouseEvents" paramter
self.graphic = GraphicsControlClass.GraphicsControl(self, -1, wx.Point(self.Bounds[0], self.Bounds[1]),
(self.Bounds[2] - self.Bounds[0], self.Bounds[3] - self.Bounds[1]),
(self.Bounds[2] - self.Bounds[0], self.Bounds[3] - self.Bounds[1]),
passMouseEvents=True)
# Add a Status Bar
self.CreateStatusBar()
# Attach the Resize Event
wx.EVT_SIZE(self, self.OnSize)
# We'll detect mouse movement in the GraphicsControlClass from out here, as
# the KeywordMap object is the object that knows what the data is on the graphic.
self.graphic.Bind(wx.EVT_MOTION, self.OnMouseMotion)
# Prepare objects for use in Printing
self.printData = wx.PrintData()
self.printData.SetPaperId(wx.PAPER_LETTER)
# Center on the screen
TransanaGlobal.CenterOnPrimary(self)
# If we have a Series name and Document Name, we are doing a Document Keyword Map
if (self.seriesName != '') and (self.documentName != ''):
# Load the Text Object, in this case a Document
self.textObj = Document.Document(self.documentNum)
# Initialize the Quote Filter List to be empty
self.quoteFilterList = []
# Initialize the Snapshot Filter List to be empty
self.snapshotFilterList = []
# Clear the drawing
self.filteredKeywordList = []
self.unfilteredKeywordList = []
# Populate the drawing
self.ProcessDocument()
# We need to draw the graph before we set the Default filter
self.DrawGraph()
# Trigger the load of the Default filter, if one exists. An event of None signals we're loading the
# Default config, and the OnFilter method will handle drawing the graph!
self.OnFilter(None)
# If we have a Series name and Episode Name, we are doing an Episode Keyword Map
elif (self.seriesName != '') and (self.episodeName != ''):
# Initialize the Clip Filter List to be empty
self.clipFilterList = []
# Initialize the Snapshot Filter List to be empty
self.snapshotFilterList = []
# Clear the drawing
self.filteredKeywordList = []
self.unfilteredKeywordList = []
# Populate the drawing
self.ProcessEpisode()
# We need to draw the graph before we set the Default filter
self.DrawGraph()
# Trigger the load of the Default filter, if one exists. An event of None signals we're loading the
# Default config, and the OnFilter method will handle drawing the graph!
self.OnFilter(None)
# If we have a Collection Number, we're doing a Collection Keyword Map
elif self.collectionNum != None:
# Create a collection object
self.collection = Collection.Collection(self.collectionNum)
# Clear the drawing
self.filteredKeywordList = []
self.unfilteredKeywordList = []
# Populate the drawing
self.ProcessCollection()
# We need to draw the graph before we set the Default filter
self.DrawGraph()
# Trigger the load of the Default filter, if one exists. An event of None signals we're loading the
# Default config, and the OnFilter method will handle drawing the graph!
self.OnFilter(None)
# Show the Frame
self.Show(True)
def SetupEmbedded(self, episodeNum, seriesName, episodeName, startTime, endTime,
filteredClipList=[], unfilteredClipList = [],
filteredSnapshotList=[], unfilteredSnapshotList = [],
filteredKeywordList=[], unfilteredKeywordList = [],
keywordColors = None, clipNum=None, configName='', loadDefault=False):
""" Complete setup for the embedded version of the Keyword Map. """
# Remember the appropriate Episode information
self.episodeNum = episodeNum
self.seriesName = seriesName
self.episodeName = episodeName
self.clipNum = clipNum
self.configName = configName
# Set the start and end time boundaries (especially important for Clips!)
self.startTime = startTime
self.endTime = endTime
# Set the StartChar and EndChar values to -1 to indicate we're dealing with Media rather than Text
self.startChar = -1
self.endChar = -1
self.CharacterLength = 0
# Toggle the Embedded labels. (Used for testing mouse-overs only)
self.showEmbeddedLabels = False
# Determine the graphic's boundaries
w = self.graphic.getWidth()
h = self.graphic.getHeight()
if (self.seriesName != '') and (self.episodeName != ''):
self.Bounds = (0, 0, w, h - 25)
else:
self.Bounds = (0, 0, w, h - 25)
# If we have a defined Episode (which we always should) ...
if (self.seriesName != '') and (self.episodeName != ''):
# Set the initial Clip Lists
self.clipFilterList = filteredClipList[:]
self.clipList = unfilteredClipList[:]
# Set the initial Snapshot Lists
self.snapshotFilterList = filteredSnapshotList[:]
self.snapshotList = unfilteredSnapshotList[:]
# set the initial keyword lists
self.filteredKeywordList = filteredKeywordList[:]
self.unfilteredKeywordList = unfilteredKeywordList[:]
# If we got keywordColors, use them!!
if keywordColors != None:
self.keywordColors = keywordColors
# Populate the drawing
self.ProcessEpisode()
# We need to draw the graph before we set the Default filter
self.DrawGraph()
# If we need to load the Default Configuration ...
if loadDefault:
# We actually need to wipe out the original graphic prior to loading the Default filter!
self.graphic.Clear()
# Trigger the load of the Default filter, if one exists. An event of None signals we're loading the
# Default config, and the OnFilter method will handle drawing the graph!
self.OnFilter(None)
def SetupTextEmbedded(self,
textObj,
startChar,
endChar,
totalLength,
filteredQuoteList=[],
unfilteredQuoteList = [],
## filteredSnapshotList=[],
## unfilteredSnapshotList = [],
filteredKeywordList=[],
unfilteredKeywordList = [],
keywordColors = None,
quoteNum=None,
configName='',
loadDefault=False):
""" Complete setup for the embedded version of the Text Keyword Map. """
# Remember the appropriate object
self.textObj = textObj
self.quoteNum = quoteNum
self.configName = configName
# Set the StartTime and EndTime values to -1 to indicate we're dealing with Text rather than media
# Actually, no. That causes problems with showing a selection in the Text Keyword Visualization!
# self.startTime = -1
self.endTime = -1
# Set the start and end position boundaries (especially important for Quotes!)
self.startChar = startChar
self.endChar = endChar
# Clear the Media Length
self.MediaLength = 0
# Remember the total character length
self.CharacterLength = totalLength
# Toggle the Embedded labels. (Used for testing mouse-overs only)
self.showEmbeddedLabels = False
# Determine the graphic's boundaries
w = self.graphic.getWidth()
h = self.graphic.getHeight()
self.Bounds = (0, 0, w, h - 25)
# If we have a defined textObj (which we always should) ...
if isinstance(self.textObj, Document.Document) or isinstance(self.textObj, Quote.Quote):
# Set the initial Quote Lists
self.quoteFilterList = filteredQuoteList[:]
self.quoteList = unfilteredQuoteList[:]
## # Set the initial Snapshot Lists
## self.snapshotFilterList = filteredSnapshotList[:]
## self.snapshotList = unfilteredSnapshotList[:]
# set the initial keyword lists
self.filteredKeywordList = filteredKeywordList[:]
self.unfilteredKeywordList = unfilteredKeywordList[:]
# If we got keywordColors, use them!!
if keywordColors != None:
self.keywordColors = keywordColors
# Populate the drawing
self.ProcessDocument()
# We need to draw the graph before we set the Default filter
self.DrawGraph()
# If we need to load the Default Configuration ...
if loadDefault:
# We actually need to wipe out the original graphic prior to loading the Default filter!
self.graphic.Clear()
# Trigger the load of the Default filter, if one exists. An event of None signals we're loading the
# Default config, and the OnFilter method will handle drawing the graph!
self.OnFilter(None)
# Define the Method that implements Filter
def OnFilter(self, event):
""" Implement the Filter Dialog call for Keyword Maps and Keyword Visualizations """
# See if we're loading the Default profile. This is signalled by an event of None!
if event == None:
loadDefault = True
else:
loadDefault = False
# Set up parameters for creating the Filter Dialog. Keyword Map/Keyword Visualization Filter requires episodeNum for the Config Save.
if not self.embedded:
# For the keyword map, the form created here is the parent
parent = self
# If we have an Episode Keyword Map ...
if self.episodeNum != None:
# Set and encode the dialog title
title = unicode(_("Episode Keyword Map Filter Dialog"), 'utf8')
# reportType=1 indicates it is for a Keyword Map.
reportType = 1
reportScope = self.episodeNum
startVal = max(self.startTime, 0)
endVal = max(self.endTime, 0)
# If we have a Collection Keyword Map
elif self.collectionNum != None:
# Set and encode the dialog title
title = unicode(_("Collection Keyword Map Filter Dialog"), 'utf8')
# reportType=16 indicates it is for a Collection Keyword Map.
reportType = 16
reportScope = self.collectionNum
startVal = max(self.startTime, 0)
endVal = max(self.endTime, 0)
# If we have a Document Keyword Map
elif self.textObj != None:
# Set and encode the dialog title
title = unicode(_("Document Keyword Map Filter Dialog"), 'utf8')
# reportType=17 indicates it is for a Document Keyword Map.
reportType = 17
reportScope = self.textObj.number
startVal = max(self.startChar, 0)
endVal = max(self.endChar, 0)
# See if there are Quotes in the Filter List
quoteFilter = (len(self.quoteFilterList) > 0)
# See if there are Clips in the Filter List
clipFilter = (len(self.clipFilterList) > 0)
# See if there are Snapshots in the Snapshot Filter List
snapshotFilter = (len(self.snapshotFilterList) > 0)
# See if there are Keywords in the Filter List
keywordFilter = (len(self.unfilteredKeywordList) > 0)
# Keyword Map and Collection Keyword Map now support Keyword Color customization, at least sometimes
keywordColors = (len(self.unfilteredKeywordList) > 0)
# We want the Options tab
options = True
# Create a Filter Dialog, passing all the necessary parameters.
dlgFilter = FilterDialog.FilterDialog(parent,
-1,
title,
reportType=reportType,
loadDefault=loadDefault,
configName=self.configName,
reportScope=reportScope,
quoteFilter=quoteFilter,
clipFilter=clipFilter,
snapshotFilter=snapshotFilter,
keywordFilter=keywordFilter,
keywordSort=True,
keywordColor=keywordColors,
options=options,
startTime=startVal,
endTime=endVal,
barHeight=self.barHeight,
whitespace=self.whitespaceHeight,
hGridLines=self.hGridLines,
vGridLines=self.vGridLines,
colorOutput=self.colorOutput,
colorAsKeywords=self.colorAsKeywords)
else:
# For the keyword visualization, the parent that was passed in on initialization is the parent
parent = self.parent
title = unicode(_("Keyword Visualization Filter Dialog"), 'utf8')
# See if there are Quotes in the Filter List
quoteFilter = (len(self.quoteFilterList) > 0)
# See if there are Clips in the Filter List
clipFilter = (len(self.clipFilterList) > 0)
# See if there are Snapshots in the Snapshot Filter List
snapshotFilter = (len(self.snapshotFilterList) > 0)
# See if there are Keywords in the Filter List
keywordFilter = (len(self.unfilteredKeywordList) > 0)
# Keyword visualization wants Keyword Color customization
keywordColors = (len(self.unfilteredKeywordList) > 0)
# We want the Options tab
options = True
# If we have an Episode Keyword Visualization ...
if self.episodeNum != None:
# reportType=2 indicates it is for a Keyword Visualization.
reportType = 2
reportScope = self.episodeNum
# If we have a Document Keyword Map
elif self.textObj != None:
# reportType=18 indicates it is for a Document Keyword Visualization.
reportType = 18
reportScope = self.textObj.number
# Create a Filter Dialog, passing all the necessary parameters.
dlgFilter = FilterDialog.FilterDialog(parent,
-1,
title,
reportType=reportType,
loadDefault=loadDefault,
configName=self.configName,
reportScope=reportScope,
quoteFilter=quoteFilter,
clipFilter=clipFilter,
snapshotFilter=snapshotFilter,
keywordFilter=keywordFilter,
keywordSort=True,
keywordColor=keywordColors,
options=options,
startTime=self.startTime,
endTime=self.endTime,
barHeight=self.barHeight,
whitespace=self.whitespaceHeight,
hGridLines=self.hGridLines,
vGridLines=self.vGridLines)
# If we requested the Quote Filter ...
if quoteFilter:
# We want the Quotes sorted in Quote ID order in the FilterDialog. We handle that out here, as the Filter Dialog
# has to deal with manual Quote ordering in some instances, though not here, so it can't deal with this.
self.quoteFilterList.sort()
# Inform the Filter Dialog of the Quotes
dlgFilter.SetQuotes(self.quoteFilterList)
# If we requested the Clip Filter ...
if clipFilter:
# We want the Clips sorted in Clip ID order in the FilterDialog. We handle that out here, as the Filter Dialog
# has to deal with manual clip ordering in some instances, though not here, so it can't deal with this.
self.clipFilterList.sort()
# Inform the Filter Dialog of the Clips
dlgFilter.SetClips(self.clipFilterList)
# if there are Snapshots ...
if snapshotFilter:
# ... populate the Filter Dialog with Snapshots
dlgFilter.SetSnapshots(self.snapshotFilterList)
# if there are Keywords ...
if keywordFilter:
# Keyword Colors must be specified before Keywords! So if we want Keyword Colors, ...
if keywordColors:
# Inform the Filter Dialog of the colors used for each Keyword
dlgFilter.SetKeywordColors(self.keywordColors)
# Populate the Filter Dialog with Keywords
dlgFilter.SetKeywords(self.unfilteredKeywordList)
# Create a dummy error message to get our while loop started.
errorMsg = 'Start Loop'
# Keep trying as long as there is an error message
while errorMsg != '':
# Clear the last (or dummy) error message.
errorMsg = ''
# If we're loading the Default configuration ...
if loadDefault:
# ... get the list of existing configuration names.
profileList = dlgFilter.GetConfigNames()
# If (translated) "Default" is in the list ...
# (NOTE that the default config name is stored in English, but gets translated by GetConfigNames!)
if unicode(_('Default'), 'utf8') in profileList:
# ... then signal that we need to load the config.
dlgFilter.OnFileOpen(None)
# Fake that we asked the user for a filter name and got an OK
result = wx.ID_OK
# If we're loading a Default profile, but there's none in the list, we can skip
# the rest of the Filter method by pretending we got a Cancel from the user.
else:
result = wx.ID_CANCEL
# If we're not loading a Default profile ...
else:
# ... we need to show the Filter Dialog here.
result = dlgFilter.ShowModal()
# If the user clicks OK (or we have a Default config)
if result == wx.ID_OK:
# If we requested Quote Filtering ...
if quoteFilter:
# ... then get the filtered quote data
self.quoteFilterList = dlgFilter.GetQuotes()
# If we requested Clip Filtering ...
if clipFilter:
# ... then get the filtered clip data
self.clipFilterList = dlgFilter.GetClips()
# If we requested Snapshot Filtering ...
if snapshotFilter:
# ... then get the filtered snapshot data
self.snapshotFilterList = dlgFilter.GetSnapshots()
# If we requested Keyword filtering ...
if keywordFilter:
# Get the complete list of keywords from the Filter Dialog. We'll deduce the filter info in a moment.
# (This preserves the "check" info for later reuse.)
self.unfilteredKeywordList = dlgFilter.GetKeywords()
# If we requested Keyword Color data ...
if keywordColors:
# ... then get the keyword color data from the Filter Dialog
self.keywordColors = dlgFilter.GetKeywordColors()
# Reset the Filtered Keyword List
self.filteredKeywordList = []
# Iterate through the entire Keword List ...
for (kwg, kw, checked) in self.unfilteredKeywordList:
# ... and determine which keywords were checked.
if checked:
# Only the checked ones go into the filtered keyword list.
self.filteredKeywordList.append((kwg, kw))
# If we had an Options Tab, extract that data.
if options:
# If we're in the Keyword Map ...
if not self.embedded:
if self.textObj == None:
# Let's get the Time Range data.
# Start Time must be 0 or greater. Otherwise, don't change it!
if Misc.time_in_str_to_ms(dlgFilter.GetStartTime()) >= 0:
self.startTime = Misc.time_in_str_to_ms(dlgFilter.GetStartTime())
else:
errorMsg += _("Illegal value for Start Time.\n")
# If the Start Time is greater than the media length, reset it to 0.
if self.startTime >= self.MediaLength:
dlgFilter.startTime.SetValue(Misc.time_in_ms_to_str(0))
errorMsg += _("Illegal value for Start Time.\n")
# End Time must be at least 0. Otherwise, don't change it!
if (Misc.time_in_str_to_ms(dlgFilter.GetEndTime()) >= 0):
self.endTime = Misc.time_in_str_to_ms(dlgFilter.GetEndTime())
else:
errorMsg += _("Illegal value for End Time.\n")
# If the end time is 0 or greater than the media length, set it to the media length.
if (self.endTime <= 0) or (self.endTime > self.MediaLength):
self.endTime = self.MediaLength
# Start time cannot equal end time (but this check must come after setting endtime == 0 to MediaLength)
if self.startTime == self.endTime:
errorMsg += _("Start Time and End Time must be different.")
# We need to alter the time values to prevent "division by zero" errors while the Filter Dialog is not modal.
self.startTime = 0
self.endTime = self.MediaLength
# If the Start Time is greater than the End Time, swap them.
if (self.endTime < self.startTime):
temp = self.startTime
self.startTime = self.endTime
self.endTime = temp
else:
try:
# Let's get the character Range data.
# Start Pos must be 0 or greater. Otherwise, don't change it!
if int(dlgFilter.GetStartTime()) >= 0:
self.startChar = int(dlgFilter.GetStartTime())
else:
errorMsg += _("Illegal value for Start Position.\n")
# If the Start Position is greater than the media length, reset it to 0.
if self.startTime >= self.CharacterLength:
dlgFilter.startTime.SetValue('0')
errorMsg += _("Illegal value for Start Position.\n")
except:
errorMsg += _("Illegal value for Start Position.\n")
try:
# End Position must be at least 0. Otherwise, don't change it!
if (int(dlgFilter.GetEndTime()) >= 0):
self.endChar = int(dlgFilter.GetEndTime())
else:
errorMsg += _("Illegal value for End Position.\n")
except:
errorMsg += _("Illegal value for End Position.\n")
# If the end position is 0 or greater than the character length, set it to the character length.
if (self.endChar <= 0) or (self.endChar > self.CharacterLength):
self.endChar = self.CharacterLength
# Start position cannot equal end position (but this check must come after setting endChar == 0 to CharacterLength)
if self.startChar == self.endChar:
errorMsg += _("Start Position and End Position must be different.")
# We need to alter the time values to prevent "division by zero" errors while the Filter Dialog is not modal.
self.startChar = 0
self.endChar = self.CharacterLength
# If the Start Position is greater than the End Position, swap them.
if (self.endChar < self.startChar):
temp = self.startChar
self.startChar = self.endChar
self.endChar = temp
# Get the colorOutput value from the dialog IF we're in the Keyword Map
self.colorOutput = dlgFilter.GetColorOutput()
# Get the colorAsKeywords value from the dialog IF we're in the Keyword Map
self.colorAsKeywords = dlgFilter.GetColorAsKeywords()
# Get the Bar Height and Whitespace Height for both versions of the Keyword Map
self.barHeight = dlgFilter.GetBarHeight()
self.whitespaceHeight = dlgFilter.GetWhitespace()
# we need to store the Bar Height, Whitespace, and colorAsKeywords values in the Configuration.
if not self.embedded:
TransanaGlobal.configData.keywordMapBarHeight = self.barHeight
TransanaGlobal.configData.keywordMapWhitespace = self.whitespaceHeight
TransanaGlobal.configData.colorAsKeywords = self.colorAsKeywords
else:
TransanaGlobal.configData.keywordVisualizationBarHeight = self.barHeight
TransanaGlobal.configData.keywordVisualizationWhitespace = self.whitespaceHeight
# Keyword Map Report, Keyword Visualization, the Series Keyword Sequence Map, and the Collection Keyword Map
# have Bar height and Whitespace parameters as well as horizontal and vertical grid lines
if reportType in [1, 2, 5, 6, 7, 16, 17, 18]:
# Get the Grid Line data from the form
self.hGridLines = dlgFilter.GetHGridLines()
self.vGridLines = dlgFilter.GetVGridLines()
# Store the Grid Line data in the Configuration
if not self.embedded:
TransanaGlobal.configData.keywordMapHorizontalGridLines = self.hGridLines
TransanaGlobal.configData.keywordMapVerticalGridLines = self.vGridLines
else:
TransanaGlobal.configData.keywordVisualizationHorizontalGridLines = self.hGridLines
TransanaGlobal.configData.keywordVisualizationVerticalGridLines = self.vGridLines
if errorMsg != '':
errorDlg = Dialogs.ErrorDialog(self, errorMsg)
errorDlg.ShowModal()
errorDlg.Destroy()
# Remember the configuration name for later reuse
self.configName = dlgFilter.configName
# Destroy the Filter Dialog. We're done with it.
dlgFilter.Destroy()
# Now we can draw the graph.
self.DrawGraph()
# Define the Method that implements Save As
def OnSaveAs(self, event):
self.graphic.SaveAs()
# Define the Method that implements Printer Setup
def OnPrintSetup(self, event):
# Destroying the printerDialog also wipes out the printData object in wxPython 2.5.1.5. Don't do this.
# printerDialog.Destroy()
# Let's use PAGE Setup here ('cause you can do Printer Setup from Page Setup.) It's a better system
# that allows Landscape on Mac.
pageSetupDialogData = wx.PageSetupDialogData(self.printData)
pageSetupDialogData.CalculatePaperSizeFromId()
pageDialog = wx.PageSetupDialog(self, pageSetupDialogData)
pageDialog.ShowModal()
self.printData = wx.PrintData(pageDialog.GetPageSetupData().GetPrintData())
pageDialog.Destroy()
# Define the Method that implements Print Preview
def OnPrintPreview(self, event):
printout = MyPrintout(_('Transana Keyword Map'), self.graphic)
printout2 = MyPrintout(_('Transana Keyword Map'), self.graphic)
self.preview = wx.PrintPreview(printout, printout2, self.printData)
if not self.preview.Ok():
self.SetStatusText(_("Print Preview Problem"))
return
theWidth = max(wx.Display(TransanaGlobal.configData.primaryScreen).GetClientArea()[2] - 180, 760) # wx.ClientDisplayRect()
theHeight = max(wx.Display(TransanaGlobal.configData.primaryScreen).GetClientArea()[3] - 200, 560) # wx.ClientDisplayRect()
frame2 = wx.PreviewFrame(self.preview, self, _("Print Preview"), size=(theWidth, theHeight))
frame2.Centre()
frame2.Initialize()
frame2.Show(True)
# Define the Method that implements Print
def OnPrint(self, event):
pdd = wx.PrintDialogData()
pdd.SetPrintData(self.printData)
printer = wx.Printer(pdd)
printout = MyPrintout(_('Transana Keyword Map'), self.graphic)
if not printer.Print(self, printout):
dlg = Dialogs.ErrorDialog(None, _("There was a problem printing this report."))
dlg.ShowModal()
dlg.Destroy()
# NO! REMOVED to prevent crash on 2nd print attempt following Filter Config.
# else:
# self.printData = printer.GetPrintDialogData().GetPrintData()
printout.Destroy()
def OnClose(self, event):
""" Handle the Close Event """
# If the report has a defined Control Object ...
if self.ControlObject != None:
# ... remove this report from the Menu Window's Window Menu
self.ControlObject.RemoveReportWindow(self.title, self.reportNumber)
# Inherit the parent Close event so things will, you know, close.
event.Skip()
# Define the Method that closes the Window on File > Exit
def CloseWindow(self, event):
self.Close()
def OnHelp(self, event):
""" Implement the Filter Dialog Box's Help function """
# Define the Help Context
HelpContext = "Keyword Map"
# If a Help Window is defined ...
if TransanaGlobal.menuWindow != None:
# ... call Help!
TransanaGlobal.menuWindow.ControlObject.Help(HelpContext)
def OnSize(self, event):
(w, h) = self.GetClientSizeTuple()
if not self.embedded:
if self.Bounds[1] == 5:
self.Bounds = (5, 5, w - 10, h - 25)
else:
self.Bounds = (5, 40, w - 10, h - 30)
else:
self.Bounds = (0, 0, w, h - 25)
# If we have data defined in the graph ...
if (self.episodeName != '') or (self.textObj != None) or (self.collection != None):
# ... redraw the graph
self.DrawGraph()
def CalcX(self, XPos):
""" Determine the proper horizontal coordinate for the given time """
# Specify a margin width
if not self.embedded:
marginwidth = (0.06 * (self.Bounds[2] - self.Bounds[0]))
else:
marginwidth = 0
# The Horizonal Adjustment is the global graphic indent
hadjust = self.graphicindent
# If we have a TIME-based Map ...
if (self.startChar == -1) and (self.endChar == -1):
lowerVal = self.startTime
upperVal = self.endTime
totalLength = self.MediaLength
# If we have a CHARACTER-based Map ...
else:
lowerVal = self.startChar
upperVal = self.endChar
totalLength = self.CharacterLength
# The Scaling Factor is the active portion of the drawing area width divided by the total media length
# The idea is to leave the left margin, self.graphicindent for Keyword Labels, and the right margin
if (totalLength > 0) and (upperVal > lowerVal):
scale = (float(self.Bounds[2]) - self.Bounds[0] - hadjust - 2 * marginwidth) / (upperVal - lowerVal)
else:
scale = 0.0
# The horizontal coordinate is the left margin plus the Horizontal Adjustment for Keyword Labels plus
# position times the scaling factor
res = marginwidth + hadjust + ((XPos - lowerVal) * scale)
# If we are in a Right-To-Left Language ...
if TransanaGlobal.configData.LayoutDirection == wx.Layout_RightToLeft:
# ... adjust for a right-to-left graph
return int(self.Bounds[2] - self.Bounds[0] - res)
# If we are in a Left-To-Right language ...
else:
# ... just return the calculated value
return int(res)
def FindTime(self, x):
""" Given a horizontal pixel position, determine the corresponding time value from
the video time line """
# determine the margin width
if not self.embedded:
marginwidth = (0.06 * (self.Bounds[2] - self.Bounds[0]))
else:
marginwidth = 0
# The Horizonal Adjustment is the global graphic indent
hadjust = self.graphicindent
# If we have a TIME-based Map ...
if (self.startChar == -1) and (self.endChar == -1):
lowerVal = self.startTime
upperVal = self.endTime
totalLength = self.MediaLength
# If we have a CHARACTER-based Map ...
else:
lowerVal = self.startChar
upperVal = self. endChar
totalLength = self.CharacterLength
# The Scaling Factor is the active portion of the drawing area width divided by the total media length
# The idea is to leave the left margin, self.graphicindent for Keyword Labels, and the right margin
if (upperVal - lowerVal) > 0:
scale = (float(self.Bounds[2]) - self.Bounds[0] - hadjust - 2 * marginwidth) / (upperVal - lowerVal)
else:
scale = 1.0
# The time is calculated by taking the total width, subtracting the margin values and horizontal indent,
# and then dividing the result by the scale factor calculated above
time = int((x - marginwidth - hadjust) / scale) + lowerVal
return time
def CalcY(self, YPos):
""" Determine the vertical position for a given keyword index """
# For the Keyword Map Report
if not self.embedded:
# Spacing is the larger of (12 pixels for label text or the bar height) plus 2 for whitespace
spacing = max(12, self.barHeight) + self.whitespaceHeight
# Top margin is 30 for titles plus 28 for the timeline
topMargin = 30 + (2 * spacing) + self.topOffset
# For the Keyword Visualization
else:
# Top margin to the line CENTER is 5 pixels plus 1/2 line width to account for line thickness.
# (The line is drawn centered around the line thickness, so this gives us a constant margin of 5
# pixels to the TOP of the line!)
topMargin = int(self.barHeight / 2) + 5 + self.topOffset
# Spacing is the line height plus the whitespace!
spacing = self.barHeight + self.whitespaceHeight
return int(spacing * YPos + topMargin)
def FindKeyword(self, y):
""" Given a vertical pixel position, determine the corresponding Keyword data """
# If the graphic is scrolled, the raw Y value does not point to the correct Keyword.
# Determine the unscrolled equivalent Y position.
(modX, modY) = self.graphic.CalcUnscrolledPosition(0, y)
# Each keyword is a fixed number of pixels high, and we need to adjust for the top margin and
# the amount of graphic scroll too.
if not self.embedded:
# Spacing is the larger of (12 pixels for label text or the bar height) plus 2 for whitespace
spacing = max(12, self.barHeight) + self.whitespaceHeight
# Top margin is 30 for titles, 28 for timeline, less 1/2 the inter-bar whitespace (bar is 5, whitespace is 9)