Class: Cosmos::QtTool

Inherits:
Qt::MainWindow
  • Object
show all
Defined in:
lib/cosmos/gui/qt_tool.rb

Overview

Base class of all COSMOS GUI Tools based on QT. It creates the help menu which contains the About menu option. It provides configuration to all tools to remember both the application window location and size across executions. It also redirects all I/O from the application (any printing to stdout or stderr) and creates popups.

Constant Summary collapse

@@redirect_io_thread =
nil

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ QtTool

Returns a new instance of QtTool.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/cosmos/gui/qt_tool.rb', line 32

def initialize(options)
  # Call QT::MainWindow constructor
  super() # MUST BE FIRST - All code before super is executed twice in RubyQt Based classes

  # Add Path for plugins
  Qt::Application.instance.addLibraryPath(Qt::PLUGIN_PATH) if Kernel.is_windows?

  # Prevent killing the parent process from killing this GUI application
  Process.setpgrp unless Kernel.is_windows?

  self.class.redirect_io if options.redirect_io

  # Configure instance variables
  @options = options
  @about_string = nil

  self.window_title = options.title
  Cosmos.load_cosmos_icon
end

Class Method Details

.create_default_optionsOptionParser, OpenStruct

Creates the default application options in a OpenStruct instance. These options include the window size and position. Options also exist to automatically size and position the window and whether to remember the previous size and position.

Returns:

  • (OptionParser, OpenStruct)

    The options parser which contains all default command line options as well as the open struct which contains the default values.



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/cosmos/gui/qt_tool.rb', line 214

def self.create_default_options
  options = OpenStruct.new
  options.auto_position = true
  options.x = 0
  options.y = 0
  options.auto_size = true
  options.width = 800
  options.height = 600
  options.command_line_geometry = false
  options.remember_geometry = true
  options.restore_position = true
  options.restore_size = true
  options.redirect_io = true
  options.title = "COSMOS Tool"

  parser = OptionParser.new do |option_parser|
    option_parser.banner = "Usage: ruby #{option_parser.program_name} [options]"
    option_parser.separator("")

    # Create the help option
    option_parser.on("-h", "--help", "Show this message") do
      puts option_parser
      exit
    end

    # Create the version option
    option_parser.on("-v", "--version", "Show version") do
      puts "COSMOS Version: #{COSMOS_VERSION}"
      puts "User Version: #{USER_VERSION}" if defined? USER_VERSION
      exit
    end

    # Create the system option
    option_parser.on("--system VALUE", "Use an alternative system.txt file") do |arg|
      System.instance(File.join(USERPATH, 'config', 'system', arg))
    end

    # Create the minimized option
    option_parser.on("--minimized", "Start the tool minimized") do |arg|
      options.startup_state = :MINIMIZED
    end

    # Create the maximized option
    option_parser.on("--maximized", "Start the tool maximized") do |arg|
      options.startup_state = :MAXIMIZED
    end

    # Create the defaultsize option
    option_parser.on("--defaultsize", "Start the tool in its default size") do |arg|
      options.startup_state = :DEFAULT
    end

    # Create the x and y position options
    option_parser.separator("")
    option_parser.separator("Window X & Y Position Options:")
    option_parser.separator("  Positive values indicate a position from the top and left of the screen.")
    option_parser.separator("  Negative values indicate a position from the bottom and right of the screen.")
    option_parser.separator("  A value of -1 indicates to place the right or bottom side of the window")
    option_parser.separator("  next to the right or bottom edge of the screen.")
    option_parser.on("-x VALUE", "--xpos VALUE", Integer, "Window X position") do |arg|
      options.x = arg
      options.auto_position = false
      options.command_line_geometry = true
    end
    option_parser.on("-y VALUE", "--ypos VALUE", Integer, "Window Y position") do |arg|
      options.y = arg
      options.auto_position = false
      options.command_line_geometry = true
    end

    # Create the width and height options
    option_parser.separator("")
    option_parser.separator("Window Width and Height Options:")
    option_parser.separator("  Specifing width and height will force the specified dimension.")
    option_parser.separator("  Otherwise the window will layout according to its defaults.")
    option_parser.on("-w VALUE", "--width VALUE", Integer, "Window width") do |arg|
      options.width = arg
      options.auto_size = false
      options.command_line_geometry = true
    end
    option_parser.on("-t VALUE", "--height VALUE", Integer, "Window height") do |arg|
      options.height = arg
      options.auto_size = false
      options.command_line_geometry = true
    end
    option_parser.separator ""
  end

  return parser, options
end

.graceful_killObject



374
375
376
# File 'lib/cosmos/gui/qt_tool.rb', line 374

def self.graceful_kill
  # Just to remove warning
end

.post_options_parsed_hook(options) ⇒ Boolean

Called after parsing all the command line options passed to the application. Users can re-implement this method to return false which will cause the application to exit without being shown. Return true to contine creating the window and execing the application.

Parameters:

  • options (OpenStruct)

    The application options as configured in the command line

Returns:

  • (Boolean)

    Whether to contine running the application



160
161
162
# File 'lib/cosmos/gui/qt_tool.rb', line 160

def self.post_options_parsed_hook(options)
  true
end

.pre_window_new_hook(options) ⇒ Object

Called after the Qt::Application has been created but before the application itself has been created. This is the last chance to execute custom code before the application executes.

Parameters:

  • options (OpenStruct)

    The application options as configured in the command line



170
171
# File 'lib/cosmos/gui/qt_tool.rb', line 170

def self.pre_window_new_hook(options)
end

.redirect_io(stdout = true, stderr = true) ⇒ Object

Redirect stdout and stderr to a stringIO and monitor it for text. If text is found create a popup titled “Unexpected STD output”. Thus applications should not print to standard output or standard error in their applications unless they are trying to warn the user.

NOTE: This is automatically called in #initialize if the redirect_io option is set which it is by default.

NOTE: For debugging purposes use STDOUT.puts “Message” which will print to the command line when run from the command line. If run from the COSMOS Launcher the output will be lost.

Parameters:

  • stdout (Boolean) (defaults to: true)

    Whether to redirect standard output

  • stderr (Boolean) (defaults to: true)

    Whether to redirect standard error



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/cosmos/gui/qt_tool.rb', line 319

def self.redirect_io(stdout = true, stderr = true)
  if stdout
    stdout_stringio = StringIO.new('', 'r+')
    $stdout = stdout_stringio
  end

  if stderr
    stderr_stringio = StringIO.new('', 'r+')
    $stderr = stderr_stringio if stderr
  end

  # Monitor for text to be written
  @@redirect_io_thread = Thread.new do
    @@redirect_io_thread_sleeper = Sleeper.new
    begin
      loop do
        if stdout and stdout_stringio.string.length > 0
          saved_string = stdout_stringio.string.dup
          stdout_stringio.string = ''
          Qt.execute_in_main_thread(true) do
            ScrollTextDialog.new(Qt::CoreApplication.instance.activeWindow, 'Unexpected STDOUT output', saved_string)
          end
        end
        if stderr and stderr_stringio.string.length > 0
          saved_string = stderr_stringio.string.dup
          stderr_stringio.string = ''
          Qt.execute_in_main_thread(true) do
            ScrollTextDialog.new(Qt::CoreApplication.instance.activeWindow, 'Unexpected STDERR output', saved_string)
          end
        end
        break if @@redirect_io_thread_sleeper.sleep(1)
      end
    rescue Exception => error
      Qt.execute_in_main_thread(true) { || ExceptionDialog.new(Qt::CoreApplication.instance.activeWindow, error, 'Exception in Redirect IO Thread') }
    end
  end
end

.restore_io(stdout = true, stderr = true) ⇒ Object

Restore stdout and stderr so text will not be captured and generate a popup. This should be called if redirect_io was called.

NOTE: #closeEvent automatically calls restore_io if the redirect_io option is set (which means redirect_io was called upon startup).

Parameters:

  • stdout (Boolean) (defaults to: true)

    Whether to redirect standard output

  • stderr (Boolean) (defaults to: true)

    Whether to redirect standard error



365
366
367
368
369
370
371
372
# File 'lib/cosmos/gui/qt_tool.rb', line 365

def self.restore_io(stdout = true, stderr = true)
  $stdout = STDOUT if stdout
  $stderr = STDERR if stderr
  @@redirect_io_thread_sleeper.cancel
  Qt::CoreApplication.processEvents()
  Cosmos.kill_thread(self, @@redirect_io_thread)
  @@redirect_io_thread = nil
end

.run(option_parser = nil, options = nil) ⇒ Object

Create the default application options and parse the command line options. Create the application instance and call exec on the Qt::Application.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/cosmos/gui/qt_tool.rb', line 176

def self.run(option_parser = nil, options = nil)
  Cosmos.set_working_dir do
    option_parser, options = create_default_options() unless option_parser and options
    option_parser.parse!(ARGV)

    if post_options_parsed_hook(options)
      @@application = nil
      begin
        @@application = Qt::Application.new(ARGV)
        @@application.addLibraryPath(Qt::PLUGIN_PATH) if Kernel.is_windows?
        pre_window_new_hook(options)
        @@window = self.new(options)
        #Qt.debug_level = Qt::DebugLevel::High
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_ALL)
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_AMBIGUOUS)
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_CALLS)
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_GC)
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_METHOD_MISSING)
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_VERBOSE)
        #Qt::Internal::setDebug(Qt::QtDebugChannel::QTDB_VIRTUAL)
        @@application.exec
      rescue Exception => error
        unless error.class == SystemExit or error.class == Interrupt
          Cosmos.handle_fatal_exception(error, false)
        end
      end
    end
  end
end

Instance Method Details

#aboutObject

Display the AboutDialog with the @about_string. The @about_string should be set by the user in the constructor of their application.



148
149
150
# File 'lib/cosmos/gui/qt_tool.rb', line 148

def about
  AboutDialog.new(self, @about_string)
end

#closeEvent(event) ⇒ Object

The closeEvent is sent to the application when it is about to close. We re-implement it in order to remember the position and size of the window for subsequent launches of the application.

Parameters:

  • event (Qt::CloseEvent)

    The close event passed by Qt



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cosmos/gui/qt_tool.rb', line 126

def closeEvent(event)
  if @options.remember_geometry and not @options.command_line_geometry
    settings = Qt::Settings.new('Ball Aerospace', self.class.to_s)
    settings.setValue('position', Qt::Variant.new(pos()))
    settings.setValue('size',     Qt::Variant.new(size()))
  end

  self.class.restore_io if @options.redirect_io

  # Close any remaining dialogs
  qt_version_split = Qt::qVersion.split('.')

  # Only closeAllWindows on Qt versions greater than 4.6
  if qt_version_split[0].to_i > 4 or (qt_version_split[0].to_i == 4 and qt_version_split[1].to_i > 6)
    Qt::Application.closeAllWindows()
  end

  super(event)
end

#complete_initializeObject

This should be called after the tool has been completely laid out and all widgets added. It resizes the application if necessary and positions the window on the screen if necessary. It also remembers the size and position of the windows for subsequent launches of the application. Finally it can initally show the application as minimized or maximized.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/cosmos/gui/qt_tool.rb', line 79

def complete_initialize
  # Handle manually sizing the window
  resize(@options.width, @options.height) unless @options.auto_size

  # Handle manually positioning the window
  unless @options.auto_position
    # Get the desktop's geometry
    desktop = Qt::Application.desktop

    # Handle position relative to right edge
    @options.x = desktop.width - frameGeometry().width + @options.x + 1 if @options.x < 0

    # Handle position relative to bottom edge
    @options.y = desktop.height - frameGeometry().height + @options.y + 1 if @options.y < 0

    # Move to the desired position
    move(@options.x, @options.y)
  end

  if @options.remember_geometry and !@options.command_line_geometry
    settings = Qt::Settings.new('Ball Aerospace', self.class.to_s)
    if settings.contains('size') and @options.restore_size and @options.startup_state != :DEFAULT
      size = settings.value('size').toSize
      resize(size)
    end
    if settings.contains('position') and @options.restore_position
      position = settings.value('position').toPoint
      move(position)
    end
  end

  case @options.startup_state
  when :MINIMIZED
    showMinimized()
  when :MAXIMIZED
    showMaximized()
  else
    show()
  end
  self.raise()
end

#initialize_actionsObject

Create the @exit_action and the @about_action. The @exit_action is not placed in the File menu and must be manually added by the user. The



55
56
57
58
59
60
61
62
63
64
65
# File 'lib/cosmos/gui/qt_tool.rb', line 55

def initialize_actions
  @exit_action = Qt::Action.new(Cosmos.get_icon('close.png'), tr('E&xit'), self)
  @exit_keyseq = Qt::KeySequence.new(tr('Ctrl+Q'))
  @exit_action.shortcut = @exit_keyseq
  @exit_action.statusTip = tr('Exit the application')
  connect(@exit_action, SIGNAL('triggered()'), self, SLOT('close()'))

  @about_action = Qt::Action.new(Cosmos.get_icon('help.png'), tr('&About'), self)
  @about_action.statusTip = tr('About the application')
  connect(@about_action, SIGNAL('triggered()'), self, SLOT('about()'))
end

#initialize_help_menuObject

Creates the Help menu and adds the @about_action to it. Thus this MUST be called after initialize_actions.



69
70
71
72
# File 'lib/cosmos/gui/qt_tool.rb', line 69

def initialize_help_menu
  @help_menu = menuBar().addMenu(tr('&Help'))
  @help_menu.addAction(@about_action)
end