@Override protected DataModel<LineChartDataSet> createChartModel(final User user, final String modelTitle) { final DataModel<LineChartDataSet> model = new DataModel<>(modelTitle, user.getPlayerColor().brighterColor, user); final Stat stat = statComboBox.getSelectedItem(); int dsCount = 0; boolean gasOnly = false; if (stat.hasMinGas) { if (separateMinsGasCheckBox.isSelected()) { if (minsCheckBox.isSelected()) dsCount++; else gasOnly = true; if (gasCheckBox.isSelected()) dsCount++; } else dsCount = minsCheckBox.isSelected() || gasCheckBox.isSelected() ? 1 : 0; } else dsCount = stat.eventFields.length; for (int i = 0; i < dsCount; i++) { // Stroke will be set during reconfiguration final LineChartDataSet ds = new LineChartDataSet( i > 0 || gasOnly ? user.getPlayerColor().brighterColor : user.getPlayerColor().color, null, new int[0], new int[0]); model.addDataSet(ds); } return model; }
@Override public List<Chart<LineChartDataSet>> createCharts() { chartList = new ArrayList<>(); final TrackerEvents trackerEvents = repProc.replay.trackerEvents; if (trackerEvents == null) { chartsComp .getChartsCanvas() .setMessage( "This chart is available only from replay version 2.0.8. This replay has version " + repProc.replay.header.versionString(false) + "."); return chartList; } createModelByPlayerIds(); switch (statComboBox.getSelectedItem()) { case SPENDING_QUOTIENT: // Spending Quotient is different than the others: it is not stored directly in the tracker // events stream calculateSQDataSets(); break; case FOOD_MADE_USED: // Food Made+Used is showing 2 player stats at once: Food Made and Food Used calculateFoodMadeUsedDataSets(); break; default: calculateDataSets(); break; } return chartList; }
/** * Creates a new {@link PlayerStatsChartFactory}. * * @param chartsComp reference to the charts component that created us */ public PlayerStatsChartFactory(final ChartsComp chartsComp) { super(chartsComp); statComboBox.addActionListener(chartsComp.chartsRebuilder); statComboBox.addActionListener( new ActionAdapter(true) { @Override public void actionPerformed(final ActionEvent event) { final boolean hasMinGas = statComboBox.getSelectedItem().hasMinGas; minsCheckBox.setVisible(hasMinGas); gasCheckBox.setVisible(hasMinGas); separateMinsGasCheckBox.setVisible(hasMinGas); } }); statComboBox.markSeparatedItems( Stat.FOOD_MADE_USED, Stat.ARMY_RES_IN_PROGRESS, Stat.ECON_RES_IN_PROGRESS); statComboBox.setMaximumRowCount(statComboBox.getItemCount()); presentationComboBox.addActionListener(chartsComp.chartsReconfigurer); minsCheckBox.setText("Minerals"); minsCheckBox.setToolTipText(Settings.PLAYER_STATS_INCLUDE_MINS.name); minsCheckBox.addActionListener(chartsComp.chartsRebuilder); gasCheckBox.setText("Gas"); gasCheckBox.setToolTipText(Settings.PLAYER_STATS_INCLUDE_GAS.name); gasCheckBox.addActionListener(chartsComp.chartsRebuilder); separateMinsGasCheckBox.setText("Separate Mins/Gas"); separateMinsGasCheckBox.setToolTipText(Settings.PLAYER_STATS_SEPARATE_MINS_GAS.name); separateMinsGasCheckBox.addActionListener(chartsComp.chartsRebuilder); }
@Override public void reconfigureChartCanvas() { final boolean gasOnly = statComboBox.getSelectedItem().hasMinGas && separateMinsGasCheckBox.isSelected() && !minsCheckBox.isSelected(); final Presentation p = presentationComboBox.getSelectedItem(); for (final Chart<?> chart : chartsComp.getChartsCanvas().getChartList()) { if (chart instanceof LineTimeChart) { final LineTimeChart lc = (LineTimeChart) chart; lc.setPresentation(p); // Stroke depends on the presentation: for (final DataModel<LineChartDataSet> model : lc.getDataModelList()) { int i = 0; for (final LineChartDataSet dataSet : model.getDataSetList()) dataSet.setStroke(i++ > 0 || gasOnly ? p.storke : p.storkeDouble); } } } }
/** Calculates the data sets. */ private void calculateDataSets() { calculateDataSets(statComboBox.getSelectedItem()); }
@Override protected void buildGui() { XToolBar toolBar = new XToolBar(); toolBarsBox.add(toolBar); toolBar.add(new XLabel("Data source:").verticalBorder(7)); dataSourceComboBox.addActionListener(rebuilderListener); dataSourceComboBox.setMaximumRowCount(dataSourceComboBox.getItemCount()); toolBar.add(dataSourceComboBox); toolBar.addSeparator(); LGuiUtils.autoCreateDisabledImage(toolBar.add(saveAction)); toolBar.addSeparator(); toolBar.add(new XLabel("Line size:")); toolBar.add(hexLineSizeComboBox); toolBar.add(new XLabel(Settings.HEX_LINE_SIZE.viewHints.getSubsequentText())); toolBar.addSeparator(); toolBar.add(new XLabel("Jump to pos:")); final XTextField jumpTextField = new XTextField(null, 6, true); jumpTextField.setValidator( new IValidator() { @Override public boolean validate(final String text) { final boolean result = validateLogic(text); if (!result) Sound.beepOnEmptyTxtSearchRslt(); return result; } private boolean validateLogic(final String text) { if (text.isEmpty() || "0x".equals(text)) return true; try { final int pos = text.startsWith("0x") ? Integer.parseInt(text.substring(2), 16) : Integer.parseInt(text); if (pos < 0 || pos >= data.length) return false; final int line = pos >> hexLineSizeComboBox.getSelectedItem().shiftCount; textList.setSelectedIndex(line); textList.scrollRectToVisible(textList.getCellBounds(line, line)); return true; } catch (final NumberFormatException nfe) { return false; } } }); jumpTextField.addActionListener( new ActionAdapter() { @Override public void actionPerformed(final ActionEvent event) { final String text = jumpTextField.getText(); jumpTextField.setText(null); jumpTextField.setText(text); } }); jumpTextField.setToolTipText( "<html>Enter a byte position to jump to, either decimal or hexa starting with <code>\"0x\"</code>.</html>"); // Focus Jump field when pressing CTRL+J jumpTextField.registerFocusHotkey( this, KeyStroke.getKeyStroke(KeyEvent.VK_J, InputEvent.CTRL_MASK)); toolBar.add(jumpTextField); toolBar.addSeparator(); toolBar.add(dataSizeLabel); toolBar.finalizeLayout(); super.buildGui(); // Extend the original tool bar toolBar = this.toolBar; // Move the info component (currently the last) to the end final Component infoComp = toolBar.getComponent(toolBar.getComponentCount() - 1); toolBar.remove(toolBar.getComponentCount() - 1); // Insert tip to the text search, before the separator: toolBar.add(new TipIcon(Tips.BINARY_DATA_TEXT_SEARCH), toolBar.getComponentCount() - 1); // Add Hex searcher hexSearchComp.textField.setValidator( new IValidator() { @Override public boolean validate(String text) { text = text.replace(" ", ""); // Remove spaces // Check: length must be even, must contain only hex digits return (text.length() & 0x01) == 0 && text.matches("[\\da-f]*"); } }); hexSearchComp.textField.setToolTipText( "<html>Search hex data, for example <code>\"06 1f\"</html>"); // Register CTRL+F for hex search (CTRL+S is taken by the other search component) // (CTRL+H is already used by JTextField!) hexSearchComp.registerFocusHotkey( this, KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK)); hexSearchComp.setSearcher(hexSearcher); toolBar.add(hexSearchComp); toolBar.addSeparator(); toolBar.add(infoComp); toolBar.finalizeLayout(); }
/** * Creates a new {@link BinaryDataComp}. * * @param repProc replay processor */ public BinaryDataComp(final RepProcessor repProc) { super(repProc); hexLineSizeComboBox = SettingsGui.createSettingComboBox( Settings.HEX_LINE_SIZE, Env.APP_SETTINGS, rebuilderListener); final Vector<DataSource> sourceVector = new Vector<>(); final List<DataSource> separatedDsList = new ArrayList<>(); // The replay file sourceVector.add(new FullFileDataSource("Replay File", repProc.file)); separatedDsList.add(sourceVector.get(sourceVector.size() - 1)); // General Replay MPQ content sourceVector.add(new MpqUserDataDataSource(repProc.file)); for (final MpqContent mpqContent : MpqContent.VALUES) sourceVector.add(new MpqContentDataSource(repProc.file, mpqContent)); separatedDsList.add(sourceVector.get(sourceVector.size() - 1)); // Replay content for (final RepContent mpqContent : RepContent.VALUES) { sourceVector.add(new MpqContentDataSource(repProc.file, mpqContent)); if (mpqContent == RepContent.TRACKER_EVENTS) separatedDsList.add(sourceVector.get(sourceVector.size() - 1)); } separatedDsList.add(sourceVector.get(sourceVector.size() - 1)); // General Map MPQ content final Path mapFile = MapParser.getMapFile(repProc); sourceVector.add(new MpqUserDataDataSource(mapFile)); for (final MpqContent mpqContent : MpqContent.VALUES) sourceVector.add(new MpqContentDataSource(mapFile, mpqContent)); separatedDsList.add(sourceVector.get(sourceVector.size() - 1)); // Map content for (final MapContent mpqContent : MapContent.VALUES) sourceVector.add(new MpqContentDataSource(mapFile, mpqContent)); dataSourceComboBox = new XComboBox<>(sourceVector); dataSourceComboBox.markSeparatedItems(separatedDsList); searcher = new ByteIntPosBaseSearcher() { /** Bytes of the search text, as <code>int</code>s. */ private int[] searchTextBytes; @Override protected void prepareNew() { // Note: Texts found in binary data are usually UTF-8 encoded. // This text search converts the searched text to bytes using the same // encoding (UTF-8), and this byte sequence will be searched. // For more details see the Tips.BINARY_DATA_TEXT_SEARCH tip. final byte[] bytes = searchText.getBytes(Env.UTF8); searchTextBytes = new int[bytes.length]; maxOffset = searchTextBytes.length - 1; for (int i = maxOffset; i >= 0; i--) searchTextBytes[i] = bytes[i] & 0xff; super.prepareNew(); } @Override public boolean matches() { // Local vars for fast access final byte[] data = BinaryDataComp.this.data; final int[] searchTextBytes = this.searchTextBytes; final int searchPos = this.searchPos; if (searchPos + maxOffset > maxPos) return false; // Would overflow in data, surely cannot match for (int offset = maxOffset; offset >= 0; offset--) { final int searchChar = searchTextBytes[offset]; final int ch = data[searchPos + offset] & 0xff; if (ch != searchChar) { // Also check in a case insensitive manner (lower-cased version of ch): if (ch >= 'A' && ch <= 'Z') { if (ch - ('A' - 'a') != searchChar) return false; } else return false; } } // Match! handleMatch(); // Clear the other searcher (so it will start from this line) hexSearcher.clearLastSearchPos(); return true; } }; hexSearcher = new ByteIntPosBaseSearcher() { /** Bytes specified by the hex search text. */ private byte[] searchTextBytes; @Override protected void prepareNew() { searchTextBytes = Utils.hexToBytes(searchText); if (searchTextBytes == null) searchTextBytes = new byte[0]; maxOffset = searchTextBytes.length - 1; super.prepareNew(); } @Override public boolean matches() { if (searchTextBytes.length == 0) return true; // To end the search // Local vars for fast access final byte[] data = BinaryDataComp.this.data; final byte[] searchTextBytes = this.searchTextBytes; final int searchPos = this.searchPos; if (searchPos + maxOffset > maxPos) return false; // Would overflow in data, surely cannot match for (int offset = maxOffset; offset >= 0; offset--) if (searchTextBytes[offset] != data[searchPos + offset]) return false; // Match! handleMatch(); // Clear the other searcher (so it will start from this line) searcher.clearLastSearchPos(); return true; } }; buildGui(); }