# encoding: ascii-8bit # Copyright 2014 Ball Aerospace & Technologies Corp. # All Rights Reserved. # # This program is free software; you can modify and/or redistribute it # under the terms of the GNU General Public License # as published by the Free Software Foundation; version 3 with # attribution addendums as found in the LICENSE.txt # This file require QT for use by COSMOS. It exists to provide a location for # adding workarounds should they be needed to work through problems with # interacting with QT from Ruby. require 'cosmos' check_filename = File.join(Cosmos::USERPATH, "#{File.basename($0, File.extname($0))}_qt_check.txt") qt_in_system_folder = false # Check for Qt dlls in Window system folders if Kernel.is_windows? windir = ENV['SystemRoot'] if windir # Check Windows system folders for existing Qt dll files ['system', 'SysWOW64', 'System32'].each do |folder| break if qt_in_system_folder ['libgcc_s_dw2-1.dll', 'mingwm10.dll', 'phonon4.dll', 'Qt3Support4.dll', 'QtCLucene4.dll', 'QtCore4.dll', 'QtDeclarative4.dll', 'QtDesigner4.dll', 'QtDesignerComponents4.dll', 'QtGui4.dll', 'QtHelp4.dll', 'QtMultimedia4.dll', 'QtNetwork4.dll', 'QtOpenGL4.dll', 'QtScript4.dll', 'QtScriptTools4.dll', 'QtSql4.dll', 'QtSvg4.dll', 'QtTest4.dll', 'QtWebKit4.dll', 'QtXml4.dll', 'QtXmlPatterns4.dll'].each do |qtfilename| break if qt_in_system_folder filename = File.join(windir, folder, qtfilename) if File.exist?(filename) qt_in_system_folder = true # Is this the first time we've detected this? if File.exist?(check_filename) # We tried before and failed File.delete(check_filename) if $0 =~ /Launcher/ require 'cosmos/win32/win32' Cosmos::Win32.message_box("Found conflicting Qt dll file at: #{filename}\nPlease overwrite all Qt dlls in the Windows system folders with the newest Qt4 version (or delete them).") end raise "Found conflicting Qt dll file at: #{filename}. Please overwrite all Qt dlls in the Windows system folders with the newest Qt4 version (or delete them)." else # First Time we've detected this - We'll create it and risk just requiring Qt once File.open(check_filename, 'w') {|file| file.write("Qt Dll Check In Progress")} end end end end end end # This will either lock up or raise an error if older Qt dlls are present in the Windows system folders require 'Qt' File.delete(check_filename) if Kernel.is_windows? and File.exist?(check_filename) module Cosmos BIN_FILE_PATTERN = "Bin Files (*.bin);;All Files (*)" TXT_FILE_PATTERN = "Txt Files (*.txt);;All Files (*)" CSV_FILE_PATTERN = "Csv Files (*.csv);;All Files (*)" CMD_FILE_PATTERN = "Cmd Log Files (*cmd*.bin);;Bin Files (*.bin);;All Files (*)" TLM_FILE_PATTERN = "Tlm Log Files (*tlm*.bin);;Bin Files (*.bin);;All Files (*)" COLORS = {} BRUSHES = {} PALETTES = {} PENS = {} FONTS = {} FONT_METRICS = {} CURSORS = {} def self.getColor(color_r, color_g = nil, color_b = nil) return color_r if (color_r.is_a? Qt::Color) || (color_r.is_a? Qt::Pen) || (color_r.is_a? Qt::LinearGradient) color = nil key = color_r key = key.to_i if key.is_a? Qt::Enum if color_r && color_g && color_b key = (color_r.to_i << 24) + (color_g.to_i << 16) + (color_b.to_i << 8) end if Cosmos::COLORS[key] color = Cosmos::COLORS[key] else if color_r && color_g && color_b color = Qt::Color.new(color_r.to_i, color_g.to_i, color_b.to_i) else color = Qt::Color.new(color_r) end Cosmos::COLORS[key] = color end color end def self.getBrush(color) return nil unless color return color if color.is_a? Qt::Brush brush = nil color = Cosmos.getColor(color) brush = BRUSHES[color] unless brush if color.is_a? Qt::LinearGradient brush = Qt::Brush.new(color) else brush = Qt::Brush.new(color, Qt::SolidPattern) end BRUSHES[color] = brush end brush end def self.getPalette(foreground, background) foreground = Cosmos.getColor(foreground) background = Cosmos.getColor(background) PALETTES[foreground] ||= {} p = PALETTES[foreground][background] unless p p = Qt::Palette.new p.setColor(Qt::Palette::Text, foreground) p.setColor(Qt::Palette::Base, background) p.setColor(Qt::Palette::Window, background) PALETTES[foreground][background] = p end p end def self.getPen(color = nil) color = Cosmos.getColor(color) if color pen = nil pen = PENS[color] unless pen if color pen = Qt::Pen.new(color) else pen = Qt::Pen.new end PENS[color] = pen end pen end def self.getFont(font_face, font_size, font_attrs = nil, font_italics = false) FONTS[font_face] ||= {} FONTS[font_face][font_size] ||= {} FONTS[font_face][font_size][font_attrs] ||= {} font = FONTS[font_face][font_size][font_attrs][font_italics] unless font if font_attrs and font_italics font = Qt::Font.new(font_face, font_size, font_attrs, font_italics) else font = Qt::Font.new(font_face, font_size) end FONTS[font_face][font_size][font_attrs][font_italics] = font end font end # Get the default small font for the platform (Windows, Mac, Linux) def self.get_default_small_font if Kernel.is_windows? Cosmos.getFont("courier", 9) else Cosmos.getFont("courier", 12) end end # Get the default font for the platform (Windows, Mac, Linux) def self.get_default_font if Kernel.is_windows? Cosmos.getFont("Courier", 10) else Cosmos.getFont("Courier", 14) end end def self.getFontMetrics(font) font_metrics = FONT_METRICS[font] unless font_metrics font_metrics = Qt::FontMetrics.new(font) FONT_METRICS[font] = font_metrics end font_metrics end def self.getCursor(shape) key = shape key = shape.to_i if shape.is_a? Qt::Enum cursor = CURSORS[key] unless cursor cursor = Qt::Cursor.new(shape) CURSORS[key] = cursor end cursor end GREEN = getColor(0, 150, 0) YELLOW = getColor(190, 135, 0) RED = getColor(Qt::red) BLUE = getColor(0, 100, 255) PURPLE = getColor(200, 0, 200) BLACK = getColor(Qt::black) WHITE = getColor(Qt::white) BLACK_NO_BRUSH = Qt::Brush.new(Cosmos::BLACK, Qt::NoBrush) DEFAULT_PALETTE = Qt::Palette.new RED_PALETTE = Qt::Palette.new(Qt::red) DASHLINE_PEN = Qt::Pen.new(Qt::DashLine) # Load the applications icon def self.load_cosmos_icon(name='COSMOS_64x64.png') icon = Cosmos.get_icon(name, false) icon = Cosmos.get_icon('COSMOS_64x64.png', false) unless icon Qt::Application.instance.setWindowIcon(icon) if icon return icon end # Load the applications icon def self.get_icon(name, fail_blank = true) icon = Qt::Icon.new(Cosmos.data_path(name)) icon = nil if icon.isNull && !fail_blank return icon end # Try to change to a configuration in a packet log reader def self.check_log_configuration(packet_log_reader, log_filename) config_change_success, change_error = packet_log_reader.open(log_filename) unless config_change_success Qt.execute_in_main_thread(true) do if change_error Qt::MessageBox.warning(Qt::Application.instance.activeWindow, 'Warning', "When opening: #{log_filename}\n\nAn error occurred when changing to saved configuration:\n#{packet_log_reader.configuration_name}\n\n#{change_error.formatted}\n\nUsing default configuration") else Qt::MessageBox.warning(Qt::Application.instance.activeWindow, 'Warning', "When opening: #{log_filename}\n\nThe following saved configuration was not found:\n#{packet_log_reader.configuration_name}\n\nUsing default configuration") end end end end end class Qt::Icon def initialize(param = nil) if param super(param) else super() end end end class Qt::Dialog def initialize(parent = Qt::Application.activeWindow, flags = (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) super(parent, flags) end end class Qt::TableWidget # Resizes the table, turns off scroll bars, and sets the minimum and maximum sizes # to the full size of the table with all the elements in view def displayFullSize resizeColumnsToContents() resizeRowsToContents() setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff) setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff) setMinimumSize(fullWidth, fullHeight) setMaximumSize(fullWidth, fullHeight) end def fullWidth 2*frameWidth() + horizontalHeader.length + verticalHeader.width end def fullHeight 2*frameWidth() + verticalHeader.length + horizontalHeader.height end end class Qt::TableWidgetItem def initialize(string = "") super(string) setFlags(Qt::ItemIsEnabled) end def textColor=(color) setForeground(Cosmos.getBrush(color)) end end class Qt::TreeWidget def initialize(parent = Qt::Application.activeWindow) super(parent) # Create a sensible default handler for clicking the checkboxes # of a tree. If you check a node all the lower nodes get checked. # If you uncheck a node all the lower nodes get unchecked. # If you check a lower node all the parent nodes get checked. connect(SIGNAL('itemClicked(QTreeWidgetItem*, int)')) do |node, col| if node.checkState == Qt::Checked # Set all nodes below this to checked node.setCheckStateAll(Qt::Checked) # Set all the nodes above this to checked while node.parent node = node.parent node.setCheckState(0, Qt::Checked) end else # Set all nodes below this to unchecked node.setCheckStateAll(Qt::Unchecked) end end end # Yields each of the top level items (those without a parent). def topLevelItems topLevelItemCount.times do |index| yield topLevelItem(index) end end end class Qt::TreeWidgetItem # Sets the check state of this TreeWidgetItem as well as all its children # recursively. # # @param state [Integer] Must be Qt::Checked or Qt::Unchecked def setCheckStateAll(state = Qt::Checked) children() do |child| child.setCheckStateAll(state) end setCheckState(0, state) end # @return [Qt::TreeWidgetItem] The top level item for this TreeWidgetItem. # The top level item does not have a parent. Note that this could return # itself. def topLevel top = self if !top.parent.nil? while true top = top.parent break if top.parent.nil? end end top end # Yields the children of the current node def children childCount.times do |index| yield child(index) end end # Define the default column to be 0 def background(column = 0) super(column) end def checkState(column = 0) super(column) end def font(column = 0) super(column) end def foreground(column = 0) super(column) end def icon(column = 0) super(column) end def statusTip(column = 0) super(column) end def sizeHint(column = 0) super(column) end def text(column = 0) super(column) end def toolTip(column = 0) super(column) end def whatsThis(column = 0) super(column) end end class Qt::TabWidget def current_name tabText(currentIndex) end def tab(tab_text) (0...count()).each do |index| return widget(index) if tabText(index) == tab_text end nil end def widgets all = [] (0...self.count()).each do |index| all << self.widget(index) end all end alias :tabs :widgets def currentWidget self.widget(self.currentIndex) end alias :currentTab :currentWidget end class Qt::LineEdit def setColors(foreground, background) setPalette(Cosmos.getPalette(foreground, background)) end def text=(value) setText(value) end end class Qt::PlainTextEdit BLANK = ''.freeze BREAK = '<br/>'.freeze AMP = '&'.freeze NBSP = ' '.freeze GT = '>'.freeze LT = '<'.freeze @@color_cache = {} @@mapping = {'&'=>AMP,"\n"=>BLANK,"\s"=>NBSP,'>'=>GT,'<'=>LT} @@regex = Regexp.union(@@mapping.keys) def add_formatted_text(text, color = nil) if text =~ /[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F-\xFF]/ text.chomp! text = text.inspect.remove_quotes text << "\n" end if text =~ /<G>/ or color == Cosmos::GREEN text.gsub!(/<G>/, BLANK) addText(text, Cosmos::GREEN) elsif text =~ /<Y>/ or color == Cosmos::YELLOW text.gsub!(/<Y>/, BLANK) addText(text, Cosmos::YELLOW) elsif text =~ /<R>/ or color == Cosmos::RED text.gsub!(/<R>/, BLANK) addText(text, Cosmos::RED) elsif text =~ /<B>/ or color == Cosmos::BLUE text.gsub!(/<B>/, BLANK) addText(text, Cosmos::BLUE) else addText(text) # default is Cosmos::BLACK end end def addText(text, color = Cosmos::BLACK) @current_text ||= '' @current_text << escape_text(text.chomp, color) << BREAK end def flush appendHtml(@current_text) @current_text.clear end def appendText(text, color = Cosmos::BLACK) appendHtml(escape_text(text.chomp, color)) end # Return the selected lines. If a partial line is selected the entire line will be returned. # If there is no selection the line with the cursor will be returned def selected_lines cursor = textCursor selection_end = cursor.selectionEnd # Initially place the cursor at the beginning of the selection # If nothing is selected this will just put the cursor at the beginning of the current line cursor.setPosition(textCursor.selectionStart) cursor.movePosition(Qt::TextCursor::StartOfLine) # Move the cursor to the end of the selection while keeping the anchor cursor.setPosition(selection_end, Qt::TextCursor::KeepAnchor) # Normally we want to select the entire line where the end of selection is # However if the user selects an exact number of lines # the cursor will be at the beginning of the following line # Therefore if the cursor is at the beginning of the line and we have a selection # we'll skip moving to the end of the line unless (cursor.atBlockStart and textCursor.hasSelection) cursor.movePosition(Qt::TextCursor::EndOfLine, Qt::TextCursor::KeepAnchor) end # If there is no selection then just return nil return nil if cursor.selectedText.nil? cursor.selection.toPlainText # The selectedText function returns the Unicode U+2029 paragraph separator character # instead of newline \n. Thus we have to unpack as Unicode, covert to newlines, and then repack #text = cursor.selectedText.unpack("U*") #text.collect! {|letter| if letter == 63 then 10 else letter end } #text.pack("U*") end # Return the line number (0 based) of the selection start def selection_start_line cursor = textCursor cursor.setPosition(textCursor.selectionStart) cursor.blockNumber end # Return the line number (0 based) of the selection end def selection_end_line cursor = textCursor cursor.setPosition(textCursor.selectionEnd) cursor.blockNumber end private def escape_text(text, color) @@color_cache[color] ||= "#%02X%02X%02X" % [color.red, color.green, color.blue] # You might think gsub! would use less memory but benchmarking proves gsub # with a regular express argument is the fastest and uses the least memory. # However, this is still an expensive operation due to how many times it is called. "<font color=\"#{@@color_cache[color]}\">#{text.gsub(@@regex,@@mapping)}</font>" end end class Qt::TextEdit def setColors(foreground, background) setPalette(Cosmos.getPalette(foreground, background)) end end class Qt::ComboBox # Helper method to remove all items from a ComboBox def clearItems (0...count).to_a.reverse_each do |index| removeItem(index) end end # Alias currentText as text to make it more compatible with LineEdit which uses text def text currentText end # Helper method to set the current item using a text String instead of the index def setCurrentText(arg) setCurrentIndex(findText(arg.to_s)) end def each (0...count).each do |index| yield self.itemText(index), self.itemData(index) end end end class Qt::ListWidget # Helper method to remove all items from a ListWidget def clearItems (0...count).each do takeItem(0).dispose end end def each (0...count).each do |index| yield self.item(index) end end def remove_selected_items # Take the selected items and call row on each to get an array of the item indexes # Sort that array because selectedItems is returned in the order selected, not numerical order # Reverse this array of indices so we remove the last item first, # this allows all the indexes to remain constant # Call takeItem of each of the indices and then dispose the resulting ListWidgetItem that is returned selectedItems.map {|x| row(x)}.sort.reverse.each { |index| takeItem(index).dispose } end end # This class attempts to duplicate Fox's FXColorList # by adding a colored icon to each item in the ListWidget class Qt::ColorListWidget < Qt::ListWidget attr_reader :original_parent def initialize(parent, enable_key_delete = true) super(parent) # When a layout adds this widget it automatically gets re-parented so # store the original parent in case someone needs it @original_parent = parent @enable_key_delete = enable_key_delete @colors = [] @maps = [] @icons = [] setUniformItemSizes(true) end def resize_to_contents return if count == 0 #setResizeMode(Qt::ListView::Adjust) # Disable the vertical scrollbar and don't display it verticalScrollBar.setDisabled(true) setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff) # Get the height of an item and multiply by the number of items setFixedHeight(visualItemRect(item(0)).height * (count + 1)) end def set_read_only setSelectionMode(Qt::AbstractItemView::NoSelection) filter = Qt::Object.new filter.define_singleton_method(:eventFilter) do |obj, event| if event.type == Qt::Event::KeyPress && (event.key == Qt::Key_Delete || event.key == Qt::Key_Backspace) return true end return false end installEventFilter(filter) end def keyPressEvent(event) if @enable_key_delete if (event.key == Qt::Key_Delete || event.key == Qt::Key_Backspace) takeItemColor(currentRow) end end super(event) end def addItemColor(text, color = Cosmos::BLACK) @colors << color @maps << Qt::Pixmap.new(20,14) color = Cosmos::getColor(color) @maps[-1].fill(color) icon = Qt::Icon.new icon.addPixmap(@maps[-1], Qt::Icon::Normal) icon.addPixmap(@maps[-1], Qt::Icon::Selected) @icons << icon item = Qt::ListWidgetItem.new(@icons[-1], text.to_s) addItem(item) end def getItemColor(index) @colors[index] end def takeItemColor(row) item = takeItem(row) if item @colors.delete_at(row) @maps.delete_at(row).dispose @icons.delete_at(row).dispose item.dispose end end def clearItems (0...count).each do takeItemColor(0) end end def dispose super() @maps.each {|map| map.dispose} @icons.each {|icon| icon.dispose} end end class Qt::Painter def setPen(pen_color) super(Cosmos::getColor(pen_color)) @pen_color = pen_color end def setBrush(brush) super(Cosmos::getBrush(brush)) @brush = brush end def addLineColor(x, y, w, h, color = Cosmos::BLACK) setPen(color) if color != @pen_color drawLine(x,y,w,h) end def addRectColor(x, y, w, h, color = Cosmos::BLACK) setPen(color) if color != @pen_color setBrush(nil) if @brush drawRect(x,y,w,h) end # Note if brush_color is not specified it will be the same as pen_color def addRectColorFill(x, y, w, h, pen_color = Cosmos::BLACK, brush_color = nil) setPen(pen_color) if pen_color != @pen_color brush_color = pen_color unless brush_color setBrush(brush_color) if brush_color != @brush drawRect(x,y,w,h) end def addSimpleTextAt(text, x, y, color = Cosmos::BLACK) setPen(color) if color != @pen_color drawText(x,y,text) end def addEllipseColor(x, y, w, h, color = Cosmos::BLACK) setPen(color) if color != @pen_color setBrush(nil) if @brush drawEllipse(x,y,w,h) end # Note if brush_color is not specified it will be the same as pen_color def addEllipseColorFill(x, y, w, h, pen_color = Cosmos::BLACK, brush_color = nil) setPen(pen_color) if pen_color != @pen_color brush_color = pen_color unless brush_color setBrush(brush_color) if brush_color != @brush drawEllipse(x,y,w,h) end end class Qt::MatrixLayout < Qt::GridLayout def initialize(num_columns) super(nil) @num_columns = num_columns @row = 0 @col = 0 end def addWidget(widget) super(widget, @row, @col) increment_row_col() end def addLayout(layout) super(layout, @row, @col) increment_row_col() end private def increment_row_col @col += 1 if @col < @num_columns if @col == @num_columns @row += 1 @col = 0 end end end class Qt::AdaptiveGridLayout < Qt::GridLayout def addWidget(widget) case (count() + 1) when 3 # reorder things to add a second column old = takeAt(1) addItem(old, 0, 1) super(widget) when 7 # reorder everything to add a third column items = [] # Take the items in reverse order because taking an item changes the indexes 5.downto(2).each {|index| items << takeAt(index)} # Reverse the items to get them back into insertion order items.reverse! addItem(items[0], 0, 2) addItem(items[1], 1, 0) addItem(items[2], 1, 1) addItem(items[3], 1, 2) super(widget) # When we have established the desired number of rows we can add things and GridLayout does the right thing else super(widget) end end # removeWidget is not implemented. If you want to remove widgets, transfer the existing to a new layout like so: # (0...old_layout.count).each do |index| # new_layout.addWidget(old_layout.takeAt(0).widget) # end end # Implement removeAll on all the layout managers. # It would be easier to add this method on Qt::Layout which is the parent class to all but # that doesn't work due to the way method_missing is used to call the actual method %w(GridLayout BoxLayout VBoxLayout HBoxLayout FormLayout StackedLayout).each do |klass| "Qt::#{klass}".to_class.class_eval do def removeAll (0...count).each do |index| item = takeAt(0) if item.layout item.removeAll else item.widget.dispose if item.widget end end end end end class Qt::BoxLayout def initialize(*args) super(*args) setSpacing(5) if Kernel.is_mac? end end class Qt::VBoxLayout def initialize(*args) super(*args) setSpacing(5) if Kernel.is_mac? end end class Qt::HBoxLayout def initialize(*args) super(*args) setSpacing(5) if Kernel.is_mac? end end class Qt::GridLayout def initialize(*args) super(*args) setSpacing(5) if Kernel.is_mac? end end