package the.bytecode.club.bytecodeviewer.gui.hexviewer; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.ParseException; import java.util.Arrays; import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.JTextComponent; import org.exbin.bined.CodeAreaUtils; import org.exbin.bined.CodeCharactersCase; import org.exbin.bined.PositionCodeType; /** * Spinner supporting multiple bases. */ @ParametersAreNonnullByDefault public class BaseSwitchableSpinnerPanel extends javax.swing.JPanel { private boolean adjusting; private final PositionSpinnerEditor spinnerEditor; private static final String SPINNER_PROPERTY = "value"; public BaseSwitchableSpinnerPanel() { initComponents(); spinnerEditor = new PositionSpinnerEditor(spinner); spinner.setEditor(spinnerEditor); init(); } private void init() { // Spinner selection workaround from http://forums.sun.com/thread.jspa?threadID=409748&forumID=57 spinnerEditor.getTextField().addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { if (e.getSource() instanceof JTextComponent) { final JTextComponent textComponent = ((JTextComponent) e.getSource()); SwingUtilities.invokeLater(textComponent::selectAll); } } }); Dimension preferredSize = baseSwitchButton.getPreferredSize(); setPreferredSize(new Dimension(preferredSize.width * 4, preferredSize.height)); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ // //GEN-BEGIN:initComponents private void initComponents() { baseSwitchPopupMenu = new javax.swing.JPopupMenu(); octalMenuItem = new javax.swing.JMenuItem(); decimalMenuItem = new javax.swing.JMenuItem(); hexadecimalMenuItem = new javax.swing.JMenuItem(); baseSwitchButton = new javax.swing.JButton(); spinner = new javax.swing.JSpinner(); octalMenuItem.setText("OCT"); octalMenuItem.setToolTipText("Octal"); octalMenuItem.addActionListener(this::octalMenuItemActionPerformed); baseSwitchPopupMenu.add(octalMenuItem); decimalMenuItem.setText("DEC"); decimalMenuItem.setToolTipText("Decimal"); decimalMenuItem.addActionListener(this::decimalMenuItemActionPerformed); baseSwitchPopupMenu.add(decimalMenuItem); hexadecimalMenuItem.setText("HEX"); hexadecimalMenuItem.setToolTipText("Hexadecimal"); hexadecimalMenuItem.addActionListener(this::hexadecimalMenuItemActionPerformed); baseSwitchPopupMenu.add(hexadecimalMenuItem); setPreferredSize(new java.awt.Dimension(400, 300)); baseSwitchButton.setText("DEC"); baseSwitchButton.setToolTipText("Decimal"); baseSwitchButton.setComponentPopupMenu(baseSwitchPopupMenu); baseSwitchButton.addActionListener(this::baseSwitchButtonActionPerformed); spinner.setModel(new javax.swing.SpinnerNumberModel(0L, null, null, 1L)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(baseSwitchButton, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(spinner, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(spinner) .addComponent(baseSwitchButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); }// //GEN-END:initComponents private void baseSwitchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_baseSwitchButtonActionPerformed PositionCodeType positionCodeType = spinnerEditor.getPositionCodeType(); switch (positionCodeType) { case OCTAL: { switchNumBase(PositionCodeType.DECIMAL); break; } case DECIMAL: { switchNumBase(PositionCodeType.HEXADECIMAL); break; } case HEXADECIMAL: { switchNumBase(PositionCodeType.OCTAL); break; } default: throw CodeAreaUtils.getInvalidTypeException(positionCodeType); } }//GEN-LAST:event_baseSwitchButtonActionPerformed private void octalMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_octalMenuItemActionPerformed switchNumBase(PositionCodeType.OCTAL); }//GEN-LAST:event_octalMenuItemActionPerformed private void decimalMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_decimalMenuItemActionPerformed switchNumBase(PositionCodeType.DECIMAL); }//GEN-LAST:event_decimalMenuItemActionPerformed private void hexadecimalMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_hexadecimalMenuItemActionPerformed switchNumBase(PositionCodeType.HEXADECIMAL); }//GEN-LAST:event_hexadecimalMenuItemActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton baseSwitchButton; private javax.swing.JPopupMenu baseSwitchPopupMenu; private javax.swing.JMenuItem decimalMenuItem; private javax.swing.JMenuItem hexadecimalMenuItem; private javax.swing.JMenuItem octalMenuItem; private javax.swing.JSpinner spinner; // End of variables declaration//GEN-END:variables private void switchNumBase(PositionCodeType codeType) { adjusting = true; long value = getValue(); int position = codeType.ordinal(); baseSwitchButton.setText(codeType.name().substring(0, 3)); baseSwitchButton.setToolTipText(((JMenuItem) baseSwitchPopupMenu.getComponent(position)).getToolTipText()); spinnerEditor.setPositionCodeType(codeType); setValue(value); adjusting = false; } public long getValue() { return (Long) spinner.getValue(); } public void setValue(long value) { spinnerEditor.setPositionValue(value); } public void acceptInput() { try { spinner.commitEdit(); } catch (ParseException ex) { // Ignore parse exception } } public void initFocus() { /* ((JSpinner.DefaultEditor) positionSpinner.getEditor()) */ spinnerEditor.getTextField().requestFocusInWindow(); } public void setMinimum(long minimum) { ((SpinnerNumberModel) spinner.getModel()).setMinimum(minimum); } public void setMaximum(long maximum) { ((SpinnerNumberModel) spinner.getModel()).setMaximum(maximum); } public void revalidateSpinner() { spinner.revalidate(); } public void addChangeListener(ChangeListener changeListener) { spinner.addChangeListener(changeListener); } public void removeChangeListener(ChangeListener changeListener) { spinner.removeChangeListener(changeListener); } @ParametersAreNonnullByDefault private class PositionSpinnerEditor extends JPanel implements ChangeListener, PropertyChangeListener, LayoutManager { private static final int LENGTH_LIMIT = 21; private PositionCodeType positionCodeType = PositionCodeType.DECIMAL; private final char[] cache = new char[LENGTH_LIMIT]; private final JTextField textField; private final JSpinner spinner; public PositionSpinnerEditor(JSpinner spinner) { this.spinner = spinner; textField = new JTextField(); init(); } private void init() { textField.setName("Spinner.textField"); textField.setText(getPositionAsString((Long) spinner.getValue())); textField.addPropertyChangeListener(this); textField.getDocument().addDocumentListener(new DocumentListener() { private final PropertyChangeEvent changeEvent = new PropertyChangeEvent(textField, SPINNER_PROPERTY, null, null); @Override public void changedUpdate(DocumentEvent e) { notifyChanged(); } @Override public void removeUpdate(DocumentEvent e) { notifyChanged(); } @Override public void insertUpdate(DocumentEvent e) { notifyChanged(); } public void notifyChanged() { propertyChange(changeEvent); } }); textField.setEditable(true); textField.setInheritsPopupMenu(true); String toolTipText = spinner.getToolTipText(); if (toolTipText != null) { textField.setToolTipText(toolTipText); } add(textField); setLayout(this); spinner.addChangeListener(this); } @Nonnull private JTextField getTextField() { return textField; } @Nonnull private JSpinner getSpinner() { return spinner; } @Override public void stateChanged(ChangeEvent e) { if (adjusting) { return; } JSpinner sourceSpinner = (JSpinner) (e.getSource()); SwingUtilities.invokeLater(() -> textField.setText(getPositionAsString((Long) sourceSpinner.getValue()))); } @Override public void propertyChange(PropertyChangeEvent e) { if (adjusting) { return; } JSpinner sourceSpinner = getSpinner(); Object source = e.getSource(); String name = e.getPropertyName(); if ((source instanceof JTextField) && SPINNER_PROPERTY.equals(name)) { Long lastValue = (Long) sourceSpinner.getValue(); // Try to set the new value try { sourceSpinner.setValue(valueOfPosition(getTextField().getText())); } catch (IllegalArgumentException iae) { // SpinnerModel didn't like new value, reset try { sourceSpinner.setValue(lastValue); } catch (IllegalArgumentException iae2) { // Still bogus, nothing else we can do, the // SpinnerModel and JFormattedTextField are now out // of sync. } } } } public void setPositionValue(long positionValue) { textField.setText(getPositionAsString(positionValue)); spinner.setValue(positionValue); } @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } /** * Returns the size of the parents insets. */ @Nonnull private Dimension insetSize(Container parent) { Insets insets = parent.getInsets(); int width = insets.left + insets.right; int height = insets.top + insets.bottom; return new Dimension(width, height); } @Nonnull @Override public Dimension preferredLayoutSize(Container parent) { Dimension preferredSize = insetSize(parent); if (parent.getComponentCount() > 0) { Dimension childSize = getComponent(0).getPreferredSize(); preferredSize.width += childSize.width; preferredSize.height += childSize.height; } return preferredSize; } @Nonnull @Override public Dimension minimumLayoutSize(Container parent) { Dimension minimumSize = insetSize(parent); if (parent.getComponentCount() > 0) { Dimension childSize = getComponent(0).getMinimumSize(); minimumSize.width += childSize.width; minimumSize.height += childSize.height; } return minimumSize; } @Override public void layoutContainer(Container parent) { if (parent.getComponentCount() > 0) { Insets insets = parent.getInsets(); int width = parent.getWidth() - (insets.left + insets.right); int height = parent.getHeight() - (insets.top + insets.bottom); getComponent(0).setBounds(insets.left, insets.top, width, height); } } @Nonnull public PositionCodeType getPositionCodeType() { return positionCodeType; } public void setPositionCodeType(PositionCodeType positionCodeType) { this.positionCodeType = positionCodeType; } @Nonnull private String getPositionAsString(long position) { if (position < 0) { return "-" + getNonNegativePostionAsString(-position); } return getNonNegativePostionAsString(position); } @Nonnull private String getNonNegativePostionAsString(long position) { Arrays.fill(cache, ' '); CodeAreaUtils.longToBaseCode(cache, 0, position, positionCodeType.getBase(), LENGTH_LIMIT, false, CodeCharactersCase.LOWER); return new String(cache).trim(); } private long valueOfPosition(String position) { return Long.parseLong(position, positionCodeType.getBase()); } } }