Edges to Rubies The Complete SketchUp Tutorial


Transformation matrix diagram.

Appendix AR—All Ruby, Sketchup-Dependent Plugin

Chapter 20 covers the complex task of writing model-dependent Ruby plugins using one part Ruby and three parts JavaScript. Here we'll look at a Ruby plugin that uses SketchUp data to pop up a WebDialog that is entirely written in Ruby. (If you are a 300-pound Ruby monster, but a 90-pound JavaScript weakling this is not, sorry to say, the answer to your prayers. You still need a heavy dose of Chapter 18.)

This Ruby Plugin pops up this WebDialog:

Table of SketchUp keyboard shortcuts, including custom shortcuts.

This Ruby's claim to fame is that when run on your machine it will highlight your own custom keyboard shortcuts.

Installing the Keyboard Shortcuts Ruby

To install, download the file and save it in your Plugins folder. Warning: some browsers default to displaying Ruby source code. Use File/Save As ... (or File/Save Page As...) to save the code into your Plugins folder.

shortcuts.rb

Where Is My Plugins Folder?

Open the Ruby Console window. Type into the white input line what you see here. The second line at the top is your Plugins directory. (The Ruby Console is case-sensitive. "SketchUp" will not work.)

Using the Ruby Console to find the Plugins directory.

With the file in your Plugins folder, restart SketchUp. You will have a new item, "Keyboard Shortcuts", in your Plugins menu. I strongly recommend assigning it a keyboard shortcut. Mine is K. (When the dialog has focus, such as immediately after it's launched, typing "k" or "K" closes the dialog.)

If you were here to install this Ruby plugin now is the time to return. The rest of this appendix is for advanced developers.

The Overall Plan

(It would be a good idea to open the shortcuts.rb file in your code editor as you follow this discussion.)

The overall plan is simple: grab the list of keyboard shortcuts with Sketchup.get_shortcuts(). Write the HTML into strings in the Ruby program. Use WebDialog.set_html() to set the HTML directly from the Ruby strings, and then show the dialog.

There are four html strings:

The top and bottom pieces hold the fixed header (main tags, stylesheet in the <head> section, etc.) and footer (including a wee bit of JavaScript). The main table is opened in the top section and closed in the "extra" section. Extras are any shortcuts that require multiple modifiers (Shift+Ctrl+W, for example. I am no fan of multiple modifiers. Alt+Z is my choice for Camera/Zoom Window.) The middle section holds the rows of the main table.

Ruby Writing JavaScript

Mostly, we have Ruby writing HTML. There is one exception. A bit of JavaScript is hardwired in the html_bottom section. I wanted "k" to pop up the Keyboard Shortcuts list. When the list was open, I wanted "k" to close it. You cannot close an MSIE window from JavaScript. (If you try, MSIE will intercept it and pop up a message asking the user if it's OK to close. The user says "Yes" and MSIE empties, but does not close the window.) Fortunately, WebDialog.close() is trouble-free. So the JavaScript has to call a Ruby action_callback, just to close the window. This is the bottom HTML, JavaScript highlighted:

html_bottom = "
<script type='text/JavaScript'>

    function key( e ) {
        e = e || event;
        key = e.which || e.keyCode;
        if ( (key == 75) || (key == 107) ) { location = 'skp:done'; }
    }    
    
    document.onkeypress = key;

</script>
</body>
</html>
"

If you are new to JavaScript, the first two lines of the key() function are a bit of the solution to every JavaScripters nightmare: MSIE does things its own way. For example, MSIE does not return an event to an event handler's parameter; it assigns an event reference to a global event variable. The first line, e = e || event takes advantage of the fact that an undefined variable is false in a boolean expression. The next line handles another incompatibility. Finally, you call the action callback with a "K" or a "k".

Ruby Writing HTML

We read the shortcuts array into Ruby. The format is zero or more modifiers (such as "Shift+" and "Ctrl+") preceding a key letter or name (B and Backspace, for examples), a tab character and the command that describes the modifiers plus key combination.

This Ruby passes the SketchUp shortcut to a Shortcut class constructor. The constructor breaks the shortcut into a Keycombo object (letter or name plus booleans for each modifier) and a description part. The Shortcut constructor also compares the shortcut to a list of Google default shortcuts to see whether it is standard or custom. A loop in the mainline aggregates all the shortcuts which have the same key and passes them to the writing code.

The controlling routine of the row-writing process is write_html(). It is handed an array of shortcuts. For instance, using unmodified Google assignments there would be an "A" with no modifiers (Draw/Arc) and an "A" with @ctrl true (Edit/Select All). This routine calls write_cell() for each of four modifiers: ['', 'Shift', 'Ctrl', 'Alt']. This is write_cell():

def write_html_cell( scuts, modi )
    s = find( scuts, modi )
    return '<td>&nbsp;</td>' if s == nil
    return '<td' + not_google( s ) + '>' + s.description() + '</td>'
end

When modi == "Ctrl", the find() routine returns nil unless one of the shortcuts in the array has a Ctrl modifier, and no other modifiers. If find() returns nil this function assembles and returns a non-breaking space wrapped in table datum tags. (Try changing this to an empty table datum tag pair. It's quite different. You may prefer it.)

If find() returns a match, that Shortcut is used to construct a table datum item that includes the command (s.description). This is complicated by the fact that non-Google shortcuts are highlighted with <td class='not_google'>...</td>.

With that background, the write_html() routine is this:

def write_html( shortcuts_arr, line )
    mod6 = (line-1) % 6 # first group is empty
    color = mod6 < 3 ? '#ffffff' : '#f0f0ff'

    return '' if shortcuts_arr.length() == 0
    
    html = '<tr style="background:' + color + '">'
    html += '<td align=center>'  + shortcuts_arr[0].keycombo.key + '</td>'
    
    html += write_html_cell( shortcuts_arr, '' )
    html += write_html_cell( shortcuts_arr, 'Shift' )
    html += write_html_cell( shortcuts_arr, 'Ctrl' )
    html += write_html_cell( shortcuts_arr, 'Alt' )
    
    html += '</tr>'
    return html
    
end # of write_html()

Ruby Being Ruby

I'll close with a pair of comments on Ruby itself. First some praise. This is a method in the Shortcut class:

    def <=>( other )
        @keycombo.key <=> other.keycombo.key
    end
Ruby's <=> operator returns minus one, zero or one, as the left-hand operand is less than, equal to or greater than the right-hand operand. Objects that are instances of a class can be compared if the class has a <=> method. If the objects can be compared, an array of objects can be sorted. This method is all it took to allow sort!()ing the array of Shortcut objects by comparing the keys embedded in the Shortcut objects' Keycombo properties. (Bear in mind that Ruby's sort_by() is three times faster, if you are sorting large arrays.)

Now on to the Shortcut constructor:

    def initialize( str )
        @string = str
        @google = $Google_shortcuts.include?( str )
        # $1 (from beginning up to, but not including a tab),
        # $2 (then any number of chars after the first tab)
        /^([^\t]*)\t(.*)/.match( str )
        @keycombo = Keycombo.new( $1 )
        @description = $2
    end

The regular expression syntax here is taken from Perl. Conveniently for us, it was also adopted by JavaScript. Its inelegant but easy-to-write use of $1-type variables is highly Perlish. This stuff works, but /^([^\t]*)\t(.*)/ could hardly be less readable. See Appendix RE if you are not familiar with Regular Expressions.

I do hope that some genius will come up with a regex replacement that combines regex power with elegant readability.


View of apartment contents. Transformation matrix diagram.