Newer
Older
jrex / src / main / java / jrex / ui / MainFrame.java
package jrex.ui;

import jrex.ui.model.MutableTableModel;
import jrex.ui.model.PivotTableModel;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Table;
import com.google.gson.Gson;
import com.ibm.icu.text.CaseMap;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.text.DefaultEditorKit;
import jrex.Expressions;
import jrex.ui.model.SimpleListModel;

/**
 * Primary user interface frame.
 *
 * @author Mark George <mark.george@otago.ac.nz>
 */
public class MainFrame extends javax.swing.JFrame {

    private final Color defaultTextColor;
    private PivotTableModel original;

    public MainFrame() {
        initComponents();
        splitPane.setResizeWeight(0.66);
        spnColumns.setModel(new SpinnerNumberModel(0, 0, Integer.MAX_VALUE, 1));

        JPopupMenu rightClickMenu = new JPopupMenu();

        Action cut = new DefaultEditorKit.CutAction();
        cut.putValue(Action.NAME, "Cut");
        cut.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control X"));
        rightClickMenu.add(cut);

        Action copy = new DefaultEditorKit.CopyAction();
        copy.putValue(Action.NAME, "Copy");
        copy.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control C"));
        rightClickMenu.add(copy);

        Action paste = new DefaultEditorKit.PasteAction();
        paste.putValue(Action.NAME, "Paste");
        paste.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control V"));
        rightClickMenu.add(paste);

        Action tab = new DefaultEditorKit.InsertTabAction();
        tab.putValue(Action.NAME, "Insert Tab");
        rightClickMenu.add(tab);

        txtInput.setComponentPopupMenu(rightClickMenu);
        txtOutput.setComponentPopupMenu(rightClickMenu);
        txtRegex.setComponentPopupMenu(rightClickMenu);
        txtFormat.setComponentPopupMenu(rightClickMenu);
        txtBlanks.setComponentPopupMenu(rightClickMenu);

        defaultTextColor = txtRegex.getBackground();
        pnlReplace.setVisible(false);
        pnlReplace.setComponents(txtInput, txtOutput);

        Icon folderIcon = desaturateIcon(UIManager.getIcon("FileView.directoryIcon"));
        Icon floppyIcon = desaturateIcon(UIManager.getIcon("FileView.floppyDriveIcon"));
        Icon fileIcon = desaturateIcon(UIManager.getIcon("FileView.fileIcon"));

        btnSave.setIcon(floppyIcon);
        btnSaveExpr.setIcon(floppyIcon);
        btnLoadExpr.setIcon(folderIcon);
        btnLoad.setIcon(folderIcon);
        btnClear.setIcon(fileIcon);
    }

    private Icon desaturateIcon(Icon icon) {
        int w = icon.getIconWidth();
        int h = icon.getIconHeight();
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        GraphicsConfiguration gc = gd.getDefaultConfiguration();
        BufferedImage iconImage = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);
        Graphics2D g2d = iconImage.createGraphics();
        icon.paintIcon(null, g2d, 0, 0);
        ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
        op.filter(iconImage, iconImage);
        return new ImageIcon(iconImage);
    }

    /**
     * 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.
     */
    @SuppressWarnings("unchecked")
   // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
   private void initComponents() {

      bgQuotedIn = new javax.swing.ButtonGroup();
      bgCase = new javax.swing.ButtonGroup();
      bgQuoted = new javax.swing.ButtonGroup();
      splitPane = new javax.swing.JSplitPane();
      pnlInput = new javax.swing.JPanel();
      btnClear = new javax.swing.JButton();
      btnLoad = new javax.swing.JButton();
      scrollInput = new javax.swing.JScrollPane();
      txtInput = new javax.swing.JTextArea();
      lblExpression = new javax.swing.JLabel();
      txtRegex = new javax.swing.JTextField();
      spnColumns = new javax.swing.JSpinner();
      btnTsvInput = new javax.swing.JButton();
      btnCsvInput = new javax.swing.JButton();
      btnMatch = new javax.swing.JButton();
      scrollTable = new javax.swing.JScrollPane();
      jTable = new javax.swing.JTable();
      cbReplace = new javax.swing.JCheckBox();
      btnPivotWideToLong = new javax.swing.JButton();
      btnPivotLongToWide = new javax.swing.JButton();
      btnTranspose = new javax.swing.JButton();
      lblColumns = new javax.swing.JLabel();
      btnSaveExpr = new javax.swing.JButton();
      btnLoadExpr = new javax.swing.JButton();
      cbGuess = new javax.swing.JCheckBox();
      cbHasHeadings = new javax.swing.JCheckBox();
      pnlQuoted = new javax.swing.JPanel();
      jLabel1 = new javax.swing.JLabel();
      rbDouble = new javax.swing.JRadioButton();
      rbSingle = new javax.swing.JRadioButton();
      jLabel3 = new javax.swing.JLabel();
      lblCase = new javax.swing.JLabel();
      pnlCase = new javax.swing.JPanel();
      cbTrim = new javax.swing.JCheckBox();
      cbBlanks = new javax.swing.JCheckBox();
      txtBlanks = new javax.swing.JTextField();
      cbStripQuotes = new javax.swing.JCheckBox();
      rbStripDouble = new javax.swing.JRadioButton();
      rbStringSingle = new javax.swing.JRadioButton();
      pnlOutput = new javax.swing.JPanel();
      lblFormat = new javax.swing.JLabel();
      txtFormat = new javax.swing.JTextField();
      btnFormat = new javax.swing.JButton();
      scrollOutput = new javax.swing.JScrollPane();
      txtOutput = new javax.swing.JTextArea();
      lblArgumentMarker = new javax.swing.JLabel();
      btnSwap = new javax.swing.JButton();
      btnCsvOutput = new javax.swing.JButton();
      btnTsvOutput = new javax.swing.JButton();
      txtMarker = new javax.swing.JTextField();
      btnSave = new javax.swing.JButton();
      rbUnique = new javax.swing.JCheckBox();
      cbIncludeHeadings = new javax.swing.JCheckBox();
      btnSql = new javax.swing.JButton();
      cbQuoteHeadings = new javax.swing.JCheckBox();
      pnlReplace = new jrex.ui.ReplacePanel();

      setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

      splitPane.setBorder(null);
      splitPane.setDividerLocation(400);
      splitPane.setDividerSize(15);
      splitPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
      splitPane.setResizeWeight(0.66);
      splitPane.setName("splitPane"); // NOI18N
      splitPane.setOneTouchExpandable(true);

      pnlInput.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Input", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 0, 12))); // NOI18N
      pnlInput.setName("pnlInput"); // NOI18N

      btnClear.setText("Clear");
      btnClear.setName("btnClear"); // NOI18N
      btnClear.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnClearActionPerformed(evt);
         }
      });

      btnLoad.setText("Open");
      btnLoad.setName("btnLoad"); // NOI18N
      btnLoad.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnLoadActionPerformed(evt);
         }
      });

      scrollInput.setName("scrollInput"); // NOI18N

      txtInput.setColumns(20);
      txtInput.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
      txtInput.setRows(5);
      txtInput.setName("txtInput"); // NOI18N
      scrollInput.setViewportView(txtInput);

      lblExpression.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
      lblExpression.setText("Capture Expression: ");
      lblExpression.setName("lblExpression"); // NOI18N

      txtRegex.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
      txtRegex.setName("txtRegex"); // NOI18N
      txtRegex.setPreferredSize(new java.awt.Dimension(14, 25));
      txtRegex.addKeyListener(new java.awt.event.KeyAdapter() {
         public void keyPressed(java.awt.event.KeyEvent evt) {
            txtRegexKeyPressed(evt);
         }
      });

      spnColumns.setName("spnColumns"); // NOI18N
      spnColumns.addMouseWheelListener(new java.awt.event.MouseWheelListener() {
         public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
            spnColumnsMouseWheelMoved(evt);
         }
      });

      btnTsvInput.setText("TSV");
      btnTsvInput.setName("btnTsvInput"); // NOI18N
      btnTsvInput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnTsvInputActionPerformed(evt);
         }
      });

      btnCsvInput.setText("CSV");
      btnCsvInput.setName("btnCsvInput"); // NOI18N
      btnCsvInput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnCsvInputActionPerformed(evt);
         }
      });

      btnMatch.setMnemonic('c');
      btnMatch.setText("Capture");
      btnMatch.setName("btnMatch"); // NOI18N
      btnMatch.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnMatchActionPerformed(evt);
         }
      });

      scrollTable.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
      scrollTable.setName("scrollTable"); // NOI18N

      jTable.setName("jTable"); // NOI18N
      scrollTable.setViewportView(jTable);

      cbReplace.setText("Search/Replace ");
      cbReplace.setName("cbReplace"); // NOI18N
      cbReplace.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            cbReplaceActionPerformed(evt);
         }
      });

      btnPivotWideToLong.setText("Pivot Wide to Long");
      btnPivotWideToLong.setName("btnPivotWideToLong"); // NOI18N
      btnPivotWideToLong.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnPivotWideToLongActionPerformed(evt);
         }
      });

      btnPivotLongToWide.setText("Pivot Long to Wide");
      btnPivotLongToWide.setName("btnPivotLongToWide"); // NOI18N
      btnPivotLongToWide.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnPivotLongToWideActionPerformed(evt);
         }
      });

      btnTranspose.setText("Transpose");
      btnTranspose.setName("btnTranspose"); // NOI18N
      btnTranspose.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnTransposeActionPerformed(evt);
         }
      });

      lblColumns.setText("Columns: ");
      lblColumns.setName("lblColumns"); // NOI18N

      btnSaveExpr.setText("Save Expressions");
      btnSaveExpr.setName("btnSaveExpr"); // NOI18N
      btnSaveExpr.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSaveExprActionPerformed(evt);
         }
      });

      btnLoadExpr.setText("Load Expressions");
      btnLoadExpr.setName("btnLoadExpr"); // NOI18N
      btnLoadExpr.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnLoadExprActionPerformed(evt);
         }
      });

      cbGuess.setSelected(true);
      cbGuess.setText("Guess?");
      cbGuess.setName("cbGuess"); // NOI18N

      cbHasHeadings.setSelected(true);
      cbHasHeadings.setText("Has Column Headings");
      cbHasHeadings.setName("cbHasHeadings"); // NOI18N
      cbHasHeadings.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            cbHasHeadingsActionPerformed(evt);
         }
      });

      pnlQuoted.setFocusable(false);
      pnlQuoted.setName("pnlQuoted"); // NOI18N
      pnlQuoted.setLayout(new java.awt.GridLayout(1, 1));

      jLabel1.setText("Quotes:");
      jLabel1.setName("jLabel1"); // NOI18N

      bgQuoted.add(rbDouble);
      rbDouble.setText("\"");
      rbDouble.setName("rbDouble"); // NOI18N

      bgQuoted.add(rbSingle);
      rbSingle.setSelected(true);
      rbSingle.setText("'");
      rbSingle.setName("rbSingle"); // NOI18N

      jLabel3.setText("Quoted:");
      jLabel3.setName("jLabel3"); // NOI18N

      lblCase.setText("Case:");
      lblCase.setName("lblCase"); // NOI18N

      pnlCase.setName("pnlCase"); // NOI18N
      pnlCase.setLayout(new java.awt.GridLayout(1, 1));

      cbTrim.setSelected(true);
      cbTrim.setText("Trim?");
      cbTrim.setName("cbTrim"); // NOI18N

      cbBlanks.setText("Replace blanks? ");
      cbBlanks.setName("cbBlanks"); // NOI18N

      txtBlanks.setText("null");
      txtBlanks.setName("txtBlanks"); // NOI18N

      cbStripQuotes.setText("Strip Quotes?");
      cbStripQuotes.setName("cbStripQuotes"); // NOI18N

      bgQuotedIn.add(rbStripDouble);
      rbStripDouble.setText("\"");
      rbStripDouble.setName("rbStripDouble"); // NOI18N

      bgQuotedIn.add(rbStringSingle);
      rbStringSingle.setSelected(true);
      rbStringSingle.setText("'");
      rbStringSingle.setName("rbStringSingle"); // NOI18N

      javax.swing.GroupLayout pnlInputLayout = new javax.swing.GroupLayout(pnlInput);
      pnlInput.setLayout(pnlInputLayout);
      pnlInputLayout.setHorizontalGroup(
         pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addComponent(scrollInput)
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addComponent(btnLoad)
            .addGap(18, 18, 18)
            .addComponent(cbReplace)
            .addGap(18, 18, 18)
            .addComponent(btnSaveExpr)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnLoadExpr)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(btnClear))
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
               .addGroup(pnlInputLayout.createSequentialGroup()
                  .addComponent(btnPivotWideToLong)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(btnPivotLongToWide)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(btnTranspose))
               .addGroup(pnlInputLayout.createSequentialGroup()
                  .addComponent(jLabel1)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(rbSingle)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(rbDouble)
                  .addGap(18, 18, 18)
                  .addComponent(cbTrim)
                  .addGap(18, 18, 18)
                  .addComponent(cbBlanks)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(txtBlanks, javax.swing.GroupLayout.PREFERRED_SIZE, 62, javax.swing.GroupLayout.PREFERRED_SIZE))
               .addGroup(pnlInputLayout.createSequentialGroup()
                  .addComponent(lblColumns)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(spnColumns, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(cbGuess)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(cbHasHeadings)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(cbStripQuotes)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(rbStringSingle)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(rbStripDouble)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(btnCsvInput)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(btnTsvInput)))
            .addContainerGap(92, Short.MAX_VALUE))
         .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnlInputLayout.createSequentialGroup()
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
               .addComponent(jLabel3)
               .addComponent(lblCase))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
               .addComponent(pnlQuoted, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(pnlCase, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(scrollTable, javax.swing.GroupLayout.Alignment.TRAILING)))
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addComponent(lblExpression)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(txtRegex, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnMatch))
      );

      pnlInputLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btnPivotLongToWide, btnPivotWideToLong, btnTranspose});

      pnlInputLayout.setVerticalGroup(
         pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(pnlInputLayout.createSequentialGroup()
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
               .addComponent(btnLoad, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(btnClear, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(cbReplace)
               .addComponent(btnSaveExpr)
               .addComponent(btnLoadExpr))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollInput, javax.swing.GroupLayout.DEFAULT_SIZE, 34, Short.MAX_VALUE)
            .addGap(11, 11, 11)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblColumns)
               .addComponent(spnColumns, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnCsvInput)
               .addComponent(btnTsvInput)
               .addComponent(cbGuess)
               .addComponent(cbHasHeadings)
               .addComponent(cbStripQuotes)
               .addComponent(rbStripDouble)
               .addComponent(rbStringSingle))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblExpression)
               .addComponent(txtRegex, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnMatch))
            .addGap(6, 6, 6)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
               .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                  .addComponent(jLabel1)
                  .addComponent(rbDouble)
                  .addComponent(rbSingle)
                  .addComponent(cbTrim))
               .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
                  .addComponent(cbBlanks)
                  .addComponent(txtBlanks, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
               .addComponent(pnlQuoted, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
               .addComponent(pnlCase, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(lblCase, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollTable, javax.swing.GroupLayout.DEFAULT_SIZE, 79, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlInputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
               .addComponent(btnPivotWideToLong)
               .addComponent(btnPivotLongToWide)
               .addComponent(btnTranspose)))
      );

      splitPane.setTopComponent(pnlInput);

      pnlOutput.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Output", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, new java.awt.Font("Dialog", 0, 12))); // NOI18N
      pnlOutput.setName("pnlOutput"); // NOI18N

      lblFormat.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
      lblFormat.setText("Format Expression: ");
      lblFormat.setName("lblFormat"); // NOI18N

      txtFormat.setName("txtFormat"); // NOI18N

      btnFormat.setMnemonic('f');
      btnFormat.setText("Format");
      btnFormat.setName("btnFormat"); // NOI18N
      btnFormat.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnFormatActionPerformed(evt);
         }
      });

      scrollOutput.setName("scrollOutput"); // NOI18N

      txtOutput.setColumns(20);
      txtOutput.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N
      txtOutput.setRows(5);
      txtOutput.setName("txtOutput"); // NOI18N
      scrollOutput.setViewportView(txtOutput);

      lblArgumentMarker.setText("Column Prefix: ");
      lblArgumentMarker.setName("lblArgumentMarker"); // NOI18N

      btnSwap.setMnemonic('o');
      btnSwap.setText("Output to Input");
      btnSwap.setName("btnSwap"); // NOI18N
      btnSwap.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSwapActionPerformed(evt);
         }
      });

      btnCsvOutput.setText("CSV");
      btnCsvOutput.setName("btnCsvOutput"); // NOI18N
      btnCsvOutput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnCsvOutputActionPerformed(evt);
         }
      });

      btnTsvOutput.setText("TSV");
      btnTsvOutput.setName("btnTsvOutput"); // NOI18N
      btnTsvOutput.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnTsvOutputActionPerformed(evt);
         }
      });

      txtMarker.setText("?");
      txtMarker.setName("txtMarker"); // NOI18N

      btnSave.setText("Save");
      btnSave.setName("btnSave"); // NOI18N
      btnSave.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSaveActionPerformed(evt);
         }
      });

      rbUnique.setText("Make Unique?");
      rbUnique.setName("rbUnique"); // NOI18N

      cbIncludeHeadings.setSelected(true);
      cbIncludeHeadings.setText("Include Column Headings?");
      cbIncludeHeadings.setName("cbIncludeHeadings"); // NOI18N

      btnSql.setText("SQL");
      btnSql.setName("btnSql"); // NOI18N
      btnSql.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnSqlActionPerformed(evt);
         }
      });

      cbQuoteHeadings.setText("Quote Headings?");
      cbQuoteHeadings.setName("cbQuoteHeadings"); // NOI18N

      javax.swing.GroupLayout pnlOutputLayout = new javax.swing.GroupLayout(pnlOutput);
      pnlOutput.setLayout(pnlOutputLayout);
      pnlOutputLayout.setHorizontalGroup(
         pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addComponent(scrollOutput)
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addComponent(lblFormat)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(txtFormat)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnFormat))
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addComponent(lblArgumentMarker)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(txtMarker, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addGap(18, 18, 18)
            .addComponent(btnCsvOutput)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnTsvOutput, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(btnSql)
            .addGap(18, 18, 18)
            .addComponent(cbIncludeHeadings)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(cbQuoteHeadings)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 39, Short.MAX_VALUE)
            .addComponent(btnSave))
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addComponent(rbUnique)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(btnSwap))
      );

      pnlOutputLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {btnCsvOutput, btnTsvOutput});

      pnlOutputLayout.setVerticalGroup(
         pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(pnlOutputLayout.createSequentialGroup()
            .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblArgumentMarker)
               .addComponent(txtMarker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnCsvOutput)
               .addComponent(btnTsvOutput)
               .addComponent(btnSave)
               .addComponent(cbIncludeHeadings)
               .addComponent(btnSql)
               .addComponent(cbQuoteHeadings))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
               .addComponent(btnSwap)
               .addComponent(rbUnique))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addGroup(pnlOutputLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER)
               .addComponent(lblFormat)
               .addComponent(txtFormat, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
               .addComponent(btnFormat))
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(scrollOutput, javax.swing.GroupLayout.DEFAULT_SIZE, 138, Short.MAX_VALUE))
      );

      splitPane.setRightComponent(pnlOutput);

      pnlReplace.setName("pnlReplace"); // NOI18N

      javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
      getContentPane().setLayout(layout);
      layout.setHorizontalGroup(
         layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(splitPane)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
            .addComponent(pnlReplace, javax.swing.GroupLayout.DEFAULT_SIZE, 286, Short.MAX_VALUE))
      );
      layout.setVerticalGroup(
         layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
         .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
               .addComponent(pnlReplace, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
               .addComponent(splitPane))
            .addContainerGap())
      );

      pack();
   }// </editor-fold>//GEN-END:initComponents

    private void match() {
        jTable.setRowSorter(null);

        txtOutput.setText("");
        String regex = txtRegex.getText();
        String text = txtInput.getText();

        String[] input = null;

        try {

            Pattern pattern = Pattern.compile(regex);

            int flagsMask = pattern.flags();

            StringBuilder flags = new StringBuilder();

            boolean dotall = false;

            // work out which flags are being used
            if ((flagsMask & Pattern.DOTALL) == Pattern.DOTALL) {
                flags.append("DOTALL ");
                dotall = true;
            }

            if ((flagsMask & Pattern.MULTILINE) == Pattern.MULTILINE) {
                flags.append("MULTILINE ");
            }

            if ((flagsMask & Pattern.CASE_INSENSITIVE) == Pattern.CASE_INSENSITIVE) {
                flags.append("CASE_INSENSITIVE ");
            }

            PivotTableModel model = new PivotTableModel();
            jTable.setModel(model);

            int numMatches = 0;

            // it DOTALL mode is on then grab the entire text rather then seperating by line
            if (dotall) {
                input = new String[1];
                input[0] = text;
            } else {
                input = text.split("\n");
            }

            for (String line : input) {
                Matcher matcher = pattern.matcher(line);
                while (matcher.find()) {

                    // group() matches the entire line if there was a match.
                    // This if-statment is to suppress extra empty groups from being
                    // produced if the regex matches the entire line
                    if (matcher.group().length() != line.length()) {
                        continue;
                    }

                    numMatches++;

                    txtOutput.append(matcher.group(0) + "\n");

                    String[] groups = new String[matcher.groupCount()];
                    for (int j = 1; j <= matcher.groupCount(); j++) {
                        String s = matcher.group(j);

                        if (cbTrim.isSelected()) {
                            s = s.trim();
                        }

                        if (cbBlanks.isSelected() && s.isEmpty()) {
                            s = txtBlanks.getText();
                        }

                        groups[j - 1] = s;
                    }

                 
                    
                    if (cbHasHeadings.isSelected() && numMatches == 1) {
                        for (String group : groups) {
                            if(group.contains("?") ||
                                group.contains("$") ||
                                group.contains("*") ||
                                group.contains("|") ||
                                group.contains("+") ||
                                group.contains("{") ||
                                group.contains("}") ||
                                group.contains("(") ||
                                group.contains(")") ||
                                group.contains("^") ||
                                group.contains("[") ||
                                group.contains("]")) {
                                JOptionPane.showMessageDialog(this, "The following column heading contains metacharacters.  Remove the metacharacters, or disable column headings.\n\n"+group+"\n\nThe following metacharacters are not able to be used in column headings:\n\n* | + { } ( ) ^ [ ] \\", "Incompatible Heading", JOptionPane.WARNING_MESSAGE);
                                return;
                            }
                        }
                        model.setColumnHeadings(groups);
                    } else {
                        model.addRow(groups);
                    }
                }
            }

            model.fireTableStructureChanged();
            TableRowSorter<PivotTableModel> sorter = new TableRowSorter<>();
            jTable.setRowSorter(sorter);
            sorter.setModel(model);

            this.original = new PivotTableModel();

            for (Table.Cell<Integer, Integer, String> cell : model.getTable().cellSet()) {
                this.original.getTable().put(cell.getRowKey(), cell.getColumnKey(), cell.getValue());
            }

            txtOutput.append("\nMatched " + numMatches + " line(s) out of "
                + input.length + " line(s) of input.");

            if (!flags.toString().isEmpty()) {
                txtOutput.append("\n\nFlags: " + flags.toString().trim());
            }

        } catch (PatternSyntaxException ex) {
            txtOutput.setText("");
            txtOutput.append(ex.getMessage());
            txtRegex.setBackground(new Color(1f, 0.3f, 0.3f));
        }

        int colCount = jTable.getColumnCount();

        pnlQuoted.removeAll();
        pnlCase.removeAll();

        for (int i = 0; i < colCount; i++) {
            JCheckBox cbQuote = new JCheckBox();
            cbQuote.setHorizontalAlignment(SwingConstants.CENTER);
            cbQuote.setName(String.valueOf(i));

            cbQuote.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    quoteCheckBoxClicked(evt);
                }
            });

            pnlQuoted.add(cbQuote);

            JComboBox cmbCase = new JComboBox();
            cmbCase.setName(String.valueOf(i));
            SimpleListModel mdlCombo = new SimpleListModel();
            mdlCombo.addElement("Unchanged");
            mdlCombo.addElement("Lower Case");
            mdlCombo.addElement("Upper Case");
            mdlCombo.addElement("Sentence Case");
            mdlCombo.addElement("Title Case");
            mdlCombo.addElement("Surname Case");

            cmbCase.setModel(mdlCombo);
            cmbCase.setSelectedIndex(0);

            cmbCase.addActionListener(new java.awt.event.ActionListener() {
                @Override
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    caseComboClicked(evt);
                }
            });

            pnlCase.add(cmbCase);

        }

        pack();
    }

    private void caseComboClicked(ActionEvent evt) {
        JComboBox cmbCase = (JComboBox) evt.getSource();
        String selectedCase = (String) cmbCase.getSelectedItem();

        int columnIdx = Integer.valueOf(cmbCase.getName());
        PivotTableModel model = (PivotTableModel) jTable.getModel();
        Map<Integer, String> column = model.getTable().column(columnIdx);

        column.keySet().forEach(key -> {
            String value = column.get(key);

            switch (selectedCase) {
                case "Upper Case":
                    value = CaseMap.toUpper().apply(Locale.getDefault(), value);
                    break;
                case "Lower Case":
                    value = CaseMap.toLower().apply(Locale.getDefault(), value);
                    break;
                case "Sentence Case":
                    value = CaseMap.Lower.toTitle().sentences().apply(Locale.getDefault(), null, value);
                    break;
                case "Title Case":
                    value = CaseMap.toTitle().apply(Locale.getDefault(), null, value);
                    break;
                case "Surname Case":
                    value = surnameCase(value);
                    break;
                case "Unchanged":
                    value = original.getTable().get(key, columnIdx);
                    break;
            }

            column.put(key, value);

        });

        model.fireTableStructureChanged();

    }

    private void quoteCheckBoxClicked(ActionEvent evt) {
        JCheckBox cb = (JCheckBox) evt.getSource();

        int columnIdx = Integer.valueOf(cb.getName());
        PivotTableModel model = (PivotTableModel) jTable.getModel();
        Map<Integer, String> column = model.getTable().column(columnIdx);
        column.keySet().forEach(key -> {
            String value = column.get(key);

            String quote = rbDouble.isSelected() ? "\"" : "'";
            if (cb.isSelected() && !value.equals(txtBlanks.getText())) {
                value = quote + value + quote;
                column.put(key, value);
            } else {
                value = value.replaceAll(quote + "(.*)" + quote, "$1");
                column.put(key, value);
            }
        });

        model.fireTableStructureChanged();
    }

    private void format() {
        if (jTable.getColumnCount() == 0) {
            return;
        }

        txtOutput.setText("");

        if (cbIncludeHeadings.isSelected()) {
            PivotTableModel model = (PivotTableModel) jTable.getModel();
            List<String> headings = model.getHeadings();
            if (cbQuoteHeadings.isSelected()) {
                String quote = rbSingle.isSelected() ? "'" : "\"";
                List<String> quoted = new ArrayList<>(headings.size());
                headings.forEach(heading -> quoted.add(quote + heading + quote));
                txtOutput.setText(formatRow(quoted) + "\n");
            } else {
                txtOutput.setText(formatRow(headings) + "\n");
            }
        }

        for (int row = 0; row < jTable.getRowCount(); row++) {

            List<String> rowData = new ArrayList<>(jTable.getColumnCount());

            for (int col = 0; col < jTable.getColumnCount(); col++) {
                rowData.add((String) jTable.getValueAt(row, col));
            }

            String formatted = formatRow(rowData);
            txtOutput.append(formatted + "\n");
        }

        if (rbUnique.isSelected()) {

            String output = txtOutput.getText();
            txtOutput.setText("");

            Stream.of(output.split("\n"))
                .distinct()
                .forEach(line -> txtOutput.append(line + "\n"));
        }

    }

    private String surnameCase(String string) {
        // See:  https://stackoverflow.com/a/15738441

        final String DELIMITERS = " -'";

        StringBuilder sb = new StringBuilder();
        boolean capNext = true;

        for (char c : string.toCharArray()) {
            c = (capNext)
                ? Character.toTitleCase(c)
                : Character.toLowerCase(c);
            sb.append(c);
            capNext = (DELIMITERS.indexOf((int) c) >= 0); // explicit cast not needed
        }
        return sb.toString();

    }

    private String formatRow(List<String> rowData) {
        String[] values = new String[rowData.size()];

        String marker = txtMarker.getText();

        String formatter = this.txtFormat.getText();

        // escape meta-charachters
        switch (marker) {
            case "?":
            case "$":
            case "*":
            case "|":
            case "+":
            case "{":
            case "}":
            case "(":
            case ")":
            case "^":
            case "[":
            case "]":
            case "\\":
                marker = "\\" + marker;
        }

        PivotTableModel model = (PivotTableModel) jTable.getModel();
        List<String> headings = model.getHeadings();
        for (String heading : headings) {
            formatter = formatter.replaceAll(marker + heading, marker + model.getHeadingIndex(heading));
        }

        formatter = formatter.replaceAll(marker + "(\\d*)", "%$1\\$s");

        for (int col = 0; col < rowData.size(); col++) {
            values[col] = rowData.get(col);
        }

        System.out.println(formatter);

        return String.format(formatter, (Object[]) values);
    }

    private void transpose() {
        PivotTableModel model = (PivotTableModel) jTable.getModel();
        model.transpose();
    }

    private void pivotWideToLong() {
        if (jTable.getColumnCount() == 0) {
            return;
        }

        PivotTableModel model = (PivotTableModel) jTable.getModel();
        ListMultimap<String, String> columns = new WideToLongPivotDialog(this).mapColumns(model);
        if (columns != null) {
            model.pivotWideToLong(columns.get("preserved"), columns.get("values"));
        }
    }

    private void pivotLongToWide() throws NonUniqueKeyException {
        PivotTableModel model = (PivotTableModel) jTable.getModel();
        ListMultimap<String, String> columns = new LongToWidePivotDialog(this).mapColumns(model);
        if (columns != null) {
            model.pivotLongToWide(columns.get("repeating"), columns.get("header").get(0), columns.get("value").get(0));
        }
    }

    private void csv() {
        Integer cols = (Integer) spnColumns.getValue();
        Boolean quoted = cbStripQuotes.isSelected();
        Boolean isSingleQuotes = rbStringSingle.isSelected();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < cols - 1; i++) {
            if (quoted && isSingleQuotes) {
                sb.append("'(.*?)',");
            } else if (quoted && !isSingleQuotes) {
                sb.append("\"(.*?)\",");
            } else {
                sb.append("(.*?),");
            }
        }
        if (quoted && isSingleQuotes) {
            sb.append("'(.*)'");
        } else if (quoted && !isSingleQuotes) {
            sb.append("\"(.*)\"");
        } else {
            sb.append("(.*)");
        }
        txtRegex.setText(sb.toString());
    }

    private void tsv() {
        Integer cols = (Integer) spnColumns.getValue();
        Boolean quoted = cbStripQuotes.isSelected();
        Boolean isSingleQuotes = rbStringSingle.isSelected();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < cols - 1; i++) {
            if (quoted && isSingleQuotes) {
                sb.append("'(.*?)'\\t");
            } else if (quoted && !isSingleQuotes) {
                sb.append("\"(.*?)\"\\t");
            } else {
                sb.append("(.*?)\\t");
            }
        }
        if (quoted && isSingleQuotes) {
            sb.append("'(.*)'");
        } else if (quoted && !isSingleQuotes) {
            sb.append("\"(.*)\"");
        } else {
            sb.append("(.*)");
        }
        txtRegex.setText(sb.toString());
    }

    private void load() {
        JFileChooser jfc = new JFileChooser();
        if (jfc.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
            try {
                String data = readFile(jfc.getSelectedFile());

                txtInput.setText(data);

            } catch (Exception ex) {
                JOptionPane.showMessageDialog(this, "Error", ex.getMessage(), JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    private void save() {
        JFileChooser jfc = new JFileChooser();
        int result = jfc.showSaveDialog(this);
        if (result == JFileChooser.APPROVE_OPTION) {
            File f = jfc.getSelectedFile();
            if (f.exists()) {
                int confirm = JOptionPane.showConfirmDialog(this, "Overwrite existing file?");
                if (confirm == JOptionPane.YES_OPTION) {
                    try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
                        writer.write(txtOutput.getText());
                    } catch (Exception e) {
                        JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
                    }
                }
            } else {
                try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
                    writer.write(txtOutput.getText());
                } catch (Exception e) {
                    JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
                }
            }
        }
    }

    public String readFile(File file) throws Exception {
        StringBuilder fileContents;
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            @SuppressWarnings("UnusedAssignment")
            String line = null;
            fileContents = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                fileContents.append(line);
                fileContents.append(System.getProperty("line.separator"));
            }
        }
        return fileContents.toString();
    }

   private void btnFormatActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFormatActionPerformed
       format();
   }//GEN-LAST:event_btnFormatActionPerformed

   private void btnSwapActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSwapActionPerformed
       txtInput.setText(txtOutput.getText());
   }//GEN-LAST:event_btnSwapActionPerformed

   private void btnCsvOutputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCsvOutputActionPerformed
       if (jTable.getRowCount() > 0) {
           cbIncludeHeadings.setSelected(true);
           TableModel model = jTable.getModel();
           String marker = txtMarker.getText();

           Integer cols = (Integer) jTable.getColumnCount();

           StringBuilder sb = new StringBuilder();
           for (int i = 0; i < cols; i++) {
               sb.append(marker).append(model.getColumnName(i)).append(",");
           }

           String format = sb.toString();

           txtFormat.setText(format.substring(0, format.length() - 1));

           format();
       }
   }//GEN-LAST:event_btnCsvOutputActionPerformed

   private void btnTsvOutputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTsvOutputActionPerformed
       if (jTable.getRowCount() > 0) {
           cbIncludeHeadings.setSelected(true);
           TableModel model = jTable.getModel();
           String marker = txtMarker.getText();

           Integer cols = (Integer) jTable.getColumnCount();

           StringBuilder sb = new StringBuilder();
           for (int i = 0; i < cols; i++) {
               sb.append(marker).append(model.getColumnName(i)).append("\t");
           }

           String format = sb.toString();

           txtFormat.setText(format.substring(0, format.length() - 1));

           format();
       }
   }//GEN-LAST:event_btnTsvOutputActionPerformed

   private void btnSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveActionPerformed
       save();
   }//GEN-LAST:event_btnSaveActionPerformed

   private void btnSqlActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSqlActionPerformed
       if (jTable.getColumnCount() == 0) {
           return;
       }

       cbIncludeHeadings.setSelected(false);
       SqlDialog sqlDialog = new SqlDialog(this, true, (PivotTableModel) jTable.getModel());
       sqlDialog.setVisible(true);
       txtFormat.setText(sqlDialog.getSql());
       format();
   }//GEN-LAST:event_btnSqlActionPerformed

   private void cbHasHeadingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbHasHeadingsActionPerformed
       // TODO add your handling code here:
   }//GEN-LAST:event_cbHasHeadingsActionPerformed

   private void btnLoadExprActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLoadExprActionPerformed
       String json = null;

       JFileChooser jfc = new JFileChooser();
       if (jfc.showOpenDialog(MainFrame.this) == JFileChooser.APPROVE_OPTION) {
           try {
               json = readFile(jfc.getSelectedFile());
           } catch (Exception ex) {
               JOptionPane.showMessageDialog(this, "Error", ex.getMessage(), JOptionPane.ERROR_MESSAGE);
           }

           Expressions exp = new Gson().fromJson(json, Expressions.class);
           txtBlanks.setText(exp.getBlanks());
           cbBlanks.setSelected(exp.getBlanksEnabled());
           txtRegex.setText(exp.getCapture());
           txtFormat.setText(exp.getFormat());
           txtMarker.setText(exp.getPrefix());

           List<List<String>> replacements = exp.getReplacements();
           MutableTableModel model = pnlReplace.getModel();
           model.getTable().clear();
           for (List<String> replacement : replacements) {
               model.addRow(replacement);
           }
       }

   }//GEN-LAST:event_btnLoadExprActionPerformed

   private void btnSaveExprActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveExprActionPerformed

       Expressions exp = new Expressions();
       exp.setBlanks(txtBlanks.getText());
       exp.setBlanksEnabled(cbBlanks.isSelected());
       exp.setCapture(txtRegex.getText());
       exp.setFormat(txtFormat.getText());
       exp.setPrefix(txtMarker.getText());
       exp.setReplacements(pnlReplace.getModel().getRows());
       String json = new Gson().toJson(exp);

       JFileChooser jfc = new JFileChooser();
       int result = jfc.showSaveDialog(this);
       if (result == JFileChooser.APPROVE_OPTION) {
           File f = jfc.getSelectedFile();
           if (f.exists()) {
               int confirm = JOptionPane.showConfirmDialog(this, "Overwrite existing file?");
               if (confirm == JOptionPane.YES_OPTION) {
                   try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
                       writer.write(json);
                   } catch (Exception e) {
                       JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
                   }
               }
           } else {
               try (BufferedWriter writer = new BufferedWriter(new FileWriter(f))) {
                   writer.write(json);
               } catch (Exception e) {
                   JOptionPane.showMessageDialog(this, "Error", e.getMessage(), JOptionPane.ERROR_MESSAGE);
               }
           }
       }
   }//GEN-LAST:event_btnSaveExprActionPerformed

   private void btnTransposeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTransposeActionPerformed
       if (jTable.getColumnCount() == 0) {
           return;
       }

       if (cbHasHeadings.isSelected()) {
           JOptionPane.showMessageDialog(this, "Turn off column headings and recapture before transposing.");
           return;
       }
       transpose();
   }//GEN-LAST:event_btnTransposeActionPerformed

   private void btnPivotLongToWideActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPivotLongToWideActionPerformed
       if (jTable.getColumnCount() == 0) {
           return;
       }

       try {
           pivotLongToWide();
       } catch (NonUniqueKeyException e) {
           JOptionPane.showMessageDialog(this, e.getMessage(), "Duplicate Data Error", JOptionPane.ERROR_MESSAGE);
       }
   }//GEN-LAST:event_btnPivotLongToWideActionPerformed

   private void btnPivotWideToLongActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPivotWideToLongActionPerformed
       pivotWideToLong();
   }//GEN-LAST:event_btnPivotWideToLongActionPerformed

   private void cbReplaceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbReplaceActionPerformed
       pnlReplace.setVisible(cbReplace.isSelected());
   }//GEN-LAST:event_cbReplaceActionPerformed

   private void btnMatchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnMatchActionPerformed
       if (txtInput.getText().isEmpty()) {
           return;
       }

       match();
   }//GEN-LAST:event_btnMatchActionPerformed

   private void btnCsvInputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCsvInputActionPerformed
       if (txtInput.getText().isEmpty()) {
           return;
       }

       if (cbGuess.isSelected()) {
           String line = txtInput.getText().split("\n")[0];
           String commas = line.replaceAll("[^,]", "");
           spnColumns.setValue(commas.length() + 1);
       }
       csv();
       match();
   }//GEN-LAST:event_btnCsvInputActionPerformed

   private void btnTsvInputActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTsvInputActionPerformed
       if (txtInput.getText().isEmpty()) {
           return;
       }

       if (cbGuess.isSelected()) {
           String line = txtInput.getText().split("\n")[0];
           String commas = line.replaceAll("[^\t]", "");
           spnColumns.setValue(commas.length() + 1);
       }
       tsv();
       match();
   }//GEN-LAST:event_btnTsvInputActionPerformed

   private void spnColumnsMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_spnColumnsMouseWheelMoved
       Integer value = (Integer) spnColumns.getValue();
       value -= evt.getUnitsToScroll();
       spnColumns.setValue(value > 0 ? value : 0);
   }//GEN-LAST:event_spnColumnsMouseWheelMoved

   private void txtRegexKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtRegexKeyPressed
       // restore default color
       txtRegex.setBackground(defaultTextColor);
   }//GEN-LAST:event_txtRegexKeyPressed

   private void btnLoadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLoadActionPerformed
       load();
   }//GEN-LAST:event_btnLoadActionPerformed

   private void btnClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnClearActionPerformed
       txtInput.setText("");
   }//GEN-LAST:event_btnClearActionPerformed

    public String getPrefix() {
        return txtMarker.getText();
    }


   // Variables declaration - do not modify//GEN-BEGIN:variables
   private javax.swing.ButtonGroup bgCase;
   private javax.swing.ButtonGroup bgQuoted;
   private javax.swing.ButtonGroup bgQuotedIn;
   private javax.swing.JButton btnClear;
   private javax.swing.JButton btnCsvInput;
   private javax.swing.JButton btnCsvOutput;
   private javax.swing.JButton btnFormat;
   private javax.swing.JButton btnLoad;
   private javax.swing.JButton btnLoadExpr;
   private javax.swing.JButton btnMatch;
   private javax.swing.JButton btnPivotLongToWide;
   private javax.swing.JButton btnPivotWideToLong;
   private javax.swing.JButton btnSave;
   private javax.swing.JButton btnSaveExpr;
   private javax.swing.JButton btnSql;
   private javax.swing.JButton btnSwap;
   private javax.swing.JButton btnTranspose;
   private javax.swing.JButton btnTsvInput;
   private javax.swing.JButton btnTsvOutput;
   private javax.swing.JCheckBox cbBlanks;
   private javax.swing.JCheckBox cbGuess;
   private javax.swing.JCheckBox cbHasHeadings;
   private javax.swing.JCheckBox cbIncludeHeadings;
   private javax.swing.JCheckBox cbQuoteHeadings;
   private javax.swing.JCheckBox cbReplace;
   private javax.swing.JCheckBox cbStripQuotes;
   private javax.swing.JCheckBox cbTrim;
   private javax.swing.JLabel jLabel1;
   private javax.swing.JLabel jLabel3;
   private javax.swing.JTable jTable;
   private javax.swing.JLabel lblArgumentMarker;
   private javax.swing.JLabel lblCase;
   private javax.swing.JLabel lblColumns;
   private javax.swing.JLabel lblExpression;
   private javax.swing.JLabel lblFormat;
   private javax.swing.JPanel pnlCase;
   private javax.swing.JPanel pnlInput;
   private javax.swing.JPanel pnlOutput;
   private javax.swing.JPanel pnlQuoted;
   private jrex.ui.ReplacePanel pnlReplace;
   private javax.swing.JRadioButton rbDouble;
   private javax.swing.JRadioButton rbSingle;
   private javax.swing.JRadioButton rbStringSingle;
   private javax.swing.JRadioButton rbStripDouble;
   private javax.swing.JCheckBox rbUnique;
   private javax.swing.JScrollPane scrollInput;
   private javax.swing.JScrollPane scrollOutput;
   private javax.swing.JScrollPane scrollTable;
   private javax.swing.JSplitPane splitPane;
   private javax.swing.JSpinner spnColumns;
   private javax.swing.JTextField txtBlanks;
   private javax.swing.JTextField txtFormat;
   private javax.swing.JTextArea txtInput;
   private javax.swing.JTextField txtMarker;
   private javax.swing.JTextArea txtOutput;
   private javax.swing.JTextField txtRegex;
   // End of variables declaration//GEN-END:variables

}