-
Notifications
You must be signed in to change notification settings - Fork 17
/
SearchDialog.py
1420 lines (1232 loc) · 67.6 KB
/
SearchDialog.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) 2003 - 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 version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
""" This module implements the Search Interface. """
USE_NOTEBOOK = True
__author__ = 'David Woods <[email protected]>'
# import wxPython
import wx
# import the CustomTreeCtrl
import wx.lib.agw.customtreectrl as CT
# import Python's cPickle module
import cPickle
# import Transana's Database interface
import DBInterface
# import Transana's common Dialogs
import Dialogs
# import Transana's Constants
import TransanaConstants
# import Transana's Globals
import TransanaGlobal
# import Transana's Images
import TransanaImages
# import Python's os module
import os
T_CHECK_ALL = wx.NewId()
T_CHECK_NONE = wx.NewId()
class SearchDialog(wx.Dialog):
""" Dialog Box that implements Transana's Search Interface. """
def __init__(self, searchName=''):
""" Initialize the Search Dialog, passing in the default Search Name. """
# Define the SearchDialog as a resizable wxDialog Box
wx.Dialog.__init__(self, TransanaGlobal.menuWindow, -1, _("Boolean Keyword Search"), wx.DefaultPosition, wx.Size(650, 600),
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
# To look right, the Mac needs the Small Window Variant.
if "__WXMAC__" in wx.PlatformInfo:
self.SetWindowVariant(wx.WINDOW_VARIANT_SMALL)
# Set the lineStarted Indicator to False
self.lineStarted = False
# Set the parensOpen Indicator to False
self.parensOpen = 0
# Let's create an "Undo Stack" for the search terms
self.ClearSearchStack()
self.configName = ''
self.reportType = 15
# Specify the minimum acceptable width and height for this window
self.SetSizeHints(500, 550)
# Define all GUI Elements for the Form
# Create the form's main VERTICAL sizer
mainSizer = wx.BoxSizer(wx.VERTICAL)
# Add Search Name Label
searchNameText = wx.StaticText(self, -1, _('Search Name:'))
mainSizer.Add(searchNameText, 0, wx.TOP | wx.LEFT, 10)
mainSizer.Add((0, 3))
# Add Search Name Text Box
self.searchName = wx.TextCtrl(self, -1)
self.searchName.SetValue(searchName)
mainSizer.Add(self.searchName, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Add Scope Label
scopeText = wx.StaticText(self, -1, _('Items to include:'))
mainSizer.Add(scopeText, 0, wx.LEFT | wx.RIGHT, 10)
mainSizer.Add((0, 3))
# Create a Row Sizer for the "include" checkboxes
includeSizer = wx.BoxSizer(wx.HORIZONTAL)
# Add a spacer
includeSizer.Add((1,1), 1, wx.EXPAND)
if TransanaConstants.proVersion:
# Add a checkbox for including Documents in the Search Results
self.includeDocuments = wx.CheckBox(self, -1, _('Documents'))
# Exclude Documents by default
self.includeDocuments.SetValue(False)
# Bind the Check event
self.includeDocuments.Bind(wx.EVT_CHECKBOX, self.OnBtnClick)
# Add the checkbox to the Include Sizer
includeSizer.Add(self.includeDocuments, 0)
# Add a spacer
includeSizer.Add((1,1), 1, wx.EXPAND)
else:
# Add a checkbox for including Documents in the Search Results
self.includeDocuments = wx.CheckBox(self, -1, _('Documents'))
# Don't Display this checkbox!
self.includeDocuments.Show(False)
# Exclude Documents from the Search
self.includeDocuments.SetValue(False)
# Add a checkbox for including Episodes in the Search Results
self.includeEpisodes = wx.CheckBox(self, -1, _('Episodes'))
# Include Episodes by default
self.includeEpisodes.SetValue(False)
# Bind the Check event
self.includeEpisodes.Bind(wx.EVT_CHECKBOX, self.OnBtnClick)
# Add the checkbox to the Include Sizer
includeSizer.Add(self.includeEpisodes, 0)
# Add a spacer
includeSizer.Add((1,1), 1, wx.EXPAND)
if TransanaConstants.proVersion:
# Add a checkbox for including Quotes in the Search Results
self.includeQuotes = wx.CheckBox(self, -1, _('Quotes'))
# Include Quotes by default
self.includeQuotes.SetValue(True)
# Bind the Check event
self.includeQuotes.Bind(wx.EVT_CHECKBOX, self.OnBtnClick)
# Add the checkbox to the Include Sizer
includeSizer.Add(self.includeQuotes, 0)
# Add a spacer
includeSizer.Add((1,1), 1, wx.EXPAND)
else:
# Add a checkbox for including Quotes in the Search Results
self.includeQuotes = wx.CheckBox(self, -1, _('Quotes'))
# Don't Display this checkbox!
self.includeQuotes.Show(False)
# Exclude Quotes from the Search
self.includeQuotes.SetValue(False)
# Add a checkbox for including Clips in the Search Results
self.includeClips = wx.CheckBox(self, -1, _('Clips'))
# Include Clips by default
self.includeClips.SetValue(True)
# Bind the Check event
self.includeClips.Bind(wx.EVT_CHECKBOX, self.OnBtnClick)
# Add the checkbox to the Include Sizer
includeSizer.Add(self.includeClips, 0)
if TransanaConstants.proVersion:
# Add a spacer
includeSizer.Add((1,1), 1, wx.EXPAND)
# Add a checkbox for including Snapshots in the Search Results
self.includeSnapshots = wx.CheckBox(self, -1, _('Snapshots'))
# Include Snapshots by default
self.includeSnapshots.SetValue(True)
# Bind the Check event
self.includeSnapshots.Bind(wx.EVT_CHECKBOX, self.OnBtnClick)
# Add the checkbox to the Include Sizer
includeSizer.Add(self.includeSnapshots)
else:
# Add a checkbox for including Snapshots in the Search Results
self.includeSnapshots = wx.CheckBox(self, -1, _('Snapshots'))
# Don't Display this checkbox!
self.includeSnapshots.Show(False)
# Exclude Snapshots from the Search
self.includeSnapshots.SetValue(False)
# Add a spacer
includeSizer.Add((1,1), 1, wx.EXPAND)
# Add the Include Sizer on the Main Sizer
mainSizer.Add(includeSizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# experimental NOTEBOOK INTERFACE
if USE_NOTEBOOK:
# Create a Notebook Control to allow different types of Search Information
selectionNotebook = wx.Notebook(self, -1)
# Set the Notebook Background to White (probably prevents a visible anomoly in Arabic!)
selectionNotebook.SetBackgroundColour(wx.WHITE)
# *********************************************************************************************************
# Add a Panel to the Notebook for Text Search information
# panelText = wx.Panel(selectionNotebook, -1)
# panelText.SetBackgroundColour(wx.RED)
# Add a Sizer to the Text Search Panel
# panelTextSizer = wx.BoxSizer(wx.VERTICAL)
# Add the Text Search Sizer to the Collections Panel
# panelText.SetSizer(panelTextSizer)
# Add the Text Search Panel to the Notebook as the initial page
# selectionNotebook.AddPage(panelText, _("Text Search"), True)
# *********************************************************************************************************
# Add a Panel to the Notebook for Collection information
panelCollections = wx.Panel(selectionNotebook, -1)
# panelCollections.SetBackgroundColour(wx.BLUE)
# Add a Sizer to the Collections Panel
panelCollectionsSizer = wx.BoxSizer(wx.VERTICAL)
collToolBar = wx.ToolBar(panelCollections, -1, style=wx.TB_HORIZONTAL | wx.NO_BORDER)
# Toggle Button to indicate if child nodes should follow parent node
self.btnChildFollow = collToolBar.AddCheckLabelTool(-1, "Checkable", TransanaImages.CheckTree.GetBitmap(), shortHelp=_("Change Nested Collections"))
# have this be toggled by default
self.btnChildFollow.Toggle()
# Create the Check All button
self.btnCheckAll = collToolBar.AddTool(T_CHECK_ALL, TransanaImages.Check.GetBitmap(), shortHelpString=_('Check All'))
# Create the Uncheck All button
self.btnCheckNone = collToolBar.AddTool(T_CHECK_NONE, TransanaImages.NoCheck.GetBitmap(), shortHelpString=_('Uncheck All'))
collToolBar.Realize()
panelCollectionsSizer.Add(collToolBar, 0, wx.EXPAND, 0)
self.Bind(wx.EVT_MENU, self.OnCollectionSelectAll, self.btnCheckAll)
self.Bind(wx.EVT_MENU, self.OnCollectionSelectAll, self.btnCheckNone)
# Create a Tree Control with Checkboxes.
# (I experimented with the TR_AUTO_CHECK_CHILD style, but that is not appropriate. Not wanting data
# from a certain collection doesn't necessarily imply we don't want it from the children.
self.ctcCollections = CT.CustomTreeCtrl(panelCollections, agwStyle=wx.TR_DEFAULT_STYLE )
# Set the TreeCtrl's background to white so it looks better
self.ctcCollections.SetBackgroundColour(wx.WHITE)
# Add the TreeCtrl to the Notebook Tab's Sizer
panelCollectionsSizer.Add(self.ctcCollections, 1, wx.EXPAND | wx.ALL, 10)
# Define the TreeCtrl's Images
image_list = wx.ImageList(16, 16, 0, 2)
image_list.Add(TransanaImages.Collection16.GetBitmap())
image_list.Add(TransanaImages.db.GetBitmap())
self.ctcCollections.SetImageList(image_list)
# Add the TreeCtrl's Root Node
self.ctcRoot = self.ctcCollections.AddRoot(_("Collections"))
self.ctcCollections.SetItemImage(self.ctcRoot, 1, wx.TreeItemIcon_Normal)
self.ctcCollections.SetItemImage(self.ctcRoot, 1, wx.TreeItemIcon_Selected)
self.ctcCollections.SetItemImage(self.ctcRoot, 1, wx.TreeItemIcon_Expanded)
self.ctcCollections.SetItemImage(self.ctcRoot, 1, wx.TreeItemIcon_SelectedExpanded)
# Create a Mapping Dictionary for the Collections so we can place Nested Collections quickly
mapDict = {}
# Add the Root Node to the Mapping Dictionary
mapDict[0] = self.ctcRoot
# Sometimes, a Collection appears in the DBInterface list before its Parent Collection has been
# established. When this happens, we need to defer processing of these items until after their
# parent collection has been added. (See DatabaseTreeTab.create_collections_node.)
# Initialize a list to hold those deferred collections here.
deferredItems = []
# Get all the Collections from the Database and iterate through them
for (collNo, collID, parentCollNo) in DBInterface.list_of_all_collections():
# If the Mapping Dictionary has the PARENT Collection ...
# (NOTE: The query's ORDER BY clause means the parent should ALWAYS exist!!)
if mapDict.has_key(parentCollNo):
# Get the Parent Node
parentItem = mapDict[parentCollNo]
# Create a new Checkbox Node for the current item a a child to the Parent Node
item = self.ctcCollections.AppendItem(parentItem, collID, ct_type=CT.TREE_ITEMTYPE_CHECK)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_Normal)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_Selected)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_Expanded)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_SelectedExpanded)
# Check the item
self.ctcCollections.CheckItem(item, True)
# Set the item's PyData to the Collection Number
self.ctcCollections.SetPyData(item, collNo)
# Add the new item to the Mapping Dictionary
mapDict[collNo] = item
# We need to check the items waiting to be processed to see if we've just added the parent
# collection for any of the items in the list. If the list is empty, though, we don't need to bother.
placementMade = (len(deferredItems) > 0)
# We do this in a while loop, as each item from the list that gets added to the tree could be the
# parent of other items in the list.
while placementMade:
# Re-initialize the while loop variable, assuming that no items will be found
placementMade = False
# Now see if any of the deferred items can be added! Loop through the list ...
# We can't just use a for loop, as we delete items from the list as we go!
# So start by defining the list index and the number of items in the list
index = 0
endPoint = len(deferredItems)
# As long as the index is less than the last list item ...
while index < endPoint: # for index in range(len(deferredItems)):
# Get the data from the list item
(dCollNo, dCollID, dParentCollNo) = deferredItems[index]
# See if the parent collection has now been added to the tree and to the map dictionary
if mapDict.has_key(dParentCollNo):
# We can identify the parent node using the map dictionary
parentItem = mapDict[dParentCollNo]
# print "SearchDialog.__init__(): ", dCollNo, dCollID, dParentCollNo, "*** ADDED ***"
# Create a new Checkbox Node for the current item a a child to the Parent Node
item = self.ctcCollections.AppendItem(parentItem, dCollID, ct_type=CT.TREE_ITEMTYPE_CHECK)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_Normal)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_Selected)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_Expanded)
self.ctcCollections.SetItemImage(item, 0, wx.TreeItemIcon_SelectedExpanded)
# Check the item
self.ctcCollections.CheckItem(item, True)
# Set the item's PyData to the Collection Number
self.ctcCollections.SetPyData(item, collNo)
# Add the new node to the map dictionary
mapDict[dCollNo] = item
# We need to indicate to the while loop that we found an entry that could be the parent of other entries
placementMade = True
# We need to remove the item we just added to the tree from the deferred items list
del deferredItems[index]
# If we delete the item from the list, we reduce the End Point by one and DO NOT increase the index
endPoint -= 1
# If the parent collection is NOT in the mapping dictionalry yet ...
else:
# ... just move on to the next item. We can't do anything with this yet.
index += 1
# If the Collection's parent is not yet in the Collection tree or the Map dictionary ...
else:
# print "SearchDialog.__init__(): ", collNo, collID, parentCollNo, "*** DEFERRED ***"
# ... we need to place that collection in the list of items to process later, once the parent Collection
# has been added to the database tree
deferredItems.append((collNo, collID, parentCollNo))
# Expand the tree's Root Node
self.ctcCollections.Expand(self.ctcRoot)
# Define the Item Check event handler.
self.ctcCollections.Bind(CT.EVT_TREE_ITEM_CHECKED, self.OnCollectionsChecked)
# Add the Collections Sizer to the Collections Panel
panelCollections.SetSizer(panelCollectionsSizer)
# Add the Collections Panel to the Notebook as the initial page
selectionNotebook.AddPage(panelCollections, _("Collections"), True)
# Add a Panel to the Notebook for the Keywords information
panelKeywords = wx.Panel(selectionNotebook, -1)
# Add a Sizer to the Keywords Panel
panelKeywordsSizer = wx.BoxSizer(wx.VERTICAL)
# Add Boolean Operators Label
operatorsText = wx.StaticText(panelKeywords, -1, _('Operators:'))
panelKeywordsSizer.Add(operatorsText, 0, wx.LEFT | wx.TOP, 10)
panelKeywordsSizer.Add((0, 3))
# Create a HORIZONTAL sizer for the first row
r1Sizer = wx.BoxSizer(wx.HORIZONTAL)
# Add AND Button
self.btnAnd = wx.Button(panelKeywords, -1, _('AND'), size=wx.Size(50, 24))
r1Sizer.Add(self.btnAnd, 0)
self.btnAnd.Enable(False)
wx.EVT_BUTTON(self, self.btnAnd.GetId(), self.OnBtnClick)
r1Sizer.Add((10, 0))
# Add OR Button
self.btnOr = wx.Button(panelKeywords, -1, _('OR'), size=wx.Size(50, 24))
r1Sizer.Add(self.btnOr, 0)
self.btnOr.Enable(False)
wx.EVT_BUTTON(self, self.btnOr.GetId(), self.OnBtnClick)
r1Sizer.Add((1, 0), 1, wx.EXPAND)
# Add NOT Button
self.btnNot = wx.Button(panelKeywords, -1, _('NOT'), size=wx.Size(50, 24))
r1Sizer.Add(self.btnNot, 0)
wx.EVT_BUTTON(self, self.btnNot.GetId(), self.OnBtnClick)
r1Sizer.Add((1, 0), 1, wx.EXPAND)
# Add Left Parenthesis Button
self.btnLeftParen = wx.Button(panelKeywords, -1, '(', size=wx.Size(30, 24))
r1Sizer.Add(self.btnLeftParen, 0)
wx.EVT_BUTTON(self, self.btnLeftParen.GetId(), self.OnBtnClick)
r1Sizer.Add((10, 0))
# Add Right Parenthesis Button
self.btnRightParen = wx.Button(panelKeywords, -1, ')', size=wx.Size(30, 24))
r1Sizer.Add(self.btnRightParen, 0)
self.btnRightParen.Enable(False)
wx.EVT_BUTTON(self, self.btnRightParen.GetId(), self.OnBtnClick)
r1Sizer.Add((1, 0), 1, wx.EXPAND)
# Add "Undo" Button
# Get the image for Undo
bmp = TransanaImages.Undo16.GetBitmap()
self.btnUndo = wx.BitmapButton(panelKeywords, -1, bmp, size=wx.Size(30, 24))
self.btnUndo.SetToolTip(wx.ToolTip(_('Undo')))
r1Sizer.Add(self.btnUndo, 0)
wx.EVT_BUTTON(self, self.btnUndo.GetId(), self.OnBtnClick)
r1Sizer.Add((10, 0))
# Add Reset Button
self.btnReset = wx.Button(panelKeywords, -1, _('Reset'), size=wx.Size(80, 24))
r1Sizer.Add(self.btnReset, 0)
wx.EVT_BUTTON(self, self.btnReset.GetId(), self.OnBtnClick)
panelKeywordsSizer.Add(r1Sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
r2Sizer = wx.BoxSizer(wx.HORIZONTAL)
# Add Keyword Groups Label
keywordGroupsText = wx.StaticText(panelKeywords, -1, _('Keyword Groups:'))
r2Sizer.Add(keywordGroupsText, 1, wx.EXPAND)
r2Sizer.Add((10, 0))
# Add Keywords Label
keywordsText = wx.StaticText(panelKeywords, -1, _('Keywords:'))
r2Sizer.Add(keywordsText, 1, wx.EXPAND)
panelKeywordsSizer.Add(r2Sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
panelKeywordsSizer.Add((0, 3))
r3Sizer = wx.BoxSizer(wx.HORIZONTAL)
# Add Keyword Groups
self.kw_group_lb = wx.ListBox(panelKeywords, -1, wx.DefaultPosition, wx.DefaultSize, [])
r3Sizer.Add(self.kw_group_lb, 1, wx.EXPAND)
# Define the "Keyword Group Select" behavior
wx.EVT_LISTBOX(self, self.kw_group_lb.GetId(), self.OnKeywordGroupSelect)
r3Sizer.Add((10, 0))
# Add Keywords
self.kw_lb = wx.ListBox(panelKeywords, -1, wx.DefaultPosition, wx.DefaultSize, [])
r3Sizer.Add(self.kw_lb, 1, wx.EXPAND)
# Define the "Keyword Select" behavior
wx.EVT_LISTBOX(self, self.kw_lb.GetId(), self.OnKeywordSelect)
# Double-clicking a Keyword is equivalent to selecting it and pressing the "Add Keyword to Query" button
wx.EVT_LISTBOX_DCLICK(self, self.kw_lb.GetId(), self.OnBtnClick)
panelKeywordsSizer.Add(r3Sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Add "Add Keyword to Query" Button
self.btnAdd = wx.Button(panelKeywords, -1, _('Add Keyword to Query'), size=wx.Size(240, 24))
panelKeywordsSizer.Add(self.btnAdd, 0, wx.ALIGN_CENTER | wx.BOTTOM, 10)
wx.EVT_BUTTON(self, self.btnAdd.GetId(), self.OnBtnClick)
# Add Search Query Label
searchQueryText = wx.StaticText(panelKeywords, -1, _('Search Query:'))
panelKeywordsSizer.Add(searchQueryText, 0, wx.LEFT | wx.RIGHT, 10)
panelKeywordsSizer.Add((0, 3))
# Add Search Query Text Box
# The Search Query is Read-Only
self.searchQuery = wx.TextCtrl(panelKeywords, -1, size = wx.Size(200, 120), style=wx.TE_MULTILINE | wx.TE_READONLY)
panelKeywordsSizer.Add(self.searchQuery, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Add the Keyword Sizer to the Keyword Panel
panelKeywords.SetSizer(panelKeywordsSizer)
# Add the Keywords Panel to tne Notebook and select it
selectionNotebook.AddPage(panelKeywords, _("Keywords"), True)
# Add the Notebook to the form's Main Sizer
mainSizer.Add(selectionNotebook, 5, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# *****************************************************************************************************
# Set the Text Search Panel to AutoLayout
# panelText.SetAutoLayout(True)
# Lay Out the Text Search Panel
# panelText.Layout()
# *****************************************************************************************************
# Set the Collection Panel to AutoLayout
panelCollections.SetAutoLayout(True)
# Lay Out the Collections Panel
panelCollections.Layout()
# Set the Keyword Panel to AutoLayout
panelKeywords.SetAutoLayout(True)
# Lay Out the Keyword Panel
panelKeywords.Layout()
# TRADITIONAL Keywords Only Interface
else:
# Add Boolean Operators Label
operatorsText = wx.StaticText(self, -1, _('Operators:'))
mainSizer.Add(operatorsText, 0, wx.LEFT, 10)
mainSizer.Add((0, 3))
# Create a HORIZONTAL sizer for the first row
r1Sizer = wx.BoxSizer(wx.HORIZONTAL)
# Add AND Button
self.btnAnd = wx.Button(self, -1, _('AND'), size=wx.Size(50, 24))
r1Sizer.Add(self.btnAnd, 0)
self.btnAnd.Enable(False)
wx.EVT_BUTTON(self, self.btnAnd.GetId(), self.OnBtnClick)
r1Sizer.Add((10, 0))
# Add OR Button
self.btnOr = wx.Button(self, -1, _('OR'), size=wx.Size(50, 24))
r1Sizer.Add(self.btnOr, 0)
self.btnOr.Enable(False)
wx.EVT_BUTTON(self, self.btnOr.GetId(), self.OnBtnClick)
r1Sizer.Add((1, 0), 1, wx.EXPAND)
# Add NOT Button
self.btnNot = wx.Button(self, -1, _('NOT'), size=wx.Size(50, 24))
r1Sizer.Add(self.btnNot, 0)
wx.EVT_BUTTON(self, self.btnNot.GetId(), self.OnBtnClick)
r1Sizer.Add((1, 0), 1, wx.EXPAND)
# Add Left Parenthesis Button
self.btnLeftParen = wx.Button(self, -1, '(', size=wx.Size(30, 24))
r1Sizer.Add(self.btnLeftParen, 0)
wx.EVT_BUTTON(self, self.btnLeftParen.GetId(), self.OnBtnClick)
r1Sizer.Add((10, 0))
# Add Right Parenthesis Button
self.btnRightParen = wx.Button(self, -1, ')', size=wx.Size(30, 24))
r1Sizer.Add(self.btnRightParen, 0)
self.btnRightParen.Enable(False)
wx.EVT_BUTTON(self, self.btnRightParen.GetId(), self.OnBtnClick)
r1Sizer.Add((1, 0), 1, wx.EXPAND)
# Add "Undo" Button
# Get the image for Undo
bmp = TransanaImages.Undo16.GetBitmap()
self.btnUndo = wx.BitmapButton(self, -1, bmp, size=wx.Size(30, 24))
self.btnUndo.SetToolTip(wx.ToolTip(_('Undo')))
r1Sizer.Add(self.btnUndo, 0)
wx.EVT_BUTTON(self, self.btnUndo.GetId(), self.OnBtnClick)
r1Sizer.Add((10, 0))
# Add Reset Button
self.btnReset = wx.Button(self, -1, _('Reset'), size=wx.Size(80, 24))
r1Sizer.Add(self.btnReset, 0)
wx.EVT_BUTTON(self, self.btnReset.GetId(), self.OnBtnClick)
mainSizer.Add(r1Sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
r2Sizer = wx.BoxSizer(wx.HORIZONTAL)
# Add Keyword Groups Label
keywordGroupsText = wx.StaticText(self, -1, _('Keyword Groups:'))
r2Sizer.Add(keywordGroupsText, 1, wx.EXPAND)
r2Sizer.Add((10, 0))
# Add Keywords Label
keywordsText = wx.StaticText(self, -1, _('Keywords:'))
r2Sizer.Add(keywordsText, 1, wx.EXPAND)
mainSizer.Add(r2Sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
mainSizer.Add((0, 3))
r3Sizer = wx.BoxSizer(wx.HORIZONTAL)
# Add Keyword Groups
self.kw_group_lb = wx.ListBox(self, -1, wx.DefaultPosition, wx.DefaultSize, [])
r3Sizer.Add(self.kw_group_lb, 1, wx.EXPAND)
# Define the "Keyword Group Select" behavior
wx.EVT_LISTBOX(self, self.kw_group_lb.GetId(), self.OnKeywordGroupSelect)
r3Sizer.Add((10, 0))
# Add Keywords
self.kw_lb = wx.ListBox(self, -1, wx.DefaultPosition, wx.DefaultSize, [])
r3Sizer.Add(self.kw_lb, 1, wx.EXPAND)
# Define the "Keyword Select" behavior
wx.EVT_LISTBOX(self, self.kw_lb.GetId(), self.OnKeywordSelect)
# Double-clicking a Keyword is equivalent to selecting it and pressing the "Add Keyword to Query" button
wx.EVT_LISTBOX_DCLICK(self, self.kw_lb.GetId(), self.OnBtnClick)
mainSizer.Add(r3Sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Add "Add Keyword to Query" Button
self.btnAdd = wx.Button(self, -1, _('Add Keyword to Query'), size=wx.Size(240, 24))
mainSizer.Add(self.btnAdd, 0, wx.ALIGN_CENTER | wx.BOTTOM, 10)
wx.EVT_BUTTON(self, self.btnAdd.GetId(), self.OnBtnClick)
# Add Search Query Label
searchQueryText = wx.StaticText(self, -1, _('Search Query:'))
mainSizer.Add(searchQueryText, 0, wx.LEFT | wx.RIGHT, 10)
mainSizer.Add((0, 3))
# Add Search Query Text Box
# The Search Query is Read-Only
self.searchQuery = wx.TextCtrl(self, -1, size = wx.Size(200, 120), style=wx.TE_MULTILINE | wx.TE_READONLY)
mainSizer.Add(self.searchQuery, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Create a Row sizer for the buttons at the bottom of the form
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
# Add the File Open button
# Create the File Open button
self.btnFileOpen = wx.BitmapButton(self, -1, TransanaImages.ArtProv_FILEOPEN.GetBitmap(), size = wx.Size(32, 24))
self.btnFileOpen.SetToolTip(wx.ToolTip(_('Load a Search')))
btnSizer.Add(self.btnFileOpen, 0)
self.btnFileOpen.Bind(wx.EVT_BUTTON, self.OnFileOpen)
btnSizer.Add((10, 0))
# Add the File Save button
# Create the File Save button
self.btnFileSave = wx.BitmapButton(self, -1, TransanaImages.ArtProv_FILESAVE.GetBitmap(), size = wx.Size(32, 24))
self.btnFileSave.SetToolTip(wx.ToolTip(_('Save a Search')))
btnSizer.Add(self.btnFileSave, 0)
self.btnFileSave.Bind(wx.EVT_BUTTON, self.OnFileSave)
self.btnFileSave.Enable(False)
btnSizer.Add((10, 0))
# Add the File Delete button
# Create the File Delete button
self.btnFileDelete = wx.BitmapButton(self, -1, TransanaImages.ArtProv_DELETE.GetBitmap(), size = wx.Size(32, 24))
self.btnFileDelete.SetToolTip(wx.ToolTip(_('Delete a Saved Search')))
btnSizer.Add(self.btnFileDelete, 0)
self.btnFileDelete.Bind(wx.EVT_BUTTON, self.OnFileDelete)
btnSizer.Add((1, 0), 1, wx.EXPAND)
# Add "Search" Button
self.btnSearch = wx.Button(self, -1, _('Search'), size = wx.Size(80, 24))
btnSizer.Add(self.btnSearch, 0)
self.btnSearch.Enable(False)
wx.EVT_BUTTON(self, self.btnSearch.GetId(), self.OnBtnClick)
btnSizer.Add((10, 0))
# Add "Cancel" Button
self.btnCancel = wx.Button(self, -1, _('Cancel'), size = wx.Size(80, 24))
btnSizer.Add(self.btnCancel, 0)
wx.EVT_BUTTON(self, self.btnCancel.GetId(), self.OnBtnClick)
btnSizer.Add((10, 0))
# Add "Help" Button
self.btnHelp = wx.Button(self, -1, _('Help'), size = wx.Size(80, 24))
btnSizer.Add(self.btnHelp, 0)
wx.EVT_BUTTON(self, self.btnHelp.GetId(), self.OnBtnClick)
mainSizer.Add(btnSizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Highlight the default Search name and set the focus to the Search Name.
# (This is done automatically on Windows, but needs to be done explicitly on the Mac.)
self.searchName.SetSelection(-1, -1)
# put the cursor firmly in the Search Name field
self.searchName.SetFocus()
self.SetSizer(mainSizer)
# Have the form handle layout changes automatically
self.SetAutoLayout(True)
# Lay out the form
self.Layout()
# Center the dialog on screen
TransanaGlobal.CenterOnPrimary(self)
# Get the Keyword Groups from the Database Interface
self.kw_groups = DBInterface.list_of_keyword_groups()
for kwg in self.kw_groups:
self.kw_group_lb.Append(kwg)
# Select the first item in the list (required for Mac)
if len(self.kw_groups) > 0:
self.kw_group_lb.SetSelection(0)
# If there are defined Keyword Groups, load the Keywords for the first Group in the list
if len(self.kw_groups) > 0:
self.kw_list = DBInterface.list_of_keywords_by_group(self.kw_groups[0])
else:
self.kw_list = []
for kw in self.kw_list:
self.kw_lb.Append(kw)
# Select the first item in the list (required for Mac)
if len(self.kw_list) > 0:
self.kw_lb.SetSelection(0)
def OnCollectionsChecked(self, event):
""" Event Handler for when a Collection is checked un-checked """
# Get the item that has been checked/unchecked
sel = event.GetItem()
# Make sure that item is expanded in the tree, so the user will see that nested collections have NOT been checked as well.
self.ctcCollections.Expand(sel)
# if the toggle is set so that check changes should be shared with children ...
if self.btnChildFollow.IsToggled():
# ... determine whether we're enabling or disabling
enable = self.ctcCollections.IsItemChecked(sel)
# ... and apply the setting to the children
self.ctcCollections.CheckChilds(sel, enable)
def EnableCollections(self, collNode, enable):
""" A recursive method for enabling or disabling all Collections in the Collections Tree """
# Get the First Child record
(childNode, cookieItem) = self.ctcCollections.GetFirstChild(collNode)
# While there are valid Child records ...
while childNode.IsOk():
# ... get the Collection Number out of the PyData
collNum = self.ctcCollections.GetPyData(childNode)
# Set the node's Checked status
self.ctcCollections.CheckItem(childNode, enable)
# If the Node has children ...
if self.ctcCollections.HasChildren(childNode):
# ... recursively call this method to set the Enabled status of children
self.EnableCollections(childNode, enable)
# If this node is not the LAST child ...
if childNode != self.ctcCollections.GetLastChild(collNode):
# ... then get the next child
(childNode, cookieItem) = self.ctcCollections.GetNextChild(collNode, cookieItem)
# if we're at the last child ...
else:
# ... we can quit
break
def OnCollectionSelectAll(self, event):
""" An event handler for the Check All Collections and Uncheck All Collections Toolbar Buttons """
# If we're Checking ALL ...
if event.GetId() == self.btnCheckAll.GetId():
# ... set Enable to True
enable = True
# If we're UnChecking ALL ...
elif event.GetId() == self.btnCheckNone.GetId():
# ... set Enable to False
enable = False
# Call the recursive Method for enabling/disabling all Collections
self.EnableCollections(self.ctcRoot, enable)
def OnBtnClick(self, event):
""" This method handles all Button Clicks for the Search Dialog. """
# "AND" Button
if event.GetId() == self.btnAnd.GetId():
# Add the appropriate text to the Search Query
self.searchQuery.AppendText(' AND\n')
# Enable the "Add" button
self.btnAdd.Enable(True)
# Disable the "And" button
self.btnAnd.Enable(False)
# Disable the "Or" button
self.btnOr.Enable(False)
# Enable the "Not" button
self.btnNot.Enable(True)
# Enable the "(" (Left Paren) button
self.btnLeftParen.Enable(True)
# Disable the ")" (Right Paren) button
self.btnRightParen.Enable(False)
# Disable the "Search" button
self.btnSearch.Enable(False)
self.btnFileSave.Enable(False)
# Add to the Search Stack
self.SaveSearchStack()
# "OR" Button
elif event.GetId() == self.btnOr.GetId():
# Add the appropriate text to the Search Query
self.searchQuery.AppendText(' OR\n')
# Enable the "Add" button
self.btnAdd.Enable(True)
# Disable the "And" button
self.btnAnd.Enable(False)
# Disable the "Or" button
self.btnOr.Enable(False)
# Enable the "Not" button
self.btnNot.Enable(True)
# Enable the "(" (Left Paren) button
self.btnLeftParen.Enable(True)
# Disable the ")" (Right Paren) button
self.btnRightParen.Enable(False)
# Disable the "Search" button
self.btnSearch.Enable(False)
self.btnFileSave.Enable(False)
# Add to the Search Stack
self.SaveSearchStack()
# "NOT" Button
elif event.GetId() == self.btnNot.GetId():
# Add the appropriate text to the Search Query
self.searchQuery.AppendText('NOT ')
# Disable the "Not" button
self.btnNot.Enable(False)
# Disable the "(" (Left Paren) button
self.btnLeftParen.Enable(False)
# Disable the ")" (Right Paren) button
self.btnRightParen.Enable(False)
# Disable the "Search" button
self.btnSearch.Enable(False)
self.btnFileSave.Enable(False)
# Add to the Search Stack
self.SaveSearchStack()
# "(" (Open Paren) Button
elif event.GetId() == self.btnLeftParen.GetId():
# Add the appropriate text to the Search Query
self.searchQuery.AppendText('(')
self.parensOpen += 1
# Disable the "And" button
self.btnAnd.Enable(False)
# Disable the "Or" button
self.btnOr.Enable(False)
# Disable the "Search" button
self.btnSearch.Enable(False)
self.btnFileSave.Enable(False)
# Add to the Search Stack
self.SaveSearchStack()
# ")" (Close Paren) Button
elif event.GetId() == self.btnRightParen.GetId():
# Add the appropriate text to the Search Query
self.searchQuery.AppendText(')')
self.parensOpen -= 1
if self.parensOpen == 0:
# Disable the ")" (Right Paren) button
self.btnRightParen.Enable(False)
# Enable the "Search" button
self.btnSearch.Enable(True)
self.btnFileSave.Enable(True)
# Add to the Search Stack
self.SaveSearchStack()
# "Reset" Button
elif event.GetId() == self.btnReset.GetId():
# Clear the Search Query Terms
self.searchQuery.Clear()
# No Line has been Started
self.lineStarted = False
# You are starting over, so enable "Add"
self.btnAdd.Enable(True)
# You can't add a Boolean Operator
self.btnAnd.Enable(False)
self.btnOr.Enable(False)
# You can add a NOT Operator
self.btnNot.Enable(True)
# You can't perform a Search yet
self.btnSearch.Enable(False)
self.btnFileSave.Enable(False)
# No Parens are Open
self.parensOpen = 0
# Reset the Search Stack
self.ClearSearchStack()
# Reset the Parens Buttons
self.btnLeftParen.Enable(True)
self.btnRightParen.Enable(False)
# Deselect the current Keyword (We don't need to deselect the Keyword Group)
try:
self.kw_lb.SetSelection(self.kw_lb.GetSelection(), False)
except:
pass
# "Add Keyword to Query" Button or (Keyword Double-Clicked when Add Button is enabled)
elif (event.GetId() == self.btnAdd.GetId()) or \
((event.GetId() == self.kw_lb.GetId()) and (self.btnAdd.IsEnabled())):
# Get the keyword group and keyword values
keywordGroup = self.kw_group_lb.GetStringSelection()
keyword = self.kw_lb.GetStringSelection()
# A Keyword MUST be selected!
if keyword <> '':
# Add the appropriate text to the Search Query
self.searchQuery.AppendText(keywordGroup + ':' + keyword)
# Disable the "Add" button
self.btnAdd.Enable(False)
# Enable the "And" button
self.btnAnd.Enable(True)
# Enable the "Or" button
self.btnOr.Enable(True)
# Disable the "Not" button
self.btnNot.Enable(False)
# See if there are still parens that need to be closed
if self.parensOpen > 0:
# If there are parens that need to be closed, enable the ")" (Right Paren) button
self.btnRightParen.Enable(True)
else:
# If there are no parens that need to be closed, enable the "Search" button
self.btnSearch.Enable(True)
# and the save button
self.btnFileSave.Enable(True)
# Disable the "(" (Left Paren) button
self.btnLeftParen.Enable(False)
# Add to the Search Stack
self.SaveSearchStack()
# "Undo" Button
elif event.GetId() == self.btnUndo.GetId():
# If the search stack has data ...
if len(self.searchStack) > 1:
# ... drop the last value added
self.searchStack.pop()
# Restore the Search Dialog based on the last data element in the Search Stack
# Restore the Search Query
self.searchQuery.SetValue(self.searchStack[-1][0])
# Restore the Search Button
self.btnSearch.Enable(self.searchStack[-1][1])
# Restore the Save Button
self.btnFileSave.Enable(self.searchStack[-1][1])
# Restore the Add Button
self.btnAdd.Enable(self.searchStack[-1][2])
# Restore the And button
self.btnAnd.Enable(self.searchStack[-1][3])
# Restore the Or button
self.btnOr.Enable(self.searchStack[-1][3])
# Restore the Not button
self.btnNot.Enable(self.searchStack[-1][4])
# Restore the Left Parens button
self.btnLeftParen.Enable(self.searchStack[-1][5])
# Restore the Right Parens button
self.btnRightParen.Enable(self.searchStack[-1][6])
# Restore the counter of open paren pairs
self.parensOpen = self.searchStack[-1][7]
# "Search" Button
elif event.GetId() == self.btnSearch.GetId():
# Close with a wxID_OK result
self.EndModal(wx.ID_OK)
# "Cancel" Button
elif event.GetId() == self.btnCancel.GetId():
# Close with a wxID_CANCEL result
self.EndModal(wx.ID_CANCEL)
# "Help" Button
elif event.GetId() == self.btnHelp.GetId():
if TransanaGlobal.menuWindow != None:
TransanaGlobal.menuWindow.ControlObject.Help('Search')
# The "include" checkboxes
elif event.GetId() in [self.includeDocuments.GetId(), self.includeEpisodes.GetId(),
self.includeQuotes.GetId(), self.includeClips.GetId(), self.includeSnapshots.GetId()]:
# If we are CHECKING one of the boxes ...
if event.IsChecked():
# Detect the current status of the query to see if it's a valid search. If so ...
if (len(self.searchQuery.GetValue()) > 0) and (self.parensOpen == 0) and \
not (self.searchQuery.GetValue().rstrip().upper()[-4:] == ' AND') and \
not (self.searchQuery.GetValue().rstrip().upper()[-3:] == ' OR') and \
not (self.searchQuery.GetValue().rstrip().upper()[-4:] in ['\nNOT', '(NOT']):
# Enable the Search Button
self.btnSearch.Enable(True)
# and the save button
self.btnFileSave.Enable(True)
# At least one of the "Include" checkboxes MUST be checked for the Search to be valid
if not (self.includeDocuments.IsChecked() or self.includeEpisodes.IsChecked() or
self.includeQuotes.IsChecked() or self.includeClips.IsChecked() or self.includeSnapshots.IsChecked()):
# If no results are included, disable the "Search" button
self.btnSearch.Enable(False)
# and the save button
self.btnFileSave.Enable(False)
def OnKeywordGroupSelect(self, event):
""" Implement Interface Changes needed when a Keyword Group is selected. """
# Get the List of Keywords for the selected Keyword Group from the Database Interface
self.kw_list = DBInterface.list_of_keywords_by_group(event.GetString())
# Reset the list of Keywords on screen
self.kw_lb.Set(self.kw_list)
def OnKeywordSelect(self, event):
""" Implement Interface Changes needed when a Keyword is selected. """
# Do nothing (!)
pass
def ClearSearchStack(self):
""" Initialize the Search Undo Stack """
# Set the initial values for the search stack:
# No Search Query
# Search and Save buttons disabled
# Add Keyword button enabled
# And and Or buttons disabled
# Not button enabled
# Left paren button enabled
# Right parent button disabled
# 0 paren pairs open
self.searchStack = [('', False, True, False, True, True, False, 0)]
def SaveSearchStack(self):
""" Add the current state of the dialog to the Search Undo Stack """
# Save the state of all elements on the Search Dialog that need to be reset:
# Current Search Query
# Search and Save buttons states
# Add Keyword button state
# And and Or buttons states
# Not button state
# Left paren button state