How to prevent multiple HTML windows from opening

I’m calling a settings tool window using this code:

cmd = UI::Command.new('Settings') { self.settings_tool }
cmd.small_icon = self.icon("settings_16")
cmd.large_icon = self.icon("settings_24")
cmd.status_bar_text = 'Settings'
cmd.tooltip = 'Settings'
cmd_settings = cmd
@commands[:settings] = cmd

toolbar = UI::Toolbar.new("TestExtension")
toolbar.add_item(cmd_settings)

def self.settings_tool
  Sketchup.active_model.select_tool(SettingsTool.new)
end

And the class looks like this:

class SettingsTool

  def activate
    settings_dialog
  end

  def settings_dialog
    html_file = File.join(PATH_HTML, 'index.html')
    html_title = "Test Extension"

    width = 15 + 400 + 15
    height = 15 + 600 + 15

    options = {
      dialog_title: html_title,
      preferences_key: "asm_extensions.htmldialog.testextension",
      style: UI::HtmlDialog::STYLE_DIALOG,
      resizable: false,
      width: width,
      height: height,
      use_content_size: true
    }

    dialog = UI::HtmlDialog.new(options)
    dialog.set_file(html_file)

    dialog.add_action_callback("settings") do |language, contextual_menu|
      settings(language, contextual_menu)
    end

    dialog.center
    dialog.show
  end

  def settings(language, contextual_menu)
  [...]
  end

end # class Settings

The issue is that a new window appears every time I press the settings button. Is there any way to prevent this? I’d like to have just one window displayed, no matter how many times you press the settings button.

I’ve tried adding something like this to the class, but it didn’t yield the expected results:

def initialize
  if @dialog_open
    return
  else
    @dialog_open = false
  end
end

Any hint about what can I do to prevent multiple HTML windows from opening? Thank you in advance!

Hi @alsomar I use this in my plugins:

    if @dialog.visible?
      @dialog.bring_to_front
    else
      @dialog.show
    end

I hope it helps…

3 Likes

Thank you so much @rtches! I figured out another way, but I’ll try to implement yours to see if it works better.

After some testing, I think I managed to resolve the issue by adding a @dialog_open true/false flag to check if the window is open or not.

class SettingsTool
  def activate
    if @dialog_open == true
      return
    else
      settings_dialog
    end
  end

  def initialize
    @dialog_open = false
  end

  def settings_dialog
    html_file = File.join(PATH_HTML, 'index.html')
    html_title = "Test Extension"

    width = 15 + 400 + 15
    height = 15 + 600 + 15

    options = {
      dialog_title: html_title,
      preferences_key: "asm_extensions.htmldialog.testextension",
      style: UI::HtmlDialog::STYLE_DIALOG,
      resizable: false,
      width: width,
      height: height,
      use_content_size: true
    }

    @dialog ||= UI::HtmlDialog.new(options)
    @dialog.set_file(html_file)

    @dialog.add_action_callback("settings") do |language, contextual_menu|
      settings(language, contextual_menu)
    end

    @dialog.center
    @dialog.show
    @dialog_open = true
  end
end

Maybe not the fanciest way to fix the issue, but it works! Anyway, I’ll try to implement @rtches’ method to see if it works better. Thank you!

Argh! My fix seems to work only in SketchUp 2024. I’ll have to figure another way to do it.

(1) The docs have been changed. It is now recommended that raster icons be 24px² for small toolbar buttons and 32px² for large toolbar buttons


(2) This makes no good sense. Ie, a Tool is meant to be an event-driven interface used by the user via mouse, trackpad or keyboard to interact with the 3D model geometry.

In other words, there is no need to change the user’s active tool in order to just display a dialog window.

This means that the method to show your settings dialog can simply be a method of the extension submodule itself.

So, the method could be …

  # Init the dialog reference variable when the module is defined:
  @dialog = nil

  def self.settings_dialog
    if @dialog && @dialog.visible?
      @dialog.bring_to_front
      return
    end
    html_file = File.join(PATH_HTML, 'index.html')
    html_title = "Test Extension"

    width = 15 + 400 + 15
    height = 15 + 600 + 15

    options = {
      dialog_title: html_title,
      preferences_key: "asm_extensions.htmldialog.testextension",
      style: UI::HtmlDialog::STYLE_DIALOG,
      resizable: false,
      width: width,
      height: height,
      use_content_size: true
    }

    @dialog = UI::HtmlDialog.new(options)
    @dialog.set_file(html_file)

    @dialog.add_action_callback("settings") do |language, contextual_menu|
      settings(language, contextual_menu)
    end

    @dialog.set_on_closed { @dialog = nil }

    @dialog.center
    @dialog.show
  end

ADD: There may have been a bug in one of the recent versions where the #center method only worked after the dialog was shown. I think it may have been fixed since.

3 Likes

You have defined a tool class (SettingsTool). Then every time you press the settings button you selected that tool by creating a new instance of it by passing it to Model#select_tool.

This tool in your example does nothing, but display a HtmlDialog.
The first question arise, why do you use tool to create dialog?

Anyway you need to handle the dialog to close within that tool (instance), because if you select an other tool (any other native or just a new instance of your SettingsTool) the HtmlDialog will (can) be left open without reference unless the user close it manually.
So you have to create for example a #deactivate method and close the HtmlDialog inside that method, so then it is called when the tool is deactivated because a different tool was selected, and the HtmlDialog will be closed. Something like:

def deactivate(view)
  @dialog.close if @dialog && @dialog.visible?
end

Bear in mind again, every time you press the settings button you are creating a new instance of the SettingsTool. The @dialog will belong to that instance of the SettingsTool object!

2 Likes

@alsomar This can bite you (or your users.) There are open issues with GUI scaling that can cause the content to be larger than you would think on a user’s high-res display. Trimble features themselves have fallen victim to this issue.

Always (at a minimum) allow your users to adjust the size of the dialog window to what they need to access the content. Scrollable however is debatable.

1 Like

Well, I was just creating an example extension to learn things, but you’re right. The reason was I thought a tool would avoid multiple windows, but it obviously didn’t work. Lesson learned.

I didn’t know, thank you for the heads up.

I thought a tool would resolve some of my problems, but it looks like it’s not the best way to fix this. I’ll try to do it as a method, as you describe. As always, thank you for pointing me in the right direction. Very much appreciated.

Not sure but I think I was following an HTML example from the SketchUp GitHub and didn’t question whether it was a good idea or not. I’ll try to follow your advice, so thank you again.

… which are a couple of years old by now. The scaling “gotcha” happened with the release of 2024 which updated the Chromium framework to 112. It came as a surprise that the dialog HTML content was additionally scaled by the user’s system text scaling on top of applying the user’s display scaling.
The cause “culprit” was not identified until too late in the initial release cycle. But it serves as a good example to educate developers on scaling issues.

Your dialog might be able to check itself in JavaScript window.devicePixelRatio against the Ruby UI.scale_factor. If they do not match, then it is likely the user’s system text scaling is more than 1.0 and has also been applied. If so, then divide JS’s window.devicePixelRatio by Ruby’s UI.scale_factor to get the user’s system text scaling factor. Then you might tell the dialog to increase it’s width and height by that factor.


FYI: The system text scaling setting (for Win 10) is at:

  • Settings > Ease of Access > Make text bigger
1 Like

I just tested and now I agree: “resizable: true” is a must for my extension. Thank you for pointing out this issue.

1 Like