cosmos 3.8.3 → 3.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -3
  3. data/Manifest.txt +14 -0
  4. data/Rakefile +35 -2
  5. data/autohotkey/config/targets/INST/screens/_footer.txt +4 -0
  6. data/autohotkey/config/targets/INST/screens/hs.txt +1 -4
  7. data/autohotkey/config/tools/table_manager/OldOneDimensionalTable_def.txt +19 -0
  8. data/autohotkey/config/tools/table_manager/OldTwoDimensionalTable_def.txt +248 -0
  9. data/autohotkey/config/tools/table_manager/OneDimensionalTable_def.txt +27 -15
  10. data/autohotkey/config/tools/table_manager/TwoDimensionalTable_def.txt +12 -232
  11. data/autohotkey/procedures/example_test.rb +4 -0
  12. data/autohotkey/tools/TableManagerAHK +4 -9
  13. data/autohotkey/tools/TableManagerAHK2 +18 -0
  14. data/autohotkey/tools/TableManagerAHK3 +18 -0
  15. data/autohotkey/tools/TableManagerAHK4 +24 -0
  16. data/autohotkey/tools/TlmViewerAHK +1 -1
  17. data/autohotkey/tools/autohotkey.rb +2 -1
  18. data/autohotkey/tools/open_gl_builder.ahk +1 -1
  19. data/autohotkey/tools/table_manager.ahk +141 -70
  20. data/cosmos.gemspec +3 -3
  21. data/data/crc.txt +70 -68
  22. data/data/legal.txt +4 -5
  23. data/demo/config/data/crc.txt +10 -9
  24. data/demo/config/targets/INST/screens/_footer.txt +4 -0
  25. data/demo/config/targets/INST/screens/hs.txt +1 -6
  26. data/demo/config/targets/INST/screens/limits.txt +3 -11
  27. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
  28. data/demo/config/tools/table_manager/MCConfigurationTable_fsw1_def.txt +33 -22
  29. data/demo/config/tools/table_manager/MCConfigurationTable_fsw2_def.txt +30 -22
  30. data/demo/config/tools/table_manager/PPSSelectionTable_def.txt +8 -7
  31. data/demo/config/tools/table_manager/TLMMonitoringTable_def.txt +13 -13
  32. data/demo/lib/example_background_task.rb +6 -12
  33. data/demo/procedures/example_test.rb +5 -0
  34. data/lib/cosmos/conversions/conversion.rb +3 -7
  35. data/lib/cosmos/core_ext/class.rb +3 -1
  36. data/lib/cosmos/core_ext/file.rb +1 -0
  37. data/lib/cosmos/core_ext/io.rb +18 -0
  38. data/lib/cosmos/core_ext/range.rb +1 -5
  39. data/lib/cosmos/core_ext/time.rb +3 -3
  40. data/lib/cosmos/gui/dialogs/about_dialog.rb +60 -36
  41. data/lib/cosmos/gui/dialogs/calendar_dialog.rb +10 -14
  42. data/lib/cosmos/gui/dialogs/cmd_details_dialog.rb +4 -5
  43. data/lib/cosmos/gui/dialogs/cmd_tlm_raw_dialog.rb +31 -17
  44. data/lib/cosmos/gui/dialogs/details_dialog.rb +63 -47
  45. data/lib/cosmos/gui/dialogs/exception_dialog.rb +77 -68
  46. data/lib/cosmos/gui/dialogs/exception_list_dialog.rb +6 -5
  47. data/lib/cosmos/gui/dialogs/legal_dialog.rb +34 -21
  48. data/lib/cosmos/gui/dialogs/packet_log_dialog.rb +19 -43
  49. data/lib/cosmos/gui/dialogs/progress_dialog.rb +79 -42
  50. data/lib/cosmos/gui/dialogs/pry_dialog.rb +9 -5
  51. data/lib/cosmos/gui/dialogs/scroll_text_dialog.rb +6 -4
  52. data/lib/cosmos/gui/dialogs/select_dialog.rb +23 -18
  53. data/lib/cosmos/gui/dialogs/set_tlm_dialog.rb +34 -10
  54. data/lib/cosmos/gui/dialogs/splash.rb +18 -8
  55. data/lib/cosmos/gui/dialogs/tlm_details_dialog.rb +38 -43
  56. data/lib/cosmos/gui/dialogs/tlm_edit_dialog.rb +51 -53
  57. data/lib/cosmos/gui/line_graph/line_graph_scaling.rb +1 -1
  58. data/lib/cosmos/gui/line_graph/lines.rb +1 -1
  59. data/lib/cosmos/gui/qt.rb +9 -2
  60. data/lib/cosmos/gui/qt_tool.rb +50 -8
  61. data/lib/cosmos/gui/widgets/packet_log_frame.rb +53 -27
  62. data/lib/cosmos/interfaces/linc_interface.rb +103 -62
  63. data/lib/cosmos/io/json_drb_object.rb +3 -3
  64. data/lib/cosmos/io/raw_logger.rb +4 -8
  65. data/lib/cosmos/io/tcpip_server.rb +2 -2
  66. data/lib/cosmos/packets/binary_accessor.rb +1 -1
  67. data/lib/cosmos/packets/limits.rb +2 -5
  68. data/lib/cosmos/packets/packet.rb +1 -1
  69. data/lib/cosmos/packets/packet_config.rb +54 -19
  70. data/lib/cosmos/packets/parsers/packet_item_parser.rb +7 -1
  71. data/lib/cosmos/script/scripting.rb +4 -5
  72. data/lib/cosmos/system/system.rb +2 -1
  73. data/lib/cosmos/system/target.rb +4 -0
  74. data/lib/cosmos/tools/cmd_tlm_server/background_task.rb +13 -5
  75. data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +37 -27
  76. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +6 -2
  77. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +7 -5
  78. data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +21 -10
  79. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +11 -11
  80. data/lib/cosmos/tools/script_runner/script_runner.rb +2 -18
  81. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +6 -6
  82. data/lib/cosmos/tools/table_manager/table.rb +32 -41
  83. data/lib/cosmos/tools/table_manager/table_config.rb +140 -729
  84. data/lib/cosmos/tools/table_manager/table_item.rb +20 -36
  85. data/lib/cosmos/tools/table_manager/table_item_parser.rb +46 -0
  86. data/lib/cosmos/tools/table_manager/table_manager.rb +754 -691
  87. data/lib/cosmos/tools/table_manager/table_manager_core.rb +172 -358
  88. data/lib/cosmos/tools/table_manager/table_parser.rb +75 -0
  89. data/lib/cosmos/tools/test_runner/results_writer.rb +1 -1
  90. data/lib/cosmos/tools/test_runner/test_runner.rb +11 -0
  91. data/lib/cosmos/tools/tlm_grapher/data_object_adders/housekeeping_data_object_adder.rb +2 -2
  92. data/lib/cosmos/tools/tlm_grapher/data_object_adders/singlexy_data_object_adder.rb +2 -2
  93. data/lib/cosmos/tools/tlm_grapher/data_object_adders/xy_data_object_adder.rb +2 -2
  94. data/lib/cosmos/tools/tlm_grapher/data_objects/data_object.rb +4 -4
  95. data/lib/cosmos/tools/tlm_grapher/data_objects/housekeeping_data_object.rb +13 -13
  96. data/lib/cosmos/tools/tlm_grapher/data_objects/linegraph_data_object.rb +9 -9
  97. data/lib/cosmos/tools/tlm_grapher/data_objects/xy_data_object.rb +9 -9
  98. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_config.rb +4 -4
  99. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +1 -1
  100. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +8 -18
  101. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +7 -4
  102. data/lib/cosmos/top_level.rb +12 -0
  103. data/lib/cosmos/version.rb +5 -5
  104. data/run_gui_tests.bat +6 -0
  105. data/spec/core_ext/array_spec.rb +1 -1
  106. data/spec/interfaces/linc_interface_spec.rb +4 -4
  107. data/spec/io/json_drb_spec.rb +2 -2
  108. data/spec/io/json_rpc_spec.rb +1 -1
  109. data/spec/io/raw_logger_spec.rb +5 -1
  110. data/spec/packet_logs/packet_log_writer_spec.rb +1 -1
  111. data/spec/packets/packet_config_spec.rb +144 -0
  112. data/spec/packets/parsers/packet_item_parser_spec.rb +60 -0
  113. data/spec/spec_helper.rb +11 -0
  114. data/spec/system/target_spec.rb +5 -1
  115. data/spec/tools/cmd_tlm_server/background_task_spec.rb +15 -3
  116. data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +117 -31
  117. data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +4 -0
  118. data/spec/tools/launcher/launcher_config_spec.rb +1 -1
  119. data/spec/tools/table_manager/table_config_spec.rb +226 -0
  120. data/spec/tools/table_manager/table_item_spec.rb +57 -0
  121. data/spec/tools/table_manager/table_parser_spec.rb +96 -0
  122. data/spec/tools/table_manager/table_spec.rb +90 -0
  123. data/spec/tools/table_manager/tablemanager_core_spec.rb +557 -0
  124. data/spec/top_level/top_level_spec.rb +9 -0
  125. data/spec/utilities/csv_spec.rb +3 -3
  126. metadata +30 -11
@@ -11,64 +11,48 @@
11
11
  require 'cosmos'
12
12
 
13
13
  module Cosmos
14
-
15
- # Maintains knowledge of an item in a Table
14
+ # Implements the attributes that are unique to a TableItem such as editable
15
+ # and hidden. All other functionality is inherited from {PacketItem}.
16
16
  class TableItem < PacketItem
17
- attr_reader :display_type
18
- attr_accessor :editable
19
- attr_reader :constraint
17
+ # @return [Boolean] Whether this item is editable
18
+ attr_reader :editable
19
+ # @return [Boolean] Whether this item is hidden (not displayed)
20
+ attr_reader :hidden
20
21
 
21
22
  # (see PacketItem#initialize)
22
- # It also initializes the attributes of the TableItem.
23
23
  def initialize(name, bit_offset, bit_size, data_type, endianness, array_size = nil, overflow = :ERROR)
24
24
  super(name, bit_offset, bit_size, data_type, endianness, array_size, overflow)
25
25
  @display_type = nil
26
26
  @editable = true
27
- @constraint = nil
27
+ @hidden = false
28
28
  end
29
29
 
30
- def display_type=(display_type)
31
- if display_type
32
- raise ArgumentError, "#{@name}: display_type must be a Symbol but is a #{display_type.class}" unless Symbol === display_type
33
- @display_type = display_type
34
- else
35
- @display_type = nil
36
- end
30
+ # @param editable [Boolean] Whether this item can be edited
31
+ def editable=(editable)
32
+ raise ArgumentError, "#{@name}: editable must be a boolean but is a #{editable.class}" unless !!editable == editable
33
+ @editable = editable
37
34
  end
38
35
 
39
- def constraint=(constraint)
40
- if constraint
41
- raise ArgumentError, "#{@name}: constraint must be a Conversion but is a #{constraint.class}" unless Cosmos::Conversion === constraint
42
- @constraint = constraint.clone
43
- else
44
- @constraint = nil
45
- end
36
+ # @param hidden [Boolean] Whether this item should be hidden
37
+ def hidden=(hidden)
38
+ raise ArgumentError, "#{@name}: hidden must be a boolean but is a #{hidden.class}" unless !!hidden == hidden
39
+ @hidden = hidden
46
40
  end
47
41
 
48
42
  # Make a light weight clone of this item
49
43
  def clone
50
44
  item = super()
51
- item.constraint = self.constraint.clone if self.constraint
45
+ item.editable = self.editable
52
46
  item
53
47
  end
54
48
  alias dup clone
55
49
 
50
+ # Create a hash of this item's attributes
56
51
  def to_hash
57
52
  hash = super()
58
- if self.display_type
59
- hash['display_type'] = self.display_type.to_s
60
- else
61
- hash['display_type'] = nil
62
- end
63
53
  hash['editable'] = self.editable
64
- if self.constraint
65
- hash['constraint'] = self.constraint.to_s
66
- else
67
- hash['constraint'] = nil
68
- end
54
+ hash['hidden'] = self.hidden
69
55
  hash
70
56
  end
71
-
72
- end # class TableItem
73
-
74
- end # module Cosmos
57
+ end
58
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2014 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+
11
+ require 'cosmos/packets/packet_config'
12
+ require 'cosmos/packets/packet_item'
13
+
14
+ module Cosmos
15
+ class TableItemParser < PacketItemParser
16
+ # @param parser [ConfigParser] Configuration parser
17
+ # @param table [Table] Table all parsed items should be added to
18
+ def self.parse(parser, table)
19
+ parser = TableItemParser.new(parser)
20
+ parser.verify_parameters(PacketConfig::COMMAND)
21
+ parser.create_table_item(table)
22
+ end
23
+
24
+ # @param table [Table] Table created items are added to
25
+ def create_table_item(table)
26
+ name = @parser.parameters[0]
27
+ if table.type == :TWO_DIMENSIONAL
28
+ name = "#{name}0"
29
+ table.num_columns += 1
30
+ end
31
+ item = TableItem.new(name, get_bit_offset(), get_bit_size(), get_data_type(),
32
+ get_endianness(table), get_array_size(), :ERROR) # overflow
33
+ item.range = get_range()
34
+ item.default = get_default()
35
+ item.description = get_description()
36
+ if append?
37
+ item = table.append(item)
38
+ else
39
+ item = table.define(item)
40
+ end
41
+ item
42
+ rescue => err
43
+ raise @parser.error(err, @usage)
44
+ end
45
+ end
46
+ end
@@ -33,16 +33,17 @@ Cosmos.disable_warnings do
33
33
  end
34
34
 
35
35
  module Cosmos
36
-
36
+ # This class applies to all items in the table but its only purpose is to
37
+ # determine if table cells should be rendered as comboboxes. Any table items
38
+ # with states are rendered as comboboxes.
37
39
  class ComboBoxItemDelegate < Qt::StyledItemDelegate
38
- def initialize(parent)
39
- @table = parent
40
- super(parent)
41
- end
42
-
40
+ # Create the combobox widget to display the values
41
+ # @param parent [Qt::Widget] Parent to created widget
42
+ # @param option [Qt::StyleOptionViewItem] Style options (not used)
43
+ # @param index [Qt::ModelIndex] Indicates which table item is active
43
44
  def createEditor(parent, option, index)
44
- table = TableManager.instance.core.table_def.get_table(TableManager.instance.currently_displayed_table_name)
45
- gui_table = TableManager.instance.gui_tables[TableManager.instance.currently_displayed_table_name]
45
+ table = TableManager.instance.core.config.table(TableManager.instance.current_table_name)
46
+ gui_table = TableManager.instance.tabbook.tab(TableManager.instance.current_table_name)
46
47
  if table.type == :TWO_DIMENSIONAL
47
48
  item_name = gui_table.horizontalHeaderItem(index.column).text + index.row.to_s
48
49
  item = table.get_item(item_name)
@@ -50,7 +51,7 @@ module Cosmos
50
51
  item_name = gui_table.verticalHeaderItem(index.row).text
51
52
  item = table.get_item(item_name)
52
53
  end
53
- if item.display_type == :STATE and item.editable
54
+ if item.states && item.editable
54
55
  combo = Qt::ComboBox.new(parent)
55
56
  combo.addItems(item.states.keys.sort)
56
57
  combo.setCurrentText(table.read(item.name).to_s)
@@ -64,6 +65,11 @@ module Cosmos
64
65
  end
65
66
  end
66
67
 
68
+ # Gets the current item from the combobox if it exists and writes it back
69
+ # to the model.
70
+ # @param editor [Qt::Widget] Editor widget
71
+ # @param model [Qt::AbstractItemModel] Model to write the gui data to
72
+ # @param index [Qt::ModelIndex] Where in the model to update the data
67
73
  def setModelData(editor, model, index)
68
74
  if Qt::ComboBox === editor
69
75
  model.setData(index, Qt::Variant.new(editor.currentText), Qt::EditRole)
@@ -72,6 +78,9 @@ module Cosmos
72
78
  end
73
79
  end
74
80
 
81
+ # Sets the current item in the combobox based on the model data
82
+ # @param editor [Qt::Widget] Editor widget
83
+ # @param index [Qt::ModelIndex] Where in the model to grab the data
75
84
  def setEditorData(editor, index)
76
85
  if Qt::ComboBox === editor
77
86
  v = index.data(Qt::EditRole)
@@ -88,13 +97,14 @@ module Cosmos
88
97
  end
89
98
 
90
99
  # A dialog box containing a text field and ok button
91
- class TextDialog < Qt::Dialog
100
+ class HexDumpDialog < Qt::Dialog
101
+ # @param parent [Qt::Widget] Dialog parent
92
102
  def initialize(parent)
93
- super(parent)
94
-
95
- layout = Qt::VBoxLayout.new
103
+ super(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
104
+ setWindowTitle("Hex Dump")
96
105
 
97
106
  @text = Qt::PlainTextEdit.new
107
+ @text.setWordWrapMode(Qt::TextOption::NoWrap)
98
108
  if Kernel.is_windows?
99
109
  @text.setFont(Cosmos.getFont('courier', 10))
100
110
  else
@@ -102,6 +112,7 @@ module Cosmos
102
112
  end
103
113
  @text.setReadOnly(true)
104
114
 
115
+ layout = Qt::VBoxLayout.new
105
116
  layout.addWidget(@text)
106
117
 
107
118
  button_layout = Qt::HBoxLayout.new
@@ -113,15 +124,18 @@ module Cosmos
113
124
 
114
125
  layout.addLayout(button_layout)
115
126
  setLayout(layout)
127
+ resize(650, 250)
116
128
  end
117
129
 
118
- # Set the title of the dialog box and the text contents
130
+ # @param title [String] Dialog title
131
+ # @param text [String] Dialog text box is overwritten with this string
119
132
  def set_title_and_text(title, text)
120
133
  self.setWindowTitle(title)
121
134
  @text.setPlainText(text)
122
135
  end
123
136
 
124
- # Set the size of the dialog box
137
+ # @param width [Integer]
138
+ # @param height [Integer]
125
139
  def set_size(width, height)
126
140
  resize(width, height)
127
141
  end
@@ -133,97 +147,162 @@ module Cosmos
133
147
  end
134
148
  end
135
149
 
136
- # TableManager uses text based configuration files (see TableConfig) to define both
137
- # the structure of the binary file and how it should be displayed. It takes this configuration
138
- # information to dynamically build a tabbed GUI containing the visual representation of the binary data.
139
- # In addition to displaying binary data it can also create the binary representation given
140
- # the text configuration file. It can display the binary data as a hex dump and creates
141
- # human readable reports of the given data.
150
+ # TableManager uses text based configuration files (see TableConfig) to define
151
+ # both the structure of the binary file and how it should be displayed. It
152
+ # takes this this configuration information to dynamically build a tabbed GUI
153
+ # containing the visual representation of the binary data.
154
+ # In addition to displaying binary data it can also create the binary
155
+ # representation given the text configuration file. It can display the binary
156
+ # data as a hex dump and creates human readable reports of the given data.
142
157
  class TableManager < QtTool
143
- slots 'handle_tab_change(int)'
144
- slots 'mouse_over(int, int)'
145
- slots 'click_callback(QTableWidgetItem*)'
146
- slots 'context_menu(const QPoint&)'
158
+ # Error raised when there is a problem saving a table
159
+ class SaveError < StandardError; end
160
+ # Error raised when there is a problem displaying a table
161
+ class DisplayError < StandardError; end
147
162
 
163
+ # @return [TableManagerCore] TableManagerCore instance
148
164
  attr_reader :core
165
+ # @return [Qt::TabWidget] TabWidget which holds the table tabs
166
+ attr_reader :tabbook
149
167
 
150
- # Hash of all the created tables with the table name as the key value
151
- attr_reader :gui_tables
152
-
153
- # Array of all the created tables ordered by the way they were created.
154
- # Thus they are ordered the same as the text configuration file.
155
- attr_reader :ordered_gui_table_names
156
-
157
- # Place holder for the current table
158
- attr_reader :table
159
-
160
- # The currently displayed table name
161
- attr_reader :currently_displayed_table_name
162
-
163
- # Label containing the full path name of the table definition file
164
- attr_reader :table_def_label
165
-
166
- # Label containing the full path name of the table binary file
167
- attr_reader :table_bin_label
168
+ # @return [TableManager] Instance of the TableManager class
169
+ def self.instance
170
+ @@instance
171
+ end
168
172
 
169
- # Top vertical layout where subclasses can put their own widgets
170
- attr_accessor :top_layout
173
+ # Entry point into the application
174
+ #
175
+ # @param option_parser [OptionParser] Parses the command line options
176
+ # @param options [OpenStruct] Contains all the parsed options
177
+ def self.run(option_parser = nil, options = nil)
178
+ Cosmos.catch_fatal_exception do
179
+ unless option_parser && options
180
+ option_parser, options = create_default_options()
181
+ options.width = 800
182
+ options.height = 600
183
+ options.title = "Table Manager"
184
+ options.auto_size = false
185
+ options.no_tables = false
186
+ option_parser.separator "Table Manager Specific Options:"
187
+ option_parser.on("-n", "--notables", "Do not include table file editing options. This will remove the 'Table' menu.") do
188
+ options.no_tables = true
189
+ end
190
+ option_parser.on("-c", "--create FILE", "Use the specified definition file to create the table") do |arg|
191
+ options.create = arg
192
+ end
193
+ option_parser.on("-o", "--output DIRECTORY", "Create files in the specified directory") do |arg|
194
+ options.output_dir = File.expand_path(arg)
195
+ end
196
+ option_parser.on("--convert FILE", "Convert the specified configuration file to the new format") do |arg|
197
+ options.convert = arg
198
+ end
199
+ end
200
+ super(option_parser, options)
201
+ end
202
+ end
171
203
 
172
- # File name of the png icon to use for the application.
173
- # The application looks in the COSMOS PATH and USERPATH directories for the icon.
174
- # This must be overloaded before calling super() in the intialize
175
- # function to allow Table Manager to use the new file name.
176
- attr_accessor :app_icon_filename
204
+ # Called after parsing all the command line options passed to the
205
+ # application. Returns false to operate without a GUI when certain command
206
+ # line options are specified.
207
+ #
208
+ # @param options [OpenStruct] The application options as configured in the
209
+ # command line
210
+ # @return [Boolean] Whether to contine running the application
211
+ def self.post_options_parsed_hook(options)
212
+ if options.create
213
+ core = TableManagerCore.new
214
+ core.file_new(options.create, options.output_dir)
215
+ return false
216
+ end
217
+ if options.convert
218
+ if options.convert.include?("/")
219
+ parts = options.convert.split("/")
220
+ else
221
+ parts = options.convert.split("\\")
222
+ end
223
+ parts[-1] = "converted_#{parts[-1]}"
224
+ filename = parts.join(File::SEPARATOR)
225
+ File.open(filename, 'w') do |out|
226
+ config = File.read(options.convert).split("\n")
227
+ config.each do |line|
228
+ /TABLE\s+(\".*\")\s+(\".*\")\s+(ONE_DIMENSIONAL)\s+(.*_ENDIAN)/.match(line) do |m|
229
+ out.puts "TABLE #{m[1]} #{m[4]} #{m[3]} #{m[2]}"
230
+ end
231
+ /TABLE\s+(\".*\")\s+(\".*\")\s+(TWO_DIMENSIONAL)\s+(.*_ENDIAN)/.match(line) do |m|
232
+ rows = config.select {|item| item.strip =~ /^DEFAULT/ }.length
233
+ out.puts "TABLE #{m[1]} #{m[4]} #{m[3]} #{rows} #{m[2]}"
234
+ end
235
+ /PARAMETER\s+(\".*\")\s+(\".*\")\s+(.*)\s+(\d+)\s+(.*)\s+(.*)\s+(.*)\s+(.*)\s?/.match(line) do |m|
236
+ out.puts " APPEND_PARAMETER #{m[1]} #{m[4]} #{m[3]} #{m[6]} #{m[7]} #{m[8]} #{m[2]}"
237
+ if m[5].include?('CHECK')
238
+ out.puts " STATE UNCHECKED #{m[6]}"
239
+ out.puts " STATE CHECKED #{m[7]}"
240
+ end
241
+ if m[5].include?('HEX')
242
+ out.puts ' FORMAT_STRING "0x%0X"'
243
+ end
244
+ if m[5].include?('-U')
245
+ out.puts ' UNEDITABLE'
246
+ end
247
+ end
248
+ if line.strip !~ /^(TABLE|PARAMETER|DEFAULT)/
249
+ out.puts line
250
+ end
251
+ end
252
+ end
253
+ puts "Created #{filename}"
254
+ return false
255
+ end
256
+ true
257
+ end
177
258
 
178
259
  # Create a TableManager instance by initializing the globals,
179
260
  # setting the program icon, creating the menu, and laying out the GUI elements.
180
261
  def initialize(options)
181
262
  super(options) # MUST BE FIRST - All code before super is executed twice in RubyQt Based classes
263
+ @app_title = options.title
182
264
  @app_icon_filename = 'table_manager.png'
183
265
  Cosmos.load_cosmos_icon(@app_icon_filename)
184
266
 
267
+ @system_def_path = File.join(::Cosmos::USERPATH, %w(config tools table_manager))
268
+ @system_bin_path = System.paths['TABLES']
269
+ @def_path = @system_def_path
270
+ @bin_path = @system_bin_path
271
+ @core = TableManagerCore.new
272
+ @@instance = self
273
+
185
274
  initialize_actions(options.no_tables)
186
275
  initialize_menus(options.no_tables)
187
276
  initialize_central_widget()
188
277
  complete_initialize()
189
- setMinimumSize(500, 300)
278
+ setMinimumSize(400, 250)
190
279
 
191
280
  statusBar.showMessage(tr("Ready")) # Show message to initialize status bar
192
-
193
- @def_path = File.join(::Cosmos::USERPATH, %w(config tools table_manager))
194
- @bin_path = System.paths['TABLES']
195
- @gui_tables = Hash.new
196
- @ordered_gui_table_names = Array.new
197
- @currently_displayed_table_name = ""
198
- @core = TableManagerCore.new
199
- @@instance = self
200
- end
201
-
202
- def self.instance
203
- @@instance
204
281
  end
205
282
 
206
283
  def initialize_actions(no_tables = false)
207
284
  super()
208
285
 
209
286
  # File Menu Actions
210
- @file_new = Qt::Action.new(Cosmos.get_icon('file.png'), tr('&New File'), self)
211
- @file_new_keyseq = Qt::KeySequence.new(Qt::KeySequence::New)
212
- @file_new.shortcut = @file_new_keyseq
213
- @file_new.statusTip = tr('Create new binary file based on definition')
214
- @file_new.connect(SIGNAL('triggered()')) { file_new() }
215
-
216
- @file_open = Qt::Action.new(Cosmos.get_icon('open.png'), tr('&Open File'), self)
217
- @file_open_keyseq = Qt::KeySequence.new(Qt::KeySequence::Open)
218
- @file_open.shortcut = @file_open_keyseq
219
- @file_open.statusTip = tr('Open binary file for display and editing')
220
- @file_open.connect(SIGNAL('triggered()')) { file_open() }
287
+ new_action = Qt::Action.new(self)
288
+ new_action.shortcut = Qt::KeySequence.new(Qt::KeySequence::New)
289
+ new_action.connect(SIGNAL('triggered()')) { file_new(@def_path) }
290
+ self.addAction(new_action) # Add it to the application
291
+
292
+ open_action = Qt::Action.new(self)
293
+ open_action.shortcut = Qt::KeySequence.new(Qt::KeySequence::Open)
294
+ open_action.connect(SIGNAL('triggered()')) { file_open(@bin_path) }
295
+ self.addAction(open_action) # Add it to the application
296
+
297
+ @file_open_both = Qt::Action.new(Cosmos.get_icon('open.png'), tr('Open &Both'), self)
298
+ @file_open_both.statusTip = tr('Specify both the binary file and the definition file to open')
299
+ @file_open_both.connect(SIGNAL('triggered()')) { file_open_both() }
221
300
 
222
301
  @file_save = Qt::Action.new(Cosmos.get_icon('save.png'), tr('&Save File'), self)
223
302
  @file_save_keyseq = Qt::KeySequence.new(Qt::KeySequence::Save)
224
303
  @file_save.shortcut = @file_save_keyseq
225
304
  @file_save.statusTip = tr('Save the displayed data back to the binary file')
226
- @file_save.connect(SIGNAL('triggered()')) { file_save(false) }
305
+ @file_save.connect(SIGNAL('triggered()')) { file_save() }
227
306
 
228
307
  @file_save_as = Qt::Action.new(Cosmos.get_icon('save_as.png'), tr('Save File &As'), self)
229
308
  @file_save_as_keyseq = Qt::KeySequence.new(Qt::KeySequence::SaveAs)
@@ -231,6 +310,12 @@ module Cosmos
231
310
  @file_save_as.statusTip = tr('Save the displayed data to a new binary file')
232
311
  @file_save_as.connect(SIGNAL('triggered()')) { file_save(true) }
233
312
 
313
+ @file_close = Qt::Action.new(Cosmos.get_icon('close.png'), tr('&Close File'), self)
314
+ @file_close_keyseq = Qt::KeySequence.new(tr("Ctrl+W")) # Qt::KeySequence::Close is Alt-F4 on Windows
315
+ @file_close.shortcut = @file_close_keyseq
316
+ @file_close.statusTip = tr('Close the current file')
317
+ @file_close.connect(SIGNAL('triggered()')) { file_close() }
318
+
234
319
  @file_check = Qt::Action.new(Cosmos.get_icon('checkmark.png'), tr('&Check All'), self)
235
320
  @file_check_keyseq = Qt::KeySequence.new(tr('Ctrl+K'))
236
321
  @file_check.shortcut = @file_check_keyseq
@@ -273,17 +358,23 @@ module Cosmos
273
358
  @table_commit.statusTip = tr('Incorporate the current table data into a binary file which already contains the table')
274
359
  @table_commit.connect(SIGNAL('triggered()')) { table_commit() }
275
360
 
276
- @table_update = Qt::Action.new(tr('&Update Definition'), self)
277
- @table_update.statusTip = tr('Change the defaults in the definition file to the displayed table data')
278
- @table_update.connect(SIGNAL('triggered()')) { table_update() }
361
+ # @table_update = Qt::Action.new(tr('&Update Definition'), self)
362
+ # @table_update.statusTip = tr('Change the defaults in the definition file to the displayed table data')
363
+ # @table_update.connect(SIGNAL('triggered()')) { table_update() }
279
364
  end
280
365
  end
281
366
 
282
367
  def initialize_menus(no_tables = false)
283
- # File Menu
284
368
  file_menu = menuBar.addMenu(tr('&File'))
285
- file_menu.addAction(@file_new)
286
- file_menu.addAction(@file_open)
369
+
370
+ file_new = file_menu.addMenu(Cosmos.get_icon('file.png'), tr("&New File")) # \tCtrl-N displays shortcut
371
+ target_dirs_action(file_new, @system_def_path, 'tools/table_manager', method(:file_new))
372
+
373
+ file_open = file_menu.addMenu(Cosmos.get_icon('open.png'), tr("&Open")) # \tCtrl-O displays shortcut
374
+ target_dirs_action(file_open, @system_bin_path, 'tables', method(:file_open))
375
+
376
+ file_menu.addAction(@file_open_both)
377
+ file_menu.addAction(@file_close)
287
378
  file_menu.addAction(@file_save)
288
379
  file_menu.addAction(@file_save_as)
289
380
  file_menu.addSeparator()
@@ -302,7 +393,7 @@ module Cosmos
302
393
  table_menu.addSeparator()
303
394
  table_menu.addAction(@table_save)
304
395
  table_menu.addAction(@table_commit)
305
- table_menu.addAction(@table_update)
396
+ # table_menu.addAction(@table_update)
306
397
  end
307
398
 
308
399
  # Help Menu
@@ -315,640 +406,380 @@ module Cosmos
315
406
  end
316
407
 
317
408
  def initialize_central_widget
318
- # Create the central widget
319
- @central_widget = Qt::Widget.new
320
- setCentralWidget(@central_widget)
321
-
322
- # Create the top level vertical layout
323
- @top_layout = Qt::VBoxLayout.new(@central_widget)
409
+ central_widget = Qt::Widget.new
410
+ setCentralWidget(central_widget)
411
+ top_layout = Qt::VBoxLayout.new(central_widget)
324
412
 
325
413
  # Create the information pane with the filenames
326
- @filename_layout = Qt::FormLayout.new
414
+ filename_layout = Qt::FormLayout.new
327
415
  @table_def_label = Qt::Label.new("")
328
- @filename_layout.addRow(tr("Definition File:"), @table_def_label)
416
+ filename_layout.addRow(tr("Definition File:"), @table_def_label)
329
417
  @table_bin_label = Qt::Label.new("")
330
- @filename_layout.addRow(tr("Binary File:"), @table_bin_label)
331
- @top_layout.addLayout(@filename_layout)
418
+ filename_layout.addRow(tr("Binary File:"), @table_bin_label)
419
+ top_layout.addLayout(filename_layout)
332
420
 
333
421
  # Separator before editor
334
- @sep1 = Qt::Frame.new(@central_widget)
335
- @sep1.setFrameStyle(Qt::Frame::HLine | Qt::Frame::Sunken)
336
- @top_layout.addWidget(@sep1)
422
+ sep1 = Qt::Frame.new(central_widget)
423
+ sep1.setFrameStyle(Qt::Frame::HLine | Qt::Frame::Sunken)
424
+ top_layout.addWidget(sep1)
337
425
 
338
426
  @tabbook = Qt::TabWidget.new
339
- connect(@tabbook, SIGNAL('currentChanged(int)'), self, SLOT('handle_tab_change(int)'))
340
- @top_layout.addWidget(@tabbook)
427
+ top_layout.addWidget(@tabbook)
341
428
 
342
429
  @check_icons = []
343
430
  @check_icons << Cosmos.get_icon("CheckBoxEmpty.gif")
344
431
  @check_icons << Cosmos.get_icon("CheckBoxCheck.gif")
345
432
  end
346
433
 
347
- # Menu option to check every table tab's values against their allowable ranges
348
- def file_check
349
- begin
350
- result = ""
351
- @ordered_gui_table_names.each do |name|
352
- save_result = save_gui_data(name)
353
- check_result = @core.table_check(name)
354
- if not save_result.nil? or not check_result.empty?
355
- result << "Error(s) in #{name}:\n" << save_result.to_s << check_result.to_s
434
+ # Menu option to create a new table binary file based on a table definition file.
435
+ #
436
+ # @param def_path [String] Path to a directory containing definition files
437
+ def file_new(def_path)
438
+ return if abort_on_modified()
439
+
440
+ filenames = Qt::FileDialog.getOpenFileNames(self, "Open Binary Definition Text File(s)",
441
+ def_path, "Config File (*.txt)\nAll Files (*)")
442
+ return if filenames.length == 0 # User cancelled dialog
443
+
444
+ bin_path = bin_path_from_def_path(File.dirname(filenames[0]))
445
+ output_path = Qt::FileDialog.getExistingDirectory(self, "Select Output Directory", bin_path)
446
+ return unless output_path # User cancelled dialog
447
+
448
+ return if abort_on_check_for_existing(filenames, output_path)
449
+
450
+ success = false
451
+ bin_files = []
452
+ ProgressDialog.execute(self, 'Create New Files', 500, 50, true, false, false, false, false) do |dialog|
453
+ begin
454
+ filenames.each do |filename|
455
+ bin_files << @core.file_new(filename, output_path) do |progress|
456
+ dialog.set_overall_progress(progress)
457
+ end
356
458
  end
459
+ success = true
460
+ dialog.close_done
461
+ rescue => err
462
+ Qt.execute_in_main_thread(true) {|| ExceptionDialog.new(self, err, "File New Errors", false)}
463
+ dialog.close_done
357
464
  end
358
- if result.empty?
359
- result = "All parameters are within their constraints."
360
- end
361
- Qt::MessageBox.information(self, "File Check", result)
362
- rescue => err
363
- ExceptionDialog.new(self, err, "File Check Errors", false)
364
465
  end
466
+ if success
467
+ file_close()
468
+ file_open(bin_files[0], filenames[0])
469
+ end
470
+ rescue TableManagerCore::CoreError => err
471
+ Qt::MessageBox.warning(self, "File New Errors", err.message)
472
+ rescue => err
473
+ ExceptionDialog.new(self, err, "File New Errors", false)
365
474
  end
366
475
 
367
- # Menu option to check the table values against their allowable ranges
368
- def table_check
369
- begin
370
- save_result = save_gui_data(@currently_displayed_table_name)
371
- check_result = @core.table_check(@currently_displayed_table_name)
372
- if save_result.nil? and check_result.empty?
373
- result = "All parameters are within their constraints."
374
- elsif save_result.nil?
375
- result = check_result
376
- elsif check_result.nil?
377
- result = save_result
378
- else
379
- result = save_result << check_result
380
- end
381
-
382
- Qt::MessageBox.information(self, "Table Check", result)
383
- rescue => err
384
- ExceptionDialog.new(self, err, "Table Check Errors", false)
476
+ # Menu option that opens a binary file (and it's associated definition
477
+ # file) for display in the GUI.
478
+ #
479
+ # @param bin_path [String] Path to the binary file or a directory
480
+ # containing binary files
481
+ # @param def_path [String] Path to the definition file. If nil, the
482
+ # definition file will be looked up automatically.
483
+ # @param user_select_definition [Boolean] If true, the user will be
484
+ # required to select the definition file. It will not be automatically
485
+ # looked up.
486
+ def file_open(bin_path, def_path = nil, user_select_definition = false)
487
+ return if abort_on_modified()
488
+
489
+ if File.directory?(bin_path)
490
+ bin_path = Qt::FileDialog.getOpenFileName(self, "Open Binary", bin_path,
491
+ "Binary File (*.bin *.dat);;All Files (*)")
492
+ return unless bin_path
385
493
  end
386
- end
387
494
 
388
- # Menu option to set all the table items to their default values
389
- def table_default
390
- begin
391
- @core.table_default(@currently_displayed_table_name)
392
- rescue => err
393
- ExceptionDialog.new(self, err, "Table Default Errors", false)
495
+ unless def_path
496
+ def_path = def_path_from_bin_path(bin_path)
497
+ def_path = get_best_def_path(def_path, bin_path) unless user_select_definition
498
+ end
499
+ if !def_path || !File.file?(def_path)
500
+ def_path = Qt::FileDialog.getOpenFileName(self, "Open Definition File", def_path,
501
+ "Definition File (*.txt);;All Files (*)")
394
502
  end
503
+ return unless def_path
504
+
505
+ Qt::Application.setOverrideCursor(Qt::Cursor.new(Qt::WaitCursor))
395
506
  begin
396
- display_gui_data(@currently_displayed_table_name)
397
- rescue => err
398
- ExceptionDialog.new(self, err, "Table Default Errors", false)
507
+ @core.file_open(bin_path, def_path)
508
+ rescue TableManagerCore::MismatchError => err
509
+ # Mismatch errors are recoverable so just warn the user
510
+ Qt::MessageBox.information(self, "Table Open Error", err.message)
399
511
  end
512
+ @def_path = File.dirname(def_path)
513
+ @bin_path = File.dirname(bin_path)
514
+ display_all_gui_data()
515
+ @table_bin_label.text = bin_path
516
+ @table_def_label.text = def_path
517
+ Qt::Application.restoreOverrideCursor()
518
+ rescue TableManagerCore::CoreError => err
519
+ Qt::Application.restoreOverrideCursor()
520
+ Qt::MessageBox.warning(self, "File Open Errors", err.message)
521
+ rescue => err
522
+ Qt::Application.restoreOverrideCursor()
523
+ ExceptionDialog.new(self, err, "File Open Errors", false)
400
524
  end
401
525
 
402
- # Menu option to create a text file report of all the table data
403
- def file_report
404
- begin
405
- @ordered_gui_table_names.each do |name|
406
- save_gui_data(name)
407
- end
526
+ # Menu option to require the user to specify both the binary and the
527
+ # definition file to parse it
528
+ def file_open_both
529
+ file_open(@bin_path, nil, true)
530
+ end
408
531
 
409
- @core.file_report
410
- statusBar.showMessage(tr("File Report Created Successfully"))
411
- rescue => err
412
- ExceptionDialog.new(self, err, "File Report Errors", false)
413
- end
532
+ # Menu option to close the open table
533
+ def file_close
534
+ return if abort_on_modified()
535
+ @core.reset
536
+ @table_bin_label.text = ''
537
+ @table_def_label.text = ''
538
+ reset_gui()
539
+ rescue TableManagerCore::CoreError => err
540
+ Qt::MessageBox.warning(self, "File Close Errors", err.message)
541
+ rescue => err
542
+ ExceptionDialog.new(self, err, "File Close Errors", false)
414
543
  end
415
544
 
416
- # Saves the binary data to a file.
545
+ # Menu option to save the binary data to a file.
417
546
  # It first commits the GUI data to the internal data structures and checks
418
547
  # for errors. If none are found, it saves the data to the table binary file.
419
548
  def file_save(save_as = false)
420
- begin
421
- @ordered_gui_table_names.each do |name|
422
- save_gui_data(name)
423
- end
424
-
425
- filename = @table_bin_label.text
426
- if save_as
427
- filename = Qt::FileDialog.getSaveFileName(self,
428
- "File Save",
429
- @table_bin_label.text,
549
+ return unless file_check(false)
550
+ filename = @table_bin_label.text
551
+ if save_as
552
+ filename = Qt::FileDialog.getSaveFileName(self, "File Save", @table_bin_label.text,
430
553
  "Binary File (*.bin *.dat);;All Files (*)")
431
-
432
- # Check for an empty string which indicates the user clicked "Cancel" on the dialog
433
- return if filename.to_s.empty?
434
- @table_bin_label.text = filename
435
- end
436
-
437
- begin
438
- @core.file_save(filename)
439
- rescue => error
440
- Qt::MessageBox.information(self, "File Save Errors", error.message)
441
- return
442
- end
443
-
444
- @ordered_gui_table_names.each do |name|
445
- display_gui_data(name)
446
- end
447
-
448
- statusBar.showMessage(tr("File Saved Successfully"))
449
- rescue => err
450
- ExceptionDialog.new(self, err, "File Save Errors", false)
554
+ return unless filename
555
+ @table_bin_label.text = filename
451
556
  end
452
- end
453
-
454
- # Menu option to display a dialog containing a hex dump of all table values
455
- def display_hex(type)
456
- begin
457
- dialog = TextDialog.new(self)
458
- if type == :file
459
- str = @core.file_hex()
460
- title = File.basename(@table_bin_label.text)
461
- else
462
- str = @core.table_hex(@currently_displayed_table_name)
463
- title = @currently_displayed_table_name
464
- end
465
- dialog.set_title_and_text("#{title} Hex Dump", str)
466
- dialog.set_size(650, 400)
467
- dialog.exec
468
- dialog.dispose
469
- rescue => err
470
- ExceptionDialog.new(self, err, "Display Hex Errors", false)
471
- end
472
- end
473
-
474
- # Menu option to save the currently displayed table to an existing table binary file
475
- # containing that table.
476
- def table_commit
477
- begin
478
- save_gui_data(@currently_displayed_table_name)
479
557
 
480
- # Ask for the file to save the current table to
481
- bin_file, def_file = get_binary_and_definition_file_paths()
482
- if bin_file.nil? or def_file.nil? then return 1 end
558
+ @core.file_save(filename)
559
+ @bin_path = File.dirname(filename)
483
560
 
484
- @core.table_commit(@currently_displayed_table_name, bin_file, def_file)
485
-
486
- # Update the labels
487
- @table_bin_label.text = bin_file
488
- @table_def_label.text = def_file
489
-
490
- # Display the new table
491
- delete_tabs()
492
- @core.table_def.get_all_tables.each do |table|
493
- create_table_tab(table)
494
- display_gui_data(table.name)
495
- end
496
- @currently_displayed_table_name = @ordered_gui_table_names[0]
497
- @currently_displayed_table_name ||= '' # ensure it's not nil
498
- rescue => err
499
- ExceptionDialog.new(self, err, "Table Commit Errors", false)
500
- end
501
- end
502
-
503
- # Menu option to save the currently displayed table as a stand alone binary file
504
- def table_save
505
- begin
506
- save_gui_data(@currently_displayed_table_name)
507
-
508
- filename = Qt::FileDialog.getSaveFileName(self,
509
- "File Save",
510
- File.join(@bin_path, "#{@currently_displayed_table_name.gsub(/\s/,'')}.dat"),
511
- "Binary File (*.bin *.dat);;All Files (*)")
512
- # Check for a 0 length string which indicates the user clicked "Cancel" on the dialog
513
- if filename.to_s.strip.length != 0
514
- @bin_path = File.dirname(filename)
515
- @core.table_save(@currently_displayed_table_name, filename)
516
- end
517
- rescue => err
518
- ExceptionDialog.new(self, err, "Table Save Errors", false)
519
- end
561
+ display_all_gui_data()
562
+ @table_bin_label.text = filename
563
+ statusBar.showMessage(tr("File Saved Successfully"))
564
+ rescue TableManagerCore::CoreError, SaveError => err
565
+ Qt::MessageBox.warning(self, "File Save Errors", err.message)
566
+ rescue => err
567
+ ExceptionDialog.new(self, err, "File Save Errors", false)
520
568
  end
521
569
 
522
- # Menu option to update the definition file defaults with the currently
523
- # displayed table values
524
- def table_update
525
- begin
526
- save_gui_data(@currently_displayed_table_name)
527
-
528
- result = Qt::MessageBox.question(self, "Update Table Definition File",
529
- "Are you sure you want to update the table definition file. This action is irreversable!",
530
- Qt::MessageBox::Yes | Qt::MessageBox::No, Qt::MessageBox::Yes)
531
- if result == Qt::MessageBox::Yes
532
- @core.table_update_def(@currently_displayed_table_name)
533
- end # end if result == MBOX_CLICKED_YES
534
- rescue => err
535
- ExceptionDialog.new(self, err, "Table Update Errors", false)
536
- end
570
+ # Menu option to check every table's values against their allowable ranges
571
+ #
572
+ # @param success_dialog [Boolean] Whether to display a dialog indicating
573
+ # success. If false simply return true.
574
+ # @return [Boolean] Whether the file check was successful
575
+ def file_check(success_dialog = true)
576
+ return false if abort_on_no_current_table()
577
+ save_all_gui_data()
578
+ Qt::MessageBox.information(self, "File Check", @core.file_check()) if success_dialog
579
+ true
580
+ rescue TableManagerCore::CoreError, SaveError => err
581
+ Qt::MessageBox.warning(self, "File Check Errors", err.message)
582
+ false
583
+ rescue => err
584
+ ExceptionDialog.new(self, err, "Unknown File Check Errors", false)
585
+ false
537
586
  end
538
587
 
539
- # Menu option that opens a dialog to allows the user to select a table binary
540
- # file named XXX.dat. The associated table definition file (XXX_def.txt)
541
- # is then opened and parsed to determine how to display the binary file in the gui.
542
- def file_open(bin_file = nil, def_file = nil)
543
- begin
544
- unless bin_file and def_file
545
- bin_file, def_file = get_binary_and_definition_file_paths()
546
- end
547
-
548
- # Do nothing if the binary or definition files are not found
549
- if bin_file.nil? or def_file.nil? then return end
550
-
551
- # Update the labels
552
- @table_bin_label.text = bin_file
553
- @table_def_label.text = def_file
554
-
555
- # open the file
556
- begin
557
- @core.file_open(def_file, bin_file)
558
- rescue => err
559
- if err.message.include?("Binary")
560
- Qt::MessageBox.information(self, "Table Open Error", err.message)
561
- else
562
- ExceptionDialog.new(self, err, "Table Open Errors", false)
563
- end
564
- end
565
-
566
- Qt::Application.setOverrideCursor(Qt::Cursor.new(Qt::WaitCursor))
567
-
568
- # display the file
569
- delete_tabs()
570
- @core.table_def.get_all_tables.each do |table|
571
- create_table_tab(table)
572
- display_gui_data(table.name)
573
- end
574
- @currently_displayed_table_name = @ordered_gui_table_names[0]
575
- @currently_displayed_table_name ||= '' # ensure it's not nil
588
+ # Menu option to create a text file report of all the table data
589
+ def file_report
590
+ return unless file_check(false)
591
+ report_path = @core.file_report(@table_bin_label.text, @table_def_label.text)
576
592
 
577
- Qt::Application.restoreOverrideCursor()
578
- rescue => err
579
- ExceptionDialog.new(self, err, "File Open Errors", false)
593
+ dialog = Qt::Dialog.new(self, Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
594
+ dialog.setWindowTitle("File Report")
595
+ dialog_layout = Qt::VBoxLayout.new
596
+ dialog_layout.addWidget(Qt::Label.new("Report file created: #{report_path}"))
597
+ button_layout = Qt::HBoxLayout.new
598
+ ok_button = Qt::PushButton.new('&Ok')
599
+ ok_button.connect(SIGNAL('clicked()')) { dialog.accept }
600
+ ok_button.setEnabled(true)
601
+ button_layout.addWidget(ok_button)
602
+ button_layout.addStretch(1)
603
+ open_button = Qt::PushButton.new('Open in &Editor')
604
+ open_button.connect(SIGNAL('clicked()')) { Cosmos.open_in_text_editor(report_path) }
605
+ button_layout.addWidget(open_button)
606
+ if Kernel.is_windows?
607
+ open_excel_button = Qt::PushButton.new('Open in E&xcel')
608
+ open_excel_button.connect(SIGNAL('clicked()')) { system("start Excel.exe \"#{report_path}\"") }
609
+ button_layout.addWidget(open_excel_button)
580
610
  end
611
+ dialog_layout.addLayout(button_layout)
612
+ dialog.setLayout(dialog_layout)
613
+ dialog.exec
614
+ rescue TableManagerCore::CoreError, SaveError => err
615
+ Qt::MessageBox.warning(self, "File Report Errors", err.message)
616
+ rescue => err
617
+ ExceptionDialog.new(self, err, "File Report Errors", false)
581
618
  end
582
619
 
583
- # Menu option to close the open table
584
- def file_close
585
- begin
586
- @core.reset
587
- @table_bin_label.text = ''
588
- @table_def_label.text = ''
589
- delete_tabs()
590
- rescue => err
591
- ExceptionDialog.new(self, err, "File New Errors", false)
620
+ # Menu option to display a dialog containing a hex dump of all table values
621
+ def display_hex(type)
622
+ return if abort_on_no_current_table()
623
+ dialog = HexDumpDialog.new(self)
624
+ if type == :file
625
+ str = @core.file_hex()
626
+ title = File.basename(@table_bin_label.text)
627
+ else # :table
628
+ str = @core.table_hex(current_table_name)
629
+ title = current_table_name
592
630
  end
631
+ dialog.set_title_and_text("#{title} Hex Dump", str)
632
+ dialog.exec
633
+ dialog.dispose
634
+ rescue TableManagerCore::CoreError => err
635
+ Qt::MessageBox.warning(self, "Display Hex Errors", err.message)
636
+ rescue => err
637
+ ExceptionDialog.new(self, err, "Display Hex Errors", false)
593
638
  end
594
639
 
595
- # Menu option to create a new table binary file based on a table definition file.
596
- def file_new
597
- if !@currently_displayed_table_name.empty?
598
- result = Qt::MessageBox.question(self, "File New",
599
- "Creating new files will close the currently open file. Are you sure?",
600
- Qt::MessageBox::Yes | Qt::MessageBox::No, Qt::MessageBox::Yes)
601
- if result != Qt::MessageBox::Yes
602
- return
603
- end
604
- end
605
-
606
- begin
607
- success = nil
608
- bin_files = []
609
- filenames = Qt::FileDialog.getOpenFileNames(self,
610
- "Open Binary Definition Text File(s)",
611
- @def_path,
612
- "Config File (*.txt)\nAll Files (*)")
613
-
614
- # Check for a 0 length string which indicates the user clicked "Cancel" on the dialog
615
- if filenames and filenames.length != 0
616
- @def_path = File.dirname(filenames[0])
617
- file_close()
618
- output_dir = Qt::FileDialog.getExistingDirectory(self, "Select Output Directory", @bin_path)
619
- if output_dir
620
- @bin_path = output_dir
621
- filenames.each do |def_file|
622
- if File.basename(def_file) =~ /_def\.txt/
623
- basename = File.basename(def_file)[0...-8] # Get the basename without the _def.txt
624
- else
625
- basename = File.basename(def_file).split('.')[0...-1].join('.') # Get the basename without the extension
626
- end
627
-
628
- # Set the current_bin so the file_report function works correctly
629
- output_filename = File.join(output_dir, "#{basename}.dat")
630
- if File.exist?(output_filename)
631
- result = Qt::MessageBox.question(self, "File New",
632
- "File: #{output_filename} already exists. Overwrite?",
633
- Qt::MessageBox::Yes | Qt::MessageBox::No, Qt::MessageBox::Yes)
634
- if result != Qt::MessageBox::Yes
635
- return
636
- end
637
- end
638
- end
639
-
640
- ProgressDialog.execute(self, 'Create New Files', 500, 50, true, false, false, false, false) do |dialog|
641
- # create the file
642
- begin
643
- bin_files = @core.file_new(filenames, output_dir, dialog)
644
- success = true
645
- dialog.close_done
646
- rescue => err
647
- Qt.execute_in_main_thread(true) {|| ExceptionDialog.new(self, err, "File New Errors", false)}
648
- dialog.close_done
649
- end
650
- end
651
- end # end output_dir.nil? (user did NOT click cancel on dialog)
652
- file_open(bin_files[0] ,filenames[0]) if success
653
- end # end filename != 0 (user did NOT click cancel on dialog)
654
- rescue => err
655
- ExceptionDialog.new(self, err, "File New Errors", false)
640
+ # Menu option to check the table values against their allowable ranges
641
+ def table_check
642
+ return if abort_on_no_current_table()
643
+ save_gui_data(current_table_name)
644
+ result = @core.table_check(current_table_name)
645
+ if result.empty?
646
+ result = "All parameters are within their constraints."
656
647
  end
648
+ Qt::MessageBox.information(self, "Table Check", result)
649
+ rescue TableManagerCore::CoreError, SaveError => err
650
+ Qt::MessageBox.warning(self, "Table Check Errors", err.message)
651
+ rescue => err
652
+ ExceptionDialog.new(self, err, "Table Check Errors", false)
657
653
  end
658
654
 
659
- # Called when the user selects another tab in the gui
660
- def handle_tab_change(index)
661
- @currently_displayed_table_name = @ordered_gui_table_names[index]
662
- @currently_displayed_table_name ||= '' # ensure it's not nil
663
- end
664
-
665
- def mouse_over(row, col)
666
- return if @currently_displayed_table_name.empty?
667
- table = @core.table_def.get_table(@currently_displayed_table_name)
668
- gui_table = @gui_tables[@currently_displayed_table_name]
669
- if table and gui_table
670
- if table.type == :TWO_DIMENSIONAL
671
- item_name = gui_table.horizontalHeaderItem(col).text + row.to_s
672
- item = table.get_item(item_name)
673
- statusBar.showMessage(item.description)
674
- else
675
- item_name = gui_table.verticalHeaderItem(row).text
676
- item = table.get_item(item_name)
677
- statusBar.showMessage(item.description)
678
- end
679
- end
655
+ # Menu option to set all the table items to their default values
656
+ def table_default
657
+ return if abort_on_no_current_table()
658
+ @core.table_default(current_table_name)
659
+ set_table_modified(true)
660
+ display_gui_data(current_table_name)
661
+ rescue TableManagerCore::CoreError, DisplayError => err
662
+ Qt::MessageBox.warning(self, "Table Default Errors", err.message)
663
+ rescue => err
664
+ ExceptionDialog.new(self, err, "Table Default Errors", false)
680
665
  end
681
666
 
682
- def click_callback(item)
683
- table = @core.table_def.get_table(@currently_displayed_table_name)
684
- gui_table = @gui_tables[@currently_displayed_table_name]
685
- gui_table.editItem(item) if (item.flags & Qt::ItemIsEditable) != 0
667
+ # Menu option to save the currently displayed table as a stand alone binary file
668
+ def table_save
669
+ return if abort_on_no_current_table()
670
+ save_gui_data(current_table_name)
671
+ filename = File.join(@bin_path, "#{current_table_name.split.collect(&:capitalize).join}Table.dat")
672
+ filename = Qt::FileDialog.getSaveFileName(self, "File Save", filename,
673
+ "Binary File (*.bin *.dat);;All Files (*)")
674
+ return unless filename
675
+ @core.table_save(current_table_name, filename)
676
+ rescue TableManagerCore::CoreError, SaveError => err
677
+ Qt::MessageBox.warning(self, "Table Save Errors", err.message)
678
+ rescue => err
679
+ ExceptionDialog.new(self, err, "Table Save Errors", false)
686
680
  end
687
681
 
688
- def context_menu(point)
689
- begin
690
- table = @core.table_def.get_table(@currently_displayed_table_name)
691
- gui_table = @gui_tables[@currently_displayed_table_name]
692
- if table and gui_table
693
- table_item = gui_table.itemAt(point)
694
- if table_item
695
- menu = Qt::Menu.new()
696
-
697
- if table.type == :TWO_DIMENSIONAL
698
- item_name = gui_table.horizontalHeaderItem(table_item.column).text + table_item.row.to_s
699
- else
700
- item_name = gui_table.verticalHeaderItem(table_item.row).text
701
- end
702
- details_action = Qt::Action.new(tr("Details"), self)
703
- details_action.statusTip = tr("Popup details about #{@currently_displayed_table_name} #{item_name}")
704
- details_action.connect(SIGNAL('triggered()')) do
705
- TlmDetailsDialog.new(nil,
706
- 'TABLE',
707
- @currently_displayed_table_name.upcase,
708
- item_name,
709
- table)
710
- end
711
- menu.addAction(details_action)
712
-
713
- default_action = Qt::Action.new(tr("Default"), self)
714
- default_action.statusTip = tr("Set item to default value")
715
- default_action.connect(SIGNAL('triggered()')) do
716
- item = table.get_item(item_name)
717
- table.write(item.name, item.default)
718
- update_gui_item(@currently_displayed_table_name, table, item, table_item.row, table_item.column)
719
- end
720
- menu.addAction(default_action)
682
+ # Menu option to save the currently displayed table to an existing table binary file
683
+ # containing that table.
684
+ def table_commit
685
+ return if abort_on_no_current_table()
686
+ return if abort_on_modified()
687
+ save_gui_data(current_table_name)
721
688
 
722
- global_point = gui_table.mapToGlobal(point)
723
- global_point.x += gui_table.verticalHeader.width
724
- menu.exec(global_point)
725
- menu.dispose
726
- end
727
- end
728
- rescue => err
729
- ExceptionDialog.new(self, err, "context_menu Errors", false)
689
+ bin_path = Qt::FileDialog.getOpenFileName(self, "Open Binary", @bin_path,
690
+ "Binary File (*.bin *.dat);;All Files (*)")
691
+ return unless bin_path
692
+ def_path = def_path_from_bin_path(bin_path)
693
+ def_path = get_best_def_path(def_path, bin_path)
694
+ if !def_path || !File.file?(def_path)
695
+ def_path = Qt::FileDialog.getOpenFileName(self, "Open Definition File", def_path,
696
+ "Definition File (*.txt);;All Files (*)")
730
697
  end
698
+ return unless def_path
699
+
700
+ @core.table_commit(current_table_name, bin_path, def_path)
701
+ rescue TableManagerCore::CoreError, SaveError, DisplayError => err
702
+ Qt::MessageBox.warning(self, "Table Commit Errors", err.message)
703
+ rescue => err
704
+ ExceptionDialog.new(self, err, "Table Commit Errors", false)
731
705
  end
732
706
 
733
- # Creates a tab in the table manager gui
734
- def create_table_tab(table_definition)
735
- begin
736
- # Table
737
- @table = Qt::TableWidget.new(self)
738
- delegate = ComboBoxItemDelegate.new(@table)
739
- @table.setItemDelegate(delegate)
740
- @table.setEditTriggers(Qt::AbstractItemView::AllEditTriggers)
741
- @table.setSelectionMode(Qt::AbstractItemView::NoSelection)
742
- #@table.setAlternatingRowColors(true)
743
- @gui_tables[table_definition.name] = @table
744
- @ordered_gui_table_names << table_definition.name
745
- @tabbook.addTab(@table, table_definition.name)
746
-
747
- @table.setRowCount(table_definition.num_rows)
748
- @table.setColumnCount(table_definition.num_columns)
749
- @table.setMouseTracking(true)
750
- connect(@table, SIGNAL('cellEntered(int, int)'), self, SLOT('mouse_over(int, int)'))
751
- connect(@table, SIGNAL('itemClicked(QTableWidgetItem*)'), self, SLOT('click_callback(QTableWidgetItem*)'))
752
- @table.setContextMenuPolicy(Qt::CustomContextMenu)
753
- connect(@table, SIGNAL('customContextMenuRequested(const QPoint&)'), self, SLOT('context_menu(const QPoint&)'))
754
- rescue => err
755
- ExceptionDialog.new(self, err, "create_table_tab Errors", false)
756
- end
707
+ def current_table_name
708
+ @tabbook.current_name
757
709
  end
758
710
 
759
711
  # Saves all the information in the given gui table name to the underlying
760
712
  # binary structure (although it does not commit it to disk).
761
713
  def save_gui_data(name)
762
- gui_table = @gui_tables[name]
763
- table = @core.table_def.get_table(name)
764
- result = ""
765
-
766
- # Cancel any table selections so the text will be visible when it is refreshed
767
- gui_table.clearSelection
714
+ table = @core.config.table(name)
715
+ gui_table = @tabbook.tab(name)
716
+ return unless table && gui_table
768
717
 
769
- # don't do anything if we can't find the table
770
- if gui_table.nil? or table.nil? then return end
718
+ result = ""
771
719
 
772
720
  # First go through the gui and set the underlying data to what is displayed
773
721
  (0...table.num_rows).each do |r|
774
722
  (0...table.num_columns).each do |c|
775
723
  if table.type == :TWO_DIMENSIONAL
776
- # get the table item definition so we know how to save it
777
- item_def = table.get_item("#{gui_table.horizontalHeaderItem(c).text}#{r}")
724
+ item = table.get_item("#{gui_table.horizontalHeaderItem(c).text}#{r}")
778
725
  else # table is ONE_DIMENSIONAL
779
- # get the table item definition so we know how to save it
780
- item_def = table.get_item(gui_table.verticalHeaderItem(r).text)
726
+ item = table.get_item(gui_table.verticalHeaderItem(r).text)
781
727
  end
728
+ next if item.hidden
782
729
 
783
- # determine how to convert the display value to the actual value
784
730
  begin
785
- case item_def.display_type
786
- when :DEC
787
- if item_def.data_type == :FLOAT
788
- x = Float(gui_table.item(r,c).text)
789
- else
790
- x = Integer(gui_table.item(r,c).text)
791
- end
792
-
793
- when :HEX
794
- x = Integer(gui_table.item(r,c).text)
795
-
796
- when :CHECK
797
- # the ItemData will be 0 for unchecked (corresponds with min value),
798
- # and 1 for checked (corresponds with max value)
799
- if gui_table.item(r,c).checkState == Qt::Checked
800
- x = item_def.range.end.to_i
801
- else
802
- x = item_def.range.begin.to_i
803
- end
804
-
805
- when :STATE
806
- x = item_def.states[gui_table.item(r,c).text]
807
-
808
- when :STRING, :NONE
809
- x = gui_table.item(r,c).text
810
-
811
- end
731
+ value = get_item_value(item, gui_table.item(r, c))
812
732
 
813
733
  # If there is a read conversion we first read the converted value before writing.
814
734
  # This is to prevent writing the displayed value (which has the conversion applied)
815
735
  # back to the binary data if they are already equal.
816
- if item_def.read_conversion
817
- converted = table.read(item_def.name, :CONVERTED)
818
- table.write(item_def.name, x) if converted != x
736
+ if item.read_conversion
737
+ converted = table.read(item.name, :CONVERTED)
738
+ table.write(item.name, value) if converted != value
819
739
  else
820
- table.write(item_def.name, x)
740
+ table.write(item.name, value)
821
741
  end
822
742
 
823
743
  # if we have a problem casting the value it probably means the user put in garbage
824
744
  # in this case force the range check to fail
825
745
  rescue => error
826
- text = gui_table.item(r,c).text
827
- result << "Error saving #{item_def.name} value of '#{text}' due to #{error.message}.\nDefault value is '#{item_def.default}'\n"
746
+ text = gui_table.item(r, c).text
747
+ result << "Error saving #{item.name} value of '#{text}' due to #{error.message}.\nDefault value is '#{item.default}'\n"
828
748
  end
829
749
  end # end each table column
830
750
  end # end each table row
831
- if result == ""
832
- result = nil
833
- end
834
- result
751
+ raise SaveError, result unless result.empty?
835
752
  end
836
753
 
837
- # Determines how to display all the binary table data based on
838
- # the table definition and displays it using the various gui elements.
839
- def display_gui_data(name)
840
- table_def = @core.table_def.get_table(name)
841
- gui_table = @gui_tables[name]
842
-
843
- # Cancel any table selections so the text will be visible when it is refreshed
844
- gui_table.clearSelection
845
-
846
- # if we can't find the table do nothing
847
- if table_def.nil? or gui_table.nil? then return end
848
-
849
- items = table_def.sorted_items
850
-
851
- if table_def.type == :TWO_DIMENSIONAL
852
- row_headers = []
853
- (0...table_def.num_rows).each {|i| row_headers << "#{i+1}" }
854
- gui_table.setVerticalHeaderLabels(row_headers)
855
-
856
- column_headers = []
857
- (0...table_def.num_columns).each {|i| column_headers << items[i].name[0...-1] }
858
- gui_table.setHorizontalHeaderLabels(column_headers)
754
+ def closeEvent(event)
755
+ if abort_on_modified()
756
+ event.ignore()
859
757
  else
860
- row_headers = []
861
- items.each {|item_def| row_headers << item_def.name }
862
- gui_table.setVerticalHeaderLabels(row_headers)
863
- gui_table.setHorizontalHeaderLabels(["Value"])
758
+ super(event)
864
759
  end
760
+ end
865
761
 
866
- table_row = 0
867
- table_column = 0
868
-
869
- items.each do |item_def|
870
- update_gui_item(name, table_def, item_def, table_row, table_column)
762
+ protected
871
763
 
872
- if table_def.type == :TWO_DIMENSIONAL
873
- # only increment our row when we've processed all the columns
874
- if table_column == table_def.num_columns - 1
875
- table_row += 1
876
- table_column = 0
877
- else
878
- table_column += 1
879
- end
880
- else
881
- table_row += 1
882
- end
764
+ # Determine the binary directory path given the definition directory path
765
+ #
766
+ # @param def_path [String] Path to the definition directory
767
+ def bin_path_from_def_path(def_path)
768
+ if def_path.include?('targets') # target directory path
769
+ bin_path = File.expand_path(File.join(def_path, '..', '..', 'tables'))
770
+ else
771
+ bin_path = @system_bin_path
883
772
  end
884
-
885
- gui_table.resizeColumnsToContents()
886
- gui_table.resizeRowsToContents()
887
773
  end
888
774
 
889
- # Updates the table by setting the value in the table to the properly formatted value.
890
- def update_gui_item(table_name, table_def, item_def, table_row, table_column)
891
- gui_table = @gui_tables[table_name]
892
-
893
- case item_def.display_type
894
- when :STATE
895
- item = Qt::TableWidgetItem.new
896
- item.setData(Qt::DisplayRole, Qt::Variant.new(table_def.read(item_def.name)))
897
- gui_table.setItem(table_row, table_column, item)
898
- if item_def.editable
899
- gui_table.item(table_row, table_column).setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable)
900
- else
901
- gui_table.item(table_row, table_column).setFlags(Qt::NoItemFlags)
902
- end
903
-
904
- when :CHECK
905
- gui_table.setItem(table_row, table_column, Qt::TableWidgetItem.new(table_def.read(item_def.name)))
906
- # the ItemData will be 0 for unchecked (corresponds with min value),
907
- # and 1 for checked (corresponds with max value)
908
- if table_def.read(item_def.name) == item_def.range.begin
909
- gui_table.item(table_row, table_column).setCheckState(Qt::Unchecked)
910
- else
911
- gui_table.item(table_row, table_column).setCheckState(Qt::Checked)
912
- end
913
- if item_def.editable
914
- gui_table.item(table_row, table_column).setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable)
915
- else
916
- gui_table.item(table_row, table_column).setFlags(Qt::NoItemFlags)
917
- end
918
-
919
- when :STRING, :NONE, :DEC
920
- gui_table.setItem(table_row, table_column, Qt::TableWidgetItem.new(tr(table_def.read(item_def.name).to_s)))
921
- if item_def.editable
922
- gui_table.item(table_row, table_column).setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled)
923
- else
924
- gui_table.item(table_row, table_column).setFlags(Qt::NoItemFlags)
925
- end
926
-
927
- when :HEX
928
- case item_def.bit_size
929
- when 8
930
- x = sprintf("%02X", table_def.read(item_def.name).to_s)
931
- # if the number was negative x will have .. and possibly another
932
- # F in the string which we remove by taking the last 4 digits
933
- x = /\w{2}$/.match(x)[0]
934
- when 16
935
- x = sprintf("%04X", table_def.read(item_def.name).to_s)
936
- # if the number was negative x will have .. and possibly another
937
- # F in the string which we remove by taking the last 4 digits
938
- x = /\w{4}$/.match(x)[0]
939
- else
940
- x = sprintf("%08X", table_def.read(item_def.name).to_s)
941
- # if the number was negative x will have .. and possibly another
942
- # F in the string which we remove by taking the last 8 digits
943
- x = /\w{8}$/.match(x)[0]
944
- end
945
- x = Integer("0x#{x}") # convert to Integer
946
- gui_table.setItem(table_row, table_column, Qt::TableWidgetItem.new(tr("0x%X" % x)))
947
- if item_def.editable
948
- gui_table.item(table_row, table_column).setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled)
949
- else
950
- gui_table.item(table_row, table_column).setFlags(Qt::NoItemFlags)
951
- end
775
+ # Determine the definition directory path given the binary directory path
776
+ #
777
+ # @param bin_path [String] Path to the binary directory
778
+ def def_path_from_bin_path(bin_path)
779
+ if bin_path.include?('targets') # target directory path
780
+ def_path = File.expand_path(File.join(File.dirname(bin_path), '..', 'tools', 'table_manager'))
781
+ else
782
+ def_path = @system_def_path
952
783
  end
953
784
  end
954
785
 
@@ -958,112 +789,344 @@ module Cosmos
958
789
  # a binary file name of XXX_YYY1_Extra.bin. A binary file of XXX_YYY2.bin
959
790
  # will not match.
960
791
  #
792
+ # @param def_path [String] Path to the definition files
793
+ # @param bin_path [String] Path to the binary file
961
794
  # @return [String|nil] Path to the best definition file or nil
962
- def get_best_definition_file(path, bin_file)
795
+ def get_best_def_path(def_path, bin_path)
796
+ return nil unless (File.exist?(def_path) && File.exist?(bin_path))
797
+ bin_path = File.basename(bin_path).split('.')[0]
963
798
  def_file = nil
964
799
 
965
- # compare the filename with all possibilities in the directory
966
- Dir.foreach(path) do |possible_def_file|
800
+ Dir.foreach(def_path) do |possible_def_file|
967
801
  # only bother checking definition files
968
802
  index = possible_def_file.index('_def.txt')
969
803
  next unless index
970
804
 
971
805
  base_name = possible_def_file[0...index]
972
- if bin_file.index(base_name)
806
+ if bin_path.index(base_name)
973
807
  # If we've already found a def_file and now found another match we
974
808
  # clear the first and stop the search. Force the user to decide.
975
809
  if def_file
976
810
  def_file = nil
977
811
  break
978
812
  end
979
- def_file = File.join(path, possible_def_file)
813
+ def_file = File.join(def_path, possible_def_file)
980
814
  end
981
- end # each file in the directory
982
-
983
- return def_file
815
+ end
816
+ # Return the original path if we couldn't find anything
817
+ def_file = def_path unless def_file
818
+ def_file
984
819
  end
985
820
 
986
- # Prompts the user to select a binary table file to open.
987
- # (e.g. MyBinary_TC2.dat or MyBinaryJMT = MyBinary_def.dat)
988
- # Returns both the path to the binary file and the table definition file or
989
- # nil for both if either can not be found.
990
- def get_binary_and_definition_file_paths
991
- bin_file = nil
992
- def_file = nil
821
+ def abort_on_check_for_existing(filenames, output_path)
822
+ user_abort = false
823
+ filenames.each do |def_file|
824
+ if File.basename(def_file) =~ /_def\.txt/
825
+ basename = File.basename(def_file)[0...-8] # Get the basename without the _def.txt
826
+ else
827
+ basename = File.basename(def_file).split('.')[0...-1].join('.') # Get the basename without the extension
828
+ end
993
829
 
994
- bin_file = Qt::FileDialog.getOpenFileName(
995
- self, "Open Binary", @bin_path, "Binary File (*.bin *.dat);;All Files (*)")
996
- unless bin_file.nil? or bin_file.empty?
997
- @bin_path = File.dirname(bin_file)
998
- bin_file_base = File.basename(bin_file).split('.')[0]
830
+ output_filename = File.join(output_path, "#{basename}.dat")
831
+ if abort_on_existing_file(output_filename)
832
+ user_abort = true
833
+ break # No need to continue processing
834
+ end
835
+ end
836
+ user_abort
837
+ end
999
838
 
1000
- # Look in the binary file's base directory
1001
- def_file = get_best_definition_file(File.dirname(bin_file), bin_file_base)
1002
- def_found = !def_file.nil?
839
+ def abort_on_existing_file(filename)
840
+ user_abort = false
841
+ if File.exist?(filename)
842
+ result = Qt::MessageBox.question(self, "File New",
843
+ "#{filename} already exists. Overwrite?",
844
+ Qt::MessageBox::Yes | Qt::MessageBox::No, Qt::MessageBox::Yes)
845
+ if result != Qt::MessageBox::Yes
846
+ user_abort = true
847
+ end
848
+ end
849
+ user_abort
850
+ end
1003
851
 
1004
- if not def_found
1005
- # Look in the stored definition file directory
1006
- def_file = get_best_definition_file(@def_path, bin_file_base)
1007
- def_found = !def_file.nil?
852
+ def abort_on_modified
853
+ user_abort = false
854
+ if self.windowTitle.include?("*")
855
+ result = Qt::MessageBox.warning(self, "Table Modified",
856
+ "Table has been modified. Continue and discard all changes?",
857
+ Qt::MessageBox::Yes | Qt::MessageBox::No, Qt::MessageBox::No)
858
+ if result != Qt::MessageBox::Yes
859
+ user_abort = true
1008
860
  end
861
+ end
862
+ user_abort
863
+ end
1009
864
 
1010
- if def_found # if the definition file was automatically found
1011
- return bin_file, def_file
1012
- else # the definition file was not automatically found
1013
- result = Qt::MessageBox.question(self, "Manually Open Definition File",
1014
- "The definition text file for #{File.basename(bin_file)} could not be found.\nWould you like to manually locate it?",
1015
- Qt::MessageBox::Yes | Qt::MessageBox::No | Qt::MessageBox::Cancel, Qt::MessageBox::Yes)
1016
- if result == Qt::MessageBox::Yes
1017
- def_file = Qt::FileDialog.getOpenFileName(self,
1018
- "Open Definition File",
1019
- @def_path,
1020
- "Definition File (*.txt)\nAll Files (*)")
1021
- unless def_file.nil? or def_file.empty?
1022
- if File.basename(def_file) =~ /\.txt/
1023
- @def_path = File.dirname(def_file)
1024
- return bin_file, def_file
1025
- else
1026
- Qt::MessageBox.information(self, "Open Definition File Errors",
1027
- "Definition file #{File.basename(def_file)} does not have .txt extension")
1028
- end
1029
- end # the user clicked Cancel on the File Open dialog
1030
- end # the user clicked No when prompted to find the definition file
1031
- end # end if the definition file wasn't found
1032
- end # end if the user did not click Cancel on the Open dialog
1033
- return nil, nil
865
+ def abort_on_no_current_table
866
+ user_abort = false
867
+ unless current_table_name
868
+ Qt::MessageBox.information(self, "No current table", "Please open a table.")
869
+ user_abort = true
870
+ end
871
+ user_abort
1034
872
  end
1035
873
 
1036
874
  # Delete all the tabs in the table manager gui and initialize globals to
1037
875
  # prepare for the next table to load.
1038
- def delete_tabs
1039
- @gui_tables = Hash.new
1040
- @ordered_gui_table_names = Array.new
1041
- @currently_displayed_table_name = ""
1042
-
876
+ def reset_gui
877
+ set_table_modified(false)
1043
878
  @tabbook.tabs.each_with_index do |tab, index|
1044
879
  tab.dispose
1045
880
  @tabbook.removeTab(index)
1046
881
  end
1047
882
  end
1048
883
 
1049
- def self.run(option_parser = nil, options = nil)
1050
- Cosmos.catch_fatal_exception do
1051
- unless option_parser and options
1052
- option_parser, options = create_default_options()
1053
- options.width = 800
1054
- options.height = 600
1055
- options.title = "Table Manager"
1056
- options.auto_size = false
1057
- options.no_tables = false
1058
- option_parser.separator "Table Manager Specific Options:"
1059
- option_parser.on("-n", "--notables", "Do not include table file editing options. This will remove the 'Table' menu.") do
1060
- options.no_tables = true
884
+ def save_all_gui_data
885
+ result = ''
886
+ @core.config.tables.each do |table_name, table|
887
+ begin
888
+ save_gui_data(table_name)
889
+ rescue SaveError => err
890
+ result << "\nErrors in #{table_name}:\n#{err.message}"
891
+ end
892
+ end
893
+ raise SaveError, result.lstrip unless result.empty?
894
+ end
895
+
896
+ def display_all_gui_data
897
+ reset_gui()
898
+ @core.config.tables.each do |table_name, table|
899
+ create_table_tab(table)
900
+ display_gui_data(table_name)
901
+ end
902
+ end
903
+
904
+ def display_gui_data(table_name)
905
+ table = @core.config.table(table_name)
906
+ gui_table = @tabbook.tab(table_name)
907
+ return unless table && gui_table
908
+
909
+ gui_table.blockSignals(true) # block signals while we programatically update it
910
+ # Cancel any table selections so the text will be visible when it is refreshed
911
+ gui_table.clearSelection
912
+
913
+ set_table_headers(table, gui_table)
914
+
915
+ row = 0
916
+ column = 0
917
+ table.sorted_items.each do |item|
918
+ next if item.hidden
919
+ update_gui_item(table_name, table, gui_table, item, row, column)
920
+
921
+ if table.type == :TWO_DIMENSIONAL
922
+ # only increment our row when we've processed all the columns
923
+ if column == table.num_columns - 1
924
+ row += 1
925
+ column = 0
926
+ else
927
+ column += 1
1061
928
  end
929
+ else
930
+ row += 1
1062
931
  end
932
+ end
1063
933
 
1064
- super(option_parser, options)
934
+ gui_table.resizeColumnsToContents()
935
+ gui_table.resizeRowsToContents()
936
+ gui_table.blockSignals(false)
937
+ end
938
+
939
+ # Updates the table by setting the value in the table to the properly formatted value.
940
+ def update_gui_item(table_name, table, gui_table, item, row, column)
941
+ value = table.read(item.name, :FORMATTED)
942
+
943
+ if item.states && item.states.keys.sort == %w(CHECKED UNCHECKED)
944
+ table_item = create_checkable_table_item(item, value)
945
+ else
946
+ table_item = create_table_item(item, value)
1065
947
  end
948
+ gui_table.setItem(row, column, table_item)
949
+ end
950
+
951
+ def get_item_value(item, gui_item)
952
+ value = nil
953
+ if (gui_item.flags & Qt::ItemIsUserCheckable) != 0
954
+ check_state = gui_item.checkState
955
+ case check_state
956
+ when Qt::Checked
957
+ value = item.states["CHECKED"]
958
+ when Qt::Unchecked
959
+ value = item.states["UNCHECKED"]
960
+ when Qt::PartiallyChecked
961
+ value = gui_item.text # convert_to_value?
962
+ end
963
+ else
964
+ text = gui_item.text
965
+ quotes_removed = text.remove_quotes
966
+ if text == quotes_removed
967
+ value = text.convert_to_value
968
+ else
969
+ value = quotes_removed
970
+ end
971
+ end
972
+ value
1066
973
  end
1067
- end
1068
974
 
975
+ def set_table_modified(modified)
976
+ title = modified ? "#{@app_title} *" : @app_title
977
+ self.setWindowTitle(title)
978
+ end
979
+
980
+ def table_item_changed(table_item)
981
+ set_table_modified(true)
982
+ # If there is a checkbox value that contains a value that doesn't map to
983
+ # the checked or unchecked state, that value is displayed next to the
984
+ # "PartiallyChecked" checkbox. Once the user clicks to check or uncheck,
985
+ # the original value will disappear due to the following code:
986
+ table = @core.config.table(current_table_name)
987
+ gui_table = @tabbook.tab(current_table_name)
988
+ if table.type == :TWO_DIMENSIONAL
989
+ item_name = gui_table.horizontalHeaderItem(table_item.column).text + table_item.row.to_s
990
+ else
991
+ item_name = gui_table.verticalHeaderItem(table_item.row).text
992
+ end
993
+ item = table.get_item(item_name)
994
+ if item.states && item.states.keys.sort == %w(CHECKED UNCHECKED)
995
+ table_item.setText('')
996
+ end
997
+ end
998
+
999
+ def set_table_headers(table, gui_table)
1000
+ items = table.sorted_items
1001
+ if table.type == :TWO_DIMENSIONAL
1002
+ row_headers = []
1003
+ (0...table.num_rows).each {|i| row_headers << "#{i + 1}" }
1004
+ gui_table.setVerticalHeaderLabels(row_headers)
1005
+
1006
+ column_headers = []
1007
+ (0...table.num_columns).each {|i| column_headers << items[i].name[0...-1] unless items[i].hidden }
1008
+ gui_table.setHorizontalHeaderLabels(column_headers)
1009
+ else
1010
+ row_headers = []
1011
+ items.each {|item| row_headers << item.name unless item.hidden }
1012
+ gui_table.setVerticalHeaderLabels(row_headers)
1013
+ gui_table.setHorizontalHeaderLabels(["Value"])
1014
+ end
1015
+ end
1016
+
1017
+ def create_checkable_table_item(item, value)
1018
+ table_item = Qt::TableWidgetItem.new()
1019
+ if item.editable
1020
+ table_item.setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate)
1021
+ else
1022
+ table_item.setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsTristate)
1023
+ end
1024
+ if value == "CHECKED"
1025
+ table_item.setCheckState(Qt::Checked)
1026
+ elsif value == "UNCHECKED"
1027
+ table_item.setCheckState(Qt::Unchecked)
1028
+ else # The value doesn't match our defined states
1029
+ table_item.setText(value) # Display the actual value
1030
+ table_item.setCheckState(Qt::PartiallyChecked) # Mark it partial since we can't tell
1031
+ end
1032
+ table_item
1033
+ end
1034
+
1035
+ def create_table_item(item, value)
1036
+ table_item = Qt::TableWidgetItem.new(value)
1037
+ if item.editable
1038
+ table_item.setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable)
1039
+ else
1040
+ table_item.setFlags(Qt::NoItemFlags)
1041
+ end
1042
+ table_item
1043
+ end
1044
+
1045
+ def context_menu(point)
1046
+ begin
1047
+ table = @core.config.table(current_table_name)
1048
+ gui_table = @tabbook.tab(current_table_name)
1049
+ if table && gui_table
1050
+ table_item = gui_table.itemAt(point)
1051
+ if table_item
1052
+ menu = Qt::Menu.new()
1053
+
1054
+ if table.type == :TWO_DIMENSIONAL
1055
+ item_name = gui_table.horizontalHeaderItem(table_item.column).text + table_item.row.to_s
1056
+ else
1057
+ item_name = gui_table.verticalHeaderItem(table_item.row).text
1058
+ end
1059
+ details_action = Qt::Action.new(tr("Details"), self)
1060
+ details_action.statusTip = tr("Popup details about #{current_table_name} #{item_name}")
1061
+ details_action.connect(SIGNAL('triggered()')) do
1062
+ TlmDetailsDialog.new(nil,
1063
+ 'TABLE',
1064
+ current_table_name.upcase,
1065
+ item_name,
1066
+ table)
1067
+ end
1068
+ menu.addAction(details_action)
1069
+
1070
+ default_action = Qt::Action.new(tr("Default"), self)
1071
+ default_action.statusTip = tr("Set item to default value")
1072
+ default_action.connect(SIGNAL('triggered()')) do
1073
+ item = table.get_item(item_name)
1074
+ table.write(item.name, item.default)
1075
+ update_gui_item(current_table_name, table, gui_table, item, table_item.row, table_item.column)
1076
+ end
1077
+ menu.addAction(default_action)
1078
+
1079
+ global_point = gui_table.mapToGlobal(point)
1080
+ global_point.x += gui_table.verticalHeader.width
1081
+ menu.exec(global_point)
1082
+ menu.dispose
1083
+ end
1084
+ end
1085
+ rescue => err
1086
+ ExceptionDialog.new(self, err, "context_menu Errors", false)
1087
+ end
1088
+ end
1089
+
1090
+ # Creates a tab in the table manager gui
1091
+ #
1092
+ # @param table [Table] Table to display
1093
+ def create_table_tab(table)
1094
+ @table = Qt::TableWidget.new(self)
1095
+ delegate = ComboBoxItemDelegate.new(@table)
1096
+ @table.setItemDelegate(delegate)
1097
+ @table.setEditTriggers(Qt::AbstractItemView::AllEditTriggers)
1098
+ @table.setSelectionMode(Qt::AbstractItemView::NoSelection)
1099
+ #@table.setAlternatingRowColors(true)
1100
+ @tabbook.addTab(@table, table.table_name)
1101
+
1102
+ @table.setRowCount(table.num_rows)
1103
+ @table.setColumnCount(table.num_columns)
1104
+ @table.setMouseTracking(true)
1105
+ @table.connect(SIGNAL('cellEntered(int, int)')) {|row, col| mouse_over(row, col) }
1106
+ @table.connect(SIGNAL('itemChanged(QTableWidgetItem*)')) {|item| table_item_changed(item) }
1107
+ @table.setContextMenuPolicy(Qt::CustomContextMenu)
1108
+ @table.connect(SIGNAL('customContextMenuRequested(const QPoint&)')) {|point| context_menu(point) }
1109
+ rescue => err
1110
+ ExceptionDialog.new(self, err, "Create New Table Tab Errors", false)
1111
+ end
1112
+
1113
+ def mouse_over(row, col)
1114
+ return unless current_table_name
1115
+ table = @core.config.table(current_table_name)
1116
+ gui_table = @tabbook.tab(current_table_name)
1117
+ if table && gui_table
1118
+ if table.type == :TWO_DIMENSIONAL
1119
+ item_name = gui_table.horizontalHeaderItem(col).text + row.to_s
1120
+ item = table.get_item(item_name)
1121
+ statusBar.showMessage(item.description)
1122
+ else
1123
+ item_name = gui_table.verticalHeaderItem(row).text
1124
+ item = table.get_item(item_name)
1125
+ statusBar.showMessage(item.description)
1126
+ end
1127
+ end
1128
+ rescue
1129
+ statusBar.showMessage('')
1130
+ end
1131
+ end
1069
1132
  end # module Cosmos