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,43 @@
1
+ require 'uri'
2
+
3
+ module QDA
4
+ module GUI
5
+ # save some typing
6
+ DEF_POS = Wx::DEFAULT_POSITION
7
+ DEF_SIZE = Wx::DEFAULT_SIZE
8
+
9
+ # Some versions of WxRuby on OS X seem not to have Wx::RUBY_PLATFORM
10
+ # correctly defined
11
+ if not defined?(Wx::RUBY_PLATFORM) and ::RUBY_PLATFORM =~ /powerpc/
12
+ Wx::RUBY_PLATFORM = 'WXMAC'
13
+ end
14
+
15
+ # What window layout strategy should we use?
16
+ if defined? ::WEFT_MDI_MODE
17
+ WINDOW_MODE = :MDI
18
+ elsif defined? ::WEFT_MULTIFRAME_MODE
19
+ WINDOW_MODE = :MULTIFRAME
20
+ end
21
+
22
+ if defined? ::WEFT_CRASH_REPORTING
23
+ REPORT_CRASHES = true
24
+ CRASH_REPORT_URL = URI.parse('http://www.pressure.to/cgi-bin/weft-crash-report.rb')
25
+ else
26
+ REPORT_CRASHES = false
27
+ end
28
+
29
+ # use windows type icons by preference on windows, otherwise settle for
30
+ # XPMs (which don't do multi-resolution).
31
+ # Also - use MDI style by default on windows, otherwise use
32
+ # multiframe style
33
+ IconType = Struct.new(:extension, :constant)
34
+ if Wx::RUBY_PLATFORM == 'WXMSW'
35
+ ICON_EXTS = [ IconType[ :ico, Wx::BITMAP_TYPE_ICO ] ,
36
+ IconType[ :xpm, Wx::BITMAP_TYPE_XPM ] ]
37
+ WINDOW_MODE = :MDI if not defined?(WINDOW_MODE)
38
+ else
39
+ ICON_EXTS = [ IconType[ :xpm, Wx::BITMAP_TYPE_XPM ] ]
40
+ WINDOW_MODE = :MULTIFRAME if not defined?(WINDOW_MODE)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ # controls are little customised WxGUI widgets that aren't frames or dialogs
2
+ lib_patts = $LOAD_PATH.map { | path | /^#{Regexp.escape(path)}\// }
3
+ Dir.glob( __FILE__.sub(/\.rb$/, File::SEPARATOR + '*.rb' ) ).each do | f |
4
+ lib_patts.find { | x | f.sub!(x, '') }
5
+ require f
6
+ end
@@ -0,0 +1,192 @@
1
+ module QDA::GUI
2
+ class CategoryDropDown < Wx::ComboBox
3
+ include QDA::Subscriber
4
+ include ListLikeItemData
5
+
6
+ MAXIMUM_DROPDOWN_LENGTH = 7
7
+ attr_accessor :sticky
8
+
9
+ # +locked+ if true will prevent the dropdown from responding to
10
+ # global category focus events.
11
+ def initialize(app, parent, text_box = nil, locked = false)
12
+ super(parent, -1, '', Wx::DEFAULT_POSITION,
13
+ Wx::DEFAULT_SIZE, [])
14
+ @locked = locked
15
+ @client_data = {}
16
+ @text_box = text_box
17
+ @sticky = false
18
+
19
+ @app = app
20
+
21
+ evt_kill_focus() do | e |
22
+ find_and_add_categories()
23
+ e.skip()
24
+ end
25
+
26
+ evt_text_enter(self.get_id) do | e |
27
+ find_and_add_categories()
28
+ end
29
+
30
+ evt_combobox(self.get_id) do | e |
31
+ e.skip()
32
+ on_item_selected(e)
33
+ end
34
+
35
+ subscribe(@app, :focus_category, :category_deleted, :category_changed)
36
+ if @app.current_category
37
+ set_active_category(@app.current_category)
38
+ end
39
+ end
40
+
41
+ def redraw()
42
+ delete(0) while count.nonzero?
43
+ data.each_with_index do | item, i |
44
+ append(item.name, nil)
45
+ end
46
+ set_selection(0)
47
+ end
48
+
49
+ def append_item(cat)
50
+ push_item_data(cat)
51
+ append(cat.name, nil)
52
+ end
53
+
54
+ def prepend_item(the_cat)
55
+ unshift_item_data(the_cat)
56
+ redraw()
57
+ end
58
+
59
+ def update_item(the_cat)
60
+ if i = value_to_ident(the_cat)
61
+ set_item_data(i, the_cat)
62
+ redraw()
63
+ end
64
+ end
65
+
66
+ def remove_item(the_cat)
67
+ if i = value_to_ident(the_cat)
68
+ delete(i)
69
+ data.delete_at(i)
70
+ if count.zero?
71
+ set_selection(0)
72
+ end
73
+ end
74
+ end
75
+
76
+ # is this dropdown responding to global :focus_category events?
77
+ def locked?
78
+ @locked ? true : false
79
+ end
80
+
81
+ # make this dropdown receive global :focus_category events
82
+ def lock
83
+ @locked = true
84
+ end
85
+
86
+ # prevent this dropdown responding to global :focus_category events
87
+ def unlock
88
+ @locked = false
89
+ end
90
+
91
+
92
+ # highlight text coded by the category on the way.
93
+ def set_selection(idx)
94
+ super(idx)
95
+ on_item_selected(nil)
96
+ end
97
+
98
+ # highlight text coded by the newly-selected category
99
+ def on_item_selected(e)
100
+ if @text_box
101
+ if category = current_category
102
+ @text_box.highlight_codingtable(category.codes)
103
+ else
104
+ @text_box.unhighlight()
105
+ end
106
+ end
107
+ end
108
+
109
+ def delete_first()
110
+ delete(0)
111
+ data.shift
112
+ end
113
+
114
+ def set_active_category(category)
115
+ # clear the first node unless it is sticky because it has been used,
116
+ # or if the category is already in the list
117
+ delete_first unless @sticky or value_to_ident(category)
118
+
119
+ trim()
120
+ prepend_item(category)
121
+ @sticky = false
122
+ set_selection(0)
123
+ end
124
+
125
+ def trim(to_length = MAXIMUM_DROPDOWN_LENGTH)
126
+ # clear any remaining excess items
127
+ while count > to_length
128
+ data.delete_at(count - 1)
129
+ delete(count - 1)
130
+ end
131
+ end
132
+
133
+ def find_and_add_categories()
134
+ typed_text = get_value()
135
+ return if find_string( typed_text )
136
+ return if typed_text.empty?
137
+ matches = @app.app.get_categories_by_path(typed_text)
138
+ matches.each do | cat |
139
+ prepend_item(cat) unless cat.parent.nil?
140
+ end
141
+ trim(matches.length) if count > MAXIMUM_DROPDOWN_LENGTH
142
+ set_selection( 0 )
143
+ end
144
+
145
+ # add the newly-focused category to this dropdown and highlight
146
+ # its text.
147
+ def receive_focus_category(cat)
148
+ set_active_category(cat) unless locked?
149
+ end
150
+
151
+ # if a category is deleted it should be removed from the list
152
+ def receive_category_deleted(cat)
153
+ remove_item(cat)
154
+ end
155
+
156
+ def receive_category_changed(cat)
157
+ update_item(cat)
158
+ end
159
+
160
+
161
+ def on_blur(e)
162
+ find_and_add_categories()
163
+ e.skip()
164
+ end
165
+
166
+ # missing in wxruby, but part of WxWidgets
167
+ # this version differs by returning nil rather than -1 on failure
168
+ def find_string(str)
169
+ ( 0 ... count ).each do | i |
170
+ return i if str == get_string(i)
171
+ end
172
+ return nil
173
+ end
174
+
175
+
176
+ # TODO - some visual cue to indicate that no category matched find-first
177
+ def set_broken(bool)
178
+
179
+ end
180
+
181
+ def set_warning(bool)
182
+
183
+ end
184
+ # returns the Category object associated with the currently
185
+ # selected item in the drop down.
186
+ def current_category
187
+ if curr_sel = get_selection() and curr_sel >= 0
188
+ return get_client_data( curr_sel )
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,314 @@
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 HashLikeItemData
8
+ include QDA::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(weft_client, parent, locked = false)
15
+ @client = weft_client
16
+ @locked = locked
17
+ # a hash whose keys are the dbids which are expanded
18
+ @expanded = {}
19
+ super(parent, -1, Wx::DEFAULT_POSITION, Wx::DEFAULT_SIZE,
20
+ CATEGORY_TREE_STYLE)
21
+ my_id = self.get_id()
22
+ evt_tree_item_activated(my_id) { | e | on_item_activated(e) }
23
+
24
+ if ! @locked
25
+ evt_tree_sel_changed(my_id) { | e | on_item_selected(e) }
26
+ evt_tree_end_drag(my_id) { | e | on_drag_end(e) }
27
+ evt_tree_begin_drag(my_id) { | e | on_drag_begin(e) }
28
+ evt_tree_begin_label_edit(my_id) { | e | on_edit_label_begin(e) }
29
+ evt_tree_end_label_edit(my_id) { | e | on_edit_label_end(e) }
30
+ evt_tree_key_down(my_id) { | e | on_key_down(e) }
31
+ evt_tree_item_expanded(my_id) { | e | on_item_expanded(e) }
32
+ evt_tree_item_collapsed(my_id) { | e | on_item_collapsed(e) }
33
+ end
34
+
35
+ subscribe(@client, :category_deleted, :category_changed, :category_added)
36
+ end
37
+
38
+ # for faked-up item data
39
+ def append_item(parent, text, data = nil)
40
+ id = super(parent, text, -1, -1)
41
+ set_item_data(id, data)
42
+ set_item_bold(id) if parent == @root_id
43
+ id
44
+ end
45
+
46
+ # for faked-up item data
47
+ def prepend_item(parent, text, img = -1, sel_img = -1, data = nil)
48
+ id = super(parent, text, img, sel_img)
49
+ set_item_data(id, data)
50
+ return id
51
+ end
52
+
53
+ # for faked-up item data
54
+ def delete(id)
55
+ super(id)
56
+ del_cat = data.delete(id)
57
+ if del_cat.parent and old_parent_id = value_to_ident(del_cat.parent)
58
+ set_item_data( old_parent_id,
59
+ @client.app.get_category(del_cat.parent.dbid) )
60
+ end
61
+ return nil
62
+ end
63
+
64
+ def populate(children)
65
+ # the root isn't shown, so the top level of +children+ is what
66
+ # appears at the base of the tree.
67
+ @root_id = add_root('ROOT')
68
+ append_recursively(@root_id, children)
69
+ refresh()
70
+ end
71
+
72
+ # returns an array of category ids
73
+ # NOT USED?
74
+ def expanded_items()
75
+ @expanded.keys
76
+ end
77
+
78
+ # Opens the category nodes corresponding to the category ids in +catids+
79
+ def expand_items(catids)
80
+ catids.keys.each do | catid |
81
+ if itemid = value_to_ident(catid)
82
+ # this is done here to prevent it being seen as a change by evt handler
83
+ @expanded[catid] = true
84
+ expand( itemid )
85
+ end
86
+ end
87
+ end
88
+
89
+ def append_recursively(parent, children)
90
+ children.each do | child_cat |
91
+ name = child_cat.name.empty? ? 'DEFAULT' : child_cat.name
92
+
93
+ id = append_item(parent, name, child_cat )
94
+ # remember this for later use
95
+ @search_id = id if name == 'SEARCHES'
96
+ @codes_id = id if name == 'CATEGORIES' or name == 'CODES'
97
+
98
+ append_recursively(id, child_cat.children)
99
+ end
100
+ end
101
+
102
+ # get the currently active category
103
+ def get_current_category()
104
+ if curr_sel = get_selection()
105
+ return nil if curr_sel == 0
106
+ category = get_item_data( curr_sel )
107
+ return nil if category.nil? # important for GTK
108
+ return nil if category.parent.nil? # don't return root nodes
109
+ category
110
+ end
111
+ end
112
+ alias :selected_category :get_current_category
113
+
114
+ def on_item_expanded(evt)
115
+ cat = get_item_data(evt.item)
116
+ return unless cat
117
+ return if @expanded[cat.dbid] # prevents futile re-saving if already open
118
+ @expanded[cat.dbid] = true
119
+ @client.app.save_preference('TreeLayout', @expanded)
120
+ end
121
+
122
+ def on_item_collapsed(evt)
123
+ cat = get_item_data(evt.item)
124
+ return unless cat
125
+ @expanded.delete(cat.dbid)
126
+ @client.app.save_preference('TreeLayout', @expanded)
127
+ end
128
+
129
+ def on_edit_label_begin(evt)
130
+ # TODO - this should really talk to the underlying category,
131
+ # rather than just assuming bold = locked
132
+ evt.veto() if bold?(evt.item)
133
+ end
134
+
135
+ def on_edit_label_end(evt)
136
+ new_text = evt.label
137
+ if new_text == ""
138
+ evt.veto()
139
+ return
140
+ end
141
+
142
+ old_text = get_item_text( evt.item )
143
+ # don't bother saving if the text isn't changed
144
+ return if old_text == new_text
145
+ c_cdata = get_item_data( evt.item )
146
+ category = @client.app.get_category( c_cdata.dbid )
147
+ Wx::BusyCursor.busy do
148
+ begin
149
+ category.name = new_text
150
+ @client.app.save_category( category)
151
+ rescue QDA::BadNameError
152
+ category.name = old_text
153
+ ErrorDialog.display( Lang::BAD_CATEGORY_NAME_TITLE,
154
+ Lang::BAD_CATEGORY_NAME_WARNING )
155
+ evt.veto()
156
+ rescue QDA::NotUniqueNameError
157
+ category.name = old_text
158
+ ErrorDialog.display( Lang::DUPLICATE_CATEGORY_NAME_TITLE,
159
+ Lang::DUPLICATE_CATEGORY_NAME_WARNING )
160
+ evt.veto()
161
+ end
162
+ end
163
+
164
+ set_item_data(evt.item, category )
165
+ end
166
+
167
+ def on_drag_begin(evt)
168
+ @drag_subject = evt.item
169
+ evt.allow()
170
+ end
171
+
172
+ # relocates or merges the coding of the draggee to the drag target
173
+ def on_drag_end(evt)
174
+ @drag_target = evt.item
175
+ # control_down is a property of a mouse event, so this doesn't work
176
+ # p "CONTROL IS DOWN" if evt.control_down
177
+ move(@drag_subject, @drag_target)
178
+
179
+ @drag_subject = nil
180
+ @drag_target = nil
181
+ end
182
+
183
+ def dont_move(from)
184
+ select_item(from) if from != 0
185
+ end
186
+
187
+
188
+ # moves the item identifed by the tree id +from+ to be the last child of
189
+ # the category with the tree id +to+
190
+ def move(from, to)
191
+ return dont_move(from) unless from and to
192
+ return dont_move(from) if from == 0 or to == 0
193
+ return dont_move(from) if from == to
194
+
195
+ movee = get_item_data( from )
196
+ destination = get_item_data( to )
197
+ # don't move root nodes
198
+ return dont_move(from) if not movee.parent
199
+ # ignore if no move
200
+ return dont_move(from) if movee.parent == destination
201
+ # don't attach to descendants
202
+ return dont_move(from) if destination.is_descendant_of?(movee)
203
+ # complain if a child with this name already attached to parent
204
+ if destination[movee.name]
205
+ ErrorDialog.display( Lang::DUPLICATE_CATEGORY_NAME_TITLE,
206
+ Lang::DUPLICATE_CATEGORY_NAME_WARNING )
207
+ return dont_move(from)
208
+ end
209
+
210
+ Wx::BusyCursor.busy do
211
+ movee.parent = destination
212
+ @client.app.save_category( movee )
213
+ end
214
+ end
215
+
216
+ # Moves the tree item identified by +from+ so that it is attached the parent
217
+ # identified by +new_parent+. Does nothing if that is already the location
218
+ # of the node.
219
+ def move_item(from, to)
220
+ # don't alter if unchanged
221
+ return from if get_item_parent(from) == to
222
+ new_child = append_item(to, get_item_text(from), get_item_data(from))
223
+ child = get_first_child(from)[0]
224
+ while child != 0
225
+ move_item(child, new_child)
226
+ child = get_first_child(from)[0]
227
+ end
228
+ delete( from )
229
+ new_child
230
+ end
231
+
232
+ # when we're asked to add an item. This will be attached to the
233
+ # currently selected category in the tree, or the default 'CATEGORIES'
234
+ # category if no item is selected
235
+ def on_create_item()
236
+ @client.on_add_category()
237
+ end
238
+
239
+ def on_item_selected(event)
240
+ if valid_selection?(event)
241
+ @client.current_category = get_item_data( event.get_item )
242
+ else
243
+ @client.current_category = nil
244
+ end
245
+ end
246
+
247
+ def on_item_activated(event)
248
+ item_id = event.get_item()
249
+ category = get_item_data(item_id)
250
+ return nil if category.parent.nil?
251
+ Wx::BusyCursor.busy() do
252
+ category = @client.app.get_category(category.dbid, true)
253
+ if category != nil
254
+ @client.on_category_open(category)
255
+ end
256
+ end
257
+ end
258
+
259
+ def valid_selection?( event )
260
+ # wxruby varies across platforms in how it indicates "no item
261
+ # selected" - on windows, get_selection returns 0, on Linux, a
262
+ # weird large integer id. Returning nil is the "correct" future behaviour
263
+ item_id = event.get_item()
264
+ if item_id.nil? or item_id == 0 or is_bold(item_id)
265
+ return false
266
+ elsif not get_item_data( item_id )
267
+ return false
268
+ else
269
+ return true
270
+ end
271
+ end
272
+
273
+ def delete_selection()
274
+ @client.on_delete_category()
275
+ end
276
+
277
+ def on_key_down(evt)
278
+ case evt.key_code()
279
+ when 127 # DEL
280
+ delete_selection()
281
+ end
282
+ end
283
+
284
+ def receive_category_deleted(cat)
285
+ # may not include this item if it's a partial subtree
286
+ if tree_id = value_to_ident(cat)
287
+ delete( tree_id )
288
+ end
289
+ end
290
+
291
+ def receive_category_changed(cat)
292
+ # may not include this item if it's a partial subtree
293
+ if tree_id = value_to_ident(cat)
294
+ # it's maybe moved
295
+ tree_id = move_item(tree_id, value_to_ident(cat.parent) )
296
+ set_item_text( tree_id, cat.name)
297
+ set_item_data( tree_id, cat)
298
+ end
299
+ end
300
+
301
+ def receive_category_added(cat)
302
+ if cat.parent.nil?
303
+ p_id = @root_id
304
+ else
305
+ p_id = value_to_ident(cat.parent)
306
+ end
307
+ return if not p_id
308
+ append_item( p_id, cat.name, cat)
309
+ expand(p_id)
310
+ id = value_to_ident(cat)
311
+ select_item(id)
312
+ end
313
+ end
314
+ end