weft-qda 0.9.6 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/lib/weft.rb +16 -1
  2. data/lib/weft/WEFT-VERSION-STRING.rb +1 -1
  3. data/lib/weft/application.rb +17 -74
  4. data/lib/weft/backend.rb +6 -32
  5. data/lib/weft/backend/sqlite.rb +222 -164
  6. data/lib/weft/backend/sqlite/category_tree.rb +52 -48
  7. data/lib/weft/backend/sqlite/database.rb +57 -0
  8. data/lib/weft/backend/sqlite/upgradeable.rb +7 -0
  9. data/lib/weft/broadcaster.rb +90 -0
  10. data/lib/weft/category.rb +139 -47
  11. data/lib/weft/codereview.rb +160 -0
  12. data/lib/weft/coding.rb +74 -23
  13. data/lib/weft/document.rb +23 -10
  14. data/lib/weft/exceptions.rb +10 -0
  15. data/lib/weft/filters.rb +47 -224
  16. data/lib/weft/filters/indexers.rb +137 -0
  17. data/lib/weft/filters/input.rb +118 -0
  18. data/lib/weft/filters/output.rb +101 -0
  19. data/lib/weft/filters/templates.rb +80 -0
  20. data/lib/weft/filters/win32backtick.rb +246 -0
  21. data/lib/weft/query.rb +169 -0
  22. data/lib/weft/wxgui.rb +349 -294
  23. data/lib/weft/wxgui/constants.rb +43 -0
  24. data/lib/weft/wxgui/controls.rb +6 -0
  25. data/lib/weft/wxgui/controls/category_dropdown.rb +192 -0
  26. data/lib/weft/wxgui/controls/category_tree.rb +314 -0
  27. data/lib/weft/wxgui/controls/document_list.rb +97 -0
  28. data/lib/weft/wxgui/controls/multitype_control.rb +37 -0
  29. data/lib/weft/wxgui/{inspectors → controls}/textcontrols.rb +235 -64
  30. data/lib/weft/wxgui/dialogs.rb +144 -41
  31. data/lib/weft/wxgui/error_handler.rb +116 -36
  32. data/lib/weft/wxgui/exceptions.rb +7 -0
  33. data/lib/weft/wxgui/inspectors.rb +61 -208
  34. data/lib/weft/wxgui/inspectors/category.rb +19 -16
  35. data/lib/weft/wxgui/inspectors/codereview.rb +90 -132
  36. data/lib/weft/wxgui/inspectors/document.rb +12 -8
  37. data/lib/weft/wxgui/inspectors/imagedocument.rb +56 -56
  38. data/lib/weft/wxgui/inspectors/query.rb +284 -0
  39. data/lib/weft/wxgui/inspectors/script.rb +147 -23
  40. data/lib/weft/wxgui/lang/en.rb +69 -0
  41. data/lib/weft/wxgui/sidebar.rb +90 -432
  42. data/lib/weft/wxgui/utilities.rb +70 -91
  43. data/lib/weft/wxgui/workarea.rb +150 -43
  44. data/share/icons/category.ico +0 -0
  45. data/share/icons/category.xpm +109 -0
  46. data/share/icons/codereview.ico +0 -0
  47. data/share/icons/codereview.xpm +54 -0
  48. data/share/icons/d_and_c.xpm +126 -0
  49. data/share/icons/document.ico +0 -0
  50. data/share/icons/document.xpm +70 -0
  51. data/share/icons/project.ico +0 -0
  52. data/share/icons/query.ico +0 -0
  53. data/share/icons/query.xpm +56 -0
  54. data/{lib/weft/wxgui → share/icons}/search.xpm +0 -0
  55. data/share/icons/weft.ico +0 -0
  56. data/share/icons/weft.xpm +62 -0
  57. data/share/icons/weft16.ico +0 -0
  58. data/share/icons/weft32.ico +0 -0
  59. data/share/templates/category_plain.html +18 -0
  60. data/share/templates/codereview_plain.html +18 -0
  61. data/share/templates/document_plain.html +13 -0
  62. data/share/templates/document_plain.txt +7 -0
  63. data/test/001-document.rb +55 -36
  64. data/test/002-category.rb +81 -6
  65. data/test/003-code.rb +8 -4
  66. data/test/004-application.rb +13 -34
  67. data/test/005-query_review.rb +139 -0
  68. data/test/006-filters.rb +54 -42
  69. data/test/007-output_filters.rb +113 -0
  70. data/test/009a-backend_sqlite_basic.rb +95 -24
  71. data/test/009b-backend_sqlite_complex.rb +43 -62
  72. data/test/009c_backend_sqlite_bench.rb +5 -10
  73. data/test/053-doc_inspector.rb +46 -0
  74. data/test/055-query_window.rb +50 -0
  75. data/test/all-tests.rb +1 -0
  76. data/test/test-common.rb +19 -0
  77. data/test/testdata/empty.qdp +0 -0
  78. data/test/testdata/simple with space.pdf +0 -0
  79. data/test/testdata/simple.pdf +0 -0
  80. data/weft-qda.rb +40 -7
  81. metadata +74 -14
  82. data/lib/weft/wxgui/category.xpm +0 -26
  83. data/lib/weft/wxgui/document.xpm +0 -25
  84. data/lib/weft/wxgui/inspectors/search.rb +0 -265
  85. data/lib/weft/wxgui/mondrian.xpm +0 -44
  86. data/lib/weft/wxgui/weft16.xpm +0 -31
@@ -0,0 +1,97 @@
1
+ module QDA::GUI
2
+ class DocumentList < Wx::ListBox
3
+ include ListLikeItemData
4
+ include QDA::Subscriber
5
+ attr_reader :client
6
+
7
+ def initialize(weft_client, *args)
8
+ @client = weft_client
9
+ super(*args)
10
+ evt_left_dclick() { | event | on_double_click(event) }
11
+ evt_key_down() { | e | on_key_down(e) }
12
+ evt_listbox( self.get_id ) { | e | on_item_selected(e) }
13
+ subscribe(@client, :document_added, :document_deleted, :document_changed)
14
+
15
+ # evt_context_menu() { | event | on_context_menu(event) }
16
+ # @menu = Wx::Menu.new()
17
+ # @menu.append(1234, 'Foo')
18
+ # @menu.append(1235, 'Bar')
19
+ end
20
+
21
+ def app()
22
+ @client.app
23
+ end
24
+ # add the document to the end of the list.
25
+ def append_item(doc)
26
+ push_item_data(doc)
27
+ append(doc.title)
28
+ end
29
+
30
+ def remove_item(item)
31
+ if i = value_to_ident(item)
32
+ data.delete_at(i)
33
+ delete(i)
34
+ end
35
+ end
36
+
37
+ def update_item(doc)
38
+ if i = value_to_ident(doc)
39
+ set_item_data(i, doc)
40
+ set_string( value_to_ident(doc), doc.title )
41
+ end
42
+ end
43
+
44
+ def get_selection_data()
45
+ get_item_data( get_selection )
46
+ end
47
+
48
+ def delete_selection()
49
+ # Hand back to the client to process, including confirmation checks
50
+ client.on_delete_document()
51
+ end
52
+
53
+ def on_item_selected(e)
54
+ @client.current_document = selected_document
55
+ e.skip()
56
+ end
57
+
58
+ def on_context_menu(e)
59
+ popup_menu_xy(@menu, e.get_x, e.get_y)
60
+ end
61
+
62
+ def on_double_click(e)
63
+ if string_selection.empty?
64
+ @client.import_document()
65
+ else
66
+ @client.on_document_open( selected_document() )
67
+ end
68
+ end
69
+
70
+ def selected_document()
71
+ sel = get_selection()
72
+ return nil unless sel and sel != -1
73
+ get_item_data( sel )
74
+ end
75
+
76
+ def receive_document_deleted(doc)
77
+ remove_item(doc)
78
+ end
79
+
80
+ def receive_document_changed(doc)
81
+ update_item(doc)
82
+ end
83
+
84
+ def receive_document_added(doc)
85
+ append_item(doc)
86
+ end
87
+
88
+ def on_key_down(evt)
89
+ case evt.key_code()
90
+ when 127 # DEL
91
+ delete_selection()
92
+ else
93
+ evt.skip()
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,37 @@
1
+ module QDA::GUI
2
+ # A control which has multiple different forms which can be shown or hidden
3
+ class MultiTypedControl < Wx::BoxSizer
4
+ attr_reader :controls, :visible
5
+
6
+ #
7
+ def initialize( first_item )
8
+ super(Wx::HORIZONTAL)
9
+ @controls = [ first_item ]
10
+ @visible = 0
11
+ add(visible_item, 1, Wx::ALL)
12
+ end
13
+
14
+ def add_control(control)
15
+ controls.push(control)
16
+ control.hide()
17
+ end
18
+
19
+ def visible_item()
20
+ controls[visible]
21
+ end
22
+
23
+ def show(i)
24
+ return if i == visible
25
+ if i < 0 or i >= controls.length
26
+ raise ArgumentError, "Bad Index"
27
+ end
28
+ visible_item.hide()
29
+ remove(visible_item)
30
+ @visible = i
31
+ add(visible_item, 1, Wx::ADJUST_MINSIZE|Wx::ALL)
32
+ visible_item.show()
33
+ layout()
34
+ end
35
+ end
36
+
37
+ end
@@ -3,41 +3,57 @@ module QDA::GUI
3
3
  # source text - addresses issues with the cross-platform
4
4
  # representation of newlines in multiline text controls.
5
5
  class TrueSelectionTextCtrl < Wx::TextCtrl
6
- # because of the varying x-platform representation of newlines,
7
- # we need a variable correction factor.
8
- if Wx::RUBY_PLATFORM == 'WXMSW'
6
+
7
+ if Wx::RUBY_PLATFORM == 'WXMSW' # Microsoft Windows
9
8
  NEWLINE_CORRECTION_FACTOR = 1
10
- else
9
+
10
+ def true_point(point)
11
+ point - get_range(0, point).count("\n")
12
+ end
13
+
14
+ # returns the start and end of the selection as indexes within
15
+ # the underlying string. This is handled differently on each platform,
16
+ # but the end result is the same.
17
+ def true_selection()
18
+ # On windows, we have to correct for the way WxWidgets returns
19
+ # newlines in TextCtrls as two characters.
20
+ lines = get_range(0, get_insertion_point()).count("\n")
21
+ [ get_insertion_point() - lines,
22
+ get_insertion_point() + get_string_selection().length - lines ]
23
+ end
24
+ else
25
+ # GTK and Mac OS X
11
26
  NEWLINE_CORRECTION_FACTOR = 0
27
+ # a no-op
28
+ def true_point(point)
29
+ point
30
+ end
31
+
32
+ def true_selection()
33
+ # on Linux and Mac OS X, the representation of newlines is not a
34
+ # problem (a single \n character in both the textctrl string and the
35
+ # underlying string), but the fact that get_selection() is missing
36
+ # from the wxruby is a problem. Additionally, WxGTK (not WxMac) returns
37
+ # different values for get_insertion_point when a selection has been
38
+ # depending on whether the selection was made left-to-right or
39
+ # right-to-left. So we do a bit of guessing.
40
+ str = get_string_selection()
41
+ val = get_value()
42
+ ins = get_insertion_point()
43
+ if str == val[ ins, str.length ] # should be always true on WxMac
44
+ return [ ins, ins + str.length ]
45
+ elsif str == val[ ins - str.length, str.length ]
46
+ return [ ins - str.length, ins ]
47
+ else
48
+ Kernel.raise "Insertion Point: #{ins}, Selection #{str}"
49
+ end
50
+ end
12
51
  end
13
52
 
14
- # background colour doesn't seem to work in MSW
15
- HEADER_STYLE = Wx::TextAttr.new(Wx::RED)
16
- # SUBHEADER_STYLE = Wx::TextAttr.new(Wx::BLUE)
17
- NORMAL_STYLE = Wx::TextAttr.new(Wx::BLACK)
18
- HIGHLIGHTED_STYLE = Wx::TextAttr.new(Wx::BLUE)
19
-
20
- def initialize(*args)
21
- super
22
- @highlights = []
23
- end
24
-
25
- # returns the start and end of the selection as indexes within
26
- # the underlying string
27
- def true_selection()
28
- lines = get_range(0, get_insertion_point()).count("\n")
29
- lines *= NEWLINE_CORRECTION_FACTOR
30
- [ get_insertion_point() - lines,
31
- get_insertion_point() + get_string_selection().length - lines ]
32
- end
33
- alias :get_true_selection :true_selection
34
-
35
53
  # returns the current insertion point as an index within the
36
54
  # underlying string
37
55
  def true_insertion_point()
38
- lines = get_range(0, get_insertion_point()).count("\n")
39
- lines *= NEWLINE_CORRECTION_FACTOR
40
- get_insertion_point() - lines
56
+ true_point( insertion_point )
41
57
  end
42
58
  alias :get_true_insertion_point :true_insertion_point
43
59
 
@@ -49,30 +65,6 @@ module QDA::GUI
49
65
  index + adjustment
50
66
  end
51
67
 
52
- # remove all highlights
53
- def unhighlight()
54
- while extent = @highlights.shift()
55
- from, to = *extent
56
- true_from, true_to = true_index_to_pos(from), true_index_to_pos(to)
57
- set_style( true_from, true_to, NORMAL_STYLE )
58
- end
59
- end
60
-
61
- # highlight the characters from +from+ to +to+. These are
62
- # characters within the underlying text - this method will
63
- # automatically translate those to real characters displayed within
64
- # the text control.
65
- def highlight(from, to)
66
- true_from, true_to = true_index_to_pos(from), true_index_to_pos(to)
67
- set_style( true_from, true_to, HIGHLIGHTED_STYLE )
68
- @highlights.push( [from, to] )
69
- end
70
-
71
- def clear()
72
- super()
73
- @highlights = []
74
- end
75
-
76
68
  # call this with a block that alters the appearance or content of
77
69
  # the text control - this includes call to set_style(), highlight()
78
70
  # as well as changing text content. It reduces flicker and keeps the
@@ -100,8 +92,178 @@ module QDA::GUI
100
92
  end
101
93
  end
102
94
 
103
- class DocTextViewer < TrueSelectionTextCtrl
95
+
96
+ class HighlightingTextCtrl < TrueSelectionTextCtrl
97
+ # TODO - using set_style on GTK (1.2) seems to trigger a serious bug
98
+ # where the text is moved out of correct position, and multiple
99
+ # successive calls trigger a segfault. This module provides stub
100
+ # functionality to hide this problem on Linux
101
+ module NullHighlighter
102
+ # pale_yellow = Wx::Colour.new(255, 255, 192)
103
+ # HIGHLIGHTED_STYLE = Wx::TextAttr.new( Wx::BLACK, pale_yellow )
104
+ def highlight(from, to)
105
+ end
106
+ def unhighlight()
107
+ end
108
+ end
109
+
110
+ # Provide support for highlighting regions within the text control by
111
+ # changing the text foreground colour - background colour doesn't work
112
+ # on MS Windows WxWidgets.
113
+ module TextColourHighlighter
114
+ # background colour doesn't seem to work in MSW
115
+ HIGHLIGHTED_STYLE = Wx::TextAttr.new(Wx::BLUE)
116
+ NORMAL_STYLE = Wx::TextAttr.new(Wx::BLACK)
117
+
118
+ def initialize(*args)
119
+ super
120
+ @highlights = []
121
+ end
122
+
123
+ # highlight the characters from +from+ to +to+. These are
124
+ # characters within the underlying text - this method will
125
+ # automatically translate those to real characters displayed within
126
+ # the text control.
127
+ def highlight(from, to)
128
+ true_from, true_to = true_index_to_pos(from), true_index_to_pos(to)
129
+ set_style( true_from, true_to, HIGHLIGHTED_STYLE )
130
+ @highlights.push( [from, to] )
131
+ end
132
+
133
+ # remove all highlights
134
+ def unhighlight()
135
+ while extent = @highlights.shift()
136
+ from, to = *extent
137
+ true_from, true_to = true_index_to_pos(from), true_index_to_pos(to)
138
+ set_style( true_from, true_to, NORMAL_STYLE )
139
+ end
140
+ end
141
+
142
+ def clear()
143
+ super()
144
+ @highlights = []
145
+ end
146
+ end
147
+
148
+ if Wx::RUBY_PLATFORM == 'WXGTK' # Linux
149
+ include NullHighlighter
150
+ else
151
+ include TextColourHighlighter
152
+ end
153
+ end
154
+
155
+ module FindableText
156
+ class UsefulFindReplaceData < Wx::FindReplaceData
157
+ attr_accessor :flags
158
+ def down?
159
+ ! up?
160
+ end
161
+
162
+ def up?
163
+ ( flags & Wx::FR_DOWN ).zero?
164
+ end
165
+
166
+ def no_matchcase?
167
+ ( flags & Wx::FR_MATCHCASE).zero?
168
+ end
169
+
170
+ def matchcase?
171
+ ! no_matchcase?
172
+ end
173
+ end
174
+
175
+ def initialize(*args)
176
+ super(*args)
177
+ @f_dialog = nil
178
+ @f_data = UsefulFindReplaceData.new(1)
179
+ @hooked = nil
180
+ end
181
+
182
+ def start_find(parent_win)
183
+ if not @f_dialog
184
+ @f_dialog = Wx::FindReplaceDialog.new( parent_win, @f_data,
185
+ "Find text", Wx::FR_NOWHOLEWORD )
186
+ end
187
+ if not @hooked
188
+ # TextCtrl doesn't receive evt_find, so route through parent
189
+ parent_win.evt_find(-1) { | e | on_find(e) }
190
+ parent_win.evt_find_next(-1) { | e | on_find(e) }
191
+ parent_win.evt_find_close(-1) { | e | on_find_close(e) }
192
+ # ensure dialog is destroyed when parent is closed
193
+ parent_win.evt_close() { | e | on_find_close(e) }
194
+ @hooked = parent_win
195
+ end
196
+ @f_dialog.show()
197
+ end
198
+
199
+ def get_subject()
200
+ if @f_data.up?
201
+ subject = get_range( 0, insertion_point )
202
+ else
203
+ subject = get_range( insertion_point, last_position )
204
+ end
205
+ if @f_data.no_matchcase?
206
+ subject.downcase!
207
+ end
208
+ return subject
209
+ end
210
+
211
+ def searching_down?
212
+ @f_data.down?
213
+ end
214
+
215
+ def searching_up?
216
+ @f_data.up?
217
+ end
218
+
219
+ def on_find(evt)
220
+ # query? bug in WxRuby 0.6.0 FindReplaceData#get_flags -
221
+ # doesn't get automatically updated as documented
222
+ @f_data.flags = evt.flags
223
+
224
+ # get the string we're searching for, bail out if nothing there
225
+ sought = @f_data.find_string()
226
+ return if sought.empty?
227
+
228
+ sought.downcase! if @f_data.no_matchcase?
229
+
230
+ # get part of our contents in the right case ready to
231
+ return unless subject = get_subject()
232
+ adjustment = 0
233
+ # hack to avoid recapturing when searching down
234
+ if searching_down? and string_selection == sought
235
+ adjustment = sought.length
236
+ subject = subject[adjustment .. -1]
237
+ end
238
+
239
+ point = searching_down? ? subject.index( sought ) :
240
+ subject.rindex( sought )
241
+ if point
242
+ if searching_up?
243
+ self.insertion_point = true_point( point )
244
+ else
245
+ self.insertion_point = true_insertion_point + point + adjustment
246
+ end
247
+
248
+ sel_start = true_index_to_pos( insertion_point )
249
+ sel_end = true_index_to_pos( insertion_point + sought.length )
250
+ self.set_selection( sel_end, sel_start )
251
+ end
252
+ end
253
+
254
+ def on_find_close(evt)
255
+ if @f_dialog
256
+ @f_dialog.destroy()
257
+ @f_dialog = nil
258
+ end
259
+ self.set_focus()
260
+ evt.skip()
261
+ end
262
+ end
263
+
264
+ class DocTextViewer < HighlightingTextCtrl
104
265
  attr_reader :docid
266
+ include FindableText
105
267
  DOCTEXT_STYLE = Wx::TE_MULTILINE|Wx::TE_READONLY|
106
268
  Wx::TE_RICH|Wx::TE_NOHIDESEL
107
269
 
@@ -137,9 +299,13 @@ module QDA::GUI
137
299
  end
138
300
 
139
301
  # a text display that is made up of text fragments from multiple
140
- # documents
141
- # It keeps track of which fragment is found at which point
142
- class CompositeText < TrueSelectionTextCtrl
302
+ # documents. It keeps track of which fragment is found at which point,
303
+ # so that when passages are marked they are routed back to the source
304
+ # document.
305
+ class CompositeText < HighlightingTextCtrl
306
+ include FindableText
307
+ HEADER_STYLE = Wx::TextAttr.new(Wx::RED)
308
+ NORMAL_STYLE = Wx::TextAttr.new(Wx::BLACK)
143
309
  class TextTable < Hash
144
310
  def initialize(*args)
145
311
  @reverse_table = {}
@@ -205,6 +371,7 @@ module QDA::GUI
205
371
  target = @reverse_table.keys.find do | frag |
206
372
  frag.docid == docid && frag.contains?(point)
207
373
  end
374
+ return nil if target.nil?
208
375
  return @reverse_table[target] - target.length +
209
376
  ( point - target.offset )
210
377
  end
@@ -231,16 +398,19 @@ module QDA::GUI
231
398
  # sort by document title
232
399
  save_position() do
233
400
  fragments.each_title do | doc_title, frags |
234
- frags.each do | frag |
235
- header = "#{doc_title} [#{frag.offset}-#{frag.end}]\n"
236
- write_range(header, nil, HEADER_STYLE)
237
- write_range(frag, frag)
238
- write_range("\n\n", nil)
239
- end
401
+ frags.each { | frag | write_frag(frag) }
240
402
  write_range("\n", nil)
241
403
  end
242
404
  end
243
405
  evt_left_dclick() { | e | jump_to_fragment(e) }
406
+ self.insertion_point = 0
407
+ end
408
+
409
+ def write_frag(frag)
410
+ header = "#{frag.doctitle} [#{frag.offset}-#{frag.end}]\n"
411
+ write_range(header, nil, HEADER_STYLE)
412
+ write_range(frag, frag)
413
+ write_range("\n\n", nil)
244
414
  end
245
415
 
246
416
  # writes the text +text+ to the control using the sytle +style+,
@@ -249,7 +419,7 @@ module QDA::GUI
249
419
  set_default_style(style)
250
420
  ins_start = get_last_position()
251
421
  append_text(text)
252
- ins_end = get_last_position
422
+ ins_end = get_last_position()
253
423
 
254
424
  if style != NORMAL_STYLE
255
425
  @saved_styles[style] ||= []
@@ -259,7 +429,7 @@ module QDA::GUI
259
429
  @cursor += text.length
260
430
  @table[ @cursor ] = bound_value
261
431
  end
262
- private :write_range
432
+ private :write_frag, :write_range
263
433
 
264
434
  def jump_to_fragment(evt)
265
435
  frag, offset = *@table.fetch(true_insertion_point)
@@ -285,6 +455,7 @@ module QDA::GUI
285
455
  areas.each do | docid, codes |
286
456
  codes.each do | code |
287
457
  translated = @table.translate(docid, code.offset)
458
+ next if translated.nil?
288
459
  highlight(translated, translated + code.length)
289
460
  end
290
461
  end