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
@@ -1,126 +1,105 @@
1
+ # temporary hack to turn c++ style set_foo(), get_foo() and is_foo()
2
+ # accessors into ruby style foo=() foo() and foo?()
3
+ wx_classes = Wx::constants.collect { | c | Wx::const_get(c) }.grep(Class)
4
+ wx_classes.each do | klass |
5
+ klass.instance_methods.grep(/^([gs]et|evt|is)_/).each do | meth |
6
+ case meth
7
+ when /^get_(\w+)$/
8
+ klass.class_eval("alias :#{$1} :#{meth}")
9
+ when /^set_(\w+)$/
10
+ klass.class_eval("alias :#{$1}= :#{meth}")
11
+ when /^is_(\w+)$/
12
+ klass.class_eval("alias :#{$1}? :#{meth}")
13
+ end
14
+ end
15
+ end
16
+
17
+ Wx::NORMAL_CURSOR = Wx::Cursor.new(Wx::CURSOR_ARROW)
18
+ Wx::BUSY_CURSOR = Wx::Cursor.new(Wx::CURSOR_WAIT)
19
+
1
20
  module QDA::GUI
2
21
  # ItemData - allows widgets to store information about assocations
3
22
  # with underlying project objects such as categories and documents.
4
23
  module ItemData
5
24
  # for faked-up item data
6
25
  def get_item_data(id)
7
- @data_table[id]
26
+ data[id]
8
27
  end
9
28
  alias :get_client_data :get_item_data
10
29
 
11
30
  # for faked-up item data
12
- def set_item_data(id, data)
13
- @data_table ||= Hash.new()
14
- @data_table[id] = data
15
- return nil
31
+ def set_item_data(id, data_obj)
32
+ data[id] = data_obj
16
33
  end
17
34
  alias :set_client_data :set_item_data
18
-
35
+
19
36
  # returns the key associated with the value +value+
20
37
  def value_to_ident(value)
21
38
  return nil if value.nil?
22
- unless value.respond_to?(:dbid)
39
+ if value.nil?
40
+ return nil
41
+ elsif value.respond_to?(:dbid)
42
+ the_id = value.dbid
43
+ elsif value.kind_of?(Fixnum)
44
+ the_id = value
45
+ else
23
46
  Kernel.raise "Cannot search for invalid value #{value.inspect}"
24
47
  end
25
- doc = @data_table.each do | k, val |
26
- return k if val.dbid == value.dbid
27
- end
28
- return nil
48
+ return index(the_id)
29
49
  end
30
50
  end
31
-
32
-
33
- # Allows an object such as the global Wx App to broadcast messages
34
- # about changes in the database and other application states that
35
- # might require the child to update its appearance.
36
- module Broadcaster
37
- SUBSCRIBABLE_EVENTS = [ :document_added, :document_changed,
38
- :document_deleted, :category_added, :category_changed,
39
- :category_deleted, :focus_category, :savestate_changed ]
40
-
41
- # intended to be module private variables to managed which widgets
42
- # are subscribing to which events
43
- attr_accessor :bc__subscribers, :bc__subscriptions
44
-
45
- # broadcast an event to all subscribers to event of type
46
- # +event_type+, optionally including +content+
47
- def broadcast(event_type, content = nil)
48
- for subscriber in bc__subscriptions[event_type]
49
- subscriber.notify(event_type, content)
50
- end
51
+
52
+ module ListLikeItemData
53
+ include ItemData
54
+ def data()
55
+ @data_table ||= Array.new()
51
56
  end
52
-
53
- # subscribe the widget +subscriber+ to receive notification of
54
- # app event types +*events+ (which should be a set of symbols)
55
- def add_subscriber(subscriber, *events)
56
- events.each do | e |
57
- bc__subscriptions[e].push(subscriber)
58
- end
59
- subscriber.evt_close do | e |
60
- delete_subscriber(subscriber)
61
- e.skip()
57
+
58
+ def delete_item_data(the_data)
59
+ if i = value_to_ident(the_data)
60
+ data.delete_at(i)
62
61
  end
63
62
  end
64
-
65
- # accepts a ruby item
66
- def delete_subscriber(subscriber)
67
- if subscriber.respond_to?(:associated_subscribers)
68
- subscriber.associated_subscribers.each do | child |
69
- delete_subscriber(child)
70
- end
71
- end
72
- bc__subscribers.delete(subscriber)
73
- bc__subscriptions.each_value do | subscriber_set |
74
- subscriber_set.delete(subscriber)
75
- end
63
+
64
+ def unshift_item_data(the_data)
65
+ delete_item_data(the_data)
66
+ data.unshift(the_data)
76
67
  end
77
68
 
78
- def Broadcaster.extended(obj)
79
- $wxapp = obj
80
- # this is where module-private variables would be great ..
81
- obj.reset_subscriptions()
69
+ def push_item_data(the_data)
70
+ delete_item_data(the_data)
71
+ data.push(the_data)
82
72
  end
83
-
84
- def reset_subscriptions()
85
- self.bc__subscribers = []
86
- self.bc__subscriptions = Hash.new do | ev |
87
- raise "Cannot subscribe to unknown event #{ev}"
73
+
74
+ def index(the_id)
75
+ data.each_with_index do | val, i |
76
+ return i if val.dbid == the_id
88
77
  end
89
- SUBSCRIBABLE_EVENTS.each { | ev | self.bc__subscriptions[ev] = [] }
78
+ return nil
90
79
  end
91
80
  end
92
81
 
93
- # Any gui element that may need to modify its appearance in response
94
- # to application updates may include the subscriber module. Instances
95
- # of the class may then call the +subscribe+ method to receive
96
- # notification of changes.
97
- module Subscriber
98
- # receive notification that an event of type +ev+ has been called,
99
- # optionally passing +content+ for additional information about the
100
- # object the event concerned.
101
- def notify(ev, content = nil)
102
- receiver = "receive_#{ev}".intern
103
- if respond_to?(receiver)
104
- send(receiver, content)
105
- else
106
- warn "#{self} received unhandled event #{ev}"
107
- end
82
+ module HashLikeItemData
83
+ include ItemData
84
+ def data()
85
+ @data_table ||= Hash.new()
108
86
  end
109
-
110
- # Subscribe this object to the events +events+, which should be a
111
- # list of symbols. For example, to receive notification of category
112
- # changes and additions, the recipient shoudl call
113
- # subscribe(:category_changed, :category_added)
114
- #
115
- # For every event type to which a subscriber subscribes, it should
116
- # implement a corresponding receive_xxx method, where xxx is the
117
- # name of the event type. To act upon category changes, the
118
- # subscriber should implement +receive_category_changed+
119
- def subscribe(*events)
120
- $wxapp.add_subscriber(self, *events)
87
+
88
+ def index(the_id)
89
+ data.each do | k, val |
90
+ return k if val.dbid == the_id
91
+ end
92
+ return nil
121
93
  end
122
94
  end
123
-
95
+
96
+ class LabelledVSizer < Wx::StaticBoxSizer
97
+ def initialize(sized_object, label)
98
+ static_box = Wx::StaticBox.new(sized_object, -1, label)
99
+ super(static_box, Wx::VERTICAL)
100
+ end
101
+ end
102
+
124
103
  # extension to Wx::Colour
125
104
  class Wx::Colour
126
105
  # Create a new colour by mixing +self_parts+ of +self+ with
@@ -1,27 +1,28 @@
1
1
  module QDA::GUI
2
2
  # The main application window, where docuemnts, categories, searches
3
3
  # etc are displayed. Only one instance per application.
4
- class WorkArea < Wx::MDIParentFrame
5
- include Subscriber
6
-
7
- attr_accessor :app
8
- def initialize(wx_app)
9
- @wx_app = wx_app
10
- @window_map = {}
11
- super(nil, -1, "Weft QDA")
12
- restore_size()
13
- subscribe(:savestate_changed)
14
- end
15
-
4
+ module InspectorWindowManager
5
+ include QDA::Subscriber
6
+
7
+ # This sets the last-focussed child. However, when later requesting info
8
+ # on the currently active window, MDI reports the active_child() call,
9
+ # whereas the @active_child variable is read by MultiframeWindowManager
10
+ # - in MDI there is a foremost window which may not have received focus.
11
+ attr_writer :active_child
16
12
  # Resizes the workarea to the state when the application was last used
17
13
  def restore_size()
18
14
  conf = Wx::ConfigBase::get()
19
15
  conf.path = "/MainFrame"
20
- x = conf.read_int("x", 200)
21
- y = conf.read_int("y", 0)
22
- w = conf.read_int("w", 300)
23
- h = conf.read_int("h", 400)
16
+ x = conf.read_int( "x", -1 )
17
+ y = conf.read_int( "y", -1 )
18
+ w = conf.read_int( "w", -1 )
19
+ h = conf.read_int( "h", -1 )
24
20
  max = conf.read_bool("max", false)
21
+
22
+ x = DEF_POS.x if x < 0
23
+ y = DEF_POS.y if y < 0
24
+ w = DEF_SIZE.width if w < 200
25
+ h = DEF_SIZE.height if h < 200
25
26
 
26
27
  if max
27
28
  maximize(true)
@@ -32,6 +33,9 @@ module QDA::GUI
32
33
  end
33
34
 
34
35
  # save layout to Config so window is same position next time
36
+
37
+
38
+
35
39
  def remember_size()
36
40
  conf = Wx::ConfigBase::get()
37
41
  # global window size settings
@@ -70,7 +74,7 @@ module QDA::GUI
70
74
  else
71
75
  w_i = window_ident(bound_obj)
72
76
  last_layout = restore_layout( w_i )
73
- win = klass.new( bound_obj, @wx_app, self, last_layout )
77
+ win = klass.new( bound_obj, @client, self, last_layout )
74
78
  @window_map[w_i] = win
75
79
 
76
80
  # set up a hook to clean up when this window is closed later
@@ -127,7 +131,7 @@ module QDA::GUI
127
131
  # remember how the windows were arranged
128
132
  def remember_layouts()
129
133
  # project specific settings
130
- if @app
134
+ if self.app
131
135
  @window_map.each do | key, window |
132
136
  remember_layout(key, window, true) unless key.nil?
133
137
  end
@@ -135,64 +139,167 @@ module QDA::GUI
135
139
  end
136
140
 
137
141
  def remember_layout(window_id, window, open = false)
138
- if @app
139
- layouts = @app.get_preference('Layouts') || {}
142
+ if self.app
143
+ layouts = app.get_preference('Layouts') || {}
140
144
  layouts[window_id] = window.layout
141
145
  layouts[window_id][:open] = open
142
- @app.save_preference('Layouts', layouts)
146
+ app.save_preference('Layouts', layouts)
143
147
  end
144
148
  end
145
149
 
146
150
  def restore_layout(window_id)
147
- if @app
148
- layouts = @app.get_preference('Layouts') || {}
151
+ if self.app
152
+ layouts = app.get_preference('Layouts') || {}
149
153
  return layouts[window_id]
150
154
  end
151
155
  end
152
156
 
153
157
  def restore_layouts()
154
- if @app
155
- layouts = @app.get_preference('Layouts')
158
+ if self.app
159
+ layouts = app.get_preference('Layouts')
156
160
  return unless layouts
157
161
  # get all previously open windows
158
162
  open = layouts.find_all { | x | x[1][:open] }
159
163
  open.each do | ident, layout |
160
164
  if ident =~ /^Document(\w+)$/
161
- $wxapp.on_document_open($1)
165
+ @client.on_document_open($1)
162
166
  elsif ident =~ /^Category(\w+)/ and
163
- cat = @app.get_category($1, true)
164
- $wxapp.on_category_open(cat)
167
+ cat = app.get_category($1, true)
168
+ @client.on_category_open(cat)
165
169
  end
166
170
  end
167
171
  end
168
172
  end
169
173
 
170
- def set_display_font(font)
171
- # super(font)
172
- @window_map.values.each do | child |
173
- child.set_display_font(font) if child.respond_to?(:set_display_font)
174
- end
175
- end
176
-
177
174
  def close_all()
178
175
  @window_map.values.each { | child | child.close() }
176
+ close_d_and_c()
179
177
  end
180
178
 
181
- def receive_savestate_changed(app)
182
- title = 'Weft QDA'
183
- if app
184
- if app.dbfile
179
+ def notify(ev, content = nil)
180
+ case ev
181
+ when :focus_category
182
+ return # ignore
183
+ when :ended
184
+ title = 'Weft QDA'
185
+ when :saved, :started
186
+ title = 'Weft QDA'
187
+ if self.app.dbfile
185
188
  title << " - #{app.dbfile}"
186
189
  else
187
190
  title << " - [New Project]"
188
191
  end
189
- if app.dirty?
190
- title << "*"
191
- end
192
+ else
193
+ title = get_title()
194
+ title << '*' if title[-1,1] != '*'
195
+ end
196
+ set_title(title)
197
+ end
198
+
199
+ def app()
200
+ @client.app
201
+ end
202
+
203
+ end
204
+
205
+ class MDIWorkArea < Wx::MDIParentFrame
206
+ include InspectorWindowManager
207
+ def initialize(weft_client)
208
+ @client = weft_client
209
+ @window_map = {}
210
+ super(nil, -1, "Weft QDA")
211
+ restore_size()
212
+ subscribe(@client, :all)
213
+ end
214
+
215
+ def d_and_c()
216
+ @sidebar
217
+ end
218
+
219
+ # display the listings of documents and categories
220
+ def show_d_and_c()
221
+ if not @sidebar
222
+ # see sidebar.rb
223
+ @sidebar = SideBar.new( self, @client )
192
224
  end
193
- set_title(title)
225
+ @sidebar.show()
226
+ end
227
+
228
+ def hide_d_and_c()
229
+ @sidebar.hide() if @sidebar
230
+ end
231
+
232
+ def close_d_and_c()
233
+ return if not @sidebar
234
+ @sidebar.remember_size()
235
+ @sidebar.close()
236
+ @sidebar = nil
237
+ end
238
+
239
+ def d_and_c_visible?()
240
+ @sidebar.shown?
241
+ end
242
+
243
+ def hide_all()
244
+ hide_d_and_c()
245
+ self.hide()
194
246
  end
195
247
  end
248
+
249
+ # presentation of main window as a standalone window, launching inspector
250
+ # windows as separate frames
251
+ class MultiFrameWorkArea < Wx::Frame
252
+ include InspectorWindowManager
253
+
254
+ attr_reader :d_and_c, :active_child
255
+
256
+ def initialize(weft_client)
257
+ @client = weft_client
258
+ @window_map = {}
259
+ super(nil, -1, "Weft QDA")
260
+ restore_size()
261
+ set_sizer( @sizer = Wx::BoxSizer.new(Wx::VERTICAL) )
262
+ subscribe(@client, :all)
263
+ end
264
+
265
+ def show_d_and_c()
266
+ if not @d_and_c
267
+ @d_and_c ||= DocumentsAndCategories.new(self, @client)
268
+ conf = Wx::ConfigBase::get()
269
+ conf.path = "/SideFrame"
270
+ split = conf.read_int("split", 200)
271
+ # sometimes weirdly ends up with negative value
272
+ split = client_size.height / 2 if split <= 0
273
+ @d_and_c.set_sash_position(split)
274
+ @sizer.add(@d_and_c, 1, Wx::GROW|Wx::ALL, 0)
275
+ @sizer.layout()
276
+ end
277
+ end
278
+
279
+ def hide_d_and_c()
280
+ # do nothing
281
+ end
282
+
283
+ def close_d_and_c()
284
+ @sizer.remove(@d_and_c)
285
+ @d_and_c.destroy()
286
+ @d_and_c = nil
287
+ end
288
+
289
+ def hide_all()
290
+ @window_map.each { | w | w.hide() }
291
+ self.hide()
292
+ end
293
+ end
294
+
295
+
296
+
297
+ if WINDOW_MODE == :MDI
298
+ WorkArea = MDIWorkArea
299
+ else
300
+ WorkArea = MultiFrameWorkArea
301
+ end
302
+
196
303
 
197
304
  class EasyMenu < Wx::Menu
198
305
  def EasyMenu.next_base()