Difference between revisions of "FreeplaneJrubyInstaller - developers"
(47 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | + | This wiki page is intended for new programmers that want to '''try Ruby, and make Freeplane ruby scripts'''. | |
+ | If you just want to '''''run a ruby script''''', then instead read [[FreeplaneJrubyInstaller_Addon|FreeplaneJrubyInstaller Addon]] <br /> | ||
+ | If you want to know '''''how the addon was made''''', then instead read [[FreeplaneJrubyInstaller_-_making_of|FreeplaneJrubyInstaller - making of]] | ||
− | |||
− | + | I estimate that only a small number of people will actually feel curious to learn ruby and make freeplane scripts with ruby, which makes us - the ''freeplane-ruby-scripts programmers'' - a '''really small and nice club''' :) | |
+ | So, if you anytime feel ''part-of-the-club'' (or just want to say hello), dont be shy and drop a comment in [https://sourceforge.net/p/freeplane/discussion/758437/thread/324b9cb0/ this forum thread: Announcing FreeplaneJrubyInstaller !!!!] | ||
− | + | Hope we all have a great time :) | |
− | |||
− | + | So here we go: learn ruby, program freeplane, have fun :) | |
− | |||
− | |||
− | |||
− | |||
+ | == Ruby scripts examples == | ||
− | + | Start programming by tinkering with existing code, and seeing what happens. When you get stuck or need to understand something better, then go read some introduction book/tutorial (just enough) and get back to tinkering again. Its the fun of tinkering that fuels to get better, understand more, go more complex. | |
− | |||
+ | === Sort children by icon === | ||
+ | [https://github.com/zipizap/freeplaneRubyScripts/raw/master/sort_by_icons.rb sort_by_icons.rb] | ||
+ | [https://github.com/zipizap/freeplaneRubyScripts/blob/master/sort_by_icons.rb see it in github] | ||
− | + | * Select a node, execute script, and the children will be ordered/sorted by their icons | |
− | + | * Related to [https://sourceforge.net/p/freeplane/discussion/758437/thread/3ada3b06/ this forum thread "Possible sort children by icon 'priority' or 'progress'?" from Cadux] | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | == Freeplane ruby script = Ruby + Freeplane Scripting Api == | |
− | |||
− | |||
− | |||
− | + | A ruby script for freeplane, is a '''ruby''' program that will use '''Freeplane java-objects from the [http://www.freeplane.org/doc/api/ Freeplane Scripting API]''' | |
− | |||
+ | To learn how to make ruby scripts for freeplane, the ideal learning-path is to: | ||
+ | # [[FreeplaneJrubyInstaller_-_developers#Learn_the_basics_of_Ruby|Learn the basics of Ruby]] | ||
+ | # Know how to call the Freeplane-java-objects in your ruby script. This is in fact 2 things: | ||
+ | ## [[FreeplaneJrubyInstaller_-_developers#Learn_what_freeplane-java-objects_are_available_for_you_-_the_Freeplane_Scripting_API|Learn what freeplane-java-objects are available for you - the Freeplane Scripting API]] | ||
+ | ## [[FreeplaneJrubyInstaller_-_developers#Learn_how_to_call_freeplane-java-objects_in_.28J.29ruby|Learn how to call freeplane-java-objects in (J)ruby]] | ||
+ | In the next sections, we'll go through this learning-path, which I trimmed down to the essentials (with links for further exploration, if/when you need) | ||
− | |||
− | |||
− | |||
− | + | === Learn the basics of Ruby === | |
− | + | Ruby is a simple language, easy to learn for new programmers, with lots of books, guides, videos and explanations in internet (almost too much) | |
− | |||
− | + | In internet there are lots of learning material about "Ruby" and about "Ruby on Rails", but stay away from "Ruby on Rails" because that is another umongous universe (using ruby to make webpages). For freeplane scripting, we just need to learn ''Ruby'' (without Rails!), and even then, just the ''basics'' of Ruby. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | To start with Ruby, I recommend you read and follow these learning links: | |
+ | * [http://tryruby.org/levels/1/challenges/0 Try ruby in 15 minutes, in the browser] | ||
+ | ** >> I think this should be your first contact with ruby: a guided interactive tour to do simple things in ruby | ||
+ | ** Its all online, no install needed, and seems like 15-30min | ||
+ | ** From here take a general idea of ruby - you'll see these things explained in more detail in other guides/books | ||
− | + | * [https://pine.fm/LearnToProgram/ Learn to Program - A Place to Start for the Future Programmer] | |
+ | ** >> I think this should be your second contact with ruby - understand a bit more of what is ruby. <br /> While reading the book, have in the side a browser open with the [https://repl.it/languages/ruby Online Interactive Ruby (online irb)] (see bellow) to copy/paste code from the book, and try for yourself | ||
+ | ** Small online book made to teach ruby for new programmers. | ||
+ | ** Covers the basics, and seems well written | ||
− | + | * [https://repl.it/languages/ruby Online Interactive Ruby (online irb)] | |
+ | ** >> Keep this close to you everytime you are learning ruby, it allows you to try ruby code without installing anything. This is the equivalent of an ''online'' '''irb'''. | ||
+ | ** Instead of installing ruby in your pc and then use its "irb" to test ruby code, you can use this ''online'' irb without installing anything :) | ||
+ | ** Good to try out small code snippets, like examples from a book, or for you to explore ruby | ||
− | + | Also, the following links are optional '''support-material''' - they will not teach you ruby but if you have them somewhere close they will be helpfull many times. | |
+ | They contain more than info than you will ever need, but are good consulting references, to clearout doubts, see examples, etc. I still use them myself: | ||
+ | * [http://www.digilife.be/quickreferences/qrc/ruby%20language%20quickref.pdf Ruby QuickRef Card (print it)] | ||
+ | ** >> Print these 2 pages in 1 sheet and keep it around in the desk - it will help you many times to remember ruby syntax. Take notes on it as you need :) | ||
+ | * [http://www.zenspider.com/ruby/quickref.html Zenspider Ruby QuickRef (bookmark it)] | ||
+ | ** >> This is '''the best quickref of ruby'''. I always search here first. Its too big to be printed (what a pitty) but whenever you want, you can open it online, and "find" what you want | ||
+ | ** Its a great reference to be consulted when needed! I never got even close to know all of it, so don't sweat. Just a reference, use it as you need it :) | ||
+ | * Search in google "TutorialsPoint Ruby Basics" - TutorialsPoint Ruby Basics (consult when needed) | ||
+ | ** >> Use it like support-material: when you have a doubt about something specific, search for it in here | ||
+ | ** It packs good info in short resumes. Sometimes a bit canonical, but the examples can be of use for you and clarifying | ||
+ | ** Use it to consult, like a ''reference'' with examples and resumes. | ||
+ | ** This wiki filters the webpage link as spam (which is not), so use google to reach it | ||
− | |||
+ | For freeplane scripts, its enough to learn only the basics of ruby - its up to you to decide how deep you want to learn it. In any case, have an overall idea of Ruby, and comeback to the above links whenever you need. | ||
− | + | <small> NOTE: If someday you want to become a ruby pro, you will want to read the "pickaxe" book: [https://pragprog.com/book/ruby4/programming-ruby-1-9-2-0 The Pragmatic Programmers' Guide, Programming Ruby] If you are a new programmer, dont start here. </small> | |
− | |||
− | + | === Know how to call the Freeplane-java-objects in your (J)ruby script === | |
− | + | ||
− | + | ==== Learn what freeplane-java-objects are available for you - the Freeplane Scripting API ==== | |
− | + | ||
− | + | Now, we will skim over the [http://www.freeplane.org/doc/api/ Freeplane Scripting API], which are a group of Java classes and its methods. | |
+ | |||
+ | |||
+ | All the Freeplane Scripting API are '''Java''' classes and methods. | ||
+ | |||
+ | This will not make much sense to you now, after having learned the basics of ruby, because you don't know how to use ''Java'' classes/methods in ''ruby''. These java classes will not be ''used'' just now, I will just ''present'' them so that you have an idea that they exist, and where you can find them latter, when you need them. | ||
+ | |||
+ | |||
+ | The freeplane-java-objects are documented in the [http://www.freeplane.org/doc/api/ Freeplane Scripting API], which is a Java API, where you can see the '''classes''' , the '''methods''' of each '''class''' and a short description of what each '''method''' does. The Freeplane Scripting Api should be open by your side while you'r making freeplane scripts - you will consult it all the time. Click the "FRAMES" link at the top, to show in the webpage a left-side-navigation-list with all the Classes available. | ||
+ | |||
+ | |||
+ | To learn freeplane scripting, the most interesting class of the [http://www.freeplane.org/doc/api/ Freeplane Scripting API], is [http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html Proxy.Node] | ||
+ | * Represented in groovy and ruby scripts by the variable <code>node</code> | ||
+ | * Look to these java methods: '''getText()''' and '''setText("whatever")''' | ||
+ | |||
+ | |||
+ | In Ruby scripts (imitating what happens with Groovy scripts), there are some handy '''special variables predefined''' for us to use in scripts, that point to usefull java-classes: | ||
+ | * <code>node, c</code> - as explained here http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/ScriptUtils.html | ||
+ | * <code>ui, logger, htmlUtils, textUtils, menuUtils, config</code> - as explained here http://www.freeplane.org/doc/api/index.html?org/freeplane/plugin/script/FreeplaneScriptBaseClass.html | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==== Learn how to call freeplane-java-objects in (J)ruby ==== | ||
+ | |||
+ | |||
+ | In last section I ''presented'' the Freeplane Scripting API, which are a group of '''Java''' classes/methods. In this section, I will focus on how to ''use'' '''Java''' classes/methods inside a '''(j)ruby''' script. | ||
+ | |||
+ | Read this section once, and take a pause. Then re-read it again. It's normal to don't understand-it-all in the first time (and second), at least that's what happened to me. <br /> So know in advance that this will not be all understood in one pass - so read it relaxedly and let it sink-in afterwards. <br /> Its a bunch of concepts, and you dont need to master all of them to start with scripts. The natural evolution is to get an idea, use examples, and if/when you need then comeback and dive deeper into understanding. | ||
− | + | <small> | |
+ | NOTE: Ignore the following if you are starting with scripting | ||
+ | If/when you want to understand more about this topic, then: | ||
+ | * have a look into the Jruby wiki page dedicated to [https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#calling-java-from-jruby Calling Java from JRuby] | ||
+ | * get this book [https://www.google.es/search?q=using+jruby+inurl%3Apdf Using JRuby - The pragmatic programmers] | ||
+ | ** read only "Chapter2 Driving Java from Ruby" and "Appendix B: Ruby/Java Interoperability" | ||
+ | ** you just need to read those 2 sections of the book, its a 2-3h read, not much | ||
+ | ** dont stress out - just read it once to get an idea, and get back to it if/when you latter need it. | ||
+ | ** NOTE: if you are a java programmer getting into jruby, this book may be '''''it''''' | ||
+ | </small> | ||
− | |||
− | |||
− | |||
− | |||
− | + | The '''ruby''' language cannot use java-objects by default, but the '''J'''ruby-complete.jar adds some special-jruby-magic that makes possible for the ruby programs executed by the '''j'''ruby-complete.jar to '''''use''''' java-objects. | |
− | |||
− | |||
− | |||
− | + | So '''Jruby''' = ruby + <some-special-magic-that-enables-us-to-call-java-objects-from-inside-jruby-scripts>. | |
+ | That jruby <special-magic> is refered as "Driving Java from Ruby", and I will resume here some basics of it, enough for freeplane-ruby-script. For more info, see links at the end of section | ||
− | |||
+ | In a normal '''R'''uby program (not '''J'''ruby, just '''R'''uby as you saw in [[FreeplaneJrubyInstaller_-_developers#Learn_the_basics_of_Ruby|Learn the basics of Ruby ]]), there is: | ||
+ | * '''ruby code''' that ''drives/uses'' | ||
+ | ** '''Ruby-objects''' (Ruby-classes/methods) | ||
− | |||
− | |||
− | + | In a '''<span style="background:GreenYellow">J</span>'''ruby program (not '''R'''uby, but '''J'''ruby now), there is: | |
+ | * '''ruby code''' that ''drives/uses'' | ||
+ | ** '''Ruby-objects''' (Ruby-classes/methods) | ||
+ | ** <span style="background:GreenYellow">'''Java-objects''' (Java classes/methods) </span> | ||
+ | Jruby can access any Java-class, however we will focus mainly on the Java objects from the [http://www.freeplane.org/doc/api/ Freeplane Scripting API] | ||
− | |||
− | ===== | + | ===== Access Scripting API Java-classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config ===== |
− | + | Jruby can access any Java-class, however we will focus mainly on the Java-classes from the Freeplane Scripting API, and not all of the API java-classes, but just in those that are most usefull. | |
− | |||
− | |||
− | + | And we are in luck, that the Freeplane Scripting Api has some '''special variables predefined''' for us, to access the most-usefull [http://www.freeplane.org/doc/api/ Freeplane Scripting API] classes: '''node, c, ui, logger, htmlUtils, textUtils, menuUtils, config''' (see [[FreeplaneJrubyInstaller_-_developers#Learn_what_freeplane-java-objects_are_available_for_you_-_the_Freeplane_Scripting_API|Learn what freeplane-java-objects are available for you - the Freeplane Scripting API]]). <br />So you can simply use '''node''' to access the [http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html Proxy.Node] Java-class, and the other special-variables to access other most-usefull java-classes from the Scripting API. | |
− | |||
+ | So, Jruby can access any of the Freeplane Scripting API Java-classes, but from aaalll the classes of the API, only a small number of them is actually usefull, and for those there are '''special variables predefined''' that give quick access to those java classes. | ||
− | + | I recommend that to start playing with Scripting API, you only use the '''[http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html node]''' (and maybe others '''special variables predefined'''). <br /> | |
− | + | The '''[http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html node]''' will allow you to read/write into a node text, details, attributes, ... so should give more than enough runway for a smooth start into scripting :) | |
− | |||
− | |||
− | + | <small> | |
− | + | NOTE: Ignore the following if you are starting with scripting | |
− | + | If/when you need to access any other Java-classes, you can do it like this: | |
+ | More info in https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby | ||
+ | # Access Java-classes of Scripting API that are -->> Proxy.Xyzzz <<-- with: | ||
+ | Java::org.freeplane.plugin.script.proxy.XyzzzProxy # Java: Proxy.Xyzzz (notice that XyzzzProxy <---- Proxy.Xyzzz ) | ||
+ | Java::org.freeplane.plugin.script.proxy.MapProxy # Java: Proxy.Map | ||
+ | Java::org.freeplane.plugin.script.proxy.NodeProxy # Java: Proxy.Node | ||
+ | # | ||
+ | # NOTE: In technicall rigor, I believe that the Java-Interface 'Proxy.Node' has a Java-Class 'NodeProxy', | ||
+ | # and that Java-Class is used in Jruby as 'NodeProxy' | ||
+ | # I believe that is why all the Java-Interfaces (all the Proxy.Xyzzz) are accessed with 'XyzzzProxy' | ||
+ | # | ||
+ | |||
+ | # Access Java-classes of Scripting API that are not -->> Proxy.Xxxxx <<-- with: | ||
+ | Java::org.freeplane.core.util.FreeplaneVersion # Java: FreeplaneVersion | ||
+ | Java::org.freeplane.core.util.HtmlUtils # Java: HtmlUtils | ||
+ | Java::org.freeplane.core.util.LogUtils # Java: LogUtils | ||
+ | </small> | ||
− | |||
− | |||
+ | ===== Call Java-methods: getXXX(), setXXX(yyy), isXXX(), ... ===== | ||
− | + | Ok, so in last section we saw we can use the special-variables to access the most usefull java-scripting-classes | |
− | + | Now we'll see how to '''call''' a ''java''-'''method''''' from those ''java-classes''. | |
− | + | In Jruby, you can call Java-methods in multiple ways - they all do the same thing, just written differently: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | # Calling java-methods in "java-style" | |
− | + | the_node_text = node.getText() | |
− | + | node.setText("hello!") | |
− | + | bool_value = node.isRoot() | |
− | + | node.createChild("Im a newborne child node") | |
− | + | ||
− | + | # In ruby the parenthesis are optional | |
− | ' | + | # So we could write it as: |
− | + | the_node_text = node.getText | |
+ | node.setText "hello!" | ||
+ | bool_value = node.isRoot | ||
+ | node.createChild "Im a newborne child node" | ||
+ | |||
+ | # Jruby also allows to call aJavaMethod() like this: a_java_method() | ||
+ | # So we could write it as: | ||
+ | the_node_text = node.get_text | ||
+ | node.set_text "hello!" | ||
+ | bool_value = node.is_Root | ||
+ | node.create_child("Im a newborne child node") | ||
+ | |||
+ | # Jruby also allows to call the java-methods getXXX(), setXXX(yyy), isXXX() like this: | ||
+ | # | ||
+ | # a_value = obj.getXXX() can be written as as a_value = obj.XXX | ||
+ | # | ||
+ | # obj.setXXX(yyy) can be written as as obj.XXX = yyy | ||
+ | # | ||
+ | # a_bool = obj.isXXX() can be written as as a_bool = obj.XXX? | ||
+ | # | ||
+ | # (for more info see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#alternative-names-and-beans-convention) | ||
+ | # So we could write it as: | ||
+ | the_node_text = node.text | ||
+ | node.text = "hello!" | ||
+ | bool_value = node.root? | ||
+ | node.createChild("Im a newborne child node") | ||
+ | |||
+ | |||
+ | It's up to you to decide which to use.<br /> For an easy start, use the <code>node.getText()</code> (the "java-style") that is closer to the Scripting API syntax. <br /> With time, you may get more confortable with <code>node.text</code> and <code>node.root?</code> | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ===== Call Java-methods: conversions between ruby-type <--> java-type (automatic and manual) ===== | ||
+ | |||
+ | So now that we know how to call a java-method from Jruby, lets see how we can give arguments and receive return-values from the java-methods in our Jruby programs. | ||
+ | This section will be a long one. Just keep the idea that there are conversions going on with ruby/java and if/when you need to go into it, then this is here to help you. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | As we saw before, in a '''<span style="background:GreenYellow">J</span>'''ruby program (not '''R'''uby, but '''J'''ruby now), there is: | |
+ | * '''ruby code''' that ''drives/uses'' | ||
+ | ** '''Ruby-objects''' (Ruby-classes/methods) | ||
+ | ** <span style="background:GreenYellow">'''Java-objects''' (Java classes/methods) </span> | ||
+ | Each Java-class has '''methods'''. | ||
− | + | Each Java method takes 0-or-more '''arguments''', and returns a '''return-value''' | |
− | |||
− | |||
+ | * In the pure '''Java-world''' there are only '''java-objects''': in a java program, you have only java-objects, and java-methods expect to work with java-objects. | ||
− | + | * In the pure '''Ruby-world''' there are only '''ruby-objects''': in a ruby program, you only have ruby-objects, and ruby-methods expect to work with ruby-objects. | |
− | + | * In the '''<span style="background:GreenYellow">J</span>ruby-world''', there are both '''java-objects''' and '''ruby-objects''' coexisting in the jruby program | |
+ | ** Jruby has a magical-invisible bridge between the '''Ruby-world''' and the '''Java-world''' | ||
+ | ** In Jruby when you call a '''java-method''' with arguments that are '''ruby-objects''', then Jruby will automatically try to convert the arguments-''ruby''-objects into appropriate arguments-''java''-objects that can be used internally by the java-method | ||
+ | ** Also, in Jruby after a java-method is called, it internally delivers a return-value that is a '''java-object''', ''but'' Jruby automatically tries to convert it into an appropriate '''ruby-object''' that is returned into the jruby-program (if it cannot convert, it returns the unconverted java-object) | ||
− | + | These conversions are predefined for the most usefull ruby/java types (see bellow) and work so well normally that you dont even think about them. | |
− | |||
− | |||
+ | But in some few cases, when there is no automatic-conversion possible, then you may need to manually convert between java-objects/ruby-objects: | ||
+ | * a java-method can only work with arguments that are java-object internally: so if in the jruby-program you give it an argument that is a ruby-object, then jruby will internally try to convert that ruby-object into a java-object; but if jruby cannot do the conversion automatically (because it does not have an automatic conversion defined for that specific pair of ruby-object>>java-object types) then '''you need to make the conversion manually in you jruby-program''' <small>(because the java-method needs ''java-objects'' as arguments, it does not understand ruby-objects)</small> and craft yourself an already-converted-java-object (instead of the unconverteable-ruby-object) to use it as the argument for the java-method. | ||
+ | * on the other hand, for return-values its much simpler: if Jruby cannot convert the internal-java-object-return-value to a suitable ruby-object, then it will simply deliver the java-object to the jruby-program (jruby-programs understand java-objects along with ruby-objects). | ||
− | |||
− | |||
− | + | The conversions can be made in 2 directions: ruby-object -> java-object and java-object -> ruby-object | |
+ | For the most used ruby-types and java-types there are predefined "conversions" that are '''automatic'''. | ||
+ | For other ruby/java types the conversion are not automatic and should be made '''manually''' by the programmer (you) | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ====== Automatic jruby<->java conversions ====== | |
+ | The '''automatic''' conversions (made seamlessly by Jruby) between most used ruby-types and java-types, are: | ||
− | + | [[Image:FreeplaneJrubyInstaller__160908_170934.png]] | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | For more indepth-info, see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#ruby-to-java and https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#java-to-ruby | ||
− | |||
+ | ====== Manual jruby<->java conversions ====== | ||
− | + | Here are some '''manual''' conversions that '''you''' as programmer might have to use, for some ruby-types >> java-types, and ruby-types << java-types: | |
+ | * '''java.util.List --> ruby-Array''' | ||
+ | # java.util.List --> ruby-Array: use .to_a | ||
+ | a_java_array_list = java.util.ArrayList.new([1,2,3]) # or Vector, LinkedList, Collection, ... | ||
+ | a_ruby_array = a_java_array_list.to_a | ||
+ | #=> [1, 2, 3] | ||
+ | * '''java.util.Map --> ruby-Hash''' | ||
+ | # java.util.Map --> ruby-Hash: use .to_hash | ||
+ | a_java_hash_map = java.util.HashMap.new(:a => 123) # or Hashtable | ||
+ | a_ruby_hash = a_java_hash_map.to_hash | ||
+ | #=> {:a = 123} | ||
− | + | * '''ruby-Time --> java.util.Date''' | |
+ | # ruby-Time --> java.util.Date: use .to_java | ||
+ | a_ruby_time = Time.now | ||
+ | a_java_date = a_ruby_time.to_java | ||
+ | #=> #<Java::JavaUtil::Date:0x747541f8> | ||
+ | * '''java.util.Date --> ruby-Time''' | ||
+ | # java.util.Date --> ruby-Time | ||
+ | a_java_date = java.util.Date.new | ||
+ | a_ruby_time = Time.at(a_java_date.getTime/1000) | ||
+ | #=> 2018-01-26 23:49:32 +0100 | ||
+ | ====== Examples of jruby<->java conversions (automatic and manual) ====== | ||
− | + | Some examples of conversions: | |
− | + | # NodeProxy.setNoteText() expects as argument a java.lang.String object | |
+ | # but we can give as argument a ruby-String, and Jruby will automatically convert the ruby-String into a java.lang.String | ||
+ | # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html#setNoteText-java.lang.String- | ||
+ | node.setNoteText("Hi Im a ruby-string") | ||
+ | # NodeProxy.getNoteText() returns a java.lang.String object, which Jruby automatically converts | ||
+ | # into a ruby-String | ||
+ | # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getNoteText-- | ||
+ | a_ruby_string = node.getNoteText() | ||
− | + | # NodeProxy.getChildren() returns a java.util.List<Proxy.Node> | |
− | + | # Jruby cannot automatically convert a java.util.List, but you can convert it manually into a ruby-Array | |
+ | # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getChildren-- | ||
+ | a_java_list = node.getChildren() | ||
+ | a_ruby_array = a_java_list.to_a | ||
+ | |||
− | + | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | === Joining the pieces together === | |
− | |||
− | |||
− | |||
− | |||
− | + | We have come a long way ! | |
− | |||
− | + | Dont stress if you didnt get all of it - the strange thing would be for someone to understand it all so fast! It takes its time. Just get an idea and re-read it when you need to. | |
− | + | The journey was: | |
+ | * we [[#Learn_the_basics_of_Ruby|learned the basics of ruby]] | ||
+ | * we [[#Learn_what_freeplane-java-objects_are_available_for_you_-_the_Freeplane_Scripting_API|saw what/where is the Freeplane Scripting Api]], and how to easily access it's most usefull java-classes via the special variables: <code>node, c, ui, logger, htmlUtils, textUtils, menuUtils, config</code> | ||
+ | * then we saw [[#Call_Java-methods:_getXXX.28.29.2C_setXXX.28yyy.29.2C_isXXX.28.29.2C_...|how to call java-methods from Jruby]] (ex: getXXX(), setXXX(yyy), isXXX(), ...) | ||
+ | * and then we saw how [[#Call_Java-methods:_conversions_between_ruby-type_.3C--.3E_java-type_.28automatic_and_manual.29|conversions between ruby/java types]] happen when you call a java-method | ||
− | |||
+ | == Create your own Freeplane-Ruby-Script == | ||
+ | Creating your own freeplane-ruby-scripts is (on purpose!) very easy: | ||
− | |||
{{FreeplaneJrubyInstallerNote| | {{FreeplaneJrubyInstallerNote| | ||
− | ''' | + | A freeplane-ruby-script should: |
− | * the freeplane-ruby | + | * be placed inside the directory '''freeplane_user_dir/scripts''' (ex: freeplane_user_dir/scripts/ruby_sample_script.rb ) |
− | + | * start the file with: | |
− | + | require "freeplane_jruby_common_environment.rb" | |
+ | # The 'freeplane_jruby_environment.rb' file enables the following: | ||
+ | # # [1] From now on, you can require the gems installed in "gem_home" (and only those) | ||
+ | # # [2] Enables these omnipresent-global-methods: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config, invokeAndWait | ||
+ | # # [3] Enables RubyLiveDebugger.open_debug_here(binding) | ||
+ | |||
+ | * A new ruby script is detected after Freeplane restarts, and since then can be executed by clicking in menu '''Tools/Scripts/Ruby_Sample_Script'''. | ||
+ | * Once the ruby script appears in freeplane, you can (re)edit the ruby-script and execute it without restarting freeplane (ie, no need to restart freeplane everytime you change an existing ruby script!). | ||
+ | |||
+ | }} | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | TODO: video example of how to use debugger to test code -> copy/paste into freeplane-script and leave or remove invokeAndWait/invokeLater | ||
− | |||
− | |||
− | |||
− | + | Inside a freeplane-ruby-script you dont have to use invokeLater/invokeAndWait, but if you do there is no problem either. So when you copy/paste code from the RubyLiveDebugger into a ruby-script, you can either leave or remove the <code>invokeLater{} and invokeAndWait{}</code> wraps (because scripts are run inside the EDT-thread by default). What is this strange talk about invokeLater/invokeAndWait and RubyLiveDebugger? Read on to the next section | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | == Playing with the RubyLiveDebugger == | ||
+ | While learning Ruby did you saw something called ''irb'' that is like a debug-window where you write ruby code and see the result when you press ENTER? The ''irb'' is just great to quickly test little pieces of ruby code and see their result. Its so usefull that someone made an irb-on-steroids that can be called '''''inside''''' a running ruby program. And even better, the FreeplaneJrubyInstaller Addon comes with an improved irb-on-steroids-for-freeplane-ruby-scripts that is called '''RubyLiveDebugger''' - just for your supreme satisfaction :) | ||
− | + | So what is this '''RubyLiveDebugger''' thing? Its simple: you can "pause" and "inspect" any freeplane-ruby-script, by just putting a line with <br /> <code>RubyLiveDebugger.open_debug_here(binding)</code> <br /> anywhere in the script; when the script is running and gets to that line, it will "pause" the execution and give you a window where you can see the values of the variables in that point, try ruby commands and see their results, and then finally write "!!@" to close the window and "unpause" the script that will continue executing normally. | |
− | ''' | + | So, its like "pause"-and-"peek" into your script, ''in the middle of execution!!!''. Very usefull for debugging your freeplane-ruby-scripts :) |
− | |||
− | ''' | + | To use the '''RubyLiveDebugger''' you need to make some preparations/requirements, and to close the window you must use '''!!@''' (and not ''exit'' nor ''quit'' nor closing the window directly!). While in "pause", the Freeplane window is "freezed" |
− | |||
+ | |||
+ | {{FreeplaneJrubyInstallerNote| | ||
+ | '''Requisites to use RubyLiveDebugger''' | ||
+ | * certain Preferences have to be enabled in Freeplane | ||
+ | [[File:Preferences_RubyLiveDebugger.png|frame|400x200px|none|link={{filepath:Preferences_RubyLiveDebugger.png}}]] | ||
+ | * the freeplane-ruby-script must have a '''require "freeplane_jruby_common_environment.rb"''' | ||
− | |||
− | |||
+ | * '''RubyLiveDebugger.open_debug_here(binding)''' | ||
+ | ** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''<code>RubyLiveDebugger.open_debug_here(binding)</code>''' and when the script is executed it will open a debug window in that point of the script. The debug-session will execute <small>(in the EDT-thread)</small> with freeplane-freezed untill the debug-session and the script finish-executing | ||
+ | ** exit from a debug session with command '''''!!@''''' - and never with 'exit' nor 'quit' nor closing the debug window directly | ||
+ | }} | ||
+ | |||
− | + | ||
+ | |||
+ | |||
+ | |||
+ | |||
− | + | ||
− | + | ||
+ | |||
+ | |||
+ | TODO: | ||
+ | * explain the debugger and how to use invokeLater/invokeAndWait | ||
+ | |||
+ | |||
+ | |||
+ | {{FreeplaneJrubyInstallerNote| | ||
+ | '''Requisites to use RubyLiveDebugger (for both ''' '''''EDT-Thread (Gui freezed)''''' ''' and ''' '''''Parallel-Thread (Gui responsive)''''' ''')''' | ||
+ | * certain Preferences have to be enabled | ||
+ | [[File:Preferences_RubyLiveDebugger.png|frame|400x200px|none|link={{filepath:Preferences_RubyLiveDebugger.png}}]] | ||
+ | * the freeplane-ruby-script must have a '''require "freeplane_jruby_common_environment.rb"''' | ||
− | |||
− | |||
− | |||
+ | * '''RubyLiveDebugger.open_debug_here(binding)''' | ||
+ | ** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''RubyLiveDebugger.open_debug_here(binding)''' and when the script is executed it will open a debug window in that point of the script. The debug-session will execute in the EDT-thread, with freeplane-freezed untill the debug-session and script finish-executing | ||
+ | ** exit from a debug session with command '''''!!@''''' - and never with 'exit' nor 'quit' nor closing the debug window directly | ||
− | RubyLiveDebugger - | + | * '''RubyLiveDebugger: Parallel-Thread (Gui responsive)''' |
− | * | + | ** all freeplane-ruby-scripts can open a debug-window at any place of the script - just put in a line '''Thread.new { RubyLiveDebugger.open_debug_here(binding) }''' and when the script is executed it will open a debug window in that point of the script. The *debug-session* will execute in a *paralell thread*, and the script will resume executing inmediately *at the same time* in the EDT-Thread untill it finishes and then make freeplane unfreeze instantaneously. The debug-session (debug-window) will continue executing in paralell to the unfreezed freeplane application. |
− | * | + | ** in the debug-session of the paralell-thread, be carefull: |
− | * | + | *** freeplane-java-objects can be '''read directly''' <br /> CORRECT: <code> puts(node.text) </code> |
− | * | + | *** freeplane-java-objects can '''not''' be '''changed directly''' <br /> INCORRECT: <code> node.text="NO, wrong. Cannot change objects in *paralell-debug* without *invokeAndWait*. Dont do this!" </code><br /> CORRECT: <code> invokeAndWait { node.text="Yes!!! *paralell-debug* can change objects (only with) *invokeAndWait*"} </code><br /> freeplane-java-objects '''can''' be '''changed indirectly''' by using '''invokeAndWait {}''' or '''invokeLater {}''' |
+ | ** exit from a debug session with command '''''!!@''''' and never with 'exit' nor 'quit' or closing the debug window directly | ||
− | + | }} | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <small> | |
+ | When/if you need, there is more (much much more) info about the RubyLiveDebugger [[FreeplaneJrubyInstaller_-_making_of#RubyLiveDebugger|in the FreeplaneJrubyInstaller - making of]] | ||
+ | </small> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ||
− | + | == Jruby coding samples and tips and whatever else == | |
− | |||
− | + | * After editing an existing script, you dont need to restart freeplane. That is, you can just edit the ruby script, save it, and then in freeplane rerun the script, without restarting freeplane! | |
− | |||
− | |||
− | + | # In Jruby to access a Java-class-field, use :: like this SomeJavaClass::SomeJavaField | |
+ | # Access FreeplaneVersion::XML_VERSION (see http://www.freeplane.org/doc/api/org/freeplane/core/util/FreeplaneVersion.html) | ||
+ | the_java_class = Java::org.freeplane.core.util.FreeplaneVersion | ||
+ | the_value_of_field = the_java_class::XML_VERSION | ||
− | + | # To call a java-class-constructor, just: | |
+ | a_java_instance = JavaClass.new() | ||
+ | a_java_instance = JavaClass.new(x,y,z) | ||
+ | |||
+ | # toggle folding of node branch | ||
+ | node.folded = ! node.folded? # same as: node.setFolded( ! node.isFolded() ) | ||
+ | |||
+ | # Show message in new window | ||
+ | ui.informationMessage("Hello World!") | ||
+ | |||
+ | # Show message in status bar (bottom of freeplane) | ||
+ | c.statusInfo = "Hello World again!" | ||
− | + | # Description: | |
+ | # Lambda that receives a target_node | ||
+ | # and returns an array of all ancestor nodes (including the root-node) | ||
+ | # Arguments: | ||
+ | # target_node - a Proxy.Node | ||
+ | # Returns: | ||
+ | # Array of nodes, where array[0] is root-node, and array[-1] is the target_node | ||
+ | # [root-node, ..., parent-of-parent-of-target_node, parent-of-target_node, target_node] | ||
+ | # Usage: | ||
+ | # the_ancestry_of_node = get_ancestry_array.call(node) | ||
+ | # the_ancestry_of_node.map {|n| n.text } | ||
+ | # | ||
+ | # the_ancestry_of_nodeX = get_ancestry_array.call(nodeX) | ||
+ | # the_ancestry_of_nodeX.map {|n| n.text } | ||
+ | # | ||
+ | get_ancestry_array = lambda do |target_node| | ||
+ | ancestry = [target_node] | ||
+ | ancestry.unshift(ancestry[0].parentNode) while ( not ancestry[0].root? ) | ||
+ | ancestry | ||
+ | end | ||
+ | |||
+ | the_ancestry_of_node = get_ancestry_array.call(node) | ||
+ | the_ancestry_of_node.map {|n| n.text } | ||
+ | |||
+ | # Example: | ||
+ | [[Image:FreeplaneJrubyInstaller__161001_230705.png]] | ||
+ | # Show the Java-class of an object | ||
+ | node.java_class | ||
+ | #=> class org.freeplane.plugin.script.proxy.NodeProxy | ||
+ | |||
− | ==== | + | * invokeAndWait{} or invokeLater{}: |
+ | ** does not returns a value from the block (always returns nil) | ||
+ | ** Exceptions occuring inside the block (which will execute in a paralell thread) will only kill their thread but not propagate to launching thread. Nonetheless the exceptions are always written to freeplane log file. | ||
+ | pry(main)> invokeAndWait { 1 } | ||
+ | => nil | ||
+ | pry(main)> invokeAndWait { 5 } | ||
+ | => nil | ||
+ | pry(main)> invokeAndWait { true } | ||
+ | => nil | ||
+ | pry(main)> invokeAndWait { "invokeAndWait always returns nil, no matter what happens inside the block" } | ||
+ | => nil | ||
+ | pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" } | ||
+ | => nil | ||
+ | pry(main)> invokeAndWait { node.thisMethodDoesNotExist } | ||
+ | => nil | ||
+ | |||
+ | STDERR: Exception in thread "AWT-EventQueue-0" | ||
+ | STDERR: org.jruby.exceptions.RaiseException: (RuntimeError) Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file | ||
+ | STDERR: at RUBY.block in evaluate_ruby((pry):18) | ||
+ | |||
+ | # exception is registered in freeplane logs: <freeplane_user_dir>/logs/log.0 | ||
+ | STDERR: Exception in thread "AWT-EventQueue-0" | ||
+ | STDERR: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `thisMethodDoesNotExist' for #<Java::OrgFreeplanePluginScriptProxy::NodeProxy:0xdbe721> | ||
+ | STDERR: at RUBY.block in evaluate_ruby((pry):6) | ||
− | + | ||
− | + | * Clipboard helpers | |
− | |||
+ | module RubyClipboard | ||
+ | # Returns: string or nil | ||
+ | def self.readClipboardContentsAsString | ||
+ | java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getContents(nil).getTransferData(java.awt.datatransfer.DataFlavor.stringFlavor) || "" | ||
+ | end | ||
+ | |||
+ | # Arguments: | ||
+ | # html_for_clipboard - a string | ||
+ | def self.writeClipboardContentAsHtml(html_for_clipboard="") | ||
+ | org.freeplane.core.util.TextUtils.copyHtmlToClipboard(html_for_clipboard) | ||
+ | end | ||
+ | end | ||
+ | |||
+ | == FreeplaneJrubyInstaller Addon - The making of == | ||
− | + | If you want to go even more deep into the rabbit hole, into how this addon was made, understanding the details, mechanism and design-decisions about the addon plugin and how this all came together into freeplane, then read the [[FreeplaneJrubyInstaller_-_making_of|FreeplaneJrubyInstaller - making of]]. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Its a completely optional and big reading - if your starting with scripting, leave it for a latter day :) | |
− |
Latest revision as of 12:03, 9 January 2019
This wiki page is intended for new programmers that want to try Ruby, and make Freeplane ruby scripts.
If you just want to run a ruby script, then instead read FreeplaneJrubyInstaller Addon
If you want to know how the addon was made, then instead read FreeplaneJrubyInstaller - making of
I estimate that only a small number of people will actually feel curious to learn ruby and make freeplane scripts with ruby, which makes us - the freeplane-ruby-scripts programmers - a really small and nice club :)
So, if you anytime feel part-of-the-club (or just want to say hello), dont be shy and drop a comment in this forum thread: Announcing FreeplaneJrubyInstaller !!!!
Hope we all have a great time :)
So here we go: learn ruby, program freeplane, have fun :)
Contents
- 1 Ruby scripts examples
- 2 Freeplane ruby script = Ruby + Freeplane Scripting Api
- 2.1 Learn the basics of Ruby
- 2.2 Know how to call the Freeplane-java-objects in your (J)ruby script
- 2.2.1 Learn what freeplane-java-objects are available for you - the Freeplane Scripting API
- 2.2.2 Learn how to call freeplane-java-objects in (J)ruby
- 2.3 Joining the pieces together
- 3 Create your own Freeplane-Ruby-Script
- 4 Playing with the RubyLiveDebugger
- 5 Jruby coding samples and tips and whatever else
- 6 FreeplaneJrubyInstaller Addon - The making of
Ruby scripts examples
Start programming by tinkering with existing code, and seeing what happens. When you get stuck or need to understand something better, then go read some introduction book/tutorial (just enough) and get back to tinkering again. Its the fun of tinkering that fuels to get better, understand more, go more complex.
Sort children by icon
- Select a node, execute script, and the children will be ordered/sorted by their icons
- Related to this forum thread "Possible sort children by icon 'priority' or 'progress'?" from Cadux
Freeplane ruby script = Ruby + Freeplane Scripting Api
A ruby script for freeplane, is a ruby program that will use Freeplane java-objects from the Freeplane Scripting API
To learn how to make ruby scripts for freeplane, the ideal learning-path is to:
- Learn the basics of Ruby
- Know how to call the Freeplane-java-objects in your ruby script. This is in fact 2 things:
In the next sections, we'll go through this learning-path, which I trimmed down to the essentials (with links for further exploration, if/when you need)
Learn the basics of Ruby
Ruby is a simple language, easy to learn for new programmers, with lots of books, guides, videos and explanations in internet (almost too much)
In internet there are lots of learning material about "Ruby" and about "Ruby on Rails", but stay away from "Ruby on Rails" because that is another umongous universe (using ruby to make webpages). For freeplane scripting, we just need to learn Ruby (without Rails!), and even then, just the basics of Ruby.
To start with Ruby, I recommend you read and follow these learning links:
- Try ruby in 15 minutes, in the browser
- >> I think this should be your first contact with ruby: a guided interactive tour to do simple things in ruby
- Its all online, no install needed, and seems like 15-30min
- From here take a general idea of ruby - you'll see these things explained in more detail in other guides/books
- Learn to Program - A Place to Start for the Future Programmer
- >> I think this should be your second contact with ruby - understand a bit more of what is ruby.
While reading the book, have in the side a browser open with the Online Interactive Ruby (online irb) (see bellow) to copy/paste code from the book, and try for yourself - Small online book made to teach ruby for new programmers.
- Covers the basics, and seems well written
- >> I think this should be your second contact with ruby - understand a bit more of what is ruby.
- Online Interactive Ruby (online irb)
- >> Keep this close to you everytime you are learning ruby, it allows you to try ruby code without installing anything. This is the equivalent of an online irb.
- Instead of installing ruby in your pc and then use its "irb" to test ruby code, you can use this online irb without installing anything :)
- Good to try out small code snippets, like examples from a book, or for you to explore ruby
Also, the following links are optional support-material - they will not teach you ruby but if you have them somewhere close they will be helpfull many times.
They contain more than info than you will ever need, but are good consulting references, to clearout doubts, see examples, etc. I still use them myself:
- Ruby QuickRef Card (print it)
- >> Print these 2 pages in 1 sheet and keep it around in the desk - it will help you many times to remember ruby syntax. Take notes on it as you need :)
- Zenspider Ruby QuickRef (bookmark it)
- >> This is the best quickref of ruby. I always search here first. Its too big to be printed (what a pitty) but whenever you want, you can open it online, and "find" what you want
- Its a great reference to be consulted when needed! I never got even close to know all of it, so don't sweat. Just a reference, use it as you need it :)
- Search in google "TutorialsPoint Ruby Basics" - TutorialsPoint Ruby Basics (consult when needed)
- >> Use it like support-material: when you have a doubt about something specific, search for it in here
- It packs good info in short resumes. Sometimes a bit canonical, but the examples can be of use for you and clarifying
- Use it to consult, like a reference with examples and resumes.
- This wiki filters the webpage link as spam (which is not), so use google to reach it
For freeplane scripts, its enough to learn only the basics of ruby - its up to you to decide how deep you want to learn it. In any case, have an overall idea of Ruby, and comeback to the above links whenever you need.
NOTE: If someday you want to become a ruby pro, you will want to read the "pickaxe" book: The Pragmatic Programmers' Guide, Programming Ruby If you are a new programmer, dont start here.
Know how to call the Freeplane-java-objects in your (J)ruby script
Learn what freeplane-java-objects are available for you - the Freeplane Scripting API
Now, we will skim over the Freeplane Scripting API, which are a group of Java classes and its methods.
All the Freeplane Scripting API are Java classes and methods.
This will not make much sense to you now, after having learned the basics of ruby, because you don't know how to use Java classes/methods in ruby. These java classes will not be used just now, I will just present them so that you have an idea that they exist, and where you can find them latter, when you need them.
The freeplane-java-objects are documented in the Freeplane Scripting API, which is a Java API, where you can see the classes , the methods of each class and a short description of what each method does. The Freeplane Scripting Api should be open by your side while you'r making freeplane scripts - you will consult it all the time. Click the "FRAMES" link at the top, to show in the webpage a left-side-navigation-list with all the Classes available.
To learn freeplane scripting, the most interesting class of the Freeplane Scripting API, is Proxy.Node
- Represented in groovy and ruby scripts by the variable
node
- Look to these java methods: getText() and setText("whatever")
In Ruby scripts (imitating what happens with Groovy scripts), there are some handy special variables predefined for us to use in scripts, that point to usefull java-classes:
node, c
- as explained here http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/ScriptUtils.htmlui, logger, htmlUtils, textUtils, menuUtils, config
- as explained here http://www.freeplane.org/doc/api/index.html?org/freeplane/plugin/script/FreeplaneScriptBaseClass.html
Learn how to call freeplane-java-objects in (J)ruby
In last section I presented the Freeplane Scripting API, which are a group of Java classes/methods. In this section, I will focus on how to use Java classes/methods inside a (j)ruby script.
Read this section once, and take a pause. Then re-read it again. It's normal to don't understand-it-all in the first time (and second), at least that's what happened to me.
So know in advance that this will not be all understood in one pass - so read it relaxedly and let it sink-in afterwards.
Its a bunch of concepts, and you dont need to master all of them to start with scripts. The natural evolution is to get an idea, use examples, and if/when you need then comeback and dive deeper into understanding.
NOTE: Ignore the following if you are starting with scripting
If/when you want to understand more about this topic, then:
- have a look into the Jruby wiki page dedicated to Calling Java from JRuby
- get this book Using JRuby - The pragmatic programmers
- read only "Chapter2 Driving Java from Ruby" and "Appendix B: Ruby/Java Interoperability"
- you just need to read those 2 sections of the book, its a 2-3h read, not much
- dont stress out - just read it once to get an idea, and get back to it if/when you latter need it.
- NOTE: if you are a java programmer getting into jruby, this book may be it
The ruby language cannot use java-objects by default, but the Jruby-complete.jar adds some special-jruby-magic that makes possible for the ruby programs executed by the jruby-complete.jar to use java-objects.
So Jruby = ruby + <some-special-magic-that-enables-us-to-call-java-objects-from-inside-jruby-scripts>.
That jruby <special-magic> is refered as "Driving Java from Ruby", and I will resume here some basics of it, enough for freeplane-ruby-script. For more info, see links at the end of section
In a normal Ruby program (not Jruby, just Ruby as you saw in Learn the basics of Ruby ), there is:
- ruby code that drives/uses
- Ruby-objects (Ruby-classes/methods)
In a Jruby program (not Ruby, but Jruby now), there is:
- ruby code that drives/uses
- Ruby-objects (Ruby-classes/methods)
- Java-objects (Java classes/methods)
Jruby can access any Java-class, however we will focus mainly on the Java objects from the Freeplane Scripting API
Jruby can access any Java-class, however we will focus mainly on the Java-classes from the Freeplane Scripting API, and not all of the API java-classes, but just in those that are most usefull.
And we are in luck, that the Freeplane Scripting Api has some special variables predefined for us, to access the most-usefull Freeplane Scripting API classes: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config (see Learn what freeplane-java-objects are available for you - the Freeplane Scripting API).
So you can simply use node to access the Proxy.Node Java-class, and the other special-variables to access other most-usefull java-classes from the Scripting API.
So, Jruby can access any of the Freeplane Scripting API Java-classes, but from aaalll the classes of the API, only a small number of them is actually usefull, and for those there are special variables predefined that give quick access to those java classes.
I recommend that to start playing with Scripting API, you only use the node (and maybe others special variables predefined).
The node will allow you to read/write into a node text, details, attributes, ... so should give more than enough runway for a smooth start into scripting :)
NOTE: Ignore the following if you are starting with scripting
If/when you need to access any other Java-classes, you can do it like this: More info in https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby
# Access Java-classes of Scripting API that are -->> Proxy.Xyzzz <<-- with: Java::org.freeplane.plugin.script.proxy.XyzzzProxy # Java: Proxy.Xyzzz (notice that XyzzzProxy <---- Proxy.Xyzzz ) Java::org.freeplane.plugin.script.proxy.MapProxy # Java: Proxy.Map Java::org.freeplane.plugin.script.proxy.NodeProxy # Java: Proxy.Node # # NOTE: In technicall rigor, I believe that the Java-Interface 'Proxy.Node' has a Java-Class 'NodeProxy', # and that Java-Class is used in Jruby as 'NodeProxy' # I believe that is why all the Java-Interfaces (all the Proxy.Xyzzz) are accessed with 'XyzzzProxy' # # Access Java-classes of Scripting API that are not -->> Proxy.Xxxxx <<-- with: Java::org.freeplane.core.util.FreeplaneVersion # Java: FreeplaneVersion Java::org.freeplane.core.util.HtmlUtils # Java: HtmlUtils Java::org.freeplane.core.util.LogUtils # Java: LogUtils
Call Java-methods: getXXX(), setXXX(yyy), isXXX(), ...
Ok, so in last section we saw we can use the special-variables to access the most usefull java-scripting-classes
Now we'll see how to call a java-method from those java-classes.
In Jruby, you can call Java-methods in multiple ways - they all do the same thing, just written differently:
# Calling java-methods in "java-style" the_node_text = node.getText() node.setText("hello!") bool_value = node.isRoot() node.createChild("Im a newborne child node") # In ruby the parenthesis are optional # So we could write it as: the_node_text = node.getText node.setText "hello!" bool_value = node.isRoot node.createChild "Im a newborne child node" # Jruby also allows to call aJavaMethod() like this: a_java_method() # So we could write it as: the_node_text = node.get_text node.set_text "hello!" bool_value = node.is_Root node.create_child("Im a newborne child node") # Jruby also allows to call the java-methods getXXX(), setXXX(yyy), isXXX() like this: # # a_value = obj.getXXX() can be written as as a_value = obj.XXX # # obj.setXXX(yyy) can be written as as obj.XXX = yyy # # a_bool = obj.isXXX() can be written as as a_bool = obj.XXX? # # (for more info see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#alternative-names-and-beans-convention) # So we could write it as: the_node_text = node.text node.text = "hello!" bool_value = node.root? node.createChild("Im a newborne child node")
It's up to you to decide which to use.
For an easy start, use the node.getText()
(the "java-style") that is closer to the Scripting API syntax.
With time, you may get more confortable with node.text
and node.root?
Call Java-methods: conversions between ruby-type <--> java-type (automatic and manual)
So now that we know how to call a java-method from Jruby, lets see how we can give arguments and receive return-values from the java-methods in our Jruby programs.
This section will be a long one. Just keep the idea that there are conversions going on with ruby/java and if/when you need to go into it, then this is here to help you.
As we saw before, in a Jruby program (not Ruby, but Jruby now), there is:
- ruby code that drives/uses
- Ruby-objects (Ruby-classes/methods)
- Java-objects (Java classes/methods)
Each Java-class has methods.
Each Java method takes 0-or-more arguments, and returns a return-value
- In the pure Java-world there are only java-objects: in a java program, you have only java-objects, and java-methods expect to work with java-objects.
- In the pure Ruby-world there are only ruby-objects: in a ruby program, you only have ruby-objects, and ruby-methods expect to work with ruby-objects.
- In the Jruby-world, there are both java-objects and ruby-objects coexisting in the jruby program
- Jruby has a magical-invisible bridge between the Ruby-world and the Java-world
- In Jruby when you call a java-method with arguments that are ruby-objects, then Jruby will automatically try to convert the arguments-ruby-objects into appropriate arguments-java-objects that can be used internally by the java-method
- Also, in Jruby after a java-method is called, it internally delivers a return-value that is a java-object, but Jruby automatically tries to convert it into an appropriate ruby-object that is returned into the jruby-program (if it cannot convert, it returns the unconverted java-object)
These conversions are predefined for the most usefull ruby/java types (see bellow) and work so well normally that you dont even think about them.
But in some few cases, when there is no automatic-conversion possible, then you may need to manually convert between java-objects/ruby-objects:
- a java-method can only work with arguments that are java-object internally: so if in the jruby-program you give it an argument that is a ruby-object, then jruby will internally try to convert that ruby-object into a java-object; but if jruby cannot do the conversion automatically (because it does not have an automatic conversion defined for that specific pair of ruby-object>>java-object types) then you need to make the conversion manually in you jruby-program (because the java-method needs java-objects as arguments, it does not understand ruby-objects) and craft yourself an already-converted-java-object (instead of the unconverteable-ruby-object) to use it as the argument for the java-method.
- on the other hand, for return-values its much simpler: if Jruby cannot convert the internal-java-object-return-value to a suitable ruby-object, then it will simply deliver the java-object to the jruby-program (jruby-programs understand java-objects along with ruby-objects).
The conversions can be made in 2 directions: ruby-object -> java-object and java-object -> ruby-object
For the most used ruby-types and java-types there are predefined "conversions" that are automatic. For other ruby/java types the conversion are not automatic and should be made manually by the programmer (you)
Automatic jruby<->java conversions
The automatic conversions (made seamlessly by Jruby) between most used ruby-types and java-types, are:
For more indepth-info, see https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#ruby-to-java and https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby#java-to-ruby
Manual jruby<->java conversions
Here are some manual conversions that you as programmer might have to use, for some ruby-types >> java-types, and ruby-types << java-types:
- java.util.List --> ruby-Array
# java.util.List --> ruby-Array: use .to_a a_java_array_list = java.util.ArrayList.new([1,2,3]) # or Vector, LinkedList, Collection, ... a_ruby_array = a_java_array_list.to_a #=> [1, 2, 3]
- java.util.Map --> ruby-Hash
# java.util.Map --> ruby-Hash: use .to_hash a_java_hash_map = java.util.HashMap.new(:a => 123) # or Hashtable a_ruby_hash = a_java_hash_map.to_hash #=> {:a = 123}
- ruby-Time --> java.util.Date
# ruby-Time --> java.util.Date: use .to_java a_ruby_time = Time.now a_java_date = a_ruby_time.to_java #=> #<Java::JavaUtil::Date:0x747541f8>
- java.util.Date --> ruby-Time
# java.util.Date --> ruby-Time a_java_date = java.util.Date.new a_ruby_time = Time.at(a_java_date.getTime/1000) #=> 2018-01-26 23:49:32 +0100
Examples of jruby<->java conversions (automatic and manual)
Some examples of conversions:
# NodeProxy.setNoteText() expects as argument a java.lang.String object # but we can give as argument a ruby-String, and Jruby will automatically convert the ruby-String into a java.lang.String # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.Node.html#setNoteText-java.lang.String- node.setNoteText("Hi Im a ruby-string")
# NodeProxy.getNoteText() returns a java.lang.String object, which Jruby automatically converts # into a ruby-String # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getNoteText-- a_ruby_string = node.getNoteText()
# NodeProxy.getChildren() returns a java.util.List<Proxy.Node> # Jruby cannot automatically convert a java.util.List, but you can convert it manually into a ruby-Array # see http://www.freeplane.org/doc/api/org/freeplane/plugin/script/proxy/Proxy.NodeRO.html#getChildren-- a_java_list = node.getChildren() a_ruby_array = a_java_list.to_a
Joining the pieces together
We have come a long way !
Dont stress if you didnt get all of it - the strange thing would be for someone to understand it all so fast! It takes its time. Just get an idea and re-read it when you need to.
The journey was:
- we learned the basics of ruby
- we saw what/where is the Freeplane Scripting Api, and how to easily access it's most usefull java-classes via the special variables:
node, c, ui, logger, htmlUtils, textUtils, menuUtils, config
- then we saw how to call java-methods from Jruby (ex: getXXX(), setXXX(yyy), isXXX(), ...)
- and then we saw how conversions between ruby/java types happen when you call a java-method
Create your own Freeplane-Ruby-Script
Creating your own freeplane-ruby-scripts is (on purpose!) very easy:
Resuming A freeplane-ruby-script should:
require "freeplane_jruby_common_environment.rb" # The 'freeplane_jruby_environment.rb' file enables the following: # # [1] From now on, you can require the gems installed in "gem_home" (and only those) # # [2] Enables these omnipresent-global-methods: node, c, ui, logger, htmlUtils, textUtils, menuUtils, config, invokeAndWait # # [3] Enables RubyLiveDebugger.open_debug_here(binding)
|
TODO: video example of how to use debugger to test code -> copy/paste into freeplane-script and leave or remove invokeAndWait/invokeLater
Inside a freeplane-ruby-script you dont have to use invokeLater/invokeAndWait, but if you do there is no problem either. So when you copy/paste code from the RubyLiveDebugger into a ruby-script, you can either leave or remove the invokeLater{} and invokeAndWait{}
wraps (because scripts are run inside the EDT-thread by default). What is this strange talk about invokeLater/invokeAndWait and RubyLiveDebugger? Read on to the next section
Playing with the RubyLiveDebugger
While learning Ruby did you saw something called irb that is like a debug-window where you write ruby code and see the result when you press ENTER? The irb is just great to quickly test little pieces of ruby code and see their result. Its so usefull that someone made an irb-on-steroids that can be called inside a running ruby program. And even better, the FreeplaneJrubyInstaller Addon comes with an improved irb-on-steroids-for-freeplane-ruby-scripts that is called RubyLiveDebugger - just for your supreme satisfaction :)
So what is this RubyLiveDebugger thing? Its simple: you can "pause" and "inspect" any freeplane-ruby-script, by just putting a line with
RubyLiveDebugger.open_debug_here(binding)
anywhere in the script; when the script is running and gets to that line, it will "pause" the execution and give you a window where you can see the values of the variables in that point, try ruby commands and see their results, and then finally write "!!@" to close the window and "unpause" the script that will continue executing normally.
So, its like "pause"-and-"peek" into your script, in the middle of execution!!!. Very usefull for debugging your freeplane-ruby-scripts :)
To use the RubyLiveDebugger you need to make some preparations/requirements, and to close the window you must use !!@ (and not exit nor quit nor closing the window directly!). While in "pause", the Freeplane window is "freezed"
Resuming Requisites to use RubyLiveDebugger
|
TODO:
* explain the debugger and how to use invokeLater/invokeAndWait
Resuming Requisites to use RubyLiveDebugger (for both EDT-Thread (Gui freezed) and Parallel-Thread (Gui responsive) )
|
When/if you need, there is more (much much more) info about the RubyLiveDebugger in the FreeplaneJrubyInstaller - making of
Jruby coding samples and tips and whatever else
- After editing an existing script, you dont need to restart freeplane. That is, you can just edit the ruby script, save it, and then in freeplane rerun the script, without restarting freeplane!
# In Jruby to access a Java-class-field, use :: like this SomeJavaClass::SomeJavaField # Access FreeplaneVersion::XML_VERSION (see http://www.freeplane.org/doc/api/org/freeplane/core/util/FreeplaneVersion.html) the_java_class = Java::org.freeplane.core.util.FreeplaneVersion the_value_of_field = the_java_class::XML_VERSION
# To call a java-class-constructor, just: a_java_instance = JavaClass.new() a_java_instance = JavaClass.new(x,y,z)
# toggle folding of node branch node.folded = ! node.folded? # same as: node.setFolded( ! node.isFolded() )
# Show message in new window ui.informationMessage("Hello World!") # Show message in status bar (bottom of freeplane) c.statusInfo = "Hello World again!"
# Description: # Lambda that receives a target_node # and returns an array of all ancestor nodes (including the root-node) # Arguments: # target_node - a Proxy.Node # Returns: # Array of nodes, where array[0] is root-node, and array[-1] is the target_node # [root-node, ..., parent-of-parent-of-target_node, parent-of-target_node, target_node] # Usage: # the_ancestry_of_node = get_ancestry_array.call(node) # the_ancestry_of_node.map {|n| n.text } # # the_ancestry_of_nodeX = get_ancestry_array.call(nodeX) # the_ancestry_of_nodeX.map {|n| n.text } # get_ancestry_array = lambda do |target_node| ancestry = [target_node] ancestry.unshift(ancestry[0].parentNode) while ( not ancestry[0].root? ) ancestry end the_ancestry_of_node = get_ancestry_array.call(node) the_ancestry_of_node.map {|n| n.text } # Example:
# Show the Java-class of an object node.java_class #=> class org.freeplane.plugin.script.proxy.NodeProxy
- invokeAndWait{} or invokeLater{}:
- does not returns a value from the block (always returns nil)
- Exceptions occuring inside the block (which will execute in a paralell thread) will only kill their thread but not propagate to launching thread. Nonetheless the exceptions are always written to freeplane log file.
pry(main)> invokeAndWait { 1 } => nil pry(main)> invokeAndWait { 5 } => nil pry(main)> invokeAndWait { true } => nil pry(main)> invokeAndWait { "invokeAndWait always returns nil, no matter what happens inside the block" } => nil pry(main)> invokeAndWait { raise "Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file" } => nil pry(main)> invokeAndWait { node.thisMethodDoesNotExist } => nil STDERR: Exception in thread "AWT-EventQueue-0" STDERR: org.jruby.exceptions.RaiseException: (RuntimeError) Exceptions that happen inside the invokeAndWait block are discarded, but get registered into the freeplane log file STDERR: at RUBY.block in evaluate_ruby((pry):18) # exception is registered in freeplane logs: <freeplane_user_dir>/logs/log.0 STDERR: Exception in thread "AWT-EventQueue-0" STDERR: org.jruby.exceptions.RaiseException: (NoMethodError) undefined method `thisMethodDoesNotExist' for #<Java::OrgFreeplanePluginScriptProxy::NodeProxy:0xdbe721> STDERR: at RUBY.block in evaluate_ruby((pry):6)
- Clipboard helpers
module RubyClipboard # Returns: string or nil def self.readClipboardContentsAsString java.awt.Toolkit.getDefaultToolkit().getSystemClipboard().getContents(nil).getTransferData(java.awt.datatransfer.DataFlavor.stringFlavor) || "" end # Arguments: # html_for_clipboard - a string def self.writeClipboardContentAsHtml(html_for_clipboard="") org.freeplane.core.util.TextUtils.copyHtmlToClipboard(html_for_clipboard) end end
FreeplaneJrubyInstaller Addon - The making of
If you want to go even more deep into the rabbit hole, into how this addon was made, understanding the details, mechanism and design-decisions about the addon plugin and how this all came together into freeplane, then read the FreeplaneJrubyInstaller - making of.
Its a completely optional and big reading - if your starting with scripting, leave it for a latter day :)