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.
- data/lib/weft.rb +16 -1
- data/lib/weft/WEFT-VERSION-STRING.rb +1 -1
- data/lib/weft/application.rb +17 -74
- data/lib/weft/backend.rb +6 -32
- data/lib/weft/backend/sqlite.rb +222 -164
- data/lib/weft/backend/sqlite/category_tree.rb +52 -48
- data/lib/weft/backend/sqlite/database.rb +57 -0
- data/lib/weft/backend/sqlite/upgradeable.rb +7 -0
- data/lib/weft/broadcaster.rb +90 -0
- data/lib/weft/category.rb +139 -47
- data/lib/weft/codereview.rb +160 -0
- data/lib/weft/coding.rb +74 -23
- data/lib/weft/document.rb +23 -10
- data/lib/weft/exceptions.rb +10 -0
- data/lib/weft/filters.rb +47 -224
- data/lib/weft/filters/indexers.rb +137 -0
- data/lib/weft/filters/input.rb +118 -0
- data/lib/weft/filters/output.rb +101 -0
- data/lib/weft/filters/templates.rb +80 -0
- data/lib/weft/filters/win32backtick.rb +246 -0
- data/lib/weft/query.rb +169 -0
- data/lib/weft/wxgui.rb +349 -294
- data/lib/weft/wxgui/constants.rb +43 -0
- data/lib/weft/wxgui/controls.rb +6 -0
- data/lib/weft/wxgui/controls/category_dropdown.rb +192 -0
- data/lib/weft/wxgui/controls/category_tree.rb +314 -0
- data/lib/weft/wxgui/controls/document_list.rb +97 -0
- data/lib/weft/wxgui/controls/multitype_control.rb +37 -0
- data/lib/weft/wxgui/{inspectors → controls}/textcontrols.rb +235 -64
- data/lib/weft/wxgui/dialogs.rb +144 -41
- data/lib/weft/wxgui/error_handler.rb +116 -36
- data/lib/weft/wxgui/exceptions.rb +7 -0
- data/lib/weft/wxgui/inspectors.rb +61 -208
- data/lib/weft/wxgui/inspectors/category.rb +19 -16
- data/lib/weft/wxgui/inspectors/codereview.rb +90 -132
- data/lib/weft/wxgui/inspectors/document.rb +12 -8
- data/lib/weft/wxgui/inspectors/imagedocument.rb +56 -56
- data/lib/weft/wxgui/inspectors/query.rb +284 -0
- data/lib/weft/wxgui/inspectors/script.rb +147 -23
- data/lib/weft/wxgui/lang/en.rb +69 -0
- data/lib/weft/wxgui/sidebar.rb +90 -432
- data/lib/weft/wxgui/utilities.rb +70 -91
- data/lib/weft/wxgui/workarea.rb +150 -43
- data/share/icons/category.ico +0 -0
- data/share/icons/category.xpm +109 -0
- data/share/icons/codereview.ico +0 -0
- data/share/icons/codereview.xpm +54 -0
- data/share/icons/d_and_c.xpm +126 -0
- data/share/icons/document.ico +0 -0
- data/share/icons/document.xpm +70 -0
- data/share/icons/project.ico +0 -0
- data/share/icons/query.ico +0 -0
- data/share/icons/query.xpm +56 -0
- data/{lib/weft/wxgui → share/icons}/search.xpm +0 -0
- data/share/icons/weft.ico +0 -0
- data/share/icons/weft.xpm +62 -0
- data/share/icons/weft16.ico +0 -0
- data/share/icons/weft32.ico +0 -0
- data/share/templates/category_plain.html +18 -0
- data/share/templates/codereview_plain.html +18 -0
- data/share/templates/document_plain.html +13 -0
- data/share/templates/document_plain.txt +7 -0
- data/test/001-document.rb +55 -36
- data/test/002-category.rb +81 -6
- data/test/003-code.rb +8 -4
- data/test/004-application.rb +13 -34
- data/test/005-query_review.rb +139 -0
- data/test/006-filters.rb +54 -42
- data/test/007-output_filters.rb +113 -0
- data/test/009a-backend_sqlite_basic.rb +95 -24
- data/test/009b-backend_sqlite_complex.rb +43 -62
- data/test/009c_backend_sqlite_bench.rb +5 -10
- data/test/053-doc_inspector.rb +46 -0
- data/test/055-query_window.rb +50 -0
- data/test/all-tests.rb +1 -0
- data/test/test-common.rb +19 -0
- data/test/testdata/empty.qdp +0 -0
- data/test/testdata/simple with space.pdf +0 -0
- data/test/testdata/simple.pdf +0 -0
- data/weft-qda.rb +40 -7
- metadata +74 -14
- data/lib/weft/wxgui/category.xpm +0 -26
- data/lib/weft/wxgui/document.xpm +0 -25
- data/lib/weft/wxgui/inspectors/search.rb +0 -265
- data/lib/weft/wxgui/mondrian.xpm +0 -44
- 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
|