├── .gitignore ├── README ├── apidocs.tar.gz ├── examples ├── button.rb ├── circle.rb ├── drag_listener.rb ├── form.rb ├── tab_folder.rb ├── tab_transfer.rb └── write_png.rb ├── jfc_test └── ts_chart_demo.rb ├── jfreechart ├── gnujaxp.jar ├── iText-2.1.5.jar ├── jcommon-1.0.16.jar ├── jfreechart-1.0.13-experimental.jar ├── jfreechart-1.0.13-swt.jar ├── jfreechart-1.0.13.jar ├── jfreechart_wrapper.rb ├── junit.jar ├── servlet.jar └── swtgraphics2d.jar └── swt ├── graphics_utils.rb ├── swt_linux.jar ├── swt_linux64.jar ├── swt_osx.jar ├── swt_osx64.jar ├── swt_win32.jar └── swt_wrapper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | apidocs/* 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | JRuby SWT Cookbook 3 | ================== 4 | 5 | JRuby and SWT is a great platform for writing cross-platform desktop applications. This repo contains 6 | examples of how to get started. 7 | 8 | Why JRuby/SWT? 9 | -------------- 10 | 11 | * Fast, compatible Ruby implementation. 12 | * JRuby and SWT are flawlessly cross-platform. 13 | * SWT has native widgets (for the most part). 14 | * SWT powers Eclipse, so there's nothing you need that it doesn't do. 15 | * You don't need to touch Java! Write everything in Ruby. 16 | 17 | Installation 18 | ------------ 19 | 20 | You will need to run these examples with JRuby. Get it from jruby.org. 21 | 22 | *API Docs* 23 | 24 | The apidocs.tar.gz file contains SWT API reference docs. Untar the file and open apidocs/index.html in 25 | your browser. The comments in all the examples refers to files in these docs. They are the Java api, however 26 | JRuby does a great job of interfacing with Java, so you can usually just call into them as if they were Ruby 27 | classes and methods. 28 | 29 | Running Examples 30 | ---------------- 31 | 32 | On Windows and Linux: 33 | 34 | $ jruby ex1_button/ex1_button.rb 35 | 36 | On OSX: 37 | 38 | $ jruby -J-XstartOnFirstThread ex1_button/ex1_button.rb 39 | -------------------------------------------------------------------------------- /apidocs.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/apidocs.tar.gz -------------------------------------------------------------------------------- /examples/button.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'java' 3 | require 'swt/swt_wrapper' 4 | 5 | class ButtonExample 6 | 7 | def initialize 8 | # A Display is the connection between SWT and the native GUI. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Display.html) 9 | display = Swt::Widgets::Display.get_current 10 | 11 | # A Shell is a window in SWT parlance. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Shell.html) 12 | @shell = Swt::Widgets::Shell.new 13 | 14 | # A Shell must have a layout. FillLayout is the simplest. 15 | layout = Swt::Layout::FillLayout.new 16 | @shell.setLayout(layout) 17 | 18 | # Create a button widget (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Button.html) 19 | button = Swt::Widgets::Button.new(@shell, Swt::SWT::PUSH) 20 | button.set_text("Click Me") 21 | 22 | # Add a button click listener 23 | button.add_selection_listener do 24 | puts "You clicked the button." 25 | end 26 | 27 | # This lays out the widgets in the Shell 28 | @shell.pack 29 | 30 | # And this displays the Shell 31 | @shell.open 32 | end 33 | 34 | # This is the main gui event loop 35 | def start 36 | display = Swt::Widgets::Display.get_current 37 | 38 | # until the window (the Shell) has been closed 39 | while !@shell.isDisposed 40 | 41 | # check for and dispatch new gui events 42 | display.sleep unless display.read_and_dispatch 43 | end 44 | 45 | display.dispose 46 | end 47 | end 48 | 49 | Swt::Widgets::Display.set_app_name "Button Example" 50 | 51 | app = ButtonExample.new 52 | app.start 53 | 54 | -------------------------------------------------------------------------------- /examples/circle.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'java' 3 | require 'swt/swt_wrapper' 4 | 5 | class CircleExample 6 | 7 | def initialize 8 | # A Display is the connection between SWT and the native GUI. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Display.html) 9 | display = Swt::Widgets::Display.get_current 10 | 11 | # A Shell is a window in SWT parlance. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Shell.html) 12 | @shell = Swt::Widgets::Shell.new 13 | 14 | @shell.add_paint_listener do |event| 15 | rect = @shell.client_area 16 | event.gc.draw_oval(0, 0, rect.width - 1, rect.height - 1) 17 | end 18 | 19 | client_area = @shell.client_area 20 | 21 | @shell.set_bounds(client_area.x + 10, client_area.y + 10, 200, 200) 22 | 23 | # And this displays the Shell 24 | @shell.open 25 | end 26 | 27 | # This is the main gui event loop 28 | def start 29 | display = Swt::Widgets::Display.get_current 30 | 31 | # until the window (the Shell) has been closed 32 | while !@shell.isDisposed 33 | 34 | # check for and dispatch new gui events 35 | display.sleep unless display.read_and_dispatch 36 | end 37 | 38 | display.dispose 39 | end 40 | end 41 | 42 | Swt::Widgets::Display.set_app_name "Circle Example" 43 | 44 | app = CircleExample.new 45 | app.start 46 | 47 | -------------------------------------------------------------------------------- /examples/drag_listener.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require File.expand_path(File.join(__FILE__, '../../swt/swt_wrapper')) 3 | 4 | class ExDragListener 5 | include org.eclipse.swt.dnd.DragSourceListener 6 | include org.eclipse.swt.dnd.DropTargetListener 7 | 8 | class MPaintListener 9 | include org.eclipse.swt.events.PaintListener 10 | 11 | attr_accessor :selected_item 12 | 13 | def initialize(tabfolder) 14 | @folder = tabfolder 15 | @selected_item = nil 16 | end 17 | 18 | def paintControl(event) 19 | if @selected_item 20 | clientArea = @selected_item.bounds 21 | event.gc.drawRectangle(clientArea) 22 | end 23 | end 24 | end 25 | 26 | def initialize(tab_folder) 27 | @tab_folder = tab_folder 28 | @paint_listener = MPaintListener.new(@tab_folder) 29 | end 30 | 31 | def dragStart(dsEvent) 32 | @dragging = true 33 | @source_item = @tab_folder.get_selection 34 | @tab_folder.addPaintListener(@paint_listener) 35 | end 36 | 37 | def dragFinished(dsEvent) 38 | @dragging = false 39 | @paint_listener.selected_item = nil 40 | @tab_folder.removePaintListener(@paint_listener) 41 | @tab_folder.redraw 42 | end 43 | 44 | def dropAccept(event) 45 | event.detail = Swt::DND::DND::DROP_NONE # Don't actually accept the drop 46 | if @dragging && @source_item 47 | target_tab_item = @tab_folder.get_item(@tab_folder.to_control(event.x, event.y)) 48 | unless target_tab_item # drop happened behing the tabs 49 | target_tab_item = @tab_folder.get_items[@tab_folder.get_items.length - 1] 50 | end 51 | unless target_tab_item == @source_item # items need to be moved 52 | insert(@source_item, target_tab_item) 53 | end 54 | end 55 | dragFinished(event) 56 | end 57 | 58 | def insert(item1, item2) 59 | if @tab_folder.index_of(item1) < @tab_folder.index_of(item2) 60 | insert_after(item1, item2) 61 | else 62 | insert_before(item1, item2) 63 | end 64 | @tab_folder.set_selection(item2) 65 | end 66 | 67 | def insert_after(item1, item2) 68 | # Exclude the last item from the list of moving items -> the moved on will go there 69 | moving_items = @tab_folder.get_items[@tab_folder.index_of(item1)...@tab_folder.index_of(item2)] 70 | # Save the item's values, that is to be moved 71 | saved_values = [item1.get_control, item1.get_font, item1.get_tool_tip_text, item1.get_text] 72 | 73 | moving_items.each do |item| # move all tabs down from right to left 74 | next_item = @tab_folder.get_item(@tab_folder.index_of(item) + 1) 75 | item.set_control(next_item.get_control) 76 | item.set_font(next_item.get_font) 77 | item.set_tool_tip_text(next_item.get_tool_tip_text) 78 | item.set_text(next_item.get_text) 79 | end 80 | 81 | # finally, update the last item to make it the new position of the dropped item 82 | item2.set_control(saved_values[0]) 83 | item2.set_font(saved_values[1]) 84 | item2.set_tool_tip_text(saved_values[2]) 85 | item2.set_text(saved_values[3]) 86 | end 87 | 88 | def insert_before(item1, item2) 89 | # Exclude the first item from the list of moving items -> the moved on will go there 90 | moving_items_reverse = @tab_folder.get_items[(@tab_folder.index_of(item2) + 1)..@tab_folder.index_of(item1)] 91 | # Save the item's values, that is to be moved to the front 92 | saved_values = [item1.get_control, item1.get_font, item1.get_tool_tip_text, item1.get_text] 93 | 94 | moving_items = moving_items_reverse.to_a 95 | #moving_items_reverse.each {|i| moving_items << i} 96 | moving_items.reverse.each do |item| # move all tabs up from left to right 97 | next_item = @tab_folder.get_item(@tab_folder.index_of(item) - 1) 98 | item.set_control(next_item.get_control) 99 | item.set_font(next_item.get_font) 100 | item.set_tool_tip_text(next_item.get_tool_tip_text) 101 | item.set_text(next_item.get_text) 102 | end 103 | 104 | # finally, update the last item to make it the new position of the dropped item 105 | item2.set_control(saved_values[0]) 106 | item2.set_font(saved_values[1]) 107 | item2.set_tool_tip_text(saved_values[2]) 108 | item2.set_text(saved_values[3]) 109 | end 110 | 111 | # Must implement the java interface 112 | def dragSetData(dsEvent); end 113 | def dragEnter(event); end 114 | def dragLeave(event); end 115 | def dragOperationChanged(event); end 116 | def dragOver(event) 117 | target_tab_item = @tab_folder.get_item(@tab_folder.to_control(event.x, event.y)) 118 | unless target_tab_item # drop happened behing the tabs 119 | target_tab_item = @tab_folder.get_items[@tab_folder.get_items.length - 1] 120 | end 121 | @paint_listener.selected_item = target_tab_item 122 | @tab_folder.redraw 123 | end 124 | def drop(event); end 125 | end -------------------------------------------------------------------------------- /examples/form.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'java' 3 | require 'swt/swt_wrapper' 4 | 5 | # Application showing equivalents of various HTML form elements 6 | 7 | class SimpleApplication 8 | def initialize 9 | # A Display is the connection between SWT and the native GUI. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Display.html) 10 | display = Swt::Widgets::Display.get_current 11 | 12 | # A Shell is a window in SWT parlance. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Shell.html) 13 | @shell = Swt::Widgets::Shell.new 14 | 15 | # A Shell must have a layout. FillLayout is the simplest. 16 | layout = Swt::Layout::FillLayout.new 17 | layout.type = Swt::SWT::VERTICAL 18 | @shell.setLayout(layout) 19 | 20 | build_app! 21 | 22 | # This lays out the widgets in the Shell 23 | @shell.pack 24 | 25 | # And this displays the Shell 26 | @shell.open 27 | end 28 | 29 | # This is the main gui event loop 30 | def start 31 | display = Swt::Widgets::Display.get_current 32 | 33 | # until the window (the Shell) has been closed 34 | while !@shell.isDisposed 35 | 36 | # check for and dispatch new gui events 37 | display.sleep unless display.read_and_dispatch 38 | end 39 | 40 | display.dispose 41 | end 42 | end 43 | 44 | class FormApplication < SimpleApplication 45 | def build_app! 46 | label1 = Swt::Widgets::Label.new(@shell, Swt::SWT::CENTER) 47 | label1.set_text("Center Label") 48 | 49 | label2 = Swt::Widgets::Label.new(@shell, Swt::SWT::LEFT) 50 | label2.set_text("Left Label") 51 | 52 | combo = Swt::Widgets::Combo.new(@shell, 0) 53 | combo.add("First Choice") 54 | combo.add("Second Choice") 55 | combo.add("Third Choice") 56 | 57 | text = Swt::Widgets::Text.new(@shell, 0) 58 | text.set_text("Text box") 59 | text.set_message("Type some text here") 60 | 61 | button = Swt::Widgets::Button.new(@shell, Swt::SWT::PUSH) 62 | button.set_text("Click Me") 63 | button.add_selection_listener do 64 | puts "You clicked the button." 65 | end 66 | end 67 | end 68 | 69 | Swt::Widgets::Display.set_app_name "Form Example" 70 | 71 | app = FormApplication.new 72 | app.start 73 | -------------------------------------------------------------------------------- /examples/tab_folder.rb: -------------------------------------------------------------------------------- 1 | 2 | require 'java' 3 | require File.expand_path(File.join(__FILE__, '../../swt/swt_wrapper')) 4 | require File.expand_path(File.join(__FILE__, '../../swt/graphics_utils')) 5 | require File.expand_path(File.join(__FILE__, '../ex_drag_listener')) 6 | require File.expand_path(File.join(__FILE__, '../tab_transfer')) 7 | 8 | class VerticalTabLabel 9 | attr_reader :active, :title 10 | attr_accessor :font 11 | 12 | include Swt::Events::MouseListener 13 | 14 | def initialize(tab, parent, style) 15 | @label = Swt::Widgets::Label.new(parent, style) 16 | @active = false 17 | @tab = tab 18 | @parent = parent 19 | @title = "" 20 | 21 | @label.image = label_image 22 | @label.add_paint_listener { |event| event.gc.draw_image(label_image, 0, 0) } 23 | @label.add_mouse_listener(self) 24 | end 25 | 26 | def label_image 27 | display = Swt::Widgets::Display.current 28 | @img = nil if @dirty 29 | @img ||= GraphicsUtils.create_rotated_text(@title, @font, @parent.foreground, @parent.background, Swt::SWT::UP) do |gc, extent| 30 | fg, bg = gc.foreground, gc.background 31 | if @active 32 | options = @tab.selection_color_options 33 | options[:percents].each_with_index do |p, idx| 34 | gc.foreground = options[:colors][idx] 35 | gc.background = options[:colors][idx + 1] 36 | if options[:vertical] 37 | h = idx > 0 ? extent.height * options[:percents][idx - 1] : 0 38 | gc.fill_gradient_rectangle(0, h, extent.width, extent.height * p, true) 39 | else 40 | h = idx > 0 ? extent.width * options[:percents][idx - 1] : 0 41 | gc.fill_gradient_rectangle(w, 0, extent.width * p, extent.height, false) 42 | end 43 | end 44 | else 45 | gc.fill_rectangle(0, 0, extent.width, extent.height) 46 | end 47 | gc.foreground = display.get_system_color(Swt::SWT::COLOR_WIDGET_NORMAL_SHADOW) 48 | gc.draw_rectangle(0, 0, extent.width - 1, extent.height - 1) 49 | gc.foreground, gc.background = fg, bg 50 | end 51 | @dirty = false 52 | @img 53 | end 54 | 55 | def activate 56 | @tab.activate 57 | end 58 | 59 | def active= boolean 60 | @active = boolean 61 | redraw 62 | end 63 | 64 | def title= (str) 65 | @title = str 66 | redraw 67 | end 68 | 69 | def redraw 70 | @dirty = true 71 | @label.image = label_image 72 | end 73 | 74 | def mouseUp(e) 75 | activate 76 | end 77 | 78 | # Unused 79 | def mouseDown(e); end 80 | def mouseDoubleClick(e); end 81 | end 82 | 83 | class VerticalTabItem 84 | attr_accessor :text, :control 85 | 86 | def initialize(parent, style) 87 | @parent = parent 88 | @parent.add_item(self) 89 | end 90 | 91 | def text= title 92 | @text = title 93 | @label.title = title 94 | end 95 | 96 | def control= control 97 | @control = control 98 | @control.visible = active? 99 | @control.layout_data = Swt::Layout::GridData.new.tap do |l| 100 | l.horizontalAlignment = Swt::Layout::GridData::FILL 101 | l.verticalAlignment = Swt::Layout::GridData::FILL 102 | l.grabExcessHorizontalSpace = true 103 | l.exclude = active? 104 | end 105 | end 106 | 107 | def draw_label(tab_area) 108 | @label = VerticalTabLabel.new(self, tab_area, Swt::SWT::NONE) 109 | end 110 | 111 | # This way up to the parent 112 | def activate 113 | @parent.selection = self 114 | end 115 | 116 | def active= boolean 117 | @label.active = boolean 118 | if @control 119 | @control.visible = boolean 120 | @control.layout_data.exclude = !boolean 121 | end 122 | end 123 | 124 | def active? 125 | @label.active 126 | end 127 | 128 | def selection_color_options 129 | @parent.selection_color_options 130 | end 131 | 132 | def font= swt_font 133 | @label.font = swt_font 134 | end 135 | 136 | def font 137 | @label.font 138 | end 139 | end 140 | 141 | class VerticalTabFolder < Swt::Widgets::Composite 142 | attr_accessor :tab_area, :content_area 143 | attr_reader :selection_color_options, :font 144 | 145 | SelectionEvent = Struct.new("Event", :item, :doit) 146 | 147 | def initialize(parent, style) 148 | super(parent, style) 149 | self.layout = Swt::Layout::GridLayout.new(2, false).tap do |l| 150 | l.horizontalSpacing = -1 151 | end 152 | 153 | @items = [] 154 | @selection_listeners = [] 155 | @font = Swt::Widgets::Display.current.system_font 156 | 157 | @tab_area = Swt::Widgets::Composite.new(self, Swt::SWT::NONE).tap do |t| 158 | t.layout_data = Swt::Layout::GridData.new(Swt::Layout::GridData::FILL_VERTICAL | Swt::Layout::GridData::GRAB_VERTICAL) 159 | t.layout = Swt::Layout::RowLayout.new.tap do |l| 160 | l.type = Swt::SWT::VERTICAL 161 | l.spacing = -1 162 | l.wrap = false 163 | l.marginLeft = 0 164 | l.marginRight = 0 165 | l.marginTop = 0 166 | l.marginBottom = 0 167 | end 168 | end 169 | end 170 | 171 | def set_selection_background(colors, percents, vertical = true) 172 | @selection_color_options = { :colors => colors, 173 | :percents => percents.collect { |i| i / 100.0 }, 174 | :vertical => vertical } 175 | end 176 | 177 | def add_item(tab) 178 | @items << tab 179 | tab.draw_label(@tab_area) 180 | tab.font = @font 181 | tab.active = true if @items.size == 1 182 | layout 183 | end 184 | 185 | def get_item(idx) 186 | return @items[idx] if idx.respond_to? :to_int 187 | raise NotImplementedError, "Getting via Point not implemented" 188 | end 189 | 190 | def item_count 191 | @items.size 192 | end 193 | 194 | def selection 195 | @items.detect { |x| x.active? } 196 | end 197 | 198 | def selection=(tab) 199 | evt = SelectionEvent.new.tap do |e| 200 | e.item = tab 201 | e.doit = true 202 | end 203 | @selection_listeners.each do |l| 204 | if l.respond_to? :call 205 | l[evt] 206 | else 207 | l.widgetSelected(evt) 208 | end 209 | end 210 | if evt.doit 211 | selection.active = false 212 | if tab.respond_to? :to_int 213 | @items[tab].active = true 214 | else 215 | tab.active = true 216 | end 217 | layout 218 | end 219 | end 220 | 221 | def selection_index 222 | index_of(selection) 223 | end 224 | 225 | def index_of(tab) 226 | @items.index(tab) 227 | end 228 | 229 | def show_item(tab) 230 | selection = tab 231 | end 232 | 233 | def add_selection_listener(listener = nil) 234 | return @selection_listeners << listener if listener 235 | raise ArgumentError, "Expected a listener or a block" unless block_given? 236 | @selection_listeners << Proc.new 237 | end 238 | 239 | def font= swt_font 240 | @font = swt_font 241 | @items.each { |tab| tab.font = swt_font } 242 | end 243 | end 244 | 245 | class ButtonExample 246 | 247 | def initialize 248 | @insertMark = -1 249 | @tab_folder = nil 250 | 251 | # A Display is the connection between SWT and the native GUI. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Display.html) 252 | display = Swt::Widgets::Display.get_current 253 | 254 | # A Shell is a window in SWT parlance. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Shell.html) 255 | @shell = Swt::Widgets::Shell.new 256 | 257 | # A Shell must have a layout. FillLayout is the simplest. 258 | layout = Swt::Layout::FillLayout.new 259 | @shell.setLayout(layout) 260 | 261 | # Create composite 262 | composite = Swt::Widgets::Composite.new(@shell, Swt::SWT::NONE) 263 | composite.setLayoutData(Swt::Layout::GridData.new(Swt::Layout::GridData::FILL_HORIZONTAL)) 264 | composite.setLayout(Swt::Layout::GridLayout.new) 265 | composite.set_size(600,500) 266 | 267 | # btn = VerticalTab.new(composite, "oh boy") 268 | 269 | # Create a tabfolder 270 | @tab_folder = VerticalTabFolder.new(composite, Swt::SWT::TOP) 271 | @tab_folder.setLayoutData(Swt::Layout::GridData.new(Swt::Layout::GridData::FILL_BOTH)); 272 | @tab_folder.set_size(500,400) 273 | colors = [ Swt::Graphics::Color.new(display, 230, 240, 255), 274 | Swt::Graphics::Color.new(display, 170, 199, 246), 275 | Swt::Graphics::Color.new(display, 135, 178, 247) ] 276 | percents = [60, 85] 277 | @tab_folder.set_selection_background(colors, percents, true) 278 | items = 4.times.collect do |idx| 279 | i = VerticalTabItem.new(@tab_folder, Swt::SWT::NULL) 280 | i.text = "Item #{idx}" 281 | i.control = (Swt::Widgets::Text.new(@tab_folder, (Swt::SWT::BORDER | Swt::SWT::MULTI)).tap do |t| 282 | t.set_text("Text for Item #{idx}") 283 | end) 284 | end 285 | 286 | 287 | 288 | # And this displays the Shell 289 | @shell.open 290 | end 291 | 292 | def resetInsertMark 293 | @tabFolder.setInsertMark(@insertMark, true) 294 | # Workaround for bug #32846 295 | if (@insertMark == -1) 296 | @tabFolder.redraw() 297 | end 298 | end 299 | 300 | # This is the main gui event loop 301 | def start 302 | display = Swt::Widgets::Display.get_current 303 | 304 | # until the window (the Shell) has been closed 305 | while !@shell.isDisposed 306 | 307 | # check for and dispatch new gui events 308 | display.sleep unless display.read_and_dispatch 309 | end 310 | 311 | display.dispose 312 | end 313 | end 314 | 315 | Swt::Widgets::Display.set_app_name "Button Example" 316 | 317 | app = ButtonExample.new 318 | app.start 319 | 320 | -------------------------------------------------------------------------------- /examples/tab_transfer.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require File.expand_path(File.join(__FILE__, '../../swt/swt_wrapper')) 3 | 4 | class TabTransfer < Swt::DND::ByteArrayTransfer 5 | 6 | class TabType 7 | # Empty class for now 8 | end 9 | 10 | TAB_TYPE = "TabType" 11 | TAB_TYPE_ID = register_type(TAB_TYPE) 12 | @@instance = nil 13 | 14 | def self.get_instance 15 | @@instance || TabTransfer.new 16 | end 17 | 18 | def java_to_native(types, transfer_data) 19 | return if (types == null || types.empty? || (types.first.class != TabType)) 20 | 21 | if (is_supported_type(transfer_data)) 22 | super(TAB_TYPE.to_java_bytes, transfer_data) 23 | end 24 | end 25 | 26 | def native_to_java(transfer_data) 27 | if (is_supported_type(transfer_data)) 28 | buffer = super(transferData) 29 | return nil unless buffer 30 | 31 | if String.from_java_bytes buffer == TAB_TYPE 32 | return [TabType.new] 33 | end 34 | end 35 | return nil 36 | end 37 | 38 | def get_type_names 39 | [TAB_TYPE].to_java(:string) 40 | end 41 | 42 | def get_type_ids 43 | [TAB_TYPE_ID].to_java(:int) 44 | end 45 | 46 | def getTypeIds 47 | get_type_ids 48 | end 49 | 50 | def getTypeNames 51 | get_type_names 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /examples/write_png.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require 'swt/swt_wrapper' 3 | 4 | display = Swt::Widgets::Display.get_current 5 | font = Swt::Graphics::Font.new(display,"Comic Sans MS", 24, Swt::SWT::BOLD) 6 | image = Swt::Graphics::Image.new(display,87,48) 7 | gc = Swt::Graphics::GC.new(image) 8 | 9 | gc.set_background(display.get_system_color(Swt::SWT::COLOR_WHITE)) 10 | gc.fill_rectangle(image.get_bounds) 11 | gc.set_font(font) 12 | gc.set_foreground(display.get_system_color(Swt::SWT::COLOR_RED)) 13 | gc.draw_string("S", 3, 0) 14 | gc.set_foreground(display.get_system_color(Swt::SWT::COLOR_GREEN)) 15 | gc.draw_string("W", 25, 0) 16 | gc.set_foreground(display.get_system_color(Swt::SWT::COLOR_BLUE)) 17 | gc.draw_string("T", 62, 0) 18 | gc.dispose 19 | 20 | loader = Swt::Graphics::ImageLoader.new 21 | loader.data = [image.get_image_data] 22 | loader.save("swt.png", Swt::SWT::IMAGE_PNG); 23 | 24 | image.dispose 25 | font.dispose 26 | display.dispose 27 | -------------------------------------------------------------------------------- /jfc_test/ts_chart_demo.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require 'swt/swt_wrapper' 3 | require 'jfreechart/jfreechart_wrapper' 4 | 5 | puts "Starting demo!" 6 | 7 | class TimeSeriesExample 8 | 9 | def initialize 10 | # A Display is the connection between SWT and the native GUI. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Display.html) 11 | display = Swt::Widgets::Display.get_current 12 | 13 | # A Shell is a window in SWT parlance. (jruby-swt-cookbook/apidocs/org/eclipse/swt/widgets/Shell.html) 14 | @shell = Swt::Widgets::Shell.new 15 | 16 | # A Shell must have a layout. FillLayout is the simplest. 17 | layout = Swt::Layout::FillLayout.new 18 | @shell.setLayout(layout) 19 | 20 | # create a jfreechart! 21 | chart = create_chart(create_dataset) 22 | frame = Jfree::Experimental::Chart::Swt::ChartComposite.new(@shell, Swt::SWT::NONE, chart, true); 23 | frame.setDisplayToolTips(true); 24 | frame.setHorizontalAxisTrace(false); 25 | frame.setVerticalAxisTrace(false); 26 | 27 | @shell.setSize(1000, 500); 28 | @shell.setText("Time series demo for jfreechart running with SWT"); 29 | 30 | # This lays out the widgets in the Shell 31 | #@shell.pack 32 | 33 | # And this displays the Shell 34 | @shell.open 35 | end 36 | 37 | def create_chart(dataset) 38 | chart = Jfree::Chart::ChartFactory.createTimeSeriesChart( 39 | "Legal & General Unit Trust Prices", # title 40 | "Date", # x-axis label 41 | "Price Per Unit", # y-axis label 42 | dataset, # data 43 | true, # create legend? 44 | true, # generate tooltips? 45 | false # generate URLs? 46 | ); 47 | 48 | chart.setBackgroundPaint(java.awt.Color.white); 49 | 50 | plot = chart.getPlot(); 51 | plot.setBackgroundPaint(java.awt.Color.lightGray); 52 | plot.setDomainGridlinePaint(java.awt.Color.white); 53 | plot.setRangeGridlinePaint(java.awt.Color.white); 54 | plot.setAxisOffset(Jfree::Ui::RectangleInsets.new(5.0, 5.0, 5.0, 5.0)); 55 | plot.setDomainCrosshairVisible(true); 56 | plot.setRangeCrosshairVisible(true); 57 | 58 | renderer = plot.getRenderer(); 59 | renderer.setBaseShapesVisible(true); 60 | renderer.setBaseShapesFilled(true); 61 | 62 | axis = plot.getDomainAxis(); 63 | axis.setDateFormatOverride(java.text.SimpleDateFormat.new("MMM-yyyy")); 64 | 65 | chart 66 | end 67 | 68 | def create_dataset 69 | s1 = Jfree::Data::Time::TimeSeries.new("L&G European Index Trust") 70 | s1.add(Jfree::Data::Time::Month.new(2, 2001), 181.8) 71 | s1.add(Jfree::Data::Time::Month.new(3, 2001), 167.3) 72 | s1.add(Jfree::Data::Time::Month.new(4, 2001), 153.8) 73 | s1.add(Jfree::Data::Time::Month.new(5, 2001), 167.6) 74 | s1.add(Jfree::Data::Time::Month.new(6, 2001), 158.8) 75 | s1.add(Jfree::Data::Time::Month.new(7, 2001), 148.3) 76 | s1.add(Jfree::Data::Time::Month.new(8, 2001), 153.9) 77 | s1.add(Jfree::Data::Time::Month.new(9, 2001), 142.7) 78 | s1.add(Jfree::Data::Time::Month.new(10, 2001), 123.2) 79 | s1.add(Jfree::Data::Time::Month.new(11, 2001), 131.8) 80 | s1.add(Jfree::Data::Time::Month.new(12, 2001), 139.6) 81 | s1.add(Jfree::Data::Time::Month.new(1, 2002), 142.9) 82 | s1.add(Jfree::Data::Time::Month.new(2, 2002), 138.7) 83 | s1.add(Jfree::Data::Time::Month.new(3, 2002), 137.3) 84 | s1.add(Jfree::Data::Time::Month.new(4, 2002), 143.9) 85 | s1.add(Jfree::Data::Time::Month.new(5, 2002), 139.8) 86 | s1.add(Jfree::Data::Time::Month.new(6, 2002), 137.0) 87 | s1.add(Jfree::Data::Time::Month.new(7, 2002), 132.8) 88 | 89 | s2 = Jfree::Data::Time::TimeSeries.new("L&G UK Index Trust") 90 | s2.add(Jfree::Data::Time::Month.new(2, 2001), 129.6) 91 | s2.add(Jfree::Data::Time::Month.new(3, 2001), 123.2) 92 | s2.add(Jfree::Data::Time::Month.new(4, 2001), 117.2) 93 | s2.add(Jfree::Data::Time::Month.new(5, 2001), 124.1) 94 | s2.add(Jfree::Data::Time::Month.new(6, 2001), 122.6) 95 | s2.add(Jfree::Data::Time::Month.new(7, 2001), 119.2) 96 | s2.add(Jfree::Data::Time::Month.new(8, 2001), 116.5) 97 | s2.add(Jfree::Data::Time::Month.new(9, 2001), 112.7) 98 | s2.add(Jfree::Data::Time::Month.new(10, 2001), 101.5) 99 | s2.add(Jfree::Data::Time::Month.new(11, 2001), 106.1) 100 | s2.add(Jfree::Data::Time::Month.new(12, 2001), 110.3) 101 | s2.add(Jfree::Data::Time::Month.new(1, 2002), 111.7) 102 | s2.add(Jfree::Data::Time::Month.new(2, 2002), 111.0) 103 | s2.add(Jfree::Data::Time::Month.new(3, 2002), 109.6) 104 | s2.add(Jfree::Data::Time::Month.new(4, 2002), 113.2) 105 | s2.add(Jfree::Data::Time::Month.new(5, 2002), 111.6) 106 | s2.add(Jfree::Data::Time::Month.new(6, 2002), 108.8) 107 | s2.add(Jfree::Data::Time::Month.new(7, 2002), 101.6) 108 | 109 | dataset = Jfree::Data::Time::TimeSeriesCollection.new 110 | dataset.addSeries(s1) 111 | dataset.addSeries(s2) 112 | 113 | dataset 114 | end 115 | 116 | # This is the main gui event loop 117 | def start 118 | display = Swt::Widgets::Display.get_current 119 | 120 | # until the window (the Shell) has been closed 121 | while !@shell.isDisposed 122 | # check for and dispatch new gui events 123 | display.sleep unless display.read_and_dispatch 124 | end 125 | display.dispose 126 | end 127 | end 128 | 129 | Swt::Widgets::Display.set_app_name "Timeseries Example" 130 | 131 | app = TimeSeriesExample.new 132 | app.start 133 | exit -------------------------------------------------------------------------------- /jfreechart/gnujaxp.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/gnujaxp.jar -------------------------------------------------------------------------------- /jfreechart/iText-2.1.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/iText-2.1.5.jar -------------------------------------------------------------------------------- /jfreechart/jcommon-1.0.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/jcommon-1.0.16.jar -------------------------------------------------------------------------------- /jfreechart/jfreechart-1.0.13-experimental.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/jfreechart-1.0.13-experimental.jar -------------------------------------------------------------------------------- /jfreechart/jfreechart-1.0.13-swt.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/jfreechart-1.0.13-swt.jar -------------------------------------------------------------------------------- /jfreechart/jfreechart-1.0.13.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/jfreechart-1.0.13.jar -------------------------------------------------------------------------------- /jfreechart/jfreechart_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require 'swt/swt_wrapper' 3 | 4 | module Jfree 5 | 6 | def self.jars 7 | [ 8 | 'jcommon-1.0.16', 9 | 'swtgraphics2d', 10 | 'jfreechart-1.0.13', 11 | 'jfreechart-1.0.13-swt', 12 | 'jfreechart-1.0.13-experimental', 13 | 'iText-2.1.5' 14 | ] 15 | end 16 | 17 | Jfree.jars.each do |jar| 18 | path = File.expand_path(File.dirname(__FILE__) + "/../jfreechart/" + jar) 19 | if File.exist?(path + ".jar") 20 | puts "loading #{jar}" 21 | require path 22 | else 23 | raise "jar file required: #{path}.jar" 24 | end 25 | end 26 | 27 | module Chart 28 | import org.jfree.chart.ChartFactory 29 | import org.jfree.chart.JFreeChart 30 | module Axis 31 | import org.jfree.chart.axis.DateAxis 32 | end 33 | module Plot 34 | import org.jfree.chart.plot.XYPlot 35 | end 36 | module Renderer 37 | module Xy 38 | import org.jfree.chart.renderer.xy.XYItemRenderer 39 | import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer 40 | end 41 | end 42 | end 43 | 44 | module Data 45 | module Time 46 | import org.jfree.data.time.Month 47 | import org.jfree.data.time.TimeSeries 48 | import org.jfree.data.time.TimeSeriesCollection 49 | end 50 | module Xy 51 | import org.jfree.data.xy.XYDataset 52 | end 53 | end 54 | 55 | module Experimental 56 | module Chart 57 | module Swt 58 | import org.jfree.experimental.chart.swt.ChartComposite 59 | end 60 | end 61 | end 62 | 63 | module Ui 64 | import org.jfree.ui.RectangleInsets 65 | end 66 | end -------------------------------------------------------------------------------- /jfreechart/junit.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/junit.jar -------------------------------------------------------------------------------- /jfreechart/servlet.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/servlet.jar -------------------------------------------------------------------------------- /jfreechart/swtgraphics2d.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/jfreechart/swtgraphics2d.jar -------------------------------------------------------------------------------- /swt/graphics_utils.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../swt_wrapper", __FILE__) 2 | 3 | class GraphicsUtils 4 | import org.eclipse.swt.SWT 5 | import org.eclipse.swt.widgets.Display 6 | import org.eclipse.swt.graphics.FontMetrics 7 | import org.eclipse.swt.graphics.GC 8 | import org.eclipse.swt.graphics.Image 9 | import org.eclipse.swt.graphics.ImageData 10 | import org.eclipse.swt.graphics.Point 11 | import org.eclipse.swt.graphics.Rectangle 12 | 13 | ## 14 | # Draws text vertically (rotates plus or minus 90 degrees). Uses the current 15 | # font, color, and background. 16 | #
27 | # Note: Only one of the style UP or DOWN may be specified. 28 | #
29 | # 30 | def self.draw_vertical_text(string, x, y, gc, style) 31 | display = Display.current 32 | 33 | fm = gc.font_metrics 34 | pt = gc.text_extent(string) 35 | 36 | string_image = Image.new(display, pt.x, pt.y) 37 | string_gc = GC.new(string_image) 38 | 39 | string_gc.foreground = gc.foreground 40 | string_gc.background = gc.background 41 | string_gc.font = gc.font 42 | string_gc.draw_text(string, 0, 0) 43 | 44 | draw_vertical_image(string_image, x, y, gc, style) 45 | 46 | string_gc.dispose 47 | string_image.dispose 48 | end 49 | 50 | ## 51 | # Draws an image vertically (rotates plus or minus 90 degrees) 52 | #63 | # Note: Only one of the style UP or DOWN may be specified. 64 | #
65 | # 66 | def self.draw_vertical_image(image, x, y, gc, style) 67 | display = Display.current 68 | 69 | sd = image.image_data 70 | dd = ImageData.new(sd.height, sd.width, sd.depth, sd.palette) 71 | up = (style == SWT::UP) 72 | 73 | # Transform all pixels 74 | sd.width.times do |sx| 75 | sd.height.times do |sy| 76 | dx = up ? sy : sd.height - sy - 1 77 | dy = up ? sd.width - sx - 1 : sx 78 | dd.set_pixel(dx, dy, sd.get_pixel(sx, sy)) 79 | end 80 | end 81 | 82 | vertical = Image.new(display, dd) 83 | gc.draw_image(vertical, x, y) 84 | vertical.dispose 85 | end 86 | 87 | ## 88 | # Creates an image containing the specified text, rotated either plus or minus 89 | # 90 degrees. 90 | #103 | # Note: Only one of the style UP or DOWN may be specified. 104 | #
105 | # 106 | def self.create_rotated_text(text, font, foreground, background, style, options = {}) 107 | options = {:padding_x => 16, :padding_y => 4}.merge(options) 108 | 109 | display = Display.current 110 | 111 | gc = GC.new(display) 112 | gc.font = font 113 | 114 | fm = gc.font_metrics 115 | pt = gc.text_extent(text) 116 | extent = Rectangle.new(0, 0, pt.x + options[:padding_x], pt.y + options[:padding_y]) 117 | gc.dispose 118 | 119 | string_image = Image.new(display, extent.width, extent.height) 120 | 121 | gc = GC.new(string_image) 122 | 123 | gc.font = font 124 | gc.foreground = foreground 125 | gc.background = background 126 | 127 | # Do customization 128 | yield(gc, extent) if block_given? 129 | 130 | gc.draw_text(text, options[:padding_x] / 2, options[:padding_y] / 2, true) 131 | 132 | image = create_rotated_image(string_image, style) 133 | gc.dispose 134 | string_image.dispose 135 | return image 136 | end 137 | 138 | ## 139 | # Creates a rotated image (plus or minus 90 degrees) 140 | #149 | # Note: Only one of the style UP or DOWN may be specified. 150 | #
151 | # 152 | def self.create_rotated_image(image, style) 153 | display = Display.current 154 | 155 | sd = image.image_data 156 | dd = ImageData.new(sd.height, sd.width, sd.depth, sd.palette) 157 | 158 | up = (style == SWT::UP) 159 | 160 | sd.width.times do |sx| 161 | sd.height.times do |sy| 162 | dx = up ? sy : sd.height - sy - 1 163 | dy = up ? sd.width - sx - 1 : sx 164 | dd.set_pixel(dx, dy, sd.get_pixel(sx, sy)) 165 | end 166 | end 167 | 168 | return Image.new(display, dd) 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /swt/swt_linux.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/swt/swt_linux.jar -------------------------------------------------------------------------------- /swt/swt_linux64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/swt/swt_linux64.jar -------------------------------------------------------------------------------- /swt/swt_osx.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/swt/swt_osx.jar -------------------------------------------------------------------------------- /swt/swt_osx64.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/swt/swt_osx64.jar -------------------------------------------------------------------------------- /swt/swt_win32.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/jruby-swt-cookbook/e536892e7819322fcc264d0c59094ea6b47453dd/swt/swt_win32.jar -------------------------------------------------------------------------------- /swt/swt_wrapper.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | 3 | module Swt 4 | def self.jar_path 5 | case Config::CONFIG["host_os"] 6 | when /darwin/i 7 | if Config::CONFIG["host_cpu"] == "x86_64" 8 | 'swt/swt_osx64' 9 | else 10 | 'swt/swt_osx' 11 | end 12 | when /linux/i 13 | if %w(amd64 x86_64).include? Config::CONFIG["host_cpu"] 14 | 'swt/swt_linux64' 15 | else 16 | 'swt/swt_linux' 17 | end 18 | when /windows|mswin/i 19 | 'swt/swt_win32' 20 | end 21 | end 22 | 23 | path = File.expand_path(File.dirname(__FILE__) + "/../" + Swt.jar_path) 24 | if File.exist?(path + ".jar") 25 | puts "loading #{Swt.jar_path}" 26 | require path 27 | else 28 | puts "SWT jar file required: #{path}.jar" 29 | exit 30 | end 31 | 32 | import org.eclipse.swt.SWT 33 | 34 | module Widgets 35 | import org.eclipse.swt.widgets.Button 36 | import org.eclipse.swt.widgets.Caret 37 | import org.eclipse.swt.widgets.Combo 38 | import org.eclipse.swt.widgets.Composite 39 | import org.eclipse.swt.widgets.Display 40 | import org.eclipse.swt.widgets.Event 41 | import org.eclipse.swt.widgets.DirectoryDialog 42 | import org.eclipse.swt.widgets.FileDialog 43 | import org.eclipse.swt.widgets.Label 44 | import org.eclipse.swt.widgets.List 45 | import org.eclipse.swt.widgets.Menu 46 | import org.eclipse.swt.widgets.MenuItem 47 | import org.eclipse.swt.widgets.MessageBox 48 | import org.eclipse.swt.widgets.Shell 49 | import org.eclipse.swt.widgets.TabFolder 50 | import org.eclipse.swt.widgets.TabItem 51 | import org.eclipse.swt.widgets.Text 52 | import org.eclipse.swt.widgets.ToolTip 53 | end 54 | 55 | def self.display 56 | if defined?(SWT_APP_NAME) 57 | Swt::Widgets::Display.app_name = SWT_APP_NAME 58 | end 59 | @display ||= (Swt::Widgets::Display.getCurrent || Swt::Widgets::Display.new) 60 | end 61 | 62 | display # must be created before we import the Clipboard class. 63 | 64 | module Custom 65 | import org.eclipse.swt.custom.CTabFolder 66 | import org.eclipse.swt.custom.CTabItem 67 | import org.eclipse.swt.custom.SashForm 68 | import org.eclipse.swt.custom.StackLayout 69 | import org.eclipse.swt.custom.ST 70 | end 71 | 72 | module DND 73 | import org.eclipse.swt.dnd.Clipboard 74 | import org.eclipse.swt.dnd.Transfer 75 | import org.eclipse.swt.dnd.TextTransfer 76 | 77 | import org.eclipse.swt.dnd.DropTarget 78 | import org.eclipse.swt.dnd.DropTargetEvent 79 | import org.eclipse.swt.dnd.DropTargetListener 80 | 81 | import org.eclipse.swt.dnd.DragSource 82 | import org.eclipse.swt.dnd.DragSourceEvent 83 | import org.eclipse.swt.dnd.DragSourceListener 84 | import org.eclipse.swt.dnd.DND 85 | 86 | import org.eclipse.swt.dnd.ByteArrayTransfer 87 | end 88 | 89 | module Layout 90 | import org.eclipse.swt.layout.FillLayout 91 | import org.eclipse.swt.layout.FormAttachment 92 | import org.eclipse.swt.layout.FormLayout 93 | import org.eclipse.swt.layout.FormData 94 | import org.eclipse.swt.layout.GridLayout 95 | import org.eclipse.swt.layout.GridData 96 | import org.eclipse.swt.layout.RowLayout 97 | import org.eclipse.swt.layout.RowData 98 | import org.eclipse.swt.custom.StackLayout 99 | end 100 | 101 | module Graphics 102 | import org.eclipse.swt.graphics.Color 103 | import org.eclipse.swt.graphics.Font 104 | import org.eclipse.swt.graphics.FontMetrics 105 | import org.eclipse.swt.graphics.GC 106 | import org.eclipse.swt.graphics.Image 107 | import org.eclipse.swt.graphics.ImageData 108 | import org.eclipse.swt.graphics.Pattern 109 | import org.eclipse.swt.graphics.Point 110 | import org.eclipse.swt.graphics.Rectangle 111 | import org.eclipse.swt.graphics.ImageLoader 112 | end 113 | 114 | module Events 115 | import org.eclipse.swt.events.KeyEvent 116 | import org.eclipse.swt.events.MouseListener 117 | end 118 | 119 | import org.eclipse.swt.browser.Browser 120 | class Browser 121 | import org.eclipse.swt.browser.BrowserFunction 122 | end 123 | 124 | class RRunnable 125 | include java.lang.Runnable 126 | 127 | def initialize(&block) 128 | @block = block 129 | end 130 | 131 | def run 132 | @block.call 133 | end 134 | end 135 | end 136 | --------------------------------------------------------------------------------