jruby_visualizer 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/jruby_visualizer +5 -0
- data/lib/jruby_visualizer.rb +9 -0
- data/lib/jruby_visualizer/about_page.rb +64 -0
- data/lib/jruby_visualizer/ast_tree_view_builder.rb +77 -0
- data/lib/jruby_visualizer/cfg_visualizer.rb +122 -0
- data/lib/jruby_visualizer/compiler_data.rb +133 -0
- data/lib/jruby_visualizer/control_flow_graph_view.rb +96 -0
- data/lib/jruby_visualizer/core_ext/ir_scope.rb +15 -0
- data/lib/jruby_visualizer/ir_pretty_printer.rb +57 -0
- data/lib/jruby_visualizer/ir_scope_registry.rb +41 -0
- data/lib/jruby_visualizer/ir_visualizer.rb +79 -0
- data/lib/jruby_visualizer/jruby_visualizer.rb +103 -0
- data/lib/jruby_visualizer/ui/about.fxml +35 -0
- data/lib/jruby_visualizer/ui/cfg-view.fxml +42 -0
- data/lib/jruby_visualizer/ui/img/jruby-icon-32.png +0 -0
- data/lib/jruby_visualizer/ui/img/jruby-icon-64.png +0 -0
- data/lib/jruby_visualizer/ui/img/jruby-logo.png +0 -0
- data/lib/jruby_visualizer/ui/ir-view.fxml +18 -0
- data/lib/jruby_visualizer/ui/jruby-visualizer.fxml +81 -0
- data/lib/jruby_visualizer/version.rb +20 -0
- data/lib/jruby_visualizer/visualizer_compiler_pass_listener.rb +40 -0
- data/lib/jruby_visualizer/visualizer_main_app.rb +284 -0
- data/spec/compiler_data_spec.rb +102 -0
- data/spec/ir_scope_registry_spec.rb +49 -0
- data/spec/jruby_visualizer_test_utils.rb +19 -0
- data/spec/spec_helper.rb +20 -0
- metadata +110 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'jrubyfx'
|
19
|
+
|
20
|
+
resource_root :images, File.join(File.dirname(__FILE__), 'ui', 'img'), 'ui/img'
|
21
|
+
fxml_root File.join(File.dirname(__FILE__), 'ui')
|
22
|
+
|
23
|
+
#
|
24
|
+
# JRubyFX Application showing information about the Visualizer
|
25
|
+
#
|
26
|
+
class AboutPage < JRubyFX::Application
|
27
|
+
|
28
|
+
def start(stage)
|
29
|
+
with(stage, title: 'About the JRuby Visualizer') do
|
30
|
+
fxml(AboutPageController)
|
31
|
+
icons.add(Image.new(resource_url(:images, 'jruby-icon-32.png').to_s))
|
32
|
+
end
|
33
|
+
|
34
|
+
stage['#jruby_logo'].set_on_mouse_clicked do |e|
|
35
|
+
open_browser_with('http://jruby.org')
|
36
|
+
end
|
37
|
+
stage['#jruby_hyperlink'].set_on_action do |e|
|
38
|
+
open_browser_with('http://jruby.org')
|
39
|
+
end
|
40
|
+
stage['#visualizer_hyperlink'].set_on_action do |e|
|
41
|
+
open_browser_with('https://github.com/jruby/jruby-visualizer')
|
42
|
+
end
|
43
|
+
stage.show
|
44
|
+
end
|
45
|
+
|
46
|
+
def open_browser_with(url)
|
47
|
+
get_host_services.show_document(url)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Controller loads the fxml file for the About Page
|
54
|
+
#
|
55
|
+
class AboutPageController
|
56
|
+
include JRubyFX::Controller
|
57
|
+
fxml 'about.fxml'
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
if __FILE__ == $PROGRAM_NAME
|
62
|
+
AboutPage.launch
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'java'
|
19
|
+
require 'jrubyfx'
|
20
|
+
|
21
|
+
#
|
22
|
+
# Custom TreeItem, to store the JRuby's AST Node within the item
|
23
|
+
#
|
24
|
+
class ASTTreeItem < Java::javafx.scene.control.TreeItem
|
25
|
+
include JRubyFX
|
26
|
+
|
27
|
+
attr_reader :node
|
28
|
+
|
29
|
+
def initialize(node)
|
30
|
+
@node = node
|
31
|
+
super(node_string)
|
32
|
+
set_expanded(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def node_string
|
36
|
+
node_information = @node.respond_to?(:name) ? ":#{@node.name}" : ''
|
37
|
+
"#{@node.node_name}#{node_information} #{@node.position.start_line}"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
#
|
44
|
+
# Builder for the custom TreeItem from an usual JRuby AST
|
45
|
+
#
|
46
|
+
class ASTTreeViewBuilder
|
47
|
+
include JRubyFX
|
48
|
+
|
49
|
+
attr_accessor :tree_view
|
50
|
+
|
51
|
+
def initialize(tree_view)
|
52
|
+
@tree_view = tree_view
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_tree_item(node)
|
56
|
+
ASTTreeItem.new(node)
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_view(root)
|
60
|
+
@tree_view.root = build_tree_item(root)
|
61
|
+
root.child_nodes.each { |child| build_view_inner(child, @tree_view.root) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_view_inner(node, parent_item)
|
65
|
+
tree_item = build_tree_item(node)
|
66
|
+
parent_item.children << tree_item
|
67
|
+
node.child_nodes.each { |child| build_view_inner(child, tree_item) }
|
68
|
+
end
|
69
|
+
private :build_view_inner
|
70
|
+
end
|
71
|
+
|
72
|
+
if __FILE__ == $PROGRAM_NAME
|
73
|
+
require 'jruby'
|
74
|
+
root = JRuby.parse('def foo; 42; end; foo')
|
75
|
+
builder = ASTTreeViewBuilder.new(nil)
|
76
|
+
builder.build_view(root)
|
77
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'jrubyfx'
|
19
|
+
require_relative 'jruby_visualizer'
|
20
|
+
require_relative 'ir_scope_registry'
|
21
|
+
require_relative 'control_flow_graph_view'
|
22
|
+
|
23
|
+
resource_root :images, File.join(File.dirname(__FILE__), 'ui', 'img'), 'ui/img'
|
24
|
+
fxml_root File.join(File.dirname(__FILE__), 'ui')
|
25
|
+
|
26
|
+
#
|
27
|
+
# UI to visualize the IR's Control Flow Graph (CFG)
|
28
|
+
# Currently as a list view with live links between the basic blocks
|
29
|
+
#
|
30
|
+
class CFGVisualizer < JRubyFX::Application
|
31
|
+
def start(stage)
|
32
|
+
compiler_data = JRubyVisualizer.compiler_data
|
33
|
+
with(stage, title: 'Visualization of Control Flow Graphs (CFG)') do
|
34
|
+
fxml(CFGVisualizerController, initialize: [compiler_data])
|
35
|
+
icons.add(Image.new(resource_url(:images, 'jruby-icon-32.png').to_s))
|
36
|
+
show
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# This controller connects the CompilerData to the CFG View and
|
43
|
+
# handles opening/updating tabs for CFGs
|
44
|
+
#
|
45
|
+
class CFGVisualizerController
|
46
|
+
include JRubyFX::Controller
|
47
|
+
fxml 'cfg-view.fxml'
|
48
|
+
|
49
|
+
attr_reader :compiler_data, :ir_registry
|
50
|
+
|
51
|
+
def initialize(compiler_data)
|
52
|
+
@compiler_data = compiler_data
|
53
|
+
|
54
|
+
# read scopes into the registry
|
55
|
+
@ir_registry = IRScopeRegistry.new(@compiler_data.ir_scope)
|
56
|
+
read_registry_into_selector
|
57
|
+
# listen to changes of the ir_scope property
|
58
|
+
@compiler_data.ir_scope_property.add_invalidation_listener do |new_scope_property|
|
59
|
+
root_scope = new_scope_property.get
|
60
|
+
@ir_registry.clear
|
61
|
+
@ir_registry.fill_registry(root_scope)
|
62
|
+
read_registry_into_selector
|
63
|
+
update_tabs
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def read_registry_into_selector
|
68
|
+
scopes_keys = @ir_registry.scopes.keys.map do |key|
|
69
|
+
key.to_s
|
70
|
+
end
|
71
|
+
scopes_keys.sort!
|
72
|
+
@ir_scope_selector.items = FXCollections.observable_array_list(scopes_keys)
|
73
|
+
@ir_scope_selector.value = @selected_scope = scopes_keys[0]
|
74
|
+
end
|
75
|
+
|
76
|
+
def select_scope
|
77
|
+
@selected_scope = @ir_scope_selector.value
|
78
|
+
open_cfg_tab
|
79
|
+
end
|
80
|
+
|
81
|
+
def open_cfg_tab
|
82
|
+
if @selected_scope.nil?
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
tabs = @cfg_scopes_view.tabs
|
87
|
+
is_tab_opened = tabs.find do |tab|
|
88
|
+
# get string value from StringProperty name
|
89
|
+
tab.text == @selected_scope
|
90
|
+
end
|
91
|
+
|
92
|
+
unless is_tab_opened
|
93
|
+
tab = Tab.new(@selected_scope)
|
94
|
+
cfg = get_selected_scope.cfg!
|
95
|
+
tab.set_content(ControlFlowGraphView.new(cfg))
|
96
|
+
tabs << tab
|
97
|
+
# set focus on selected tab
|
98
|
+
@cfg_scopes_view.selection_model.select(tab)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_selected_scope
|
103
|
+
@ir_registry.scopes[@selected_scope.to_sym]
|
104
|
+
end
|
105
|
+
|
106
|
+
def update_tabs
|
107
|
+
@cfg_scopes_view.tabs.each do |tab|
|
108
|
+
scope_name = tab.text
|
109
|
+
# TODO read and diff on custom cfg objects
|
110
|
+
cfg = @ir_registry.scopes[scope_name.to_sym].cfg!
|
111
|
+
# TODO listen to events if the ir scope changes
|
112
|
+
tab.set_content(ControlFlowGraphView.new(cfg))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
if __FILE__ == $PROGRAM_NAME
|
119
|
+
JRubyVisualizer.compiler_data = CompilerData.new(
|
120
|
+
"\nclass Foo\n\ndef bar; 42; end; end;\nFoo.new.bar")
|
121
|
+
CFGVisualizer.launch
|
122
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'jrubyfx'
|
19
|
+
|
20
|
+
#
|
21
|
+
# JavaFX Property that fires always when the set method is called
|
22
|
+
#
|
23
|
+
class FireChangeObjectProperty < Java::javafx.beans.property.SimpleObjectProperty
|
24
|
+
def initialize(*args)
|
25
|
+
raise ArgumentError.new "wrong number of arguments (#{args.length} for 3)" if args.length > 3
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def set(new_val)
|
30
|
+
super(new_val)
|
31
|
+
fire_value_changed_event
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Data container for
|
37
|
+
# * Abstract Syntax Tree (AST),
|
38
|
+
# * Intermediate Code (IR)
|
39
|
+
# * Ruby Code
|
40
|
+
# It models and handles the dependencies between those compiler artifacts with
|
41
|
+
# JRubyFX properties
|
42
|
+
#
|
43
|
+
class CompilerData
|
44
|
+
include JRubyFX
|
45
|
+
|
46
|
+
@@ir_builder = nil
|
47
|
+
|
48
|
+
property_accessor :ruby_code, :ast_root, :ir_scope
|
49
|
+
attr_accessor :current_pass, :next_pass
|
50
|
+
|
51
|
+
def self.parse(code)
|
52
|
+
JRuby.parse(code)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.create_ir_builder
|
56
|
+
ir_manager = JRuby::runtime.ir_manager
|
57
|
+
ir_manager.dry_run = true
|
58
|
+
|
59
|
+
org.jruby.ir.IRBuilder::createIRBuilder(JRuby::runtime, ir_manager)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.build_ir(root_node)
|
63
|
+
unless @@ir_builder
|
64
|
+
@@ir_builder = create_ir_builder
|
65
|
+
end
|
66
|
+
@@ir_builder.build_root(root_node)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.pass_to_s(pass)
|
70
|
+
pass.class.to_s.split("::").last
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.compiler_passes_names
|
74
|
+
scheduler = JRuby::runtime.ir_manager.schedule_passes
|
75
|
+
scheduler.map do |pass|
|
76
|
+
pass_to_s(pass)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def reset_scheduler
|
81
|
+
ir_manager = JRuby::runtime.ir_manager
|
82
|
+
@scheduler = ir_manager.schedule_passes.iterator
|
83
|
+
if @scheduler.has_next
|
84
|
+
@current_pass = nil
|
85
|
+
@next_pass = @scheduler.next
|
86
|
+
else
|
87
|
+
@current_pass = @next_pass = nil
|
88
|
+
end
|
89
|
+
# trigger to re build the old ir scope
|
90
|
+
@ir_scope.set(self.class.build_ir(@ast_root.get))
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(ruby_code='')
|
94
|
+
@ruby_code = SimpleStringProperty.new(ruby_code)
|
95
|
+
@ast_root = SimpleObjectProperty.new(self, 'ast_root', self.class.parse(ruby_code))
|
96
|
+
@ir_scope = FireChangeObjectProperty.new(self, 'ir_scope', self.class.build_ir(@ast_root.get))
|
97
|
+
# bind change of Ruby code to reparsing an AST and set the property
|
98
|
+
@ruby_code.add_invalidation_listener do |new_code_property|
|
99
|
+
@ast_root.set(self.class.parse(new_code_property.get))
|
100
|
+
end
|
101
|
+
# bind change of AST to rebuilding IR and set the property
|
102
|
+
@ast_root.add_invalidation_listener do |new_ast_property|
|
103
|
+
@ir_scope.set(self.class.build_ir(new_ast_property.get))
|
104
|
+
end
|
105
|
+
|
106
|
+
# initialize scheduler
|
107
|
+
reset_scheduler
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.run_pass_on_all_scopes(pass, scope)
|
111
|
+
pass.run(scope)
|
112
|
+
scope.lexical_scopes.each do |lex_scope|
|
113
|
+
run_pass_on_all_scopes(pass, lex_scope)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def step_ir_passes
|
118
|
+
if @next_pass
|
119
|
+
@current_pass = @next_pass
|
120
|
+
@next_pass =
|
121
|
+
if @scheduler.has_next
|
122
|
+
@scheduler.next
|
123
|
+
else
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
scope = @ir_scope.get
|
127
|
+
self.class.run_pass_on_all_scopes(@current_pass, scope)
|
128
|
+
@ir_scope.set(scope)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
=begin
|
2
|
+
JRuby Visualizer
|
3
|
+
Copyright (C) 2013 The JRuby Team
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
16
|
+
=end
|
17
|
+
|
18
|
+
require 'jrubyfx'
|
19
|
+
|
20
|
+
#
|
21
|
+
# A custom JRubyFX control for a basic block of a CFG. This view is a composite
|
22
|
+
# with a TextArea for the basic blocks and live links to basic blocks that
|
23
|
+
# can succeed after this one
|
24
|
+
#
|
25
|
+
class BasicBlockBox < Java::javafx.scene.layout.VBox
|
26
|
+
include JRubyFX
|
27
|
+
|
28
|
+
attr_reader :basic_block, :instrs_box, :successors, :successor_buttons
|
29
|
+
|
30
|
+
def initialize(basic_block, cfg, cfg_list_view)
|
31
|
+
super(5)
|
32
|
+
@basic_block = basic_block
|
33
|
+
instructions = instrs
|
34
|
+
@instrs_box = TextArea.new(instructions)
|
35
|
+
line_no = instructions.lines.count
|
36
|
+
@instrs_box.set_pref_row_count(line_no)
|
37
|
+
@instrs_box.set_style('-fx-font-family: monospaced')
|
38
|
+
@instrs_box.set_editable(false)
|
39
|
+
@successors = cfg.get_outgoing_destinations(@basic_block).to_a
|
40
|
+
if @successors.empty?
|
41
|
+
get_children << @instrs_box
|
42
|
+
else
|
43
|
+
@successor_buttons = @successors.map do |bb|
|
44
|
+
button = Button.new(bb.to_s)
|
45
|
+
button.set_on_action do
|
46
|
+
# TODO rewrite this ugly code
|
47
|
+
i = button.get_text.scan(/.*\[(\d+):.*\]/)[0][0]
|
48
|
+
index = i.to_i - 1
|
49
|
+
cfg_list_view.selection_model.select(index)
|
50
|
+
cfg_list_view.focus_model.focus(index)
|
51
|
+
cfg_list_view.scroll_to(index)
|
52
|
+
end
|
53
|
+
button
|
54
|
+
end
|
55
|
+
|
56
|
+
successors_layout = HBox.new(5)
|
57
|
+
successors_layout.get_children << Label.new('Successors: ')
|
58
|
+
successors_layout.get_children.add_all(@successor_buttons)
|
59
|
+
|
60
|
+
get_children.add_all(@instrs_box, successors_layout)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def instrs
|
65
|
+
instrs_string = @basic_block.to_string_instrs
|
66
|
+
if instrs_string.end_with?("\n")
|
67
|
+
instrs_string[0...-1]
|
68
|
+
else
|
69
|
+
instrs_string
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# The UI for the CFG as a wrapper for a ListView that is directly built
|
77
|
+
# from JRuby's CFG. In order to get a resizable UI element, a BorderPane is
|
78
|
+
# used
|
79
|
+
#
|
80
|
+
class ControlFlowGraphView < Java::javafx.scene.layout.BorderPane
|
81
|
+
include JRubyFX
|
82
|
+
|
83
|
+
def initialize(cfg)
|
84
|
+
super()
|
85
|
+
@cfg = cfg
|
86
|
+
@cfg_list_view = ListView.new
|
87
|
+
@bb_cells = FXCollections.observable_array_list([])
|
88
|
+
@cfg.sorted_basic_blocks.each do |bb|
|
89
|
+
bb_cell = BasicBlockBox.new(bb, cfg, @cfg_list_view)
|
90
|
+
@bb_cells << bb_cell
|
91
|
+
end
|
92
|
+
@cfg_list_view.set_items(@bb_cells)
|
93
|
+
set_center(@cfg_list_view)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|