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
@@ -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()