weft-qda 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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