Advertisement
nickmcski

Untitled

Jan 20th, 2015
293
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 41.17 KB | None | 0 0
  1. require 'thread'
  2.  
  3. class SD::Designer
  4. include JRubyFX::Controller
  5. java_import 'dashfx.lib.data.DataInitDescriptor'
  6. java_import 'dashfx.lib.data.InitInfo'
  7. java_import 'dashfx.lib.registers.ControlRegister'
  8.  
  9. property_accessor :running, :vc_index
  10. attr_accessor :view_controllers
  11.  
  12. fxml "SFX.fxml"
  13.  
  14. @@singleton = nil
  15.  
  16. def initialize
  17. raise "Multiple designers created!" if @@singleton
  18. @@singleton = self
  19. # best to catch missing stuff now
  20. @toolbox= @left_gutter
  21. @selected_items = []
  22. @dnd_ids = []
  23. @dnd_opts = {}
  24. @add_tab = @AddTab
  25. @toolbox_group = {}
  26. @root = @GridPane
  27. @savedSelection = 1
  28. @preparsed = 0
  29. @ui2pmap = {}
  30. @wingman = {}
  31. @selSem = false
  32. @mode = :design
  33. @toolbox_status = :hidden
  34. @aa_tree = @AATreeview
  35. @data_core_sublist = []
  36. @aa_tree_list = []
  37. @aa_tree.root = tree_item("/")
  38. @aa_tree_hash = {"/" => @aa_tree.root, "." => @aa_tree.root}
  39. @layout_managers = {}
  40. @view_controllers = FXCollections.observableArrayList
  41. @aa_name_trees = {}
  42. @aa_name_trees_threads = {}
  43. @running = simple_boolean_property(self, "running", false)
  44. @aa_ignores = []
  45. @vc_focus = []
  46. @vc_index = simple_integer_property(self, "vc_index", 0)
  47. @nested_list = {}
  48.  
  49. @overlay_pod.min_width_property.bind(@spain.width_property.subtract(2.0))
  50. @overlay_pod.min_height_property.bind(@spain.height_property.subtract(2.0))
  51.  
  52.  
  53. # load the custom regexer
  54. @aa_regexer.text_property.add_change_listener do |ov, ol, new|
  55. begin
  56. regx = Regexp.new(new, "i")
  57. # @aa_filter.regex = regx # TODO: fix
  58. # filter the list
  59. @aa_tree_list.each do |child|
  60. child[:fout] = child[:value].match(regx) == nil
  61. end
  62. # clear all the children
  63. @aa_tree.root.children.clear
  64. @aa_tree_hash.values.each do |v|
  65. v.children.clear
  66. end
  67. # add anything that matches
  68. @aa_tree_list.find_all{|x|!x[:fout]}.each{|x|add_known(x[:value])}
  69. @aa_regexer.style = "-fx-border-color: green;"
  70. @aa_regex_message.text = "Valid regex"
  71. rescue Exception => e
  72. @aa_regex_message.text = e.message
  73. @aa_regexer.style = "-fx-border-color: red;"
  74. end
  75. end
  76. aa_hide_regex_panel() # it shows by default
  77.  
  78. # On shown and on closing handlers
  79. @stage.set_on_shown { on_shown }
  80. end
  81.  
  82. def self.require_thread
  83. Thread.new do
  84. @@requires = [Mutex.new, Mutex.new, Mutex.new]
  85. @@requires[0].synchronize do
  86. require 'yaml'
  87. q = Dir["#{File.dirname(__FILE__)}/plugins/*.rb"]
  88. q.each {|file| require file}
  89. require 'rubygems/package'
  90. end
  91. while @@singleton == nil
  92. sleep 0.01
  93. end
  94. @@singleton.init_stage_1
  95. end
  96. end
  97.  
  98. def on_shown
  99. require 'designer_support/aa_tree_cell'
  100.  
  101. # Set the auto add tree cells
  102. @aa_tree.set_cell_factory do |q|
  103. SD::DesignerSupport::AATreeCell.new do
  104. hide_toolbox
  105. end
  106. end
  107.  
  108. require 'designer_support/preferences'
  109. # Create our data core. TODO: use preferences to configure it.
  110. @data_core = Java::dashfx.lib.data.DataCore.new()
  111. # when the data core finds about new names, let us know!
  112. @data_core.known_names.add_change_listener do |change|
  113. change.next # change is an "iterator" of stuff, so use next to get the added list
  114. change.added_sub_list.each do |new_name|
  115. next if @aa_ignores.include? new_name
  116. add_known new_name
  117. @view_controllers.each do |vc|
  118. if tmp = vc.should_add?(new_name, @data_core.known_names.get)
  119. bits = new_name.split('/').reject(&:empty?)
  120. mutex, thread, time_func = @aa_name_trees_threads[vc]
  121. mutex.synchronize do
  122. root = @aa_name_trees[vc]
  123. namepart = ""
  124. bits.each do |namebit|
  125. namepart += "/" # Can't use << here or we modify the strings that are already in the treeview
  126. namepart << namebit
  127. child = root.children[namebit]
  128. unless child
  129. child = root.children[namebit] = SD::DesignerSupport::AANameTree.new(namepart, root, time_func)
  130. end
  131. root = child
  132. end
  133. end
  134. thread.run
  135. end
  136. end
  137. end
  138. end
  139. # now! "shown"
  140. self.message = "Loading..."
  141. end
  142.  
  143. def init_stage_1
  144. # Load preferences
  145.  
  146. require 'designer_support/preferences' # might not be loaded yet
  147. @prefs = SD::DesignerSupport::Preferences
  148.  
  149. # get the team number
  150. # if the team number is set in prefs, use it
  151. ip = if !@prefs.team_number_auto and (1..9001).include? @prefs.team_number
  152. @prefs.team_number || 0
  153. else # otherwise, snag from nics and if we have not set any preferences, save it.
  154. snag_ip.tap do |x|
  155. if x and (1..9001).include? x and not @prefs.has_key? :team_number_auto
  156. @prefs.team_number_auto = false
  157. @prefs.team_number = x
  158. end
  159. end
  160. end
  161. if ip
  162. InitInfo.team_number = ip
  163. end
  164. @@requires[1].synchronize do
  165. # get all toolbox bits and add them to the ui toolbox
  166. run_later do
  167. pts = find_toolbox_parts
  168. #load recent files
  169. build_open_menu
  170. @@requires[1].synchronize do
  171. pts.each do |key, data|
  172. data.sort{|a, b| a.name <=> b.name }.each{|i| @toolbox_group[key].children.add SD::DesignerSupport::ToolboxItem.new(i, method(:associate_dnd_id))}
  173. end
  174. end
  175.  
  176.  
  177. SD::DesignerSupport::AANameTree.observable = ->(name){@data_core.get_observable name}
  178.  
  179. # do this now so props are fast to load
  180. @properties = SD::DesignerSupport::PropertiesPopup.new
  181. @locked_props = false
  182.  
  183. # when we blur, hide the properties window. TODO: property window needs improvements
  184. @stage.focused_property.add_change_listener do |v, o, new|
  185. unless @locked_props
  186. unless new
  187. @was_showing = @properties.showing?
  188. @properties.hide
  189. else
  190. @properties.show(@stage) if @was_showing
  191. end
  192. end
  193. end
  194.  
  195. @stage.on_close_request &method(:on_close_request)
  196.  
  197. # Add known tab and any plugin tabs
  198. main_vc = SD::Windowing::DefaultViewController.new
  199. main_vc.on_focus_request do |focus|
  200. tab_auto_focus(main_vc, focus)
  201. end
  202. main_tab = add_tab(main_vc)
  203. SD::Plugins.view_controllers.find_all{|x|x.default > 0}.each do |x|
  204. vc = x.new
  205. vc.on_focus_request do |focus|
  206. tab_auto_focus(vc, focus)
  207. end
  208. add_tab(vc)
  209. end
  210. tab_select(main_tab) unless @cur_canvas # don't override any auto_focus requests
  211.  
  212.  
  213. #TODO: use preferences for this. DEMO.
  214. @data_core.mountDataEndpoint(DataInitDescriptor.new(Java::dashfx.lib.data.endpoints.NetworkTables.new, "Default", InitInfo.new, "/"))
  215.  
  216. require 'playback'
  217. #TODO: use standard plugin arch for this
  218. @playback = SD::Playback.new(@data_core, @stage)
  219. @current_save_data = SD::IOSupport::DashObject.parse_scene_graph(@view_controllers.to_a, @data_core)
  220. if ARGV.length > 0 && File.exist?(ARGV[0])
  221. self.message = "Opening #{ARGV[0]}"
  222. open_file(ARGV[0])
  223. else
  224. self.message = "Ready"
  225. end
  226. # now! "finished"
  227. end
  228. require 'designer_support/toolbox_item'
  229. require 'designer_support/properties_popup'
  230. require 'designer_support/aa_name_tree'
  231. require "windowing/default_view_controller"
  232. end
  233. require 'designer_support/properties_popup'
  234. require 'ostruct'
  235. %W[designer_support plugins io_support windowing designers utils].each do |x|
  236. Dir["#{File.dirname(__FILE__)}/#{x}/*.rb"].each {|file|require file; }
  237. end
  238. end
  239.  
  240. # END INIT AREA
  241.  
  242. def on_close_request(event)
  243. stop_it = false
  244. # TODO: multi-windows
  245. # TODO: I cant seem to prevent window from closing
  246. if YAML.dump(SD::IOSupport::DashObject.parse_scene_graph(@view_controllers.to_a, @data_core)) != YAML.dump(@current_save_data)
  247. answer = SD::DesignerSupport::SaveQuestion.ask(@stage)
  248. if answer == :cancel
  249. event.consume
  250. stop_it = true
  251. elsif answer == :save
  252. event.consume if true == (stop_it = !save)
  253. end
  254. end
  255. #@aa_name_trees_threads.each{|k, v|v[1].kill}
  256. @data_core.dispose unless stop_it
  257. end
  258.  
  259. def lock_props
  260. @locked_props = true
  261. if block_given?
  262. q = yield
  263. unlock_props
  264. return q
  265. end
  266. end
  267.  
  268. def unlock_props
  269. @locked_props = false
  270. end
  271.  
  272. # iterate over all the interfaces on this computer and find one that matches 10.x.y.z, where x and y < 100
  273. def snag_ip
  274. java.net.NetworkInterface.network_interfaces.each do |networkInterface|
  275. networkInterface.inet_addresses.each do |inet|
  276. addr = inet.address
  277. if addr[0] == 10 && addr.length == 4 && addr[1] < 100 && addr[2] < 100 # TODO: will fail for alt 10.4.151.x
  278. teamn = addr[1] * 100 + addr[2]
  279. return teamn
  280. end
  281. end
  282. end
  283. end
  284.  
  285. # easy dnd way to send objects. should use actual text or an hash
  286. def associate_dnd_id(val, opts=nil)
  287. hide_toolbox # TODO: cheap hack
  288. @dnd_ids << val unless @dnd_ids.include?(val)
  289. @dnd_opts[val] = opts
  290. @dnd_ids.index(val)
  291. end
  292.  
  293. def reload_toolbox_style
  294. @toolbox_group.each do |key, values|
  295. values.children.each do |val|
  296. val.reload_fxml
  297. end
  298. end
  299. end
  300.  
  301. # Scrounge around and find everything that should go in the toolbox.
  302. def find_toolbox_parts
  303. unless @found_plugins # cache it as its expensive
  304. # TODO: exceptions
  305. SD::Plugins.load "built-in", lambda {|url|ControlRegister.java_class.resource url}
  306.  
  307. # check for the plugins folder
  308. plugin_yaml = $PLUGIN_DIR
  309. if Dir.exist? plugin_yaml
  310. Dir["#{plugin_yaml}/*"].each do |plugin_path|
  311. if plugin_path.end_with? ".css" # overload styles
  312. olss = @GridPane.stylesheets.to_a
  313. @GridPane.stylesheets.clear # required to cause refresh
  314. @GridPane.stylesheets.add_all(*olss, "file:" + plugin_path)
  315. next
  316. end
  317. SD::Plugins.load(plugin_path, if plugin_path.end_with? ".jar"
  318. require plugin_path
  319. class_loader = java.net.URLClassLoader.new([java.net.URL.new("file:#{plugin_path}")].to_java(java.net.URL))
  320. lambda {|url| class_loader.find_resource(url.gsub(%r{^/}, ''))}
  321. else
  322. lambda {|url| java.net.URL.new("file:#{plugin_path}/#{url}")}
  323. end)
  324. end
  325. end
  326. @toolbox_bits = SD::Plugins.controls.group_by{|x| x.category}
  327. @toolbox_bits.keys.each do |key|
  328. lfp = nil
  329. @accord.panes << (titled_pane(text: "Toolbox - #{key.nil? ? "Ungrouped" : key}") do
  330. sp = scroll_pane(fit_to_width: true, max_height: 1.0/0.0, max_width: 1.0/0.0) do
  331. setHbarPolicy Java::JavafxSceneControl::ScrollPane::ScrollBarPolicy::NEVER
  332. setPannable false
  333. setPrefHeight -1.0
  334. setPrefViewportWidth 0
  335. setPrefWidth -1.0
  336. styleClass.add "toolbox-panes"
  337. setContent(lfp = flow_pane(pref_height: 200, pref_width: 200))
  338. end
  339. setContent sp
  340. end)
  341. @toolbox_group[key] = lfp
  342. end
  343. @found_plugins = true
  344. end
  345. @toolbox_bits
  346. end
  347.  
  348. # called to wrap the control in an overlay and to place in a control
  349. def add_designable_control(control, xy, parent, oobj)
  350. ovl = SD::DesignerSupport::Overlay
  351. designer = ovl.new(if control.is_a? ovl then
  352. control = control.child
  353. else
  354. control
  355. end, self, parent, oobj)
  356.  
  357. # if its designable, add hooks for nested editing to work
  358. if control.is_a? Java::dashfx.lib.controls.DesignablePane
  359. designer.set_on_drag_dropped &method(:drag_drop)
  360. designer.set_on_drag_over &method(:drag_over)
  361. @layout_managers[control] = SD::Windowing::LayoutManager.new(control)
  362. end
  363. @layout_managers[parent].layout_controls({designer => xy})
  364. # Add it to the map so we can get the controller later if needed from UI tree
  365. if designer != control # don't add if it exists
  366. @ui2pmap[control.ui] = control
  367. self.message = "Added new #{oobj.id}"
  368. end
  369. designer
  370. end
  371.  
  372. def ui2p_add(ui, control)
  373. @ui2pmap[ui] = control
  374. end
  375.  
  376. def ui2p(ui)
  377. tmp = @ui2pmap[ui]
  378. until tmp
  379. ui = ui.parent
  380. tmp = @ui2pmap[ui]
  381. end
  382. tmp
  383. end
  384.  
  385. def add_known(name)
  386. return @aa_tree_hash[name] if name == "." || name == "/"
  387. item = name.sub(/\/$/, '')
  388. ti = if @aa_tree_hash.has_key? item
  389. @aa_tree_hash[item]
  390. else
  391. val = {value: item, fout: false}
  392. @aa_tree_list << val
  393. @aa_tree_hash[item] = tree_item(value: val, expanded: true)
  394. end
  395. children = add_known(File.dirname(item)).children
  396. children << ti unless children.contains ti
  397. ti
  398. end
  399.  
  400.  
  401. def drag_over(event)
  402. if event.gesture_source != self && event.dragboard.hasString
  403. event.acceptTransferModes(TransferMode::COPY)
  404. end
  405. event.consume();
  406. end
  407.  
  408. # whenever we drop something on the canvas, this is called. There are two cases: auto add droppings, or normal droppings
  409. def drag_drop(event)
  410. db = event.dragboard
  411. event.setDropCompleted(
  412. if db.hasString
  413. if db.string.start_with? "AutoAdd:"
  414. id = db.string[8..-1] #strip prefix
  415. # get the type that we are dealing with so we can filter for it
  416. typeo = @data_core.getObservable(id)
  417. #open a popup and populate it
  418. tbx_popup = SD::DesignerSupport::ToolboxPopup.new # TODO: cache these items so we don't have to reparse fxml
  419. find_toolbox_parts.each do |key, data|
  420. data.each do |i|
  421. next unless i.can_display? typeo.type, typeo.group_name
  422. ti = SD::DesignerSupport::ToolboxItem.new(i, method(:associate_dnd_id), :assign_name => id)
  423. ti.set_on_mouse_clicked do
  424. drop_add associate_dnd_id(i, :assign_name => id), event.x, event.y, event.source
  425. @on_mouse.call if @on_mouse # hide it
  426. end
  427. tbx_popup.add ti, key
  428. end
  429. end
  430. # position the popup at the location of the mouse
  431. tbx_popup.x = event.screen_x
  432. tbx_popup.y = event.screen_y
  433. # when we click other places, hide the toolbox
  434. register_clickoff do
  435. tbx_popup.hide
  436. end
  437. tbx_popup.show @stage
  438. SD::DesignerSupport::Overlay.preparse_new(1)
  439. else
  440. drop_add(db.string.to_i, event.x, event.y, event.source)
  441. end
  442. true
  443. else
  444. false
  445. end)
  446.  
  447. event.consume()
  448. end
  449. # called to add a namable control to the items
  450. def drop_add(id,x, y, source)
  451. pare = source == current_vc.ui ? current_vc.pane : source.child
  452. dnd_obj = @dnd_ids[id]
  453. obj = dnd_obj.new # create the object that we are dragging on
  454. if @dnd_opts[dnd_obj]
  455. # TODO: check for other options
  456. obj.name = @dnd_opts[dnd_obj][:assign_name] if obj.respond_to? :name=
  457. end
  458. dcrl = add_designable_control(obj, MousePoint.new(x, y), pare, @dnd_ids[id])
  459. if @dnd_opts[dnd_obj] && SD::DesignerSupport::Preferences.add_labels
  460. dcrl.decor_manager.add(Java::dashfx.lib.decorators.LabelDecorator.java_class).label = "#{File.basename(@dnd_opts[dnd_obj][:assign_name])}: "
  461. end
  462. hide_toolbox
  463. @clickoff_fnc.call if @clickoff_fnc
  464. @on_mouse.call(nil) if @on_mouse
  465. end
  466.  
  467. def morph_child(overlay, event) #TODO: drip drip drip leakage
  468. tmp = overlay.child
  469. id = if tmp.respond_to? :getName
  470. tmp.getName
  471. elsif tmp.respond_to? :getPath
  472. tmp.getPath
  473. else
  474. nil
  475. end
  476. typeo = @data_core.getObservable(id) if id
  477. tbx_popup = SD::DesignerSupport::ToolboxPopup.new # TODO: cache these items so we don't have to reparse fxml
  478. find_toolbox_parts.each do |key, data|
  479. data.each do |i|
  480. # find controls that could possibly replace this control
  481. next unless !i or i.can_display? typeo.type, typeo.group_name or i === overlay.ctrl_info
  482. ti = SD::DesignerSupport::ToolboxItem.new(i, method(:associate_dnd_id))
  483. ti.set_on_mouse_clicked do
  484. obj = i.new
  485. yield(obj, i)
  486. @ui2pmap[obj.ui] = obj
  487. self.message = "Added new #{obj.java_class.name}"
  488. hide_toolbox
  489. @clickoff_fnc.call if @clickoff_fnc
  490. @on_mouse.call(nil) if @on_mouse
  491. end
  492. tbx_popup.add ti, key
  493. end
  494. end
  495. # position the popup at the location of the mouse
  496. tbx_popup.x = overlay.local_to_scene(overlay.bounds_in_local).min_x + @stage.x
  497. tbx_popup.y = overlay.local_to_scene(overlay.bounds_in_local).min_y + @stage.y
  498. # when we click other places, hide the toolbox
  499. register_clickoff do
  500. tbx_popup.hide
  501. end
  502. tbx_popup.show @stage
  503. end
  504.  
  505. # These functions are used to do "clickoffs" aka close a window when you click anywhere
  506. def register_clickoff(&fnc)
  507. @clickoff_fnc = fnc
  508. on_mouse = @on_mouse = Proc.new do |e|
  509. e.consume if e
  510. @GridPane.remove_event_filter(MouseEvent::MOUSE_PRESSED,on_mouse)
  511. fnc.call
  512. @on_mouse = nil
  513. end
  514. @GridPane.add_event_filter(MouseEvent::MOUSE_PRESSED,on_mouse)
  515. end
  516.  
  517. # TODO: somehow merge with above method
  518. def register_toolbox_clickoff(&fnc)
  519. @clickoff_tbx = fnc
  520. on_tmouse = @on_tmouse = Proc.new do |e|
  521. q = e.nil? ? false : e.target
  522. while q
  523. if q == @toolbox
  524. q = false
  525. else
  526. q = q.parent
  527. end
  528. end
  529. if q == nil # no parents found
  530. @GridPane.remove_event_filter(MouseEvent::MOUSE_PRESSED,on_tmouse)
  531. @clickoff_tbx.call
  532. @on_tmouse = nil
  533. end
  534. end
  535. @GridPane.add_event_filter(MouseEvent::MOUSE_PRESSED,on_tmouse)
  536. end
  537.  
  538. def multiple_selected?
  539. @selected_items.length > 1
  540. end
  541.  
  542. def multi_drag(original)
  543. @just_dragged = true
  544. (@selected_items - [original])
  545. end
  546.  
  547. def run
  548. @mode = :run
  549. self.running = true
  550. hide_controls
  551. hide_toolbox
  552. hide_properties
  553. end
  554.  
  555. def design
  556. @mode = :design
  557. self.running = false
  558. show_controls
  559. end
  560.  
  561. def hide_controls
  562. animate_controls(true)
  563. end
  564.  
  565. # this is for play/pause bottom gutter hiding/showing
  566. def animate_controls(hide)
  567. mul = hide ? -1 : 1
  568. nul = hide ? 0 : 1
  569. # TODO: this should be cleaned up
  570. bg = @bottom_gutter
  571. sbs = [@stop_button, @playback_button]
  572. stg = @stage
  573. oy = stg.y
  574. ox = stg.x
  575. # The properties are read only because of OS issues, so we just create a proxy
  576. stg_hap = SimpleDoubleProperty.new(stg.height)
  577. stg_hap.add_change_listener {|ov, old, new| stg.setHeight(new); stg.y = oy; stg.x = ox; }
  578. timeline do
  579. animate bg.translateYProperty, 0.ms => 500.ms, (32 * nul) => (32 - 32 * nul)
  580. sbs.each do |sb|
  581. animate sb.visibleProperty, 0.ms => 500.ms, (!hide) => hide
  582. end
  583. animate stg_hap, 0.ms => 500.ms, stg.height => (stg.height + 32 * mul)
  584. animate bg.pref_height_property, 0.ms => 500.ms, (32 - 32 * nul) => (32 * nul)
  585. end.play
  586. end
  587.  
  588.  
  589. def show_controls
  590. animate_controls(false)
  591. end
  592.  
  593. # set the popup message at the bottom
  594. def message=(msg)
  595. am = @alert_msg
  596. with(@msg_carrier) do |mc|
  597. timeline do
  598. animate mc.translateYProperty, 0.sec => [200.ms, 5.sec, 5.2.sec], 30.0 => [0.0, 0.0, 30.0]
  599. animate mc.minWidthProperty, 0.sec => [200.ms, 5.sec, 5.2.sec], 0 => [200, 200, 0]
  600. animate am.textProperty, 0.sec => [200.ms, 5.sec, 5.2.sec, 5.21.sec], "" => [msg, msg, msg, ""]
  601. end.play
  602. end
  603. end
  604.  
  605. def save
  606. if @currently_open_file
  607. save_file(@currently_open_file)
  608. else
  609. save_as
  610. end
  611. end
  612.  
  613. def save_as
  614. dialog = file_chooser(:title => "Save Layout...") do
  615. add_extension_filter("-fx:SmartDashboard save files (*.fxsdash)")
  616. end
  617. file = dialog.showSaveDialog(@stage)
  618. return false unless file
  619. save_file file.path
  620. end
  621.  
  622. def save_file(file)
  623. file += ".fxsdash" unless file.end_with? ".fxsdash"
  624. File.open(file, "wb") do |io|
  625. Gem::Package::TarWriter.new(io) do |tar|
  626. tar.add_file("version", 0644) {|f|f.write("0.2")}
  627. tar.add_file("data.yml", 0644) do |yml|
  628. psg = SD::IOSupport::DashObject.parse_scene_graph(@view_controllers.to_a, @data_core)
  629. yml.write YAML.dump(psg)
  630. @current_save_data = psg
  631. end
  632. end
  633. end
  634. self.message = "Saved!"
  635. @stage.title = "SmartDashboard : #{File.basename(file, ".fxsdash")}"
  636. @currently_open_file = file
  637. update_recent_opens(file)
  638. build_open_menu
  639. return true ## TODO: detect failures
  640. end
  641.  
  642. def update_recent_opens(new)
  643. recently_open = @prefs.recently_open
  644. if recently_open.include? new
  645. # remove it and put it at the front
  646. recently_open -= [new]
  647. end
  648. recently_open = ([new] + recently_open).first(10)
  649. @prefs.recently_open = recently_open
  650. end
  651.  
  652. def build_open_menu
  653. recently_open = @prefs.recently_open
  654. this = self
  655. with(@open_btn) do
  656. items.clear
  657. menu_item("Open", style: "-fx-font-weight: bold").on_action {this.open}
  658. separator_menu_item
  659. recently_open.each do |i|
  660. menu_item(File.basename(i, ".fxsdash")).on_action do
  661. this.open_file(i)
  662. end
  663. end
  664. end
  665. end
  666.  
  667. def open
  668. dialog = file_chooser(:title => "Open Layout...") do
  669. add_extension_filter("-fx:SmartDashboard save files (*.fxsdash)")
  670. end
  671. file = dialog.show_open_dialog(@stage)
  672. return unless file
  673. open_file(file.path)
  674. end
  675.  
  676. def open_file(file)
  677. update_recent_opens(file)
  678. build_open_menu
  679. data = {} # TODO: very memory inefficient
  680. File.open(file, "rb") do |io|
  681. Gem::Package::TarReader.new(io) do |tar|
  682. tar.each do |entry|
  683. data[entry.full_name] = entry.read
  684. end
  685. end
  686. end
  687. if data["version"].to_f != 0.2
  688. self.message = "Unknown file version '#{data["version"]}'!"
  689. return
  690. end
  691. doc = YAML.load(data['data.yml'])
  692. @current_save_data = doc
  693. # TODO: tab support
  694. clear_tabs()
  695. @aa_ignores = doc.known_names
  696. vc_first = nil
  697. doc.vcs.each do |ro|
  698. vc = ro.new
  699. vc_first = vc unless vc_first
  700. add_tab(vc)
  701. ro.children.each {|x| open_visitor(x, vc.pane)}
  702. end
  703. tab_select(vc_first.tab)
  704. @currently_open_file = file
  705. @stage.title = "SmartDashboard : #{File.basename(file, ".fxsdash")}"
  706. self.message = "File Load Successfull"
  707. end
  708.  
  709. def open_visitor(cdesc, parent)
  710. desc = SD::Plugins::ControlInfo.find(cdesc.object)
  711. obj = desc.new
  712. obj.ui.setPrefWidth cdesc.sprops["Width"] if cdesc.sprops["Width"] > 0
  713. obj.ui.setPrefHeight cdesc.sprops["Height"] if cdesc.sprops["Height"] > 0
  714. add_designable_control(obj, MousePoint.new(cdesc.sprops["LayoutX"], cdesc.sprops["LayoutY"], false), parent, desc).load_extra(cdesc.extra)
  715. cdesc.props.each do |prop, val|
  716. nom = "set#{prop}"
  717. next if prop == "Value"
  718. # TODO: fix old/new values
  719. begin
  720. if obj.respond_to? nom
  721. obj
  722. else
  723. obj.ui
  724. end.send(nom, (val.kind_of?(SD::IOSupport::ComplexObject) ? val.to_value : val))
  725. rescue NoMethodError
  726. puts $!
  727. puts "unable to call #{nom} on #{obj}"
  728. end
  729. end
  730. cdesc.children.each {|x| open_visitor(x, obj) }
  731. end
  732.  
  733. def new_document
  734. # TODO: check for unsaved changes
  735. # assign the root canvas node from preferences
  736. answer = SD::DesignerSupport::SaveQuestion.ask(@stage)
  737.  
  738. if answer == :save then
  739. return unless save
  740. end
  741.  
  742. return if answer == :cancel
  743.  
  744. @current_save_data = @currently_open_file = nil
  745. @stage.title = "SmartDashboard : Untitled"
  746. clear_tabs()
  747. # TODO: don't copy this in the ctor
  748.  
  749. main_vc = SD::Windowing::DefaultViewController.new
  750. main_vc.on_focus_request do |focus|
  751. tab_auto_focus(main_vc, focus)
  752. end
  753. main_tab = add_tab(main_vc)
  754. SD::Plugins.view_controllers.find_all{|x|x.default > 0}.each do |x|
  755. vc = x.new
  756. vc.on_focus_request do |focus|
  757. tab_auto_focus(vc, focus)
  758. end
  759. add_tab(vc)
  760. end
  761. tab_select(main_tab)
  762. @current_save_data = SD::IOSupport::DashObject.parse_scene_graph(@view_controllers.to_a, @data_core)
  763. end
  764.  
  765. def hide_properties_ctx(ctx)
  766. @properties.hide if @properties
  767. @ctx_menu_open = ctx
  768. end
  769.  
  770. def hide_properties
  771. @properties.hide if @properties
  772. @ctx_menu_open.hide if @ctx_menu_open
  773. end
  774.  
  775. def update_properties
  776. if @selected_items.length < 1 or @selected_items.find_all { |i| !i.editing_nested }.length != 1
  777. @propertiesFor = @selected_items[0]
  778. hide_properties
  779. else
  780. # don't clobber open/close stats
  781. if @propertiesFor != @selected_items[0]
  782. @propertiesFor = @selected_items[0]
  783. @properties.decor_manager = @selected_items[0].decor_manager
  784. @properties.properties = @selected_items[0].properties
  785. end
  786. sic = @selected_items[0].child
  787. name = sic.name if sic.respond_to? :name
  788. name = @selected_items[0].original_name unless name && name != ""
  789. name = sic.java_class.name.split(".").last unless name && name != ""
  790. @properties.title = name
  791. properties_show_around @selected_items[0]
  792. end
  793. end
  794.  
  795. def properties_draghide
  796. hide_properties
  797. end
  798.  
  799. def properties_dragshow(elt)
  800. properties_show_around(elt)
  801. end
  802.  
  803. # display the properties near some element
  804. def properties_show_around(elt)
  805. bnds = elt.localToScene(elt.getBoundsInLocal)
  806. scr_x = @scene.x + @stage.x
  807. scr_y = @scene.y + @stage.y
  808. scr = Screen.getScreensForRectangle(scr_x + bnds.min_x, scr_y + bnds.min_y, 1, 1)[0]
  809. unless scr
  810. lamb = lambda{|x,y|Screen.getScreensForRectangle(scr_x + x, scr_y + y, 1, 1)[0]}
  811. scr = lamb.call(bnds.max_x, bnds.max_y) || lamb.call(bnds.max_x, bnds.min_y) || lamb.call(bnds.min_x, bnds.max_y) || Screen.primary
  812. end
  813. loc_x = bnds.max_x + scr_x + 5
  814. loc_y = bnds.min_y + scr_y
  815. if scr.bounds.max_x <= loc_x + @properties.width
  816. loc_x = bnds.min_x + scr_x - 5 - @properties.width
  817. end
  818. if scr.bounds.max_y <= loc_y + @properties.height
  819. loc_y = bnds.max_y + scr_y - @properties.height
  820. end
  821. @properties.y = loc_y
  822. @properties.x = loc_x
  823. @properties.show(@stage)
  824. end
  825.  
  826. def show_hide_toolbox
  827. if @toolbox_status == :hidden
  828. show_toolbox
  829. else
  830. hide_toolbox
  831. end
  832. end
  833.  
  834. def hide_toolbox
  835. return if @toolbox_status == :hidden
  836. @toolbox_status = :hidden
  837. asl = @add_slider
  838. gshadow = @gutter_shadow
  839. @clickoff_tbx = Proc.new {|x|}
  840. @on_tmouse.call if @on_tmouse
  841. with(@left_gutter) do |tbx|
  842. timeline do
  843. animate tbx.translateXProperty, 0.ms => 500.ms, 0.0 => -266.0
  844. animate asl.minWidthProperty, 0.ms => 500.ms, 268.0 => 0.0
  845. animate gshadow.translateXProperty, 0.ms => 500.ms, 266.0 => 0.0
  846. animate gshadow.minWidthProperty, 100.ms => 500.ms, 30.0 => 0.0
  847. end.play
  848. end
  849. end
  850.  
  851. def show_toolbox
  852. hide_properties
  853. return if @toolbox_status == :visible
  854. @toolbox_status = :visible
  855. asl = @add_slider
  856. gshadow = @gutter_shadow
  857. with(@left_gutter) do |tbx|
  858. timeline do
  859. animate tbx.translateXProperty, 0.sec => 500.ms, -266.0 => 0.0
  860. animate asl.minWidthProperty, 0.ms => 500.ms, 0.0 => 268.0
  861. animate gshadow.translateXProperty, 0.ms => 500.ms, 0.0 => 266.0
  862. animate gshadow.minWidthProperty, 0.ms => 400.ms, 0.0 => 30.0
  863. end.play
  864. end
  865. register_toolbox_clickoff do
  866. hide_toolbox
  867. end
  868. end
  869.  
  870. def canvas_click(e)
  871. return if e.button != MouseButton::PRIMARY
  872. hide_properties
  873. if @just_dragged
  874. @just_dragged = false
  875. return
  876. end
  877. return if @mode != :design
  878. q = e.target
  879. new_selections = e.control_down? ? @selected_items : []
  880. begin
  881. if q.is_a? SD::DesignerSupport::Overlay
  882. if e.control_down? and new_selections.include? q
  883. new_selections -= [q]
  884. else
  885. new_selections << q
  886. end
  887. break
  888. end
  889. end while (q = q.parent) && q != current_vc.pane
  890. select(*new_selections)
  891. end
  892.  
  893. def select(*items)
  894. new_selections = items
  895. (@selected_items + new_selections).each do |si|
  896. si.selected = new_selections.include? si
  897. end
  898. @selected_items = new_selections
  899. update_properties
  900. if @selected_items.length > 0
  901. @selected_items[0].request_focus
  902. @properties.focus_default!
  903. end
  904. end
  905.  
  906. def try_reparent(child, x, y)
  907. @last_reparent_try.hide_nestable if @last_reparent_try
  908. @last_reparent_try = nil
  909. # puts "----"
  910. # current_vc.ui.children.each do |ch|
  911. # if ch.is_a? SD::DesignerSupport::Overlay
  912. # ch.print_tree
  913. # else
  914. # p ch
  915. # end
  916. # end
  917. # puts "--"
  918. try_reparent_cc([SD::DesignerSupport::OverlayRootWrapper.new(current_vc.ui, !@nested_list[current_vc].last)], child, x, y)
  919. end
  920.  
  921. def try_reparent_cc(ui, child, x, y)
  922. childs = ui.reverse
  923. if new_parent = childs.find { |n| n != child && n.can_nest? && n.is_inside?(x,y) }
  924. if !try_reparent_cc(new_parent.child.children.to_a, child, x, y)
  925. if new_parent.child.children.include?(child) # TODO: && n != child.parent ????
  926. return true if new_parent.editing_nested
  927. return try_reparent_cc(ui - [new_parent], child, x, y)
  928. end
  929. @last_reparent_try = new_parent
  930. new_parent.show_nestable
  931. end
  932. true
  933. else
  934. @last_reparent_try = nil
  935. false
  936. end
  937. end
  938.  
  939. def reparent!(child, x, y)
  940. child.parent.children.remove(child)
  941. pare = @last_reparent_try.child == current_vc.ui ? current_vc.pane : @last_reparent_try.child
  942. add_designable_control(child, MousePoint.new(*@last_reparent_try.scene_to_local(x, y), false), pare, child.ctrl_info)
  943. @last_reparent_try.hide_nestable
  944. @last_reparent_try = nil
  945. end
  946.  
  947. # TODO: store this state on the overlay control if possible
  948. def reparent?
  949. !!@last_reparent_try
  950. end
  951.  
  952. def do_playback_mode
  953. @playback.launch
  954. end
  955.  
  956. def delete_selected
  957. @selected_items.each do |si|
  958. # Remove the item from its parent, this supports nesting ad-infinitum
  959. si.parent.children.remove(si)
  960. end
  961. @selected_items = []
  962. hide_properties
  963. end
  964.  
  965. def canvas_keyup(e)
  966. if e.code == KeyCode::DELETE
  967. if @properties.focus_default?
  968. delete_selected
  969. e.consume
  970. end
  971. elsif e.control_down?
  972.  
  973. callback = {
  974. KeyCode::R => lambda{@mode == :run ? design : run} ,
  975. KeyCode::S => lambda{save},
  976. KeyCode::O => lambda{open},
  977. KeyCode::N => lambda{new_document},
  978. KeyCode::F => lambda{show_toolbox; aa_show_regex_panel},
  979. KeyCode::TAB => lambda{focus_related_tab(e.shift_down? ? -1 : 1)}
  980.  
  981. }[e.code]
  982. callback.call if callback
  983. end
  984. end
  985.  
  986. def compute_wingmen(chb)
  987. parb = @spain.local_to_scene(@spain.bounds_in_local)
  988. # north east south west (css style)
  989. OpenStruct.new(north: chb.min_y - parb.min_y, east: parb.max_x - chb.max_x,
  990. width: parb.width, south: parb.max_y - chb.max_y, west: chb.min_x - parb.min_x)
  991. end
  992.  
  993. def show_wingmen(child)
  994. nesting = !!child
  995. @wingman[current_vc] = child
  996. @north_wing.visible = @south_wing.visible = @east_wing.visible = @west_wing.visible = nesting
  997.  
  998. if nesting
  999. ccb = child.control_bounds
  1000. tmp = compute_wingmen(ccb)
  1001. end
  1002. @west_wing.pref_width = nesting ? tmp.west : 0
  1003. @east_wing.pref_width = nesting ? tmp.east : 0 # TODO: binding
  1004. @west_wing.layout_y = @east_wing.layout_y = @north_wing.pref_height = nesting ? tmp.north : 0
  1005. @south_wing.pref_height = nesting ? tmp.south : 0
  1006.  
  1007. @west_wing.pref_height = @east_wing.pref_height = ccb.height if nesting
  1008. end
  1009.  
  1010. def nested_edit(octrl)
  1011. return false if @mode == :run
  1012. show_wingmen(octrl)
  1013. @nested_list[current_vc] << octrl
  1014. return true
  1015. end
  1016.  
  1017. def surrender_nest(e)
  1018. if e.click_count > 1 # Run away!
  1019. @nested_list[current_vc].length > 0 && @nested_list[current_vc].pop.exit_nesting
  1020. show_wingmen(@nested_list[current_vc].last)
  1021. end
  1022. end
  1023.  
  1024. # helper function for traversing parents when nesting editing
  1025. def nested_traverse(octrl, after, &eachblock)
  1026. return if octrl == current_vc.ui || octrl == (@last_nested && @last_nested.last)
  1027. ctrl = octrl
  1028. begin
  1029. saved = (ctrl.parent.children.to_a.find_all{|i| i != ctrl})
  1030. saved.each &eachblock
  1031. after.call(ctrl)
  1032. ctrl = ctrl.parent
  1033. end while ctrl != current_vc.ui && ctrl != (@last_nested && @last_nested.last)
  1034. end
  1035.  
  1036. # Settings for the canvas TODO: add other canvas properties
  1037. def show_data_sources
  1038. require 'data_source_selector'
  1039. # convert all the endpoints so we can use bindings
  1040. stg = @stage
  1041. points = observable_array_list()
  1042. (@data_core.all_data_endpoints.to_a + @data_core.video_core.all_video_endpoints.to_a).uniq.each do |ep|
  1043. points << SD::BindableDilItem.new(ep)
  1044. end
  1045. get_meta = lambda{|clz| clz && clz.annotation(Java::dashfx.lib.controls.DesignableData.java_class) }
  1046. possible = SD::Plugins.data_sources
  1047. on_save = lambda{|pts|
  1048. @data_core.clearAllDataEndpoints
  1049. @data_core.video_core.clearAllVideoEndpoints
  1050. pts.each do |pt|
  1051. did = Java::dashfx.lib.data.DataInitDescriptor.new(pt.class_type.ruby_class.new, pt.name, pt.init_info, pt.path)
  1052. if get_meta.(pt.class_type).types.include? Java::dashfx.lib.data.DataProcessorType::DataSource
  1053. @data_core.mountDataEndpoint(did)
  1054. end
  1055. if get_meta.(pt.class_type).types.include? Java::dashfx.lib.data.DataProcessorType::VideoSource
  1056. @data_core.video_core.mountVideoEndpoint(did)
  1057. end
  1058. end
  1059. }
  1060. stage(init_style: :utility, init_modality: :app, title: "Data Source Selector") do
  1061. init_owner stg
  1062. center_on_screen
  1063. fxml SD::DataSourceSelector, :initialize => [points, possible, get_meta, on_save]
  1064. end.show_and_wait
  1065. end
  1066.  
  1067. # Add all known controls
  1068. def aa_add_all
  1069. # for each of the items in the tree view (TODO: NOT A TREE VIEW), add it as the pref type
  1070. aa_add_some(current_vc.pane, *@aa_tree.root.children.map{|x| x.value[:value]}.find_all{|x| !@aa_regex_showing || x.match(@aa_filter.regex)})
  1071. end
  1072.  
  1073. def aa_add_some(pane, *names)
  1074. names.each do |ctrl_name|
  1075. ctrl = @data_core.get_observable(ctrl_name)
  1076. begin
  1077. new_objd = if !ctrl.type || ctrl.type == 64
  1078. SD::Plugins.controls.find{|x| x.group_types == ctrl.group_name}
  1079. else
  1080. SD::DesignerSupport::PrefTypes.for(ctrl.type)
  1081. end
  1082. new_obj = new_objd.new
  1083. if new_obj
  1084. add_designable_control with(new_obj, name: ctrl.name), NilPoint.new, pane, new_objd
  1085. else
  1086. puts "Warning: no default control for #{ctrl.type.mask}"
  1087. end
  1088. rescue
  1089. puts "Warning: error finding default control for #{ctrl}"
  1090. end
  1091. end
  1092. hide_toolbox
  1093. end
  1094. # TODO: next few lines can be cleaned up im sure
  1095. def aa_add_new
  1096. @aa_filter.always_add = !@aa_filter.always_add
  1097. end
  1098.  
  1099. def aa_hide_regex_panel
  1100. @aa_ctrl_panel.children.remove(@aa_ctrl_regex)
  1101. @aa_expand_panel.text = "Search"
  1102. end
  1103.  
  1104. def aa_show_regex_panel
  1105. @aa_ctrl_panel.children.add(@aa_ctrl_regex)
  1106. @aa_expand_panel.text = "Hide search"
  1107. @aa_regexer.request_focus
  1108. end
  1109.  
  1110. def aa_toggle_panel
  1111. @aa_regex_showing = !@aa_regex_showing
  1112. if @aa_regex_showing
  1113. aa_show_regex_panel
  1114. else
  1115. aa_hide_regex_panel
  1116. end
  1117. end
  1118.  
  1119. def current_vc
  1120. @view_controllers[vc_index]
  1121. end
  1122.  
  1123. def new_tab
  1124. @count ||= 0
  1125. @count += 1
  1126. new_vc = SD::Windowing::DefaultViewController.new("NewTab-#{@count}", false)
  1127. btn = add_tab(new_vc)
  1128. tab_select(btn)
  1129. delete_it = lambda{delete_tab btn}
  1130. btn.graphic = button("x"){
  1131. set_on_action do |e|
  1132. delete_it.call
  1133. e.consume
  1134. end
  1135. style_class << "delete"
  1136. }
  1137. btn.content_display = Java::javafx.scene.control.ContentDisplay::RIGHT
  1138. end
  1139.  
  1140. def delete_tab(tab)
  1141. vc = @view_controllers.find{|x| x.tab == tab}
  1142. @view_controllers.remove vc
  1143. @tab_box.children.remove(tab)
  1144. @aa_name_trees.delete(vc)
  1145. @aa_name_trees_threads.delete(vc) # TODO: kill thread
  1146. @ui2pmap.delete vc.pane
  1147. @ui2pmap.delete vc.ui
  1148. tab_select(@view_controllers[0].tab)
  1149. end
  1150.  
  1151. def add_tab(vc)
  1152. vc.tab = button(vc.name)
  1153. vc.tab.set_on_action &method(:tab_clicked)
  1154. @view_controllers << vc
  1155. @tab_box.children.add(@tab_box.children.length - 1, vc.tab)
  1156. os = OpenStruct.new({can_nest?: true, child: vc.pane})
  1157. @aa_name_trees[vc] = SD::DesignerSupport::AANameTree.new("", SD::DesignerSupport::AANameTree.new("", os, proc{}).tap{|x|x.data[:descriptor] = os}, proc{})
  1158. @aa_name_trees_threads[vc] = [Mutex.new, Thread.new do
  1159. loop {
  1160. Thread.stop
  1161. tdiff = (@aa_name_trees_threads[vc][3] + 0.5) - Time.now
  1162. while tdiff > 0
  1163. sleep(tdiff)
  1164. tdiff = (@aa_name_trees_threads[vc][3] + 0.5) - Time.now
  1165. end
  1166. run_later do
  1167. @aa_name_trees_threads[vc][0].synchronize do
  1168. @aa_name_trees[vc].process lambda { |ctrl, np, nd, ci|
  1169. dcrl = add_designable_control(ctrl, np, nd, ci)
  1170. if SD::DesignerSupport::Preferences.add_labels && vc.should_label?(ctrl)
  1171. dcrl.decor_manager.add(Java::dashfx.lib.decorators.LabelDecorator.java_class).label = "#{File.basename(ctrl.name)}: "
  1172. end
  1173. dcrl
  1174. }
  1175. end
  1176. end
  1177. }
  1178. end, lambda{|x| @aa_name_trees_threads[vc][3] = x}, Time.now]
  1179. @ui2pmap[vc.pane] = vc
  1180. @ui2pmap[vc.ui] = vc.pane
  1181. @layout_managers[vc.pane] = vc.layout_manager
  1182. @layout_managers[vc.ui] = vc.layout_manager
  1183. vc.pane.registered(@data_core)
  1184. @nested_list[vc] = []
  1185. return vc.tab
  1186. end
  1187.  
  1188.  
  1189. def focus_related_tab(which)
  1190. tab_select(@view_controllers[(vc_index + which) % @view_controllers.length].tab)
  1191. end
  1192.  
  1193. def tab_select(tab)
  1194. hide_properties
  1195. hide_toolbox
  1196. vc = @view_controllers.find{|x|x.tab == tab}
  1197. @view_controllers.each do |lm|
  1198. lm.tab.style_class.remove("active")
  1199. end
  1200. vc.tab.style_class.add("active")
  1201. self.root_canvas = vc
  1202. @vc_index.value = @view_controllers.index(vc)
  1203. show_wingmen @wingman[current_vc]
  1204. end
  1205.  
  1206. def tab_clicked(e)
  1207. @vc_focus = [] # when we manually click, make it default to current
  1208. tab_select(e.target)
  1209. end
  1210.  
  1211. def tab_auto_focus(vc, focus)
  1212. unless focus
  1213. @vc_focus -= [vc]
  1214. else
  1215. if @vc_focus.include? vc or @view_controllers[vc_index] == vc
  1216. return # there will be no pillaging tonight
  1217. end
  1218. @vc_focus << vc
  1219. if @vc_focus.length == 1
  1220. @vc_focus = [@view_controllers[vc_index]] + @vc_focus
  1221. end
  1222. end
  1223. tab_select(@vc_focus.last.tab) if @vc_focus.length > 0
  1224. end
  1225.  
  1226. def clear_tabs
  1227. @tab_box.children.remove_all(*@view_controllers.map(&:tab))
  1228. @view_controllers.clear
  1229. @vc_index.value = 0
  1230. @aa_name_trees = {}
  1231. @aa_name_trees_threads = {}
  1232. @ui2pmap = {}
  1233. end
  1234.  
  1235. # edit the smart dashboard settings
  1236. def edit_settings
  1237. hide_properties
  1238. require 'settings_dialog'
  1239. stg = @stage
  1240. this = self
  1241. stage(init_style: :utility, init_modality: :app, title: "SmartDashboard Settings") do
  1242. init_owner stg
  1243. fxml SD::SettingsDialog, :initialize => [this]
  1244. show_and_wait
  1245. end
  1246. end
  1247.  
  1248. # Assign the designer surface and set up handlers
  1249. def root_canvas=(cvs)
  1250. @overlay_pod.children.remove @cur_canvas.ui if @cur_canvas
  1251. @overlay_pod.children.add 0, cvs.ui
  1252. raise "overlay pod has #{@overlay_pod.children.length} children instead of expected 5" unless @overlay_pod.children.length == 5
  1253. @cur_canvas = cvs
  1254. cvs.ui.min_width_property.bind(@spain.width_property.subtract(2.0))
  1255. cvs.ui.min_height_property.bind(@spain.height_property.subtract(2.0))
  1256. cvs.ui.pref_width = -1 # autosize
  1257. cvs.ui.pref_height = -1 # autosize
  1258. @overlay_pod.pref_width_property.bind cvs.ui.width_property
  1259. @overlay_pod.pref_height_property.bind cvs.ui.height_property
  1260. @layout_managers[cvs.pane] = cvs.layout_manager
  1261. @layout_managers[cvs.ui] = cvs.layout_manager
  1262. cvs.ui.setOnDragDropped &method(:drag_drop)
  1263. cvs.ui.setOnDragOver &method(:drag_over)
  1264. cvs.ui.setOnMouseReleased &method(:canvas_click)
  1265. end
  1266.  
  1267. def force_layout
  1268. @cur_canvas.ui.request_layout
  1269. end
  1270.  
  1271. def self.instance
  1272. @@singleton
  1273. end
  1274. end
  1275. # require stuff in the background
  1276. SD::Designer.require_thread
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement