require 'sketchup.rb'

module MSketchyPhysics3

def array_to_HTMLselect()
    html = '<select>'
    self.each { |ae|
        html += '<OPTION>'+ae.to_s
    }
    html += '</select>'
    return html
end

class PhysicsAppObserver < Sketchup::AppObserver

    def createSelectionObserver
        obj = PhysicsSelectionObserver.new
        Sketchup.active_model.selection.add_observer(obj)
        MSketchyPhysics3.clear_unique_objects
    end

    def initialize
        createSelectionObserver
    end

    def onNewModel(xx)
        createSelectionObserver
    end

    def onOpenModel(xx)
        createSelectionObserver
    end

end # class PhysicsAppObserver


class PhysicsSelectionObserver < Sketchup::SelectionObserver

    def initialize
        $spObjectInspector.cleanup if $spObjectInspector
        $spObjectInspector = PhysicsObjectInspector.new()
        @changed = false
    end

    def onSelectionAdded(selection, element)
        onSelectionBulkChange(selection)
    end

    def onSelectionRemoved(selection, element)
        onSelectionBulkChange(selection)
    end

    def onSelectionBulkChange(selection)
        $spObjectInspector.selectionChanged(selection) if $spObjectInspector
        @changed = true
    end

    def onSelectionCleared(selection)
        @changed = false
        UI.start_timer(0.01, false){
            next if @changed
            $spObjectInspector.selectionChanged(selection) if $spObjectInspector
        }
    end

end # class PhysicsSelectionObserver


class SketchyPhysics

    def self.addWatermarkText(x, y, text, component = 'versiontext.skp')
        dir = File.dirname(__FILE__)
        path = File.join(dir, "components/#{component}")
        return unless File.exists?(path)
        model = Sketchup.active_model
        view = model.active_view
        cd = model.definitions.load(path)
        ray = view.pickray(x,y)
        loc = ray[0]+ray[1]
        ci = model.entities.add_instance(cd, Geom::Transformation.new(loc))
        tt = ci.explode[0]
        tt.text = text
        tt.set_attribute('SketchyPhysics', 'name', 'VersionWaterMark')
        mat = model.materials.add('WatermarkText')
        mat.color = [70, 80, 100]
        tt.material = nil
        tt.material = mat
        tt
    end

    def self.updateWaterMark(new_text)
        found = false
        Sketchup.active_model.entities.each { |ent|
            next unless ent.is_a?(Sketchup::Text)
            if ent.get_attribute('SketchyPhysics', 'name', nil) == 'VersionWaterMark'
                @old_sp_version ? (ent.text = "#{new_text}\n#{ent.text}") : (ent.text = new_text)
                found = true
            end
        }
        found
    end

    def self.checkVersion
        model = Sketchup.active_model
        return false if model == @last_active_model
        @last_active_model = model
        model_version = model.get_attribute('sketchyphysics', 'version', nil)
        sp_version = MSketchyPhysics3::VERSION
        version_text = "Requires SketchyPhysics #{sp_version} !"

        if model_version == nil
          addWatermarkText(10, 12, version_text)
          model.set_attribute('sketchyphysics', 'version', sp_version)
        elsif model_version > sp_version
          msg = "Model SketchyPhysics version is #{model_version}."
          msg << "\nYour SketchyPhysics version is #{sp_version}."
          msg << "\n\nDo you want to download the latest SP version?"
          msg << "\nOr just update the version attributes."
          msg << "\n\nWarning! Updating version attribute may prevent "
          msg << "advanced scripted model from working in the future!"
          res = UI.messagebox(msg, MB_YESNO)
          if res == IDYES
            $old_version = true # This reset the simulation
            model = Sketchup.active_model
            if MSketchyPhysics3::JointConnectionTool.active?
              model.select_tool nil
              model.selection.clear
            end
            UI.openURL("http://www.sphysics.esy.es/sphysics.html")
          else
            update = true
          end
          @old_sp_version = true
        elsif model_version < sp_version
          update = true
        end

        if update
          state = updateWaterMark(version_text)
          if state && $spObjectInspector
            model.start_operation('Fix Physics Errors')
            $spObjectInspector.inspectModel
            model.commit_operation
          end
          model.set_attribute('sketchyphysics', 'version', sp_version)
        end
        model_version = sp_version unless model_version
        $sketchyphysics_version_loaded = model_version.to_f
        true
    end

    def updateBodies
        @bodies = []
        Sketchup.active_model.entities.each { |ent|
            @bodies << ent if isBody(ent)
        }
    end

    def updateCollision(group)
    end

    def inspectCollision(group)
        shape = group.get_attribute('SPOBJ', 'shape', nil)
        shape = group.get_attribute('SPOBJ', 'advancedShape', nil) unless shape
        shapes = []
        shapes << shape if shape
        joints = []
        MSketchyPhysics3.get_entities(group).each { |ent|
            if isJoint(ent)
                joints << ent.get_attribute('SPJOINT', 'name', nil)
            elsif shape.nil? && isShape(ent)
                shapes << inspectCollision(ent)
            end
        }
        shapes << 'default' if shapes.empty?
        return shapes,joints
    end

end # class SketchyPhysics


class PhysicsObjectInspector

    def isPotentialPhysicsObject(ent)
        if ent.is_a?(Sketchup::Group) || ent.is_a?(Sketchup::ComponentInstance)
            return ent.get_attribute('SPOBJ', 'ignore', nil) ? false : true
        end
        false
    end

    def isShape(ent)
        if ent.is_a?(Sketchup::Group) || ent.is_a?(Sketchup::ComponentInstance)
            return ent.get_attribute('SPOBJ', 'ignore', nil) ? false : true
        end
        false
    end

    def isBody(ent)
        if (ent.is_a?(Sketchup::Group) || ent.is_a?(Sketchup::ComponentInstance)) && ent.parent.is_a?(Sketchup::Model)
            return ent.get_attribute('SPOBJ', 'ignore', nil) ? false : true
        end
        false
    end

    def isJoint(ent)
        if (ent.is_a?(Sketchup::Group) || ent.is_a?(Sketchup::ComponentInstance)) #~ && ent.parent.is_a?(Sketchup::Model)
            return ent.get_attribute('SPJOINT', 'name', nil) ? true : false
        end
        false
    end

    def initialize
        @idToGroup = {}
        @allJoints = {}
        @selected_object = nil
        @dirty_field = nil
        @settingsDialog = nil
        @activatedDlg = nil
        @_hideJoints = true
        @_hideCPanel = false
        selectionChanged(Sketchup.active_model.selection)
    end

    def cleanup
        @settingsDialog.close() if @settingsDialog
        @settingsDialog = nil
    end

    # Anytime you see a joint register it with this function; improves speed.
    def registerJoint(grp)
        name = grp.get_attribute('SPJOINT', 'name', nil)
        raise(ArgumentError, 'Expected a joint.') unless name
        @allJoints[name] = grp
    end

    def findJoint(name)
        grp = @allJoints[name]
        return grp if grp && grp.valid?
        findAllJoints()
        @allJoints[name]
    end

    def makeJointsUnique(group)
        while group.parent.instances.length > 1
            nent = group.parent.instances[1]
            nent.make_unique
            nname = group.get_attribute('SPJOINT', 'type', nil) + nent.entityID.to_s + rand(1000).to_s
            nent.set_attribute('SPJOINT', 'name', nname)
            nent.name = nname
        end
    end

    def findAllJoints
        @allJoints = {}
        Sketchup.active_model.definitions.each { |cd|
            cd.instances.each { |ci|
                jname = ci.get_attribute('SPJOINT', 'name', nil)
                next unless jname
                # Confirm joint is unique
                if ci.parent.class != Sketchup::Model && ci.parent.instances.length > 1
                end
                if @allJoints[jname]
                end
                @allJoints[jname] = ci
            }
        }
        @allJoints
    end

    def onDialogSelectJoint(id, name)
        group = @idToGroup[id]
        unless group
            group = findJoint(name)
            unless group
                puts 'Error cant find joint'
                return
            end
        end
        inspectJointProperties(group)
    end

    def selectionChanged(selection)
        return unless @settingsDialog
        dlg = @settingsDialog
        grps = []
        ents = []
        ent = []
        Sketchup.active_model.selection.each { |e|
            grps << e if e.is_a?(Sketchup::Group) || e.is_a?(Sketchup::ComponentInstance)
            ents << e
        }
        ent = ents.first
        if ent
            index = Sketchup.active_model.entities.to_a.index(ent)
            id = ent.entityID
            java = "document.getElementById('entity_index_info').innerHTML = '#{index}';"
            java += "document.getElementById('entity_id_info').innerHTML = '#{id}';"
            java += "document.getElementById('entity_info').style.display = 'block';"
            java += "document.getElementById('no_selected').style.display = 'none';"
            dlg.execute_script(java)
        else
            java = "document.getElementById('entity_info').style.display = 'none';"
            java += "document.getElementById('no_selected').style.display = 'block';"
            dlg.execute_script(java)
        end
        dlg.execute_script("clearForm();")
        if grps.size != 1
            @selected_object = nil
            return
        end
        @selected_object = grps.first
        @idToGroup[@selected_object.entityID.to_s] = @selected_object
        inspectGroup(@selected_object)
    end

    def toggleDialog
        if @settingsDialog
            @settingsDialog.close
        else
            showDialog
            selectionChanged(Sketchup.active_model.selection)
            unless @_start_settings
              model = Sketchup.active_model
              model.set_attribute('global_set', 'theme', 'sp_light')
              model.set_attribute('global_set', 'menu', 'inspector')
              model.set_attribute('global_set', 'hide_joints', 'true')
              model.set_attribute('global_set', 'hide_cpanel', 'false')
              @_start_settings = true
            end
        end
    end

    # This close the settings dialog on simulation.
    # Location: sp_tool.rb / activate_standard, deactivate_standard
    def toggle_dialog_on_simulation
        return unless @activatedDlg
        if @settingsDialog
          @settingsDialog.close if MSketchyPhysics3::SketchyPhysicsClient.active?
        else
          showDialog
          selectionChanged(Sketchup.active_model.selection)
        end
    end

    # This security function to SUp 16
    # Location: sp_tool.rb / activate_standard
    def dialog_bring_to_front
      @settingsDialog.bring_to_front if @settingsDialog
    end

    def dialogVisible?
        @settingsDialog ? true : false
    end

    def dialogActive
        @settingsDialog
    end

    def dialogCheckDirty
        if @dirty_field
            puts "Web dialog is dirty #{@dirty_field.inspect}" if @dirty_field
            return true
        end
        false
    end

    def dialogMarkDirty(id, dict, key, value)
        if @dirty_field
            @dirty_field = [id, dict, key, value]
            puts "Marking dirty #{@dirty_field.inspect}"
        end
    end

    def dialogClearDirty(id, dict, key, value)
        if @dirty_field
            if [id, dict, key, value] != @dirty_field
                puts "Warn:dirty value do not match: #{@dirty_field.inspect}"
            end
            @dirty_field = nil
            puts 'Cleared dirty'
        end
    end

    def dialogForceUpdate
        if @dirty_field
            java = 'document.getElementsByName("myInput")'
            @settingsDialog.execute_script(java)
        end
    end

    def showDialog
        @activatedDlg = true
        view = Sketchup.active_model.active_view
        sizeX = view.vpheight / 1.5
        sizeY = view.vpheight / 1.2
        dlg = UI::WebDialog.new("Settings", true, "SP3_Settings_ui_#{MSketchyPhysics3::VERSION}", sizeX, sizeY, 5, 50)
        @settingsDialog = dlg
        last_called_time = nil
        dir = File.dirname(__FILE__)
        path = File.join(dir, 'html/settings_ui.html')
        dlg.set_file path
        MSketchyPhysics3.soundEmbedder
        dlg.min_width = 380
        dlg.min_height = 500
        dlg.set_on_close {
          @settingsDialog = nil
          @activatedDlg = nil unless MSketchyPhysics3::SketchyPhysicsClient.active?
          cmd = 'active = document.activeElement;'
          cmd << 'if (active && active.tagName.indexOf("BODY") == -1 && active.tagName.indexOf("HTML") == -1) document.activeElement.blur();'
          dlg.execute_script(cmd)
        }
        if RUBY_PLATFORM =~ /mswin|mingw/i
            dlg.show { selectionChanged(Sketchup.active_model.selection) }
        else
            dlg.show_modal { selectionChanged(Sketchup.active_model.selection) }
        end
        dlg.add_action_callback('puts'){ |d,p| puts p }
        dlg.add_action_callback('open_link'){ |d, p|
            UI.openURL(p)
        }
        dlg.add_action_callback('open_ruby_core'){ |d, p|
            v = RUBY_VERSION =~ /1.8/ ? '1.8.6' : '2.0.0'
            UI.openURL("http://ruby-doc.org/core-#{v}/")
        }
        dlg.add_action_callback('valueUpdated'){ |d,p|
            path = p.split('=')[0].split('.')
            id = path[0]
            dict = path[1]
            key = path[2]
            value = p.split("=",2)[1].to_s
            value = d.get_element_value(value)
            @dirty_field = [id, dict, key, value]
            group = @idToGroup[id]
        }
        dlg.add_action_callback('setAttribute'){ |d,p|
            path = p.split('.')
            id = path[0]
            dict = path[1]
            key = path[2].split("=")[0]
            value = p.split("=")[1].to_s.to_f
            group = @idToGroup[id]
            @dirty_field = nil
            group.set_attribute(dict, key, value)
        }
        dlg.add_action_callback('setAttributeBool'){ |d,p|
            path = p.split('.')
            id = path[0]
            dict = path[1]
            key = path[2].split("=")[0]
            value = p.split("=")[1].to_s
            value = (value == 'true')
            group = @idToGroup[id]
            group.set_attribute(dict, key, value)
        }
        dlg.add_action_callback('setAttributeString'){ |d, p|
            # This prevents it from being called twice with no delay.
            next if (last_called_time && Time.now - last_called_time < 0.25)
            last_called_time = Time.now
            path = p.split('=')[0].split('.')
            id = path[0]
            dict = path[1]
            key = path[2]
            value = d.get_element_value(key)
            @dirty_field = nil
            group = @idToGroup[id]
            next unless group
            begin
                # Fix hard spaces.
                value = value.gsub(/[\xC2\xA0]/, "\s")
                if key == 'script'
                    # Add syntax checking here
                else
                    if $spExperimentalFeatures
                        cc = MSketchyPhysics3::SP3xControllerContext.new
                    else
                        cc = MSketchyPhysics3::ControllerContext.new
                    end
                    $curEvalGroup = group
                    cbinding = cc.getBinding(0)
                    #0.upto(value.length-1){ |i| puts [value[i], value[i].chr] }
                    result = eval(value, cbinding)
                end
                group.set_attribute(dict, key, value)
            rescue Exception => e
                if @settingsDialog
                    UI.messagebox "Script Error:\n#{e}"
                end
            ensure
                $curEvalGroup = nil
            end
        }
        dlg.add_action_callback('setAttributeString2'){ |d, p|
          path = p.split('.')
          id = path[0]
          dict = path[1]
          key = path[2].split('=')[0]
          value = p.split('=')[1].to_s
          group = @idToGroup[id]
          @dirty_field = nil
          group.set_attribute(dict, key, value) if group
        }
        dlg.add_action_callback('onSelectJoint'){ |d, p|
            param = p.split(',')
            id = param[0]
            name = param[1]
            onDialogSelectJoint(id, name)
        }
        dlg.add_action_callback('evalToNumeric'){ |d, id|
          val = d.get_element_value(id)
          begin
            num = eval(val)
            raise unless num.is_a?(Numeric)
          rescue Exception => e
            num = 0
          end
          cmd = "document.getElementById('#{id}').value = '#{sprintf('%.2f', num)}'"
          dlg.execute_script(cmd)
        }
        model = Sketchup.active_model
        dlg.add_action_callback('setAceTheme'){ |d, p|
          model.set_attribute('global_set', 'theme', p)
        }
        dlg.add_action_callback('setMenu'){ |d, p|
          model.set_attribute('global_set', 'menu', p)
        }
        dlg.add_action_callback('setHideJoints'){ |d, p|
          (p == 'true') ? (@_hideJoints = true) : (@_hideJoints = false)
          model.set_attribute('global_set', 'hide_joints', p)
        }
        dlg.add_action_callback('setHideCPanel'){ |d, p|
          (p == 'true') ? (@_hideCPanel = true) : (@_hideCPanel = false)
          model.set_attribute('global_set', 'hide_cpanel', p)
        }
        dlg.add_action_callback('cleaner_buttons'){ |d, p|
          if p == '1'
            model.definitions.purge_unused
            model.layers.purge_unused
            model.materials.purge_unused
            model.styles.purge_unused
          elsif p == '2'
            Sketchup.send_action(21044) # Windows only!
          elsif p == '3'
            SKETCHUP_CONSOLE.clear # SketchUp 14+
          end
        }
    end

    # Hide Joints and Controller Panel.
    # Location: sp_tool.rb/activate_standard and deactivate_standard
    def hide_joints_and_cpanel(state)
      on = MSketchyPhysics3::SketchyPhysicsClient.active? && @_hideJoints
      # All Joints
      Sketchup.active_model.entities.each { |ent|
        next unless ent.is_a?(Sketchup::Group) || ent.is_a?(Sketchup::ComponentInstance)
        # External
        if ent.get_attribute('SPJOINT', 'type', nil)
          state ? (ent.visible = false if ent.visible? && on) : (ent.visible = true unless ent.visible?)
        end
        # Internal
        MSketchyPhysics3.get_entities(ent).each { |joints|
          if joints.get_attribute('SPJOINT', 'type', nil)
            state ? (joints.visible = false if joints.visible? && on) : (joints.visible = true unless joints.visible?)
          end
        }
      }
      # Controller panel
      (state && @_hideCPanel) ? ($hide_CPanel = true) : ($hide_CPanel = nil)
    end

    def clearPropertyGrid
        return unless @settingsDialog
        @settingsDialog.execute_script("clearPropertyGrid()")
    end

    def propertyGridAddHeader(name)
        return unless @settingsDialog
        @settingsDialog.execute_script("addPropertyGridHeader('#{name}')")
    end

    def propertyGridAddRow(id, name, value, type)
        return unless @settingsDialog
        cmd = "addPropertyGridRow('#{id}','#{name}','#{value}','#{type}')"
        @settingsDialog.execute_script(cmd)
    end

    def inspectorAddObjectRef(container, id, name)
        return unless @settingsDialog
        cmd = "addObjectRef('#{container}','#{id}','#{name}')"
        @settingsDialog.execute_script(cmd)
    end

    def appendHTML(id, html)
        return unless @settingsDialog
        cmd = "document.getElementById('#{id}').innerHTML+='"+html+"';"
        @settingsDialog.execute_script(cmd)
    end

    def insertCell(tableId, text)
    end

    def inspectConnections(group)
        jnames = JointConnectionTool.getParentJointNames(group)
        return if jnames.empty?
        java = "addFlowTableHeader('childJointsGrid','<span class='+'titles'+'>Connected To</span>');"
        @settingsDialog.execute_script(java)
        jnames.each { |jn|
            joint = findJoint(jn)
            if joint
                id = joint.entityID.to_s
                java = "addFlowTableCell('childJointsGrid','#{id}','#{jn}','<span class='+'jointName'+'>#{jn}</span>');"
                @settingsDialog.execute_script(java)
                @idToGroup[id] = group
                @idToGroup[id] = joint
            else
                #puts "Parent #{jn} not found. Delete connection?"
                JointConnectionTool.disconnectJointNamed(group, jn)
            end
        }
    end

    def inspectJoint(group)
        name = group.get_attribute('SPJOINT', 'name', 'error')
        type = group.get_attribute('SPJOINT', 'type', 'error')
        id = group.entityID.to_s
        java = "addFlowTableCell('childJointsGrid','#{id}','#{name}','<span class='+'jointName'+'>#{name}</span>');"
        @settingsDialog.execute_script(java)
        @idToGroup[id] = group
    end

    def inspectEvents(group)
        %w(timer onFreeze onUnfreeze onValueChanged).each do |key|
            value = group.get_attribute('SPEVENTS', key, nil)
        end
    end

    def escapeHTML(str)
        str = str.to_s.inspect[1..-2]
        str = str.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/\'/n, '&apos;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;').gsub(/\n/n,"\\n").gsub(/\r/n,"")
    end

    def checkJointVersion(joint)
        controller = joint.get_attribute('SPJOINT', 'controller', nil)
        return unless controller
        MSketchyPhysics3.convertControlledJoint(joint)
    end

    def inspectJointProperties(group)
        # Convert to new style hinge.
        # This probably belongs elsewhere.
        checkJointVersion(group)
        tableBody = ""
        %w(min max accel maxAccel damp Controller breakingForce ConnectedCollide).each { |key|
            value = group.get_attribute('SPJOINT', key, nil)
            next if value.nil?
            if key == 'min'
              tableBody += "<tr><td style='width:40%'>Min.</td>"
            elsif key == 'max'
              tableBody += "<tr><td style='width:40%'>Max.</td>"
            elsif key == 'accel'
              tableBody += "<tr><td style='width:40%'>Acceleration</td>"
            elsif key == 'maxAccel'
              tableBody += "<tr><td style='width:40%'>Max. Acceleration</td>"
            elsif key == 'damp'
              tableBody += "<tr><td style='width:40%'>Strength</td>"
            elsif key == 'Controller'
              tableBody += "<tr><td style='width:40%'>Controller</td>"
            elsif key == 'breakingForce'
              tableBody += "<tr><td style='width:40%'>Breaking Force</td>"
            elsif key == 'ConnectedCollide'
              tableBody += "<tr><td style='width:40%'>Connected Collide</td>"
            end
            fullName = group.entityID.to_s + '.SPJOINT.' + key
            tableBody += "<td>"
            if value.is_a?(Array)
                tableBody += array_to_HTMLselect(value)
            elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
                fullName = group.entityID.to_s + '.SPJOINT.' + key
                onclick = "setSkpAttributeBool(this.name, this.checked)"
                checked = value ? 'checked' : ''
                tableBody += "<input type='checkbox' onchange='#{onclick}' name='#{fullName}' #{checked}></input>"
            elsif value.is_a?(String)
                onchange = "setSkpAttributeString(this.name, this.id)"
                onkeydown = "tab_to_tab(event, this)"
                if key == 'Controller'
                    value = escapeHTML(value)
                    tableBody += "<input type='text' class='formulaInput' size='15' id='#{key}' onblur='#{onchange}' onkeyup='markDirty(this, this.name, this.id);' name='#{fullName}' value=#{value}></input>"
                else
                    tableBody+="<input type='text' class='whiteInput' size='15' id='#{key}' onblur='#{onchange}' onkeyup='markDirty(this, this.name, this.id);' name='#{fullName}' value=#{value}></input>"
                end
            else
                onchange = "evalToNumeric(this.id); setSkpAttribute(this.name, this.value);"
                tableBody += "<input type='text' class='whiteInput' size='15' id='#{key}' onblur='#{onchange}' name='#{fullName}' value='#{sprintf('%.2f', value.to_f)}'></input>"
            end
            tableBody += "</td></tr>"
        }
        type = group.get_attribute('SPJOINT', 'type', nil)
        if %w(hinge servo corkscrew ball).include?(type)
          name = group.entityID.to_s + '.SPJOINT.' + 'angleUnit'
          units = %w(radian degree)
          selected = group.get_attribute('SPJOINT', 'angleUnit', 'degree')
          selected = 'degree' unless units.include?(selected)
          tableBody += "<tr><td>Min/Max Angle Unit</td>"
          tableBody += "<td><select name='#{name}' style='font-size: 2vh; border: 1px solid #555;' onchange='setSkpAttributeString2(this.name, this.options[this.selectedIndex].value)'>"
          units.each { |unit|
            tableBody += "<option #{unit == selected ? 'selected' : ''} value='#{unit}'>#{unit.capitalize}</option>"
          }
          tableBody += "</select></td></tr>"
        elsif %w(slider piston).include?(type)
          name = group.entityID.to_s + '.SPJOINT.' + 'lengthUnit'
          units = %w(millimeter centimeter decimeter meter inch foot yard)
          selected = group.get_attribute('SPJOINT', 'lengthUnit', 'inch')
          selected = 'inch' unless units.include?(selected)
          tableBody += "<tr><td>Min/Max Length Unit</td>"
          tableBody += "<td><select name='#{name}' style='font-size: 2vh; border: 1px solid #555;' onchange='setSkpAttributeString2(this.name, this.options[this.selectedIndex].value)'>"
          units.each { |unit|
            tableBody += "<option #{unit == selected ? 'selected' : ''} value='#{unit}'>#{unit.capitalize}</option>"
          }
          tableBody += "</select></td></tr>"
        end

        html = "<table class='propertyGrid' style='width:99%;'>" + tableBody + "</table>"
        java = 'document.getElementById("childJointProperties").innerHTML="'+html+'";'
        @settingsDialog.execute_script(java+"visibleForm();")
    end

    def xxinspectJoint(group)
        name = group.get_attribute('SPJOINT', 'name', 'error')
        str = "Joint:" + name
        propertyGridAddHeader(str)
        type = group.get_attribute('SPJOINT', 'type', 'error')
        desc = ''
        # Create a new property grid
        tableBody = ''
        group.attribute_dictionary('SPJOINT').each_pair { |key, value|
            next if key == 'name' or key == 'type'
            propertyGridAddRow(group.entityID, 'spjoint.'+key, value, 'string')
            tableBody += "<tr><td>#{key}:#{value.class}</td>"
            fullName = group.entityID.to_s+".SPJOINT."+key
            tableBody += "<td>"
            if value.is_a?(Array)
                tableBody += array_to_HTMLselect(value)
            elsif value.class.is_a?(String)
                onchange = "setSkpAttributeString(this.name, this.value)"
                tableBody += "<input type='text' size='8' onchange='#{onchange}' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);'  onselect='puts(document.selection.createRange().text)' name='#{fullName}' value=#{value}></input>"
            else
                onchange="setSkpAttribute(this.name,this.value)"
                tableBody += "<input type='text' size='8' onchange='#{onchange}' onblur='#{onchange}' onkeypress='validateNumber();' onkeyup='markDirty(this,this.name,this.id);' onselect='puts(document.selection.createRange().text)' name='#{fullName}' value=#{value}></input>"
            end
            tableBody += "</td></tr>"
        }
        desc = type
        tableDef = "<img src='../images/medium/#{type}.png' onclick='toggleDiv(\\\"#{name}\\\")' style='width:16px;height:16px'>#{desc}<img src='../images/plus.gif' onclick='toggleDiv(\\\"#{name}\\\")'>"
        tableDef += "<div id='#{name}' style='display:none'><table class='propertyGrid' style='width:99%;'>"
        html = tableDef + tableBody + "</table></div>"
        java = 'document.getElementById("childJointsContainer").innerHTML+="'+html+'"'
        @settingsDialog.execute_script(java)
        @idToGroup[group.entityID.to_s] = group
    end

    def inspectShape(group)
        shape = group.get_attribute('SPOBJ', 'shape', nil)
        shape = group.get_attribute('SPOBJ', 'advancedShape', 'default') unless shape
        html = "<img src='../images/large_6_15/#{shape}.png' style='width:2vh;height:2vh;'>#{shape}</img>"
        java = 'document.getElementById("childObjectsContainer").innerHTML+="'+html+'";'
        @settingsDialog.execute_script(java)
        propertyGridAddRow(group.entityID, 'Shape', shape, 'static')
    end

    def inspectCollision(group)
        shape = group.get_attribute('SPOBJ', 'shape', nil)
        shape = group.get_attribute('SPOBJ', 'advancedShape', nil) unless shape
        shapes = []
        shapes << shape if shape
        joints = []
        MSketchyPhysics3.get_entities(group).each { |ent|
            if isJoint(ent)
                joints << ent.get_attribute('SPJOINT', 'name', nil)
            elsif shape.nil? && isShape(ent)
                shapes << inspectCollision(ent)
            end
        }
        shapes << 'default' if shapes.empty?
        return shapes,joints
    end

    def inspectModel
        allJoints = {}
        Sketchup.active_model.definitions.each { |cd|
            cd.instances.each { |ci|
                # Convert old style joint connections.
                if ci.get_attribute('SPOBJ', 'numParents', nil)
                    JointConnectionTool.convertConnections(ci)
                end
                if isJoint(ci)
                    checkJointVersion(ci)
                    name = ci.get_attribute('SPJOINT', 'name', nil)
                    #puts "  Joint:#{name}"
                    puts "!!Duplicated joint #{name}" if allJoints[name]
                    allJoints[name] = ci
                end
            }
        }
        children = []
        Sketchup.active_model.entities.each { |ent|
            if isBody(ent)
                shapes,joints=inspectCollision(ent)
                # Remove any hierarchy.
                shapes.flatten!
                joints.flatten!
                parents = JointConnectionTool.getParentJointNames(ent)
                if MSketchyPhysics3.get_definition(ent).instances[0]!=ent && (parents.length>0 || joints.length>0)
                    unless parents.empty?
                        puts "!!Dupe body with connections!"
                    end
                    unless joints.empty?
                        puts "!!Dupe body with joints!"
                        #~ ent.make_unique
                        MSketchyPhysics3.get_entities(ent).each { |pe|
                            type = pe.get_attribute('SPJOINT', 'type', nil)
                            next unless type
                            # Rename joint.
                            #~ pe.make_unique
                            nname = type + (20000 + rand(80000)).to_s
                            pe.set_attribute('SPJOINT', 'name', nname)
                            pe.name = nname
                            puts "Renaming to #{nname}."
                        }
                    end
                end
                common = parents & joints
                unless common.empty?
                    puts "!!Body is connected to #{common.length} of its own joints. Disconnecting."
                    common.each { |jn|
                        JointConnectionTool.disconnectJointNamed(ent,jn)
                    }
                end
                children << ent unless parents.empty?
            end
            if isJoint(ent)
                name = ent.get_attribute('SPJOINT', 'name', nil)
                #puts "  Joint:#{ent} Name:#{name}" if $debug
            end
        }
        #puts " Connections" if $debug
        children.each { |ent|
            parents = JointConnectionTool.getParentJointNames(ent)
            parents.each { |parentName|
                joint = allJoints[parentName]
                if joint
                    #puts "  Body #{ent} connected to #{parentName}" if $debug
                else
                    puts "!!Joint not found: #{parentName}"
                    JointConnectionTool.disconnectJointNamed(ent, parentName)
                    # fix
                end
            }
        }
    end

    def inspectGroup(group)
        return unless @settingsDialog
        propertyGridAddHeader('State')
        html = "<table class='stateTable' cellspacing='0'><tr>"
        statenames = %w(ignore static nocollison staticmesh magnetic frozen showcollision noautofreeze)
        statenames.each{ |sn|
            value = group.get_attribute('SPOBJ', sn, false)
            fullName = group.entityID.to_s + '.SPOBJ.' + sn
            onclick = "setSkpAttributeBool(this.name, this.checked)"
            checked = value ? 'checked' : ''
            if sn == 'ignore'
              html += "<td><span class='checkInput'><input type='checkbox' id='ig' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='ig'>Ignore</label></span></td>"
            elsif sn == 'static'
              html += "<td><span class='checkInput'><input type='checkbox' id='sta' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='sta'>Static</label></span></td>"
            elsif sn == 'nocollison'
              html += "<td><span class='checkInput'><input type='checkbox' id='noc' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='noc'>Not Collidable</label></span></td>"
            elsif sn == 'staticmesh'
              html += "<td><span class='checkInput'><input type='checkbox' id='stam' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='stam'>Static Mesh</label></span></td>"
            elsif sn == 'magnetic'
              html += "<tr><td><span class='checkInput'><input type='checkbox' id='mag' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='mag'>Magnetic</label></span></td>"
            elsif sn == 'frozen'
              html += "<td><span class='checkInput'><input type='checkbox' id='fro' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='fro'>Frozen</label></span></td>"
            elsif sn == 'showcollision'
              html += "<td><span class='checkInput'><input type='checkbox' id='sho' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='sho'>Show Collision</label></span></td>"
            elsif sn == 'noautofreeze'
              html += "<td><span class='checkInput'><input type='checkbox' id='noa' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='noa'>No Auto Freeze</label></span></td></tr>"
            end
        }
        html += "</tr></table>"
        model = Sketchup.active_model
        menu = model.get_attribute('global_set', 'menu', nil)
        hide_joints = model.get_attribute('global_set', 'hide_joints', nil)
        hide_cpanel = model.get_attribute('global_set', 'hide_cpanel', nil)
        cmd = "document.getElementById('stateContainer').innerHTML+=\"#{html}\";"
        cmd += "visibleForm();getMenu('#{menu}');getHideJoints('#{hide_joints}');getHideCPanel('#{hide_cpanel}');"
        @settingsDialog.execute_script(cmd)

        str = ''
        unless isJoint(group)
            statenames = %w(density magnet thruster emitter touchable tickable scripted) # materialid
            statenames.each { |sn|
                value = group.get_attribute('SPOBJ', sn, nil)
                fullName = group.entityID.to_s + '.SPOBJ.' + sn
                onclick = "setSkpAttributeBool(this.name, this.checked)"
                checked = value ? 'checked' : ''
                if sn == 'magnet'
                    strength = escapeHTML(group.get_attribute('SPOBJ', 'strength', 0.0))
                    strname = group.entityID.to_s + '.SPOBJ.' + 'strength'
                    onchange = "setSkpAttributeString(this.name, this.id)"
                    html = "<div class='propertyGrid' style='border-style: none solid none solid'><table width='100%'><tr><td width='20%' valign='center'>"
                    html += "<span class='checkInput'><input type='checkbox' id='magn' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='magn'>Magnet</label></span></td>"
                    html += "<td width='20%' align='right' valign='center'>Strength=</td>"
                    html += "<td><input type='text' class='formulaInput' id='strength' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{strength}></input>"
                    html += "</td></tr></table></div>"
                elsif sn == 'thruster'
                    strength = escapeHTML(group.get_attribute('SPOBJ', 'tstrength', 0.0))
                    strname = group.entityID.to_s + '.SPOBJ.' + 'tstrength'
                    onchange = "setSkpAttributeString(this.name,this.id)"
                    html = "<div class='propertyGrid'><table width='100%'><tr><td width='20%'>"
                    html += "<span class='checkInput'><input type='checkbox' id='thr' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='thr'>Thruster</label></span></td>"
                    html += "<td width='20%' align='right' valign='center'>Strength=</td>"
                    html += "<td><input type='text' class='formulaInput' id='tstrength' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{strength}></input>"
                    html += "</td></tr></table></div>"
                elsif sn == 'materialid'
                    matName = escapeHTML(group.get_attribute('SPOBJ', 'materialid', 0))
                    strname = group.entityID.to_s+'.SPOBJ.'+ 'materialid'
                    onchange = "setSkpAttribute(this.name, this.value)"
                    html = "<div class='propertyGrid'><table width='100%'><tr><td width='60%'>"
                    html += "</td><td>"
                    html += "</td><td>MaterialID<input type='text' size='5' onblur='#{onchange}' onkeypress='validateNumber();' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{matName}></input>"
                    html+="</td></tr></table></div>"
                elsif sn == 'density'
                    default = model.get_attribute('SPSETTINGS', 'defaultobjectdensity', 0.2).to_f
                    density = group.get_attribute('SPOBJ', 'density', default).to_f
                    density = 0.2 if density <= 0
                    density = escapeHTML(density)
                    strname = group.entityID.to_s + '.SPOBJ.' + 'density'
                    onchange = "setSkpAttribute(this.name, this.value)"
                    html = "<div class='propertyGrid'><table><tr>"
                    html += "<td>Density=</td>"
                    html += "<td><input type='text' class='whiteInput' style='float: left' size='3' onblur='#{onchange}'  onkeypress='validateNumber();' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{density}></input></td>"
                    html += "<span class='links' style='position:absolute;right:2vw;margin-top:2px;'>"
                    html += "<a class='buttons' href='#' onclick='cleaner_buttons(2)'>Delete Guides</a>" if RUBY_PLATFORM =~ /mswin|mingw/i && Sketchup.version.to_i.between?(13,16)
                    html += "<a class='buttons' href='#' onclick='cleaner_buttons(1)'>Purge Unused</a></span>"
                    html += "</tr></table></div>"
                elsif sn == 'emitter'
                    html = "<div class='propertyGrid' style='border-style: none solid none solid'><table width='100%'><tr><td width='20%'>"
                    html += "<span class='checkInput'><input type='checkbox' id='emi' onblur='#{onclick}' onchange='#{onclick}' name='#{fullName}' #{checked}></input><label for='emi'>Emitter</label></span></td>"
                    onchange = "setSkpAttributeString(this.name,this.id)"
                    strname = group.entityID.to_s + '.SPOBJ.' + 'emitterstrength'
                    strength = escapeHTML(group.get_attribute('SPOBJ', 'emitterstrength', 0.0))
                    html += "<td width='20%' align='right' valign='center'>Strength=</td>"
                    html += "<td><input type='text' class='formulaInput' id='emitterstrength' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{strength}></input></td>"
                    html += "</tr></table>"
                    onchange = "setSkpAttributeString(this.name, this.id)"
                    rate = escapeHTML(group.get_attribute('SPOBJ', 'emitterrate', 0))
                    ratestrname = group.entityID.to_s + '.SPOBJ.' + 'emitterrate'
                    html += "<table width='100%'>"
                    html += "<tr><td width='20%'></td><td width='20%' align='right' valign='center'>Rate=</td>"
                    html += "<td><input type='text' class='formulaInput' id='emitterrate' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{ratestrname}' value=#{rate}></input></td>"
                    html += "</tr></table>"
                    lifetime = escapeHTML(group.get_attribute('SPOBJ', 'lifetime', 0).to_i)
                    strname = group.entityID.to_s + '.SPOBJ.' + 'lifetime'
                    onchange = "setSkpAttribute(this.name, this.value)"
                    html += "<table width='100%'>"
                    html += "<tr><td width='20%'></td><td width='20%' align='right' valign='center'>Lifetime=</td>"
                    html += "<td><input type='text' class='whiteInput' onblur='#{onchange}' id='lifetime' onkeypress='validateNumber();' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{lifetime}></td>"
                    html += "</tr></table>"
                    c = group.get_attribute('SPOBJ', 'emit_continuous_collision_mode', false) ? 'checked' : ''
                    html += "<table width='100%'><tr>"
                    html += "<td><span class='checkInput'><input type='checkbox' id='emic' onchange='#{onclick}' name='#{group.entityID.to_s + '.SPOBJ.' + 'emit_continuous_collision_mode'}' #{c}></input><label for='emic'>Emit Continuous Collision Mode</label></span></td>"
                    html += "</tr></table>"
                    html += "</div>"
                elsif sn == 'touchable'
                    toggleclick = onclick + ';setDivVisibility(\"ontouch\",this.checked)'
                    html = "<div class='propertyGrid'><table width='100%'><tr>"
                    html += "<td valign='bottom' width='25%'><span class='checkInput'><input type='checkbox' id='onth' onblur='#{onclick}' onchange='#{toggleclick}' name='#{fullName}' #{checked}></input><label for='onth'>OnTouch</label></span></td>"
                    onchange = "setSkpAttributeString(this.name,this.id)"
                    rate = escapeHTML(group.get_attribute('SPOBJ', 'touchrate', 0))
                    ratestrname = group.entityID.to_s + '.SPOBJ.' + 'touchrate'
                    html += "<td width='15%' align='right' valign='center'>Rate=</td><td><input type='text' class='whiteInput' id='touchrate' onkeypress='validateNumber();'  onkeyup='markDirty(this,this.name,this.id);' onblur='#{onchange}' name='#{ratestrname}' value=#{rate}></input>"
                    html += "</td></tr></table>"
                    strname = group.entityID.to_s + '.SPOBJ.' + 'ontouch'
                    ontouch = escapeHTML(group.get_attribute('SPOBJ', 'ontouch', ''))
                    vis = (checked == 'checked') ? 'block' : 'none'
                    html += "<table width='99%'><tr><td><textarea class='formulaTextarea' type='text' WRAP=OFF cols=30 ROWS=6 style='width:100%; display:#{vis};' id='ontouch' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);'  name='#{strname}' value=#{ontouch}>#{ontouch}</textarea>"
                    html += "</td></tr></table></div>"
                elsif sn == 'tickable'
                    toggleclick = onclick + ';setDivVisibility(\"ontick\",this.checked)'
                    html = "<div class='propertyGrid' style='border-style: none solid solid solid'><table width='100%'><tr>"
                    html += "<td valign='bottom' width='25%'><span class='checkInput'><input type='checkbox' id='ontk' onblur='#{onclick}' onchange='#{toggleclick}' name='#{fullName}' #{checked}></input><label for='ontk'>OnTick</label></span></td>"
                    onchange = "setSkpAttributeString(this.name, this.id)"
                    rate = escapeHTML(group.get_attribute('SPOBJ', 'tickrate', 0))
                    ratestrname = group.entityID.to_s + '.SPOBJ.' + 'tickrate'
                    html += "<td width='15%' align='right' valign='center'>Rate=</td><td><input type='text' class='whiteInput' id='tickrate' onkeypress='validateNumber();' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{ratestrname}' value=#{rate}></input>"
                    html += "</td></tr></table>"
                    strname = group.entityID.to_s + '.SPOBJ.' + 'ontick'
                    ontick = escapeHTML(group.get_attribute('SPOBJ', 'ontick', ''))
                    vis = (checked == 'checked') ? 'block' : 'none'
                    html += "<table width='99%'><tr><td valign='bottom'><textarea class='formulaTextarea' type='text' WRAP=OFF cols=30 ROWS=6 style='width:100%; display:#{vis};' id='ontick' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{ontick}>#{ontick}</textarea>"
                    html += "</td></tr></table></div>"
                elsif sn == 'scripted'
                    toggleclick = onclick + ';setDivVisibility(\"script\",this.checked)'
                    html = "<div class='propertyGrid' style='border-style: none solid solid solid'><table width='100%'><tr>"
                    html += "<td width='100%'><span class='checkInput' style='position: absolute; margin-top: 2px;'><input id='inspector_script_check_box' type='checkbox' onblur='#{onclick}' onchange='#{toggleclick}' name='#{fullName}' onclick='checking_escb(#{checked});selectedObject();' #{checked}></input><label for='inspector_script_check_box'>Script</label></span>"
                    html += "<div class='links'><span class='buttons' id='http://www.sketchup.com/intl/en/developer/' onclick='open_link(this.id);'>SketchUp API</span><span class='buttons' id='ruby_core' onclick='open_ruby_core();'>Ruby Core</span><span class='buttons' id='http://sketchyphysics.wikia.com/wiki/Script_Overview' onclick='open_link(this.id);'>SP Wiki</span><span class='buttons' id='http://www.sphysics.esy.es/sphysics.html' onclick='open_link(this.id);'>SketchyPhysics</span></div></td>"
                    onchange = "setSkpAttributeString(this.name,this.id)"
                    strname = group.entityID.to_s + '.SPOBJ.' + 'script'
                    script = escapeHTML(group.get_attribute('SPOBJ', 'script', ''))
                    vis = (checked == 'checked') ? 'block' : 'none'
                    html += "<table width='99%'><tr><td><textarea id='script' class='formulaTextarea' type='text' WRAP=OFF cols=30 ROWS=6 style='width:100%; display:#{vis};' onblur='#{onchange}' onkeyup='markDirty(this,this.name,this.id);' name='#{strname}' value=#{script}>#{script}</textarea>"
                    html += "</td></tr></table></div>"
                end
                str += html
            }
            if isPotentialPhysicsObject(group)
                # Inspector titles
                titles = 'document.getElementById("propertiesTitle").innerHTML="Properties";'
                titles += 'document.getElementById("shapesTitle").innerHTML="Shapes";'
                titles += 'document.getElementById("jointsTitle").innerHTML="Joints";'
                # Editor panel - 2 buttons on bottom.
                bottom_buttons = "<span class='links' style='margin-right:3vh;margin-left:0'>"
                bottom_buttons += "<a class='buttons' href='#' onclick='cleaner_buttons(3)'>Clear&nbsp;Ruby&nbsp;Console</a>" if Sketchup.version.to_i > 13
                bottom_buttons += "<a class='buttons' href='ace_shortcuts.html' target='_blank'>ACE&nbsp;Shortcuts</a></span>"
                str = 'document.getElementById("propertiesContainer").innerHTML="'+str+'";'
                str2 = 'document.getElementById("editor_buttons").innerHTML="'+bottom_buttons+'";'
                # Get ace theme
                ace_theme = model.get_attribute('global_set', 'theme', nil)
                @settingsDialog.execute_script(str + str2 + titles + "selectedObject();getAceTheme('#{ace_theme}');hideChecks('_false')")
            else
                html = "<div class='ign'>The Properties is not available in 'Ignore' Group/Component!</div>"
                str = 'document.getElementById("propertiesContainer").innerHTML="'+html+'";'
                @settingsDialog.execute_script(str + "hideEditor();hideChecks('_true')")
            end
        end
        str = ''
        if isJoint(group)
            str = 'document.getElementById("selectedObjectName").innerHTML+="('+group.get_attribute('SPJOINT', 'type', 'error')+')";'
            @settingsDialog.execute_script(str+"hideEditor();hideChecks('_false')")
            # Set dialog name field
            str = 'document.getElementById("selectedObjectName").innerHTML="Joint:'+group.name+'";'
            @settingsDialog.execute_script(str)
            inspectJoint(group)
            onDialogSelectJoint(group.entityID, group.get_attribute('SPJOINT', 'name', 'error'))
            return
        end
        if isPotentialPhysicsObject(group)
          inspectConnections(group)
          shape = group.get_attribute('SPOBJ', 'shape', nil)
          shape = group.get_attribute('SPOBJ', 'advancedShape', nil) unless shape
          if shape
              inspectShape(group)
              propertyGridAddHeader('Shape:' + shape)
              str = 'document.getElementById("selectedObjectName").innerHTML+="('+shape+')";'
              @settingsDialog.execute_script(str)
              # set dialog name field
              str = 'document.getElementById("selectedObjectName").innerHTML="Body:'+group.name+'";'
              @settingsDialog.execute_script(str)
              # Now find joints. Probably want to do this at the same time as above.
              java = "addFlowTableHeader('childJointsGrid','<span class='+'titles'+'>Internal</span>');"
              @settingsDialog.execute_script(java)
              MSketchyPhysics3.get_entities(group).each { |ent|
                  inspectJoint(ent) if isJoint(ent)
              }
              return
          end
          # case if user selected an object in a group.
          unless group.parent.is_a?(Sketchup::Model)
              propertyGridAddHeader('Shape:default')
              inspectShape(group)
              str = 'document.getElementById("selectedObjectName").innerHTML="Shape:'+group.name+'";'
              @settingsDialog.execute_script(str)
              return
          end
          bIsCompound = false
          MSketchyPhysics3.get_entities(group).each { |ent|
              if isShape(ent)
                  unless bIsCompound
                      bIsCompound = true
                      propertyGridAddHeader('Compound Object')
                      str = 'document.getElementById("selectedObjectName").innerHTML="Compound Object:'+group.name+'";'
                      @settingsDialog.execute_script(str)
                  end
                  inspectShape(ent)
              end
          }
          java = "addFlowTableHeader('childJointsGrid','<span class='+'titles'+'>Internal</span>');"
          @settingsDialog.execute_script(java)
          # Now find joints. Probably want to do this at the same time as above.
          MSketchyPhysics3.get_entities(group).each { |ent|
              inspectJoint(ent) if isJoint(ent)
          }
        end
        if shape.nil? && !bIsCompound
            propertyGridAddHeader('Body is default shape')
            str = 'document.getElementById("selectedObjectName").innerHTML="Body:(box):'+group.name+'";'
            @settingsDialog.execute_script(str)
        end
    end

    def dialogSelect(grp)
    end

    def inspectSelection
        dialogSelect(Sketchup.active_model.selection[0])
    end

    def onLButtonDown(flags, x, y, view)
        @dragCount = 0
        @lButtonDown = true
        @inputPoint = Sketchup::InputPoint.new
        @inputPoint.pick view, x, y
        ph = view.pick_helper
        num = ph.do_pick x,y
        ent = ph.best_picked
        if ent.is_a?(Sketchup::Group) || ent.is_a?(Sketchup::ComponentInstance)
            Sketchup.active_model.selection.clear
            Sketchup.active_model.selection.add(ent)
            inspectSelection
        end
    end

    def deactivate(view)
        @settingsDialog.close if @settingsDialog
        @scriptDlg.close if @scriptDlg
        Sketchup.set_status_text ''
    end

    def draw(view)
        #@relatedBounds.each { |rb| view.draw(GL_LINE_STRIP, rb) }
    end

    def getExtents
        bb = Geom::BoundingBox.new
        if(@inputPoint!=nil)
            bb.add @inputPoint.position
            bb.add @inputPoint.position
        end
    end

    # This is called followed directly by onRButtonDown
    def getMenu(menu)
    end

    def onCancel(reason, menu)
    end

    def onKeyDown(key, rpt, flags, view)
        @ctrlDown = true if key == COPY_MODIFIER_KEY && rpt == 1
        @shiftDown = true if key == CONSTRAIN_MODIFIER_KEY && rpt == 1
    end

    def onKeyUp(key, rpt, flags, view)
        @ctrlDown = false if key == COPY_MODIFIER_KEY
        @shiftDown = false if key == CONSTRAIN_MODIFIER_KEY
    end

    def pickEmbeddedJoint(x,y,view)
        ph = view.pick_helper
        num = ph.do_pick x,y
        item = nil
        path = ph.path_at(1)
        return item unless path
        path.length.downto(0){ |i|
            if (path[i].is_a?(Sketchup::Group) &&
                    (path[i].parent.is_a?(Sketchup::Model) || path[i].get_attribute('SPJOINT', 'name', nil) != nil))
                item = path[i]
                #puts "ParentGroup = #{item}" if $debug
                break
            end
        }
        return item
    end

    # NOTE: Called after onLButtonDown and onLButtonUp.
    def onLButtonDoubleClick(flags, x, y, view)
    end

    def onLButtonUp(flags, x, y, view)
    end

    def onMouseMove(flags, x, y, view)
    end

    #def onMouseEnter(view)
    #end

    #def onMouseLeave(view)
    #end

    # NOTE: Called after onrButtonDown and onRButtonUp.
    def onRButtonDoubleClick(flags, x, y, view)
    end

    def onMButtonDoubleClick(flags, x, y, view)
    end

    def onRButtonDown(flags, x, y, view)
    end

    def onMButtonDown(flags, x, y, view)
    end

    def onMButtonUp(flags, x, y, view)
    end

    # Called not only right after a onRButtonDown, but after a onRButtonDoubleClick?
    def onRButtonUp(flags, x, y, view)
    end

    # NOTE: onReturn is followed directly by onKeyDown
    def onReturn(view)
    end

    def onUserText(text, view)
    end

    # Called when I double-click middle mouse button (exits out of orbit)
    def resume(view)
        @ctrlDown = false
        @shiftDown = false
    end

    # Called when I press middle mouse button (goes into orbit)
    def suspend(view)
    end

end # class PhysicsObjectInspector

Sketchup.add_observer(PhysicsAppObserver.new)

end # module MSketchyPhysics3
