weft-qda 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/lib/weft.rb +21 -0
  2. data/lib/weft/WEFT-VERSION-STRING.rb +1 -0
  3. data/lib/weft/application.rb +130 -0
  4. data/lib/weft/backend.rb +39 -0
  5. data/lib/weft/backend/marshal.rb +26 -0
  6. data/lib/weft/backend/mysql.rb +267 -0
  7. data/lib/weft/backend/n6.rb +366 -0
  8. data/lib/weft/backend/sqlite.rb +633 -0
  9. data/lib/weft/backend/sqlite/category_tree.rb +104 -0
  10. data/lib/weft/backend/sqlite/schema.rb +152 -0
  11. data/lib/weft/backend/sqlite/upgradeable.rb +55 -0
  12. data/lib/weft/category.rb +157 -0
  13. data/lib/weft/coding.rb +355 -0
  14. data/lib/weft/document.rb +118 -0
  15. data/lib/weft/filters.rb +243 -0
  16. data/lib/weft/wxgui.rb +687 -0
  17. data/lib/weft/wxgui/category.xpm +26 -0
  18. data/lib/weft/wxgui/dialogs.rb +128 -0
  19. data/lib/weft/wxgui/document.xpm +25 -0
  20. data/lib/weft/wxgui/error_handler.rb +52 -0
  21. data/lib/weft/wxgui/inspectors.rb +361 -0
  22. data/lib/weft/wxgui/inspectors/category.rb +165 -0
  23. data/lib/weft/wxgui/inspectors/codereview.rb +275 -0
  24. data/lib/weft/wxgui/inspectors/document.rb +139 -0
  25. data/lib/weft/wxgui/inspectors/imagedocument.rb +56 -0
  26. data/lib/weft/wxgui/inspectors/script.rb +35 -0
  27. data/lib/weft/wxgui/inspectors/search.rb +265 -0
  28. data/lib/weft/wxgui/inspectors/textcontrols.rb +304 -0
  29. data/lib/weft/wxgui/lang.rb +17 -0
  30. data/lib/weft/wxgui/lang/en.rb +45 -0
  31. data/lib/weft/wxgui/mondrian.xpm +44 -0
  32. data/lib/weft/wxgui/search.xpm +25 -0
  33. data/lib/weft/wxgui/sidebar.rb +498 -0
  34. data/lib/weft/wxgui/utilities.rb +148 -0
  35. data/lib/weft/wxgui/weft16.xpm +31 -0
  36. data/lib/weft/wxgui/workarea.rb +249 -0
  37. data/test/001-document.rb +196 -0
  38. data/test/002-category.rb +138 -0
  39. data/test/003-code.rb +370 -0
  40. data/test/004-application.rb +52 -0
  41. data/test/006-filters.rb +139 -0
  42. data/test/009a-backend_sqlite_basic.rb +280 -0
  43. data/test/009b-backend_sqlite_complex.rb +175 -0
  44. data/test/009c_backend_sqlite_bench.rb +81 -0
  45. data/test/010-backend_nudist.rb +5 -0
  46. data/test/all-tests.rb +1 -0
  47. data/test/manual-gui-script.txt +24 -0
  48. data/test/testdata/autocoding-test.txt +15 -0
  49. data/test/testdata/iso-8859-1.txt +5 -0
  50. data/test/testdata/sample_doc.txt +19 -0
  51. data/test/testdata/search_results.txt +1254 -0
  52. data/test/testdata/text1-dos-ascii.txt +2 -0
  53. data/test/testdata/text1-unix-utf8.txt +2 -0
  54. data/weft-qda.rb +28 -0
  55. metadata +96 -0
@@ -0,0 +1,17 @@
1
+ module QDA::GUI
2
+ module Lang
3
+ # +lang_code+ = 'En'
4
+ def Lang.set_language(lang_code)
5
+ begin
6
+ require "weft/wxgui/lang/#{lang_code.downcase}"
7
+ rescue LoadError
8
+ raise LoadError, "No language file for language '#{lang_code}'"
9
+ end
10
+ language = QDA::GUI::Lang.const_get(lang_code)
11
+ language.constants.each do | interface_element |
12
+ translation = language::const_get(interface_element)
13
+ const_set( interface_element, translation )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module QDA::GUI::Lang
2
+ module En
3
+ UNSAVED_CHANGES_CONFIRM = "You have unsaved changes in your project; do you wish to save these?"
4
+ UNSAVED_CHANGES_CONFIRM_TITLE = "Save changes?"
5
+ FILE_ALREADY_EXISTS = "A project file with this name already exists. If you continue, all data in it will be lost. Are you sure?"
6
+ SIDEBAR_TITLE = "Documents & Categories"
7
+ SEARCH_RESULTS = "search results"
8
+ REINDEX_DOCS_WARNING =
9
+ "This will reindex all previously imported documents, which will take
10
+ some time. You only need to do this once if:
11
+ 1) you first created this project in version 0.9.3 or earlier OR
12
+ 2) you have recently imported documents from N6.
13
+ Otherwise there is no need to run this command.
14
+
15
+ Are you sure you wish to proceed and reindex all documents? "
16
+
17
+ REINDEX_DOCS_WARNING_TITLE = "Reindex all documents?"
18
+
19
+ NO_SEARCH_TERM_SPECIFIED = "No search term specified"
20
+ DOC_PANEL_TITLE = 'Documents'
21
+ IMPORT_DOC_BUTTON = 'Import...'
22
+ VIEW_DOC_BUTTON = 'View'
23
+ CAT_PANEL_TITLE = 'Categories'
24
+ NEW_CAT_BUTTON = 'New...'
25
+ VIEW_CAT_BUTTON = 'View'
26
+ DISPLAY_OPTIONS_LABEL = 'Display options'
27
+
28
+ HELP_ABOUT_MESSAGE = "Thank you for trying Weft QDA
29
+ Version '#{::WEFT_VERSION}'\n\n-- qda@pressure.to"
30
+
31
+ if RUBY_PLATFORM =~ /mswin/
32
+ HELP_HELP_MESSAGE = <<HELP
33
+ Help files are installed with Weft QDA.
34
+ Please check in your 'Start' menu for a shortcut the Weft Manual help file.
35
+
36
+ Alternatively, read the manual online
37
+ http://www.pressure.to/qda/doc/
38
+ HELP
39
+
40
+ else
41
+ HELP_HELP_MESSAGE = "Please see the online documentation at
42
+ http://www.pressure.to/qda/doc/"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ /* XPM */
2
+ static char *mondrian_xpm[] = {
3
+ /* columns rows colors chars-per-pixel */
4
+ "32 32 6 1",
5
+ " c Black",
6
+ ". c Blue",
7
+ "X c #00bf00",
8
+ "o c Red",
9
+ "O c Yellow",
10
+ "+ c Gray100",
11
+ /* pixels */
12
+ " ",
13
+ " oooooo +++++++++++++++++++++++ ",
14
+ " oooooo +++++++++++++++++++++++ ",
15
+ " oooooo +++++++++++++++++++++++ ",
16
+ " oooooo +++++++++++++++++++++++ ",
17
+ " oooooo +++++++++++++++++++++++ ",
18
+ " oooooo +++++++++++++++++++++++ ",
19
+ " oooooo +++++++++++++++++++++++ ",
20
+ " ",
21
+ " ++++++ ++++++++++++++++++ .... ",
22
+ " ++++++ ++++++++++++++++++ .... ",
23
+ " ++++++ ++++++++++++++++++ .... ",
24
+ " ++++++ ++++++++++++++++++ .... ",
25
+ " ++++++ ++++++++++++++++++ .... ",
26
+ " ++++++ ++++++++++++++++++ ",
27
+ " ++++++ ++++++++++++++++++ ++++ ",
28
+ " ++++++ ++++++++++++++++++ ++++ ",
29
+ " ++++++ ++++++++++++++++++ ++++ ",
30
+ " ++++++ ++++++++++++++++++ ++++ ",
31
+ " ++++++ ++++++++++++++++++ ++++ ",
32
+ " ++++++ ++++++++++++++++++ ++++ ",
33
+ " ++++++ ++++++++++++++++++ ++++ ",
34
+ " ++++++ ++++++++++++++++++ ++++ ",
35
+ " ++++++ ++++++++++++++++++ ++++ ",
36
+ " ++++++ ++++ ",
37
+ " ++++++ OOOOOOOOOOOO XXXXX ++++ ",
38
+ " ++++++ OOOOOOOOOOOO XXXXX ++++ ",
39
+ " ++++++ OOOOOOOOOOOO XXXXX ++++ ",
40
+ " ++++++ OOOOOOOOOOOO XXXXX ++++ ",
41
+ " ++++++ OOOOOOOOOOOO XXXXX ++++ ",
42
+ " ++++++ OOOOOOOOOOOO XXXXX ++++ ",
43
+ " "
44
+ };
@@ -0,0 +1,25 @@
1
+ /* XPM */
2
+ static char *search[] = {
3
+ /* columns rows colors chars-per-pixel */
4
+ "16 16 3 1",
5
+ " c black",
6
+ ". c gray100",
7
+ "X c None",
8
+ /* pixels */
9
+ "XXXXXXXXXXXXXXXX",
10
+ "XXXXXXXXX XXX",
11
+ "XXXXXXXX .... XX",
12
+ "XXXXXXX ...... X",
13
+ "XXXXXXX ...... X",
14
+ "XXXXXXX ...... X",
15
+ "XXXXXXX ...... X",
16
+ "XXXXXXXX .... XX",
17
+ "XXXXXXX X XXX",
18
+ "XXXXXX XXXXXXXXX",
19
+ "XXXXX XXXXXXXXXX",
20
+ "XXXX XXXXXXXXXXX",
21
+ "XXX XXXXXXXXXXXX",
22
+ "XX XXXXXXXXXXXXX",
23
+ "XXXXXXXXXXXXXXXX",
24
+ "XXXXXXXXXXXXXXXX"
25
+ };
@@ -0,0 +1,498 @@
1
+ module QDA::GUI
2
+ # the category tree list for the side panel
3
+ class CategoryTree < Wx::TreeCtrl
4
+ CATEGORY_TREE_STYLE = Wx::TR_HIDE_ROOT|Wx::TR_HAS_BUTTONS|
5
+ Wx::TR_LINES_AT_ROOT|Wx::TR_EDIT_LABELS
6
+
7
+ include ItemData
8
+ include Subscriber
9
+
10
+ attr_reader :root_id
11
+
12
+ # a +locked+ control is one that is not editable, and does not
13
+ # affect the selection of categories in other widgetes.
14
+ def initialize(app, parent, locked = false)
15
+ @app = app
16
+ @locked = locked
17
+ super(parent, -1, Wx::DEFAULT_POSITION, Wx::DEFAULT_SIZE,
18
+ CATEGORY_TREE_STYLE)
19
+ my_id = self.get_id()
20
+ evt_tree_item_activated(my_id) { | e | on_item_activated(e) }
21
+
22
+ if ! @locked
23
+ evt_tree_sel_changed(my_id) { | e | on_item_selected(e) }
24
+ evt_tree_end_drag(my_id) { | e | on_drag_end(e) }
25
+ evt_tree_begin_drag(my_id) { | e | on_drag_begin(e) }
26
+ evt_tree_begin_label_edit(my_id) { | e | on_edit_label_begin(e) }
27
+ evt_tree_end_label_edit(my_id) { | e | on_edit_label_end(e) }
28
+ evt_tree_key_down(my_id) { | e | on_key_down(e) }
29
+ end
30
+
31
+ subscribe(:category_deleted, :category_changed, :category_added)
32
+ end
33
+
34
+ # for faked-up item data
35
+ def append_item(parent, text, data = nil)
36
+ id = super(parent, text, -1, -1)
37
+ set_item_data(id, data)
38
+ set_item_bold(id) if parent == @root_id
39
+ id
40
+ end
41
+
42
+ # for faked-up item data
43
+ def prepend_item(parent, text, img = -1, sel_img = -1, data = nil)
44
+ id = super(parent, text, img, sel_img)
45
+ set_item_data(id, data)
46
+ return id
47
+ end
48
+
49
+ # for faked-up item data
50
+ def delete(id)
51
+ super(id)
52
+ @data_table.delete(id)
53
+ return nil
54
+ end
55
+
56
+ def populate(children)
57
+ # the root isn't shown, so the top level of +children+ is what
58
+ # appears at the base of the tree.
59
+ @root_id = add_root('ROOT')
60
+ append_recursively(@root_id, children)
61
+ refresh()
62
+ end
63
+
64
+ def append_recursively(parent, children)
65
+ children.each do | child_cat |
66
+ name = child_cat.name.empty? ? 'DEFAULT' : child_cat.name
67
+
68
+ id = append_item(parent, name, child_cat )
69
+ # remember this for later use
70
+ @search_id = id if name == 'SEARCHES'
71
+ @codes_id = id if name == 'CATEGORIES' or name == 'CODES'
72
+
73
+ append_recursively(id, child_cat.children)
74
+ end
75
+ end
76
+
77
+ # get the currently active category
78
+ def get_current_category()
79
+ if curr_sel = get_selection()
80
+ return nil if curr_sel == 0
81
+ category = get_item_data( curr_sel )
82
+ return nil if category.nil? # important for GTK
83
+ return nil if category.parent.nil? # don't return root nodes
84
+ category
85
+ end
86
+ end
87
+ alias :selected_category :get_current_category
88
+
89
+
90
+ def on_edit_label_begin(evt)
91
+ # TODO - this should really talk to the underlying category,
92
+ # rather than just assuming bold = locked
93
+ evt.veto() if bold?(evt.item)
94
+ end
95
+
96
+ def on_edit_label_end(evt)
97
+ new_text = evt.label
98
+ if new_text == ""
99
+ evt.veto()
100
+ return
101
+ end
102
+
103
+ old_text = get_item_text( evt.item )
104
+ # don't bother saving if the text isn't changed
105
+ return if old_text == new_text
106
+
107
+ Wx::BusyCursor.busy do
108
+ c_cdata = get_item_data( evt.item )
109
+ category = @app.app.get_category( c_cdata.dbid )
110
+ category.name = new_text
111
+ @app.app.save_category( category)
112
+ set_item_data(evt.item, category )
113
+ $wxapp.broadcast(:category_changed, category)
114
+ end
115
+ end
116
+
117
+ def on_drag_begin(evt)
118
+ @drag_subject = evt.item
119
+ evt.allow()
120
+ end
121
+
122
+ # relocates or merges the coding of the draggee to the drag target
123
+ def on_drag_end(evt)
124
+ @drag_target = evt.item
125
+ # control_down is a property of a mouse event, so this doesn't work
126
+ # p "CONTROL IS DOWN" if evt.control_down
127
+ move_item(@drag_subject, @drag_target)
128
+
129
+ @drag_subject = nil
130
+ @drag_target = nil
131
+ end
132
+
133
+ def dont_move(from)
134
+ select_item(from) if from != 0
135
+ end
136
+ # moves the item identifed by +from+ to be the last child of
137
+ # +to+
138
+ def move_item(from, to)
139
+ return dont_move(from) unless from and to
140
+ return dont_move(from) if from == 0 or to == 0
141
+ return dont_move(from) if from == to
142
+
143
+ movee = get_item_data( from )
144
+ destination = get_item_data( to )
145
+ # don't move root nodes
146
+ return dont_move(from) if not movee.parent
147
+ # ignore if no move
148
+ return dont_move(from) if movee.parent == destination
149
+ # don't attach to descendants
150
+ return dont_move(from) if @app.app.is_descendant?(movee, destination)
151
+
152
+ shift_nodes = Proc.new do | node, parent |
153
+ c_text = get_item_text( node )
154
+ category = get_item_data( node )
155
+ new_parent = get_item_data( parent )
156
+
157
+ # TODO - make expanded persist
158
+ # c_expand = is_expanded( node )
159
+ category.parent = new_parent
160
+ new_child = append_item(parent, c_text, category)
161
+ child = get_first_child(node)[0]
162
+ while child != 0
163
+ shift_nodes.call(child, new_child)
164
+ child = get_first_child(node)[0]
165
+ end
166
+ delete( node )
167
+ new_child
168
+ end
169
+ Wx::BusyCursor.busy do
170
+ new_cat = shift_nodes.call(from, to)
171
+ @app.app.save_category( get_item_data(new_cat) )
172
+ end
173
+ end
174
+
175
+ # when we're asked to add an item. This will be attached to the
176
+ # currently selected category in the tree, or the default 'CATEGORIES'
177
+ # category if no item is selected
178
+ def on_create_item()
179
+ # attach to currently selected
180
+ parent_item = get_selection()
181
+ # or default to 'CATEGORIES'
182
+ if parent_item.nil? || parent_item == 0
183
+ parent_item = @codes_id
184
+ end
185
+
186
+ dialog = Wx::TextEntryDialog.new(@app.sidebar, "Add Category\n",
187
+ "Enter the new category name",
188
+ "", Wx::OK | Wx::CANCEL)
189
+
190
+ if dialog.show_modal() == Wx::ID_OK
191
+ parent = get_item_data(parent_item)
192
+ cat = QDA::Category.new( dialog.get_value(), parent )
193
+ @app.app.save_category(cat)
194
+ $wxapp.broadcast(:category_added, cat)
195
+
196
+ expand(parent_item)
197
+ id = value_to_ident(cat)
198
+ select_item(id)
199
+ end
200
+ end
201
+
202
+ def valid_selection(event)
203
+ item_id = event.get_item()
204
+ return false if item_id.nil? or item_id == 0
205
+ return false if is_bold(item_id)
206
+ get_item_data(item_id)
207
+ end
208
+
209
+ def on_item_selected(event)
210
+ category = valid_selection(event) or return()
211
+ @app.current_category = category
212
+ end
213
+
214
+ def on_item_activated(event)
215
+ item_id = event.get_item()
216
+ category = get_item_data(item_id)
217
+ return nil if category.parent.nil?
218
+ Wx::BusyCursor.busy() do
219
+ category = @app.app.get_category(category.dbid, 1)
220
+ if category != nil
221
+ @app.on_category_open(category)
222
+ end
223
+ end
224
+ end
225
+
226
+ def delete_item(itemid)
227
+ Wx::BusyCursor.busy() do
228
+ return false if is_bold(itemid)
229
+ cat = get_item_data(itemid)
230
+ if cat
231
+ # delete_category is recursive and so several categories may be
232
+ # removed, and each is broadcast separately
233
+ dels = @app.app.delete_category(cat)
234
+ dels.each do | deletion |
235
+ $wxapp.broadcast(:category_deleted, deletion)
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ def on_key_down(evt)
242
+ sel = get_selection()
243
+ case evt.key_code()
244
+ when 127 # DEL
245
+ delete_item(sel)
246
+ end
247
+ end
248
+
249
+ def receive_category_deleted(cat)
250
+ # may not include this item if it's a partial subtree
251
+ if tree_id = value_to_ident(cat)
252
+ delete( value_to_ident(cat) )
253
+ @data_table.delete(cat)
254
+ end
255
+ end
256
+
257
+ def receive_category_changed(cat)
258
+ # may not include this item if it's a partial subtree
259
+ if tree_id = value_to_ident(cat)
260
+ set_item_data( value_to_ident(cat), cat)
261
+ set_item_text( value_to_ident(cat), cat.name)
262
+ end
263
+ end
264
+
265
+ def receive_category_added(cat)
266
+ if cat.parent.nil?
267
+ p_id = @root_id
268
+ else
269
+ p_id = value_to_ident(cat.parent)
270
+ end
271
+ append_item( p_id, cat.name, cat) if p_id
272
+ end
273
+ end
274
+
275
+ class DocumentList < Wx::ListBox
276
+ include ItemData
277
+ include Subscriber
278
+
279
+ def initialize(app, *args)
280
+ @app = app
281
+ super(*args)
282
+ evt_left_dclick() { | event | on_double_click(event) }
283
+ evt_key_down() { | e | on_key_down(e) }
284
+
285
+ subscribe(:document_added, :document_deleted, :document_changed)
286
+
287
+ # evt_context_menu() { | event | on_context_menu(event) }
288
+ # @menu = Wx::Menu.new()
289
+ # @menu.append(1234, 'Foo')
290
+ # @menu.append(1235, 'Bar')
291
+ end
292
+
293
+ # add the document to the end of the list.
294
+ def append(doc)
295
+ set_item_data(count, doc)
296
+ super(doc.title)
297
+ end
298
+
299
+ # needs to rejig the datatable to reflect the fact that all items
300
+ # below the deleted item have been deleted, and the datatable maps
301
+ # positions in the list to document values. Urgh. TODO - would be
302
+ # easier if data_table was an Array under the hood - but it is
303
+ # currently shared with TreeList data which is more Hash-ish.
304
+ def delete(item)
305
+ super(item)
306
+ @data_table.delete(item)
307
+ @data_table.keys.find_all { | k | k > item }.sort.each do | k |
308
+ set_item_data(k - 1, get_item_data(k) )
309
+ @data_table.delete(k)
310
+ end
311
+ # p @data_table
312
+ end
313
+
314
+
315
+ def on_context_menu(e)
316
+ popup_menu_xy(@menu, e.get_x, e.get_y)
317
+ end
318
+
319
+ def on_double_click(e)
320
+ if string_selection != ''
321
+ @app.on_document_open( selected_document() )
322
+ else
323
+ @app.import_document()
324
+ end
325
+ end
326
+
327
+ def selected_document()
328
+ sel = get_selection()
329
+ return nil unless sel and sel != -1
330
+ get_item_data( sel )
331
+ end
332
+
333
+ def receive_document_deleted(doc)
334
+ 0.upto(count - 1) do | i |
335
+ if get_item_data(i).dbid == doc.dbid
336
+ delete(i)
337
+ return true
338
+ end
339
+ end
340
+ end
341
+
342
+ def receive_document_changed(doc)
343
+ set_string( value_to_ident(doc), doc.title )
344
+ end
345
+
346
+ def receive_document_added(doc)
347
+ append(doc)
348
+ end
349
+
350
+ def on_key_down(evt)
351
+ sel = get_selection()
352
+ return if sel == -1
353
+ case evt.key_code()
354
+ when 127 # DEL
355
+ doc = get_item_data(sel)
356
+ @app.app.delete_document( doc.dbid )
357
+ $wxapp.broadcast(:document_deleted, doc)
358
+ else
359
+ evt.skip()
360
+ end
361
+ end
362
+ end
363
+
364
+ class SideBar < Wx::Frame
365
+ attr_reader :tree_list
366
+
367
+ def initialize(parent, app)
368
+ super(parent, -1, Lang::SIDEBAR_TITLE,
369
+ Wx::Point.new(0, 0),
370
+ parent.proportional_size(0.3, 0.995) )
371
+ @app = app
372
+ self.construct()
373
+
374
+ conf = Wx::ConfigBase::get()
375
+ conf.path = "/SideFrame"
376
+ x = conf.read_int("x", 0)
377
+ y = conf.read_int("y", 0)
378
+ w = conf.read_int("w", 200)
379
+ h = conf.read_int("h", 400)
380
+ split = conf.read_int("split", 200)
381
+ split = 200 if split.zero? # otherwise "Documents" are invisible
382
+ move( Wx::Point.new(x, y) )
383
+ set_size( Wx::Size.new(w, h) )
384
+ @splitter.set_sash_position(split)
385
+
386
+
387
+ evt_close() { | e | on_close(e) }
388
+ end
389
+
390
+ def on_close(e)
391
+ if shown?
392
+ @app.on_toggle_dandc(e)
393
+ end
394
+ e.veto() # don't actually destroy the window
395
+ end
396
+
397
+ # save layout to Config so window is same position next time
398
+ def remember_size()
399
+ conf = Wx::ConfigBase::get()
400
+ # global window size settings
401
+ if conf
402
+ size = get_size()
403
+ pos = get_position
404
+ conf.path = '/SideFrame'
405
+ conf.write("x", pos.x)
406
+ conf.write("y", pos.y)
407
+ conf.write("w", size.width)
408
+ conf.write("h", size.height)
409
+ conf.write("split", @splitter.sash_position)
410
+ end
411
+ end
412
+
413
+ def construct()
414
+ # build the sidebar - should go in sidebar.rb
415
+ @splitter = Wx::SplitterWindow.new(self, -1,
416
+ Wx::DEFAULT_POSITION,
417
+ Wx::DEFAULT_SIZE,Wx::SP_3DSASH)
418
+
419
+ panel_docs = Wx::Panel.new(@splitter)
420
+ panel_docs_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
421
+
422
+ doc_label = Wx::StaticBox.new(panel_docs, -1, Lang::DOC_PANEL_TITLE)
423
+ doc_box_sizer = Wx::StaticBoxSizer.new(doc_label, Wx::VERTICAL)
424
+
425
+ # Document List
426
+ @doc_list = DocumentList.new(@app, panel_docs)
427
+ doc_box_sizer.add(@doc_list, 1, Wx::GROW|Wx::ALL|Wx::ADJUST_MINSIZE, 4)
428
+
429
+ # Buttons Below Document List
430
+ butt_panel = Wx::Panel.new(panel_docs)
431
+ butt_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
432
+
433
+ button = Wx::Button.new(butt_panel, -1, Lang::VIEW_DOC_BUTTON)
434
+ @view_doc_button = button
435
+ button.evt_button(button.get_id) do | e |
436
+ if @doc_list.selected_document
437
+ @app.on_document_open( @doc_list.selected_document )
438
+ end
439
+ end
440
+ button.disable()
441
+ evt_listbox( @doc_list.get_id ) { | e | @view_doc_button.enable(true) }
442
+
443
+ butt_sizer.add(button, 0, Wx::ALL|Wx::ALIGN_LEFT, 4)
444
+
445
+ button = Wx::Button.new(butt_panel, -1, Lang::IMPORT_DOC_BUTTON)
446
+ button.evt_button(button.get_id) { | e | @app.on_import_document(e) }
447
+ butt_sizer.add(button, 0, Wx::ALL|Wx::ALIGN_RIGHT, 4)
448
+
449
+ butt_panel.set_sizer(butt_sizer)
450
+ doc_box_sizer.add(butt_panel, 0, Wx::ALL|Wx::GROW|Wx::ADJUST_MINSIZE)
451
+ panel_docs_sizer.add(doc_box_sizer, 1, Wx::ALL|Wx::GROW, 4)
452
+ panel_docs.set_sizer(panel_docs_sizer)
453
+
454
+ # Category Tree
455
+ panel_cats = Wx::Panel.new(@splitter)
456
+ panel_cats_sizer = Wx::BoxSizer.new(Wx::VERTICAL)
457
+
458
+ cats_label = Wx::StaticBox.new(panel_cats, -1, Lang::CAT_PANEL_TITLE)
459
+ cats_box_sizer = Wx::StaticBoxSizer.new(cats_label, Wx::VERTICAL)
460
+
461
+ @tree_list = CategoryTree.new(@app, panel_cats, false)
462
+ cats_box_sizer.add(@tree_list, 3, Wx::GROW|Wx::ALL, 4)
463
+
464
+ # Buttons below Category Tree
465
+ butt_panel = Wx::Panel.new(panel_cats)
466
+ butt_sizer = Wx::BoxSizer.new(Wx::HORIZONTAL)
467
+
468
+ button = Wx::Button.new(butt_panel, -1, Lang::VIEW_CAT_BUTTON)
469
+ button.evt_button(button.get_id) do | e |
470
+ if @tree_list.selected_category
471
+ @app.on_category_open( @tree_list.selected_category )
472
+ end
473
+ end
474
+ button.disable()
475
+ @view_cat_button = button
476
+ evt_tree_sel_changing( @tree_list.get_id ) do | e |
477
+ @tree_list.valid_selection(e) ?
478
+ @view_cat_button.enable(true) : @view_cat_button.disable()
479
+ end
480
+ butt_sizer.add(button, 0, Wx::ALL|Wx::ALIGN_RIGHT, 4)
481
+
482
+ button = Wx::Button.new(butt_panel, -1, Lang::NEW_CAT_BUTTON)
483
+ button.evt_button(button.get_id) { | e | @tree_list.on_create_item() }
484
+ butt_sizer.add(button, 0, Wx::ALL|Wx::ALIGN_LEFT, 4)
485
+
486
+ butt_panel.set_sizer(butt_sizer)
487
+ cats_box_sizer.add(butt_panel, 0, Wx::ALL|Wx::GROW|Wx::ADJUST_MINSIZE)
488
+ panel_cats_sizer.add(cats_box_sizer, 1, Wx::ALL|Wx::GROW, 4)
489
+ panel_cats.set_sizer(panel_cats_sizer)
490
+
491
+
492
+ @app.app.each_doc { | d | @doc_list.append(d) }
493
+ @tree_list.populate( @app.app.get_all_categories() )
494
+
495
+ @splitter.split_horizontally(panel_docs, panel_cats, 200)
496
+ end
497
+ end
498
+ end