forked from sanyaade-machine-learning/Transana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
LibraryMap.py
2199 lines (2013 loc) · 134 KB
/
LibraryMap.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 multiple Episodes in a Series"""
__author__ = "David K. Woods <dwoods@wcer.wisc.edu>"
DEBUG = False
if DEBUG:
print "LibraryMap DEBUG is ON!!"
# import Python's os and sys modules
import os, sys
# import Python's platform module
import platform
# import Python's string module
import string
# load wxPython for GUI
import wx
# load the GraphicsControl
import GraphicsControlClass
# Load the Printout Class
from KeywordMapPrintoutClass import MyPrintout
# Import Transana's Database Interface
import DBInterface
# Import Transana's Dialogs
import Dialogs
# Import Transana's Filter Dialog
import FilterDialog
# import Transana's Keyword Object
import KeywordObject
# import Transana's Globals
import TransanaGlobal
# Import Transana's Images
import TransanaImages
# import Transana Miscellaneous functions
import Misc
# 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 LibraryMap(wx.Frame):
""" This is the main class for the Series Map application. """
def __init__(self, parent, title, seriesNum, seriesName, reportType, controlObject=None):
# reportType 1 is the Sequence Mode, showing relative position of keywords in the Episodes
# reportType 2 is the Bar Graph mode, showing a bar graph of total time for each keyword
# reportType 3 is the Percentage mode, showing percentage of total Episode length for each keyword
# Set the Cursor to the Hourglass while the report is assembled
TransanaGlobal.menuWindow.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
# It's always important to remember your ancestors.
self.parent = parent
# Remember the title
self.title = title
# Initialize the Report Number
self.reportNumber = 0
# Remember the Report Type
self.reportType = reportType
# 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()
# 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, -1, 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)
# Initialize Media Length to 0
self.MediaLength = 0
# Initialize all the data Lists to empty
self.episodeList = []
self.filteredEpisodeList = []
self.clipList = []
self.clipFilterList = []
self.snapshotList = []
self.snapshotFilterList = []
self.unfilteredKeywordList = []
self.filteredKeywordList = []
# 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
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}
# Get the Configuration values for the Series Map Options
self.barHeight = TransanaGlobal.configData.seriesMapBarHeight
self.whitespaceHeight = TransanaGlobal.configData.seriesMapWhitespace
self.hGridLines = TransanaGlobal.configData.seriesMapHorizontalGridLines
self.vGridLines = TransanaGlobal.configData.seriesMapVerticalGridLines
self.singleLineDisplay = TransanaGlobal.configData.singleLineDisplay
self.showLegend = TransanaGlobal.configData.showLegend
# 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 number of lines per page for multi-page reports
self.linesPerPage = 66
# If we have a Series Keyword Sequence Map in multi-line mode ...
if (self.reportType == 1) and (not self.singleLineDisplay):
# ... initialize the Episode Name Keyword Lookup Table here.
self.epNameKWGKWLookup = {}
# Initialize the Episode Counter, used for vertical placement.
self.episodeCount= 0
# We need to be able to look up Episode Lengths for the Bar Graph. Let's remember them.
self.episodeLengths = {}
# Remember the appropriate Episode information
self.seriesNum = seriesNum
self.seriesName = seriesName
# indicate that we're not working from a Clip. (The Series Maps are never Clip-based.)
self.clipNum = None
# 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)
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'))
# Disable Print Setup for Right-To-Left languages
# if (TransanaGlobal.configData.LayoutDirection == wx.Layout_RightToLeft):
# self.toolBar.EnableTool(T_FILE_PRINTSETUP, False)
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'))
# Disable Print Setup for Right-To-Left languages
# if (TransanaGlobal.configData.LayoutDirection == wx.Layout_RightToLeft):
# self.toolBar.EnableTool(T_FILE_PRINT, False)
# 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 "Printer Setup" to the File Menu
# Disable Print Setup for Right-To-Left languages
# if (TransanaGlobal.configData.LayoutDirection == wx.Layout_RightToLeft):
# self.menuFile.Enable(M_FILE_PRINTSETUP, False)
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)
self.menuFile.Append(M_FILE_EXIT, _("E&xit"), _("Exit the Series 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()
self.Bounds = (5, 5, w - 10, h - 25)
# 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)
# Show the Frame
self.Show(True)
# Populate the drawing
self.ProcessSeries()
self.DrawGraph()
# Trigger the load of the Default Filter, if one exists. An event of None signals we're loading the
# Default config, an dhte OnFilter method will handle drawing the graph!
self.OnFilter(None)
# Restore Cursor to Arrow
TransanaGlobal.menuWindow.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
# Define the Method that implements Filter
def OnFilter(self, event):
""" Implement the Filter Dialog call for Series Maps """
if event == None:
loadDefault = True
else:
loadDefault = False
# Set the Cursor to the Hourglass while the report is assembled
TransanaGlobal.menuWindow.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
# Set up parameters for creating the Filter Dialog. Series Map Filter requires Series Number (as episodeNum) for the Config Save.
title = string.join([self.title, unicode(_("Filter Dialog"), 'utf8')], ' ')
# See if the Series Map wants the Clip Filter
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)
# Series Map wants Keyword Color customization if it has keywords.
keywordColors = (len(self.unfilteredKeywordList) > 0)
# We want the Options tab
options = True
# reportType=5 indicates it is for a Series Sequence Map.
# reportType=6 indicates it is for a Series Bar Graph.
# reportType=7 indicates it is for a Series Percentage Map
reportType = self.reportType + 4
# The Series Keyword Sequence Map has all the usual parameters plus Time Range data and the Single Line Display option
if self.reportType in [1]:
# Create a Filter Dialog, passing all the necessary parameters.
dlgFilter = FilterDialog.FilterDialog(self,
-1,
title,
reportType=reportType,
loadDefault=loadDefault,
configName=self.configName,
reportScope=self.seriesNum,
episodeFilter=True,
episodeSort=True,
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,
singleLineDisplay=self.singleLineDisplay,
showLegend=self.showLegend,
colorOutput=self.colorOutput)
elif self.reportType in [2, 3]:
# Create a Filter Dialog, passing all the necessary parameters.
dlgFilter = FilterDialog.FilterDialog(self,
-1,
title,
reportType=reportType,
loadDefault=loadDefault,
configName=self.configName,
reportScope=self.seriesNum,
episodeFilter=True,
episodeSort=True,
clipFilter=clipFilter,
snapshotFilter=snapshotFilter,
keywordFilter=keywordFilter,
keywordSort=True,
keywordColor=keywordColors,
options=options,
barHeight=self.barHeight,
whitespace=self.whitespaceHeight,
hGridLines=self.hGridLines,
vGridLines=self.vGridLines,
showLegend=self.showLegend,
colorOutput=self.colorOutput)
# Sort the Episode List
self.episodeList.sort()
# Inform the Filter Dialog of the Episodes
dlgFilter.SetEpisodes(self.episodeList)
# 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)
# Keyword Colors must be specified before Keywords! So if we want Keyword Colors, ...
if keywordColors:
# If we're in grayscale mode, the colors are probably mangled, so let's fix them before
# we send them to the Filter dialog.
if not self.colorOutput:
# A shallow copy of the dictionary object should get the job done.
self.keywordColors = self.rememberedKeywordColors.copy()
# Inform the Filter Dialog of the colors used for each Keyword
dlgFilter.SetKeywordColors(self.keywordColors)
if keywordFilter:
# Inform the Filter Dialog of the Keywords
dlgFilter.SetKeywords(self.unfilteredKeywordList)
# Set the Cursor to the Arrow now that the filter dialog is assembled
TransanaGlobal.menuWindow.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
# 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 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()
# Show the Filter Dialog and see if the user clicks OK
if result == wx.ID_OK:
# Get the Episode Data from the Filter Dialog
self.episodeList = dlgFilter.GetEpisodes()
# If we requested Clip Filtering ...
if clipFilter:
# ... then get the filtered clip data
self.clipFilterList = dlgFilter.GetClips()
if snapshotFilter:
self.snapshotFilterList = dlgFilter.GetSnapshots()
# 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:
# Only the Series Keyword Sequence Map needs the Time Range options.
if self.reportType in [1]:
# 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
# Get the Bar Height and Whitespace Height for all versions of the Series Map
self.barHeight = dlgFilter.GetBarHeight()
self.whitespaceHeight = dlgFilter.GetWhitespace()
# we need to store the Bar Height and Whitespace values in the Configuration.
TransanaGlobal.configData.seriesMapBarHeight = self.barHeight
TransanaGlobal.configData.seriesMapWhitespace = self.whitespaceHeight
# Get the Grid Line data from the form
self.hGridLines = dlgFilter.GetHGridLines()
self.vGridLines = dlgFilter.GetVGridLines()
# Store the Grid Line data in the Configuration
TransanaGlobal.configData.seriesMapHorizontalGridLines = self.hGridLines
TransanaGlobal.configData.seriesMapVerticalGridLines = self.vGridLines
# Only the Series Keyword Sequence Graph needs the Single Line Display Option data.
if self.reportType in [1]:
# Get the singleLineDisplay value from the dialog
self.singleLineDisplay = dlgFilter.GetSingleLineDisplay()
# Remember the value.
TransanaGlobal.configData.singleLineDisplay = self.singleLineDisplay
# Get the showLegend value from the dialog
self.showLegend = dlgFilter.GetShowLegend()
# Remember the value. (This doesn't get saved.)
TransanaGlobal.configData.showLegend = self.showLegend
# Detect if the colorOutput value is actually changing.
if (self.colorOutput != dlgFilter.GetColorOutput()):
# If we're going from color to grayscale ...
if self.colorOutput:
# ... remember what the colors were before they get all screwed up by displaying
# the graphic without them.
self.rememberedKeywordColors = {}
self.rememberedKeywordColors = self.keywordColors.copy()
# Get the colorOutput value from the dialog
self.colorOutput = dlgFilter.GetColorOutput()
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):
# 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):
lineHeight = self.CalcY(1) - self.CalcY(0)
printout = MyPrintout(self.title, self.graphic, multiPage=True, lineStart=self.CalcY(0) - int(lineHeight / 2.0), lineHeight=lineHeight)
printout2 = MyPrintout(self.title, self.graphic, multiPage=True, lineStart=self.CalcY(0) - int(lineHeight / 2.0), lineHeight=lineHeight)
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)
lineHeight = self.CalcY(1) - self.CalcY(0)
printout = MyPrintout(self.title, self.graphic, multiPage=True, lineStart=self.CalcY(0) - int(lineHeight / 2.0), lineHeight=lineHeight)
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):
# Close!
self.Close()
def OnHelp(self, event):
""" Implement the Filter Dialog Box's Help function """
# Define the Help Context
HelpContext = "Library Keyword Graphs"
# If a Help Window is defined ...
if TransanaGlobal.menuWindow != None:
# ... call Help!
TransanaGlobal.menuWindow.ControlObject.Help(HelpContext)
def OnSize(self, event):
""" Handle Resize Events by resizing the Graphic Control and redrawing the graphic """
(w, h) = self.GetClientSizeTuple()
if self.Bounds[1] == 5:
self.Bounds = (5, 5, w - 10, h - 25)
else:
self.Bounds = (5, 40, w - 10, h - 30)
self.DrawGraph()
def CalcX(self, XPos):
""" Determine the proper horizontal coordinate for the given time """
# We need to start by defining the legal range for the type of graph we're working with.
# The Sequence Map is tied to the start and end time variables.
if self.reportType == 1:
startVal = self.startTime
endVal = self.endTime
# The Bar Graph stretches from 0 to the time line Maximum variable
elif self.reportType == 2:
startVal = 0.0
if self.timelineMax == 0:
endVal = 1
else:
endVal = self.timelineMax
# The Percentage Graph ranges from 0 to 100!
elif self.reportType == 3:
startVal = 0.0
endVal = 100.0
# Specify a margin width
marginwidth = (0.06 * (self.Bounds[2] - self.Bounds[0]))
# The Horizonal Adjustment is the global graphic indent
hadjust = self.graphicindent
# 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 self.MediaLength > 0:
scale = (float(self.Bounds[2]) - self.Bounds[0] - hadjust - 2 * marginwidth) / (endVal - startVal)
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 - startVal) * 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
marginwidth = (0.06 * (self.Bounds[2] - self.Bounds[0]))
# The Horizonal Adjustment is the global graphic indent
hadjust = self.graphicindent
# 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 self.MediaLength > 0:
scale = (float(self.Bounds[2]) - self.Bounds[0] - hadjust - 2 * marginwidth) / (self.endTime - self.startTime)
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) + self.startTime
return time
def CalcY(self, YPos):
""" Determine the vertical position for a given keyword index """
# 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)
return int(spacing * YPos + topMargin)
def FindKeyword(self, y):
""" Given a vertical pixel position, determine the corresponding Keyword data """
# NOTE: This method is only valid if self.reportType == 1, the Sequence Map.
# Other variations of the Series maps may use different key values for the dictionary.
if self.reportType != 1:
return None
# 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)
# Now we need to get the keys for the Lookup Dictionary
keyVals = self.epNameKWGKWLookup.keys()
# We need the keys to be in order, so we can quit when we've found what we're looking for.
keyVals.sort()
# The single-line display and the multi-line display handle the lookup differently, of course.
# Let's start with the single-line display.
if self.singleLineDisplay:
# Initialize the return value to None in case nothing is found. The single-line version expects an Episode Name.
returnVal = None
# We also need a temporary value initialized to None. Our data structure returns complex data, from which we
# extract the desired value.
tempVal = None
# Iterate through the sorted keys. The keys are actually y values for the graph!
for yVal in keyVals:
# If we find a key value that is smaller than the unscrolled Graphic y position ...
if yVal <= modY:
# ... then we've found a candidate for what we're looking for. But we keep iterating,
# because we want the LARGEST yVal that's smaller than the graphic y value.
tempVal = self.epNameKWGKWLookup[yVal]
# Once our y values are too large ...
else:
# ... we should stop iterating through the (sorted) keys.
break
# If we found a valid data structure ...
if tempVal != None:
# ... we can extract the Episode name by looking at the first value of the first value of the first key.
returnVal = tempVal[tempVal.keys()[0]][0][0]
# Here, we handle the multi-line display of the Sequence Map.
else:
# Initialize the return value to a tuple of three Nones in case nothing is found.
# The multi-line version expects an Episode Name, Keyword Group, Keyword tuple.
returnVal = (None, None, None)
# Iterate through the sorted keys. The keys are actually y values for the graph!
for yVal in keyVals:
# If we find a key value that is smaller than the unscrolled Graphic y position ...
if yVal <= modY:
# ... then we've found a candidate for what we're looking for. But we keep iterating,
# because we want the LARGEST yVal that's smaller than the graphic y value.
returnVal = self.epNameKWGKWLookup[yVal]
# Once our y values are too large ...
else:
# ... we should stop iterating through the (sorted) keys.
break
# Return the value we found, or None
return returnVal
def GetScaleIncrements(self, MediaLength):
# The general rule is to try to get logical interval sizes with 8 or fewer time increments.
# You always add a bit (20% at the lower levels) of the time interval to the MediaLength
# because the final time is placed elsewhere and we don't want overlap.
# This routine covers from 1 second to 18 hours in length.
# media Length of 9 seconds or less = 1 second intervals
if MediaLength < 9001:
Num = int(round((MediaLength + 200) / 1000.0))
Interval = 1000
# media length of 18 seconds or less = 2 second intervals
elif MediaLength < 18001:
Num = int(round((MediaLength + 400) / 2000.0))
Interval = 2000
# media length of 30 seconds or less = 5 second intervals
elif MediaLength < 30001:
Num = int(round((MediaLength + 2000) / 5000.0))
Interval = 5000
# media length of 50 seconds or less = 5 second intervals
elif MediaLength < 50001:
Num = int(round((MediaLength + 1000) / 5000.0))
Interval = 5000
# media Length of 1:30 or less = 10 second intervals
elif MediaLength < 90001:
Num = int(round((MediaLength + 2000) / 10000.0))
Interval = 10000
# media length of 2:50 or less = 20 second intervals
elif MediaLength < 160001:
Num = int(round((MediaLength + 4000) / 20000.0))
Interval = 20000
# media length of 4:30 or less = 30 second intervals
elif MediaLength < 270001:
Num = int(round((MediaLength + 6000) / 30000.0))
Interval = 30000
# media length of 6:00 or less = 60 second intervals
elif MediaLength < 360001:
Num = int(round((MediaLength + 12000) / 60000.0))
Interval = 60000
# media length of 10:00 or less = 60 second intervals
elif MediaLength < 600001:
Num = int(round((MediaLength + 8000) / 60000.0))
Interval = 60000
# media length of 16:00 or less = 2 minute intervals
elif MediaLength < 960001:
Num = int(round((MediaLength + 24000) / 120000.0))
Interval = 120000
# media length of 40:00 or less = 5 minute intervals
elif MediaLength < 2400001:
Num = int(round((MediaLength + 60000) / 300000.0))
Interval = 300000
# media length if 1:10:00 or less get 10 minute intervals
elif MediaLength < 4200001:
Num = int(round((MediaLength + 80000) / 600000.0))
Interval = 600000
# media length if 3:00:00 or less get 30 minute intervals
elif MediaLength < 10800001:
Num = int(round((MediaLength + 240000) / 1800000.0))
Interval = 1800000
# media length if 4:00:00 or less get 30 minute intervals
elif MediaLength < 14400001:
Num = int(round((MediaLength + 60000) / 1800000.0))
Interval = 1800000
# media length if 9:00:00 or less get 60 minute intervals
elif MediaLength < 32400001:
Num = int(round((MediaLength + 120000) / 3600000.0))
Interval = 3600000
# Longer videos get 2 hour intervals
else:
Num = int(round((MediaLength + 240000) / 7200000.0))
Interval = 7200000
return Num, Interval
def ProcessSeries(self):
# Initialize Media Length to 0
self.MediaLength = 0
# Initialize all the data Lists to empty
self.episodeList = []
self.filteredEpisodeList = []
self.clipList = []
self.clipFilterList = []
self.snapshotList = []
self.snapshotFilterList = []
self.unfilteredKeywordList = []
self.filteredKeywordList = []
if self.reportType == 2:
epLengths = {}
# Get Series Number, Episode Number, Media File Name, and Length
SQLText = """SELECT e.EpisodeNum, e.EpisodeID, e.SeriesNum, e.MediaFile, e.EpLength, s.SeriesID
FROM Episodes2 e, Series2 s
WHERE s.SeriesNum = e.SeriesNum AND
s.SeriesNum = %s
ORDER BY EpisodeID """
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
# Execute the query
self.DBCursor.execute(SQLText, (self.seriesNum, ))
for (EpisodeNum, EpisodeID, SeriesNum, MediaFile, EpisodeLength, SeriesID) in self.DBCursor.fetchall():
EpisodeID = DBInterface.ProcessDBDataForUTF8Encoding(EpisodeID)
SeriesID = DBInterface.ProcessDBDataForUTF8Encoding(SeriesID)
MediaFile = DBInterface.ProcessDBDataForUTF8Encoding(MediaFile)
self.episodeList.append((EpisodeID, SeriesID, True))
if (EpisodeLength > self.MediaLength):
self.MediaLength = EpisodeLength
self.endTime = self.MediaLength
# Remember the Episode's length
self.episodeLengths[(EpisodeID, SeriesID)] = EpisodeLength
# Get the list of Keywords to be displayed
SQLText = """SELECT ck.KeywordGroup, ck.Keyword
FROM Clips2 cl, ClipKeywords2 ck
WHERE cl.EpisodeNum = %s AND
cl.ClipNum = ck.ClipNum
GROUP BY ck.keywordgroup, ck.keyword
ORDER BY KeywordGroup, Keyword, ClipStart"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
self.DBCursor.execute(SQLText, (EpisodeNum, ))
for (kwg, kw) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
if not (kwg, kw) in self.filteredKeywordList:
self.filteredKeywordList.append((kwg, kw))
if not (kwg, kw, True) in self.unfilteredKeywordList:
self.unfilteredKeywordList.append((kwg, kw, True))
# Get the list of WHOLE SNAPSHOT Keywords to be displayed
SQLText = """SELECT ck.KeywordGroup, ck.Keyword
FROM Snapshots2 sn, ClipKeywords2 ck
WHERE sn.EpisodeNum = %s AND
sn.SnapshotNum = ck.SnapshotNum
GROUP BY ck.keywordgroup, ck.keyword
ORDER BY KeywordGroup, Keyword, SnapshotTimeCode"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
self.DBCursor.execute(SQLText, (EpisodeNum, ))
for (kwg, kw) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
if not (kwg, kw) in self.filteredKeywordList:
self.filteredKeywordList.append((kwg, kw))
if not (kwg, kw, True) in self.unfilteredKeywordList:
self.unfilteredKeywordList.append((kwg, kw, True))
# Get the list of SNAPSHOT CODING Keywords to be displayed
SQLText = """SELECT ck.KeywordGroup, ck.Keyword
FROM Snapshots2 sn, SnapshotKeywords2 ck
WHERE sn.EpisodeNum = %s AND
sn.SnapshotNum = ck.SnapshotNum
GROUP BY ck.keywordgroup, ck.keyword
ORDER BY KeywordGroup, Keyword, SnapshotTimeCode"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
self.DBCursor.execute(SQLText, (EpisodeNum, ))
for (kwg, kw) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
if not (kwg, kw) in self.filteredKeywordList:
self.filteredKeywordList.append((kwg, kw))
if not (kwg, kw, True) in self.unfilteredKeywordList:
self.unfilteredKeywordList.append((kwg, kw, True))
# Sort the Keyword List
self.unfilteredKeywordList.sort()
# Create the Keyword Placement lines to be displayed. We need them to be in ClipStart, ClipNum order so colors will be
# distributed properly across bands.
SQLText = """SELECT ck.KeywordGroup, ck.Keyword, cl.ClipStart, cl.ClipStop, cl.ClipNum, cl.ClipID, cl.CollectNum
FROM Clips2 cl, ClipKeywords2 ck
WHERE cl.EpisodeNum = %s AND
cl.ClipNum = ck.ClipNum
ORDER BY ClipStart, cl.ClipNum, KeywordGroup, Keyword"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
self.DBCursor.execute(SQLText, (EpisodeNum, ))
for (kwg, kw, clipStart, clipStop, clipNum, clipID, collectNum) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
clipID = DBInterface.ProcessDBDataForUTF8Encoding(clipID)
# If we're dealing with an Episode, self.clipNum will be None and we want all clips.
# If we're dealing with a Clip, we only want to deal with THIS clip!
if (self.clipNum == None) or (clipNum == self.clipNum):
self.clipList.append((kwg, kw, clipStart, clipStop, clipNum, clipID, collectNum, EpisodeID, SeriesID))
if not ((clipID, collectNum, True) in self.clipFilterList):
self.clipFilterList.append((clipID, collectNum, True))
# Create the WHOLE SNAPSHOT Keyword Placement lines to be displayed. We need them to be in SnapshotTimeCode, SnapshotNum order so colors will be
# distributed properly across bands.
SQLText = """SELECT ck.KeywordGroup, ck.Keyword, sn.SnapshotTimeCode, sn.SnapshotDuration, sn.SnapshotNum, sn.SnapshotID, sn.CollectNum
FROM Snapshots2 sn, ClipKeywords2 ck
WHERE sn.EpisodeNum = %s AND
sn.SnapshotNum = ck.SnapshotNum
ORDER BY SnapshotTimeCode, sn.SnapshotNum, KeywordGroup, Keyword"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
self.DBCursor.execute(SQLText, (EpisodeNum, ))
for (kwg, kw, SnapshotTimeCode, SnapshotDuration, SnapshotNum, SnapshotID, collectNum) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
SnapshotID = DBInterface.ProcessDBDataForUTF8Encoding(SnapshotID)
# If we're dealing with an Episode, self.clipNum will be None and we want all clips.
# If we're dealing with a Clip, we only want to deal with THIS clip!
if (self.clipNum == None):
self.snapshotList.append((kwg, kw, SnapshotTimeCode, SnapshotTimeCode + SnapshotDuration, SnapshotNum, SnapshotID, collectNum, EpisodeID, SeriesID))
if not ((SnapshotID, collectNum, True) in self.snapshotFilterList):
self.snapshotFilterList.append((SnapshotID, collectNum, True))
# Create the SNAPSHOT CODING Keyword Placement lines to be displayed. We need them to be in SnapshotTimeCode, SnapshotNum order so colors will be
# distributed properly across bands.
SQLText = """SELECT ck.KeywordGroup, ck.Keyword, sn.SnapshotTimeCode, sn.SnapshotDuration, sn.SnapshotNum, sn.SnapshotID, sn.CollectNum
FROM Snapshots2 sn, SnapshotKeywords2 ck
WHERE sn.EpisodeNum = %s AND
sn.SnapshotNum = ck.SnapshotNum
ORDER BY SnapshotTimeCode, sn.SnapshotNum, KeywordGroup, Keyword"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
self.DBCursor.execute(SQLText, (EpisodeNum, ))
for (kwg, kw, SnapshotTimeCode, SnapshotDuration, SnapshotNum, SnapshotID, collectNum) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
SnapshotID = DBInterface.ProcessDBDataForUTF8Encoding(SnapshotID)
# If we're dealing with an Episode, self.clipNum will be None and we want all clips.
# If we're dealing with a Clip, we only want to deal with THIS clip!
if (self.clipNum == None):
self.snapshotList.append((kwg, kw, SnapshotTimeCode, SnapshotTimeCode + SnapshotDuration, SnapshotNum, SnapshotID, collectNum, EpisodeID, SeriesID))
if not ((SnapshotID, collectNum, True) in self.snapshotFilterList):
self.snapshotFilterList.append((SnapshotID, collectNum, True))
# Sort the Keyword List
self.filteredKeywordList.sort()
def UpdateKeywordVisualization(self):
""" Update the Keyword Visualization following something that could have changed it. """
print "LibraryMap.UpdateKeywordVisualization(): This should NEVER get called!!"
# Clear the Clip List
self.clipList = []
# Clear the Filtered Clip List
self.clipFilterList = []
# Clear the graphic itself
self.graphic.Clear()
# Before we start, make a COPY of the keyword list so we can check for keywords that are no longer
# included on the Map and need to be deleted from the KeywordLists
delList = self.unfilteredKeywordList[:]
# Now let's create the SQL to get all relevant Clip and Clip Keyword records
SQLText = """SELECT ck.KeywordGroup, ck.Keyword, cl.ClipStart, cl.ClipStop, cl.ClipNum, cl.ClipID, cl.CollectNum, ep.EpisodeName
FROM Clips2 cl, ClipKeywords2 ck, Episodes2 ep
WHERE cl.EpisodeNum = %s AND
cl.ClipNum = ck.ClipNum AND
ep.EpisodeNum = cl.EpisodeNum
ORDER BY ClipStart, cl.ClipNum, KeywordGroup, Keyword"""
# Adjust the query for sqlite if needed
SQLText = DBInterface.FixQuery(SQLText)
# Execute the query
self.DBCursor.execute(SQLText, (self.episodeNum, ))
# Iterate through the results ...
for (kwg, kw, clipStart, clipStop, clipNum, clipID, collectNum, episodeName) in self.DBCursor.fetchall():
kwg = DBInterface.ProcessDBDataForUTF8Encoding(kwg)
kw = DBInterface.ProcessDBDataForUTF8Encoding(kw)
clipID = DBInterface.ProcessDBDataForUTF8Encoding(clipID)
episodeName = DBInterface.ProcessDBDataForUTF8Encoding(episodeName)
# If we're dealing with an Episode, self.clipNum will be None and we want all clips.
# If we're dealing with a Clip, we only want to deal with THIS clip!
if (self.clipNum == None) or (clipNum == self.clipNum):
# If a Clip is not found in the clipList ...
if not ((kwg, kw, clipStart, clipStop, clipNum, clipID, collectNum, episodeName, seriesName) in self.clipList):
# ... add it to the clipList ...