/*
 * TextKeyMatcherView.java
 */

package textkeymatcher.ui;

import org.apache.commons.lang3.StringUtils;
import org.jdesktop.application.Task;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.TaskMonitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Timer;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import textkeymatcher.TextKeyMatcherApp;
import textkeymatcher.service.LineDataBuilder;
import textkeymatcher.service.LineDataBuilderFactory;
import textkeymatcher.service.LineDataCollector;
import textkeymatcher.service.LineDataLoader;
import textkeymatcher.ui.model.ImportDataDialogModel;

/**
 * The application's main frame.
 */
public class TextKeyMatcherView extends FrameView {
    
    /**
     * ロガー
     */
    private static final Logger logger = Logger.getLogger(TextKeyMatcherView.class.getName());

    
    public TextKeyMatcherView(SingleFrameApplication app) {
        super(app);

        initComponents();
        afterInitComponents();

        // status bar initialization - message timeout, idle icon and busy animation, etc
        ResourceMap resourceMap = getResourceMap();
        int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
        messageTimer = new Timer(messageTimeout, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                statusMessageLabel.setText("");
            }
        });
        messageTimer.setRepeats(false);
        int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
        for (int i = 0; i < busyIcons.length; i++) {
            busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
        }
        busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
                statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
            }
        });
        idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
        statusAnimationLabel.setIcon(idleIcon);
        progressBar.setVisible(false);

        // connecting action tasks to status bar via TaskMonitor
        TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
        taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
            @Override
            public void propertyChange(java.beans.PropertyChangeEvent evt) {
                String propertyName = evt.getPropertyName();
                if ("started".equals(propertyName)) {
                    if (!busyIconTimer.isRunning()) {
                        statusAnimationLabel.setIcon(busyIcons[0]);
                        busyIconIndex = 0;
                        busyIconTimer.start();
                    }
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(true);
                } else if ("done".equals(propertyName)) {
                    busyIconTimer.stop();
                    statusAnimationLabel.setIcon(idleIcon);
                    progressBar.setVisible(false);
                    progressBar.setValue(0);
                } else if ("message".equals(propertyName)) {
                    String text = (String)(evt.getNewValue());
                    statusMessageLabel.setText((text == null) ? "" : text);
                    messageTimer.restart();
                } else if ("progress".equals(propertyName)) {
                    int value = (Integer)(evt.getNewValue());
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(false);
                    progressBar.setValue(value);
                }
            }
        });
    }

    private void afterInitComponents() {
        // 終了アクション
        javax.swing.ActionMap actionMap = org.jdesktop.application.Application
                .getInstance(textkeymatcher.TextKeyMatcherApp.class)
                .getContext().getActionMap(TextKeyMatcherView.class, this);
        
        javax.swing.Action actQuit = actionMap.get("quit"); // NOI18N
        
        // ウィンドウを閉じるキー(CMD-W)
        if (System.getProperty("os.name").toLowerCase().indexOf("mac os x") >= 0) {
            JRootPane root = getRootPane();
            InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK), actQuit);

            root.getActionMap().put(actQuit, actQuit);
        }
    }

    @Action
    public void showAboutBox() {
        if (aboutBox == null) {
            JFrame mainFrame = TextKeyMatcherApp.getApplication().getMainFrame();
            aboutBox = new TextKeyMatcherAboutBox(mainFrame);
            aboutBox.setLocationRelativeTo(mainFrame);
        }
        TextKeyMatcherApp.getApplication().show(aboutBox);
    }

    /** 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() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        mainPanel = new javax.swing.JPanel();
        dataViewTableSP = new javax.swing.JScrollPane();
        dateViewTable = new javax.swing.JTable();
        menuBar = new javax.swing.JMenuBar();
        javax.swing.JMenu fileMenu = new javax.swing.JMenu();
        newMenuItem = new javax.swing.JMenuItem();
        openMenuItem = new javax.swing.JMenuItem();
        saveMenuItem = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
        dataMenu = new javax.swing.JMenu();
        loadDataMenuItem = new javax.swing.JMenuItem();
        jSeparator2 = new javax.swing.JPopupMenu.Separator();
        removeMenuItem = new javax.swing.JMenuItem();
        mergeMenuItem = new javax.swing.JMenuItem();
        keyMenu = new javax.swing.JMenu();
        textNormalRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        textIgnoreCaseRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        numberRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        viewMenu = new javax.swing.JMenu();
        allRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        matchedRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        unmatchedRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        existsLeftRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        missingLeftRadioButtonMenuItem = new javax.swing.JRadioButtonMenuItem();
        javax.swing.JMenu helpMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
        statusPanel = new javax.swing.JPanel();
        javax.swing.JSeparator statusPanelSeparator = new javax.swing.JSeparator();
        statusMessageLabel = new javax.swing.JLabel();
        statusAnimationLabel = new javax.swing.JLabel();
        progressBar = new javax.swing.JProgressBar();
        viewButtonGroup = new javax.swing.ButtonGroup();
        keyButtonGroup = new javax.swing.ButtonGroup();
        dataViewTableModel = new textkeymatcher.ui.model.DataViewTableModel();
        keyMatcherBinder = new textkeymatcher.ui.model.KeyMatcherBinder();

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

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

        dateViewTable.setModel(dataViewTableModel);
        dateViewTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(textkeymatcher.TextKeyMatcherApp.class).getContext().getResourceMap(TextKeyMatcherView.class);
        dateViewTable.setGridColor(resourceMap.getColor("dateViewTable.gridColor")); // NOI18N
        dateViewTable.setName("dateViewTable"); // NOI18N
        dateViewTable.setShowGrid(true);
        dataViewTableSP.setViewportView(dateViewTable);

        javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel);
        mainPanel.setLayout(mainPanelLayout);
        mainPanelLayout.setHorizontalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(dataViewTableSP, javax.swing.GroupLayout.DEFAULT_SIZE, 487, Short.MAX_VALUE)
        );
        mainPanelLayout.setVerticalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(dataViewTableSP, javax.swing.GroupLayout.DEFAULT_SIZE, 404, Short.MAX_VALUE)
        );

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

        fileMenu.setMnemonic('F');
        fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
        fileMenu.setName("fileMenu"); // NOI18N

        newMenuItem.setMnemonic('N');
        newMenuItem.setName("newMenuItem"); // NOI18N
        newMenuItem.setText(resourceMap.getString("newMenuItem.text")); // NOI18N
        fileMenu.add(newMenuItem);

        openMenuItem.setName("openMenuItem"); // NOI18N
        openMenuItem.setText(resourceMap.getString("openMenuItem.text")); // NOI18N
        fileMenu.add(openMenuItem);

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(textkeymatcher.TextKeyMatcherApp.class).getContext().getActionMap(TextKeyMatcherView.class, this);
        saveMenuItem.setAction(actionMap.get("onSave")); // NOI18N
        saveMenuItem.setText(resourceMap.getString("saveMenuItem.text")); // NOI18N
        saveMenuItem.setName("saveMenuItem"); // NOI18N
        fileMenu.add(saveMenuItem);

        jSeparator1.setName("jSeparator1"); // NOI18N
        fileMenu.add(jSeparator1);

        exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
        exitMenuItem.setName("exitMenuItem"); // NOI18N
        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        dataMenu.setMnemonic('D');
        dataMenu.setText(resourceMap.getString("dataMenu.text")); // NOI18N
        dataMenu.setName("dataMenu"); // NOI18N

        loadDataMenuItem.setAction(actionMap.get("onImportData")); // NOI18N
        loadDataMenuItem.setText(resourceMap.getString("loadDataMenuItem.text")); // NOI18N
        loadDataMenuItem.setName("loadDataMenuItem"); // NOI18N
        dataMenu.add(loadDataMenuItem);

        jSeparator2.setName("jSeparator2"); // NOI18N
        dataMenu.add(jSeparator2);

        removeMenuItem.setName("removeMenuItem"); // NOI18N
        removeMenuItem.setText(resourceMap.getString("removeMenuItem.text")); // NOI18N
        dataMenu.add(removeMenuItem);

        mergeMenuItem.setName("mergeMenuItem"); // NOI18N
        mergeMenuItem.setText(resourceMap.getString("mergeMenuItem.text")); // NOI18N
        dataMenu.add(mergeMenuItem);

        menuBar.add(dataMenu);

        keyMenu.setMnemonic('K');
        keyMenu.setText(resourceMap.getString("keyMenu.text")); // NOI18N
        keyMenu.setName("keyMenu"); // NOI18N

        keyButtonGroup.add(textNormalRadioButtonMenuItem);
        textNormalRadioButtonMenuItem.setText(resourceMap.getString("textNormalRadioButtonMenuItem.text")); // NOI18N
        textNormalRadioButtonMenuItem.setName("textNormalRadioButtonMenuItem"); // NOI18N

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, keyMatcherBinder, org.jdesktop.beansbinding.ELProperty.create("${text}"), textNormalRadioButtonMenuItem, org.jdesktop.beansbinding.BeanProperty.create("selected"));
        bindingGroup.addBinding(binding);

        keyMenu.add(textNormalRadioButtonMenuItem);

        keyButtonGroup.add(textIgnoreCaseRadioButtonMenuItem);
        textIgnoreCaseRadioButtonMenuItem.setText(resourceMap.getString("textIgnoreCaseRadioButtonMenuItem.text")); // NOI18N
        textIgnoreCaseRadioButtonMenuItem.setName("textIgnoreCaseRadioButtonMenuItem"); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, keyMatcherBinder, org.jdesktop.beansbinding.ELProperty.create("${textIgnoreCase}"), textIgnoreCaseRadioButtonMenuItem, org.jdesktop.beansbinding.BeanProperty.create("selected"));
        bindingGroup.addBinding(binding);

        keyMenu.add(textIgnoreCaseRadioButtonMenuItem);

        keyButtonGroup.add(numberRadioButtonMenuItem);
        numberRadioButtonMenuItem.setText(resourceMap.getString("numberRadioButtonMenuItem.text")); // NOI18N
        numberRadioButtonMenuItem.setName("numberRadioButtonMenuItem"); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, keyMatcherBinder, org.jdesktop.beansbinding.ELProperty.create("${numeric}"), numberRadioButtonMenuItem, org.jdesktop.beansbinding.BeanProperty.create("selected"));
        bindingGroup.addBinding(binding);

        keyMenu.add(numberRadioButtonMenuItem);

        menuBar.add(keyMenu);

        viewMenu.setMnemonic('V');
        viewMenu.setText(resourceMap.getString("viewMenu.text")); // NOI18N
        viewMenu.setName("viewMenu"); // NOI18N

        viewButtonGroup.add(allRadioButtonMenuItem);
        allRadioButtonMenuItem.setSelected(true);
        allRadioButtonMenuItem.setText(resourceMap.getString("allRadioButtonMenuItem.text")); // NOI18N
        allRadioButtonMenuItem.setName("allRadioButtonMenuItem"); // NOI18N
        viewMenu.add(allRadioButtonMenuItem);

        viewButtonGroup.add(matchedRadioButtonMenuItem);
        matchedRadioButtonMenuItem.setText(resourceMap.getString("matchedRadioButtonMenuItem.text")); // NOI18N
        matchedRadioButtonMenuItem.setName("matchedRadioButtonMenuItem"); // NOI18N
        viewMenu.add(matchedRadioButtonMenuItem);

        viewButtonGroup.add(unmatchedRadioButtonMenuItem);
        unmatchedRadioButtonMenuItem.setText(resourceMap.getString("unmatchedRadioButtonMenuItem.text")); // NOI18N
        unmatchedRadioButtonMenuItem.setName("unmatchedRadioButtonMenuItem"); // NOI18N
        viewMenu.add(unmatchedRadioButtonMenuItem);

        viewButtonGroup.add(existsLeftRadioButtonMenuItem);
        existsLeftRadioButtonMenuItem.setText(resourceMap.getString("existsLeftRadioButtonMenuItem.text")); // NOI18N
        existsLeftRadioButtonMenuItem.setName("existsLeftRadioButtonMenuItem"); // NOI18N
        viewMenu.add(existsLeftRadioButtonMenuItem);

        viewButtonGroup.add(missingLeftRadioButtonMenuItem);
        missingLeftRadioButtonMenuItem.setText(resourceMap.getString("missingLeftRadioButtonMenuItem.text")); // NOI18N
        missingLeftRadioButtonMenuItem.setName("missingLeftRadioButtonMenuItem"); // NOI18N
        viewMenu.add(missingLeftRadioButtonMenuItem);

        menuBar.add(viewMenu);

        helpMenu.setMnemonic('H');
        helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
        helpMenu.setName("helpMenu"); // NOI18N

        aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
        aboutMenuItem.setName("aboutMenuItem"); // NOI18N
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        statusPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 32));
        statusPanel.setName("statusPanel"); // NOI18N

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

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

        statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N

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

        javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel);
        statusPanel.setLayout(statusPanelLayout);
        statusPanelLayout.setHorizontalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(statusPanelSeparator, javax.swing.GroupLayout.DEFAULT_SIZE, 454, Short.MAX_VALUE)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(statusMessageLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 258, Short.MAX_VALUE)
                .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(statusAnimationLabel)
                .addContainerGap())
        );
        statusPanelLayout.setVerticalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addComponent(statusPanelSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(statusMessageLabel)
                    .addComponent(statusAnimationLabel)
                    .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(3, 3, 3))
        );

        keyMatcherBinder.bindDataViewTableModel(dataViewTableModel);

        setComponent(mainPanel);
        setMenuBar(menuBar);
        setStatusBar(statusPanel);

        bindingGroup.bind();
    }// </editor-fold>//GEN-END:initComponents

    private ImportDataDialogModel importDataDialogModel;

    /**
     * データのロードアクション.
     * @return 
     */
    @Action(block = Task.BlockingScope.WINDOW)
    public Task onImportData() {
        // データインポートダイアログの構築
        ImportDataDialog importDataDialog = new ImportDataDialog(getFrame(), true);
        importDataDialog.setLocationRelativeTo(importDataDialog.getParent());
        
        // 前回のデータを適用する.(あれば)
        if (importDataDialogModel != null) {
            importDataDialog.setModel(importDataDialogModel);
        }

        // 表示する.
        importDataDialog.setVisible(true);
        ImportDataDialogModel dlgModel = importDataDialog.getResultModel();
        if (dlgModel == null) {
            // キャンセルされた場合
            return null;
        }
        
        // 保存する.
        this.importDataDialogModel = dlgModel;
        
        return new OnImportDataTask(getApplication(), dlgModel);
    }

    /**
     * データロードの非同期ブロッキングアクション
     */
    private class OnImportDataTask extends org.jdesktop.application.Task<LineDataCollector, Void> {

        /**
         * インポートダイアログ
         */
        private ImportDataDialogModel dlgModel;
        
        /**
         * 補完されたタイトル.
         */
        private String title;

        
        OnImportDataTask(org.jdesktop.application.Application app, ImportDataDialogModel dlgModel) {
            // Runs on the EDT.  Copy GUI state that
            // doInBackground() depends on from parameters
            // to OnImportDataTask fields, here.
            super(app);
            this.dlgModel = dlgModel;

            // タイトルが空であれば適当につける
            String titleOrg = dlgModel.getTitle();
            if (StringUtils.isBlank(titleOrg)) {
                title = "(" + dataViewTableModel.getColumnCount() + ")";
            } else {
                title = titleOrg.trim();
            }
        }
        

        @Override protected LineDataCollector doInBackground() throws Exception {
            LineDataBuilderFactory factory = new LineDataBuilderFactory();
            LineDataBuilder builder = factory.createLineDataBuilder(dlgModel);
            
            LineDataCollector collector = new LineDataCollector(title);
            LineDataLoader loader = new LineDataLoader(builder, collector);
            
            if (dlgModel.isDirect()) {
                loader.load(dlgModel.getDirectText());

            } else {
                File sourceFile = new File(dlgModel.getSourceFile());
                loader.load(sourceFile, dlgModel.getCharset());
            }
            
            return collector;
        }
        
        /**
         * 失敗した場合
         * @param ex 
         */
        @Override protected void failed(Throwable ex) {
            ErrorDialog.showErrorDialog(ex);
        }
        
        /**
         * 成功した場合
         * @param collector 
         */
        @Override protected void succeeded(LineDataCollector collector) {
            dataViewTableModel.addLineDataList(collector);
            checkEnableSave();
            JOptionPane.showMessageDialog(getFrame(), "done.");
        }
    }

    private File lastUseDirectory;
    
    /**
     * 保存アクション
     */
    @Action(enabledProperty = "enableSave")
    public void onSave() {
        JFileChooser fileChooser = new JFileChooser(lastUseDirectory);
        int ret = fileChooser.showSaveDialog(getFrame());
        if (ret != JFileChooser.APPROVE_OPTION) {
            return;
        }
        
        File selFile = fileChooser.getSelectedFile();
        try {
            dataViewTableModel.save(selFile);

        } catch (Exception ex) {
            logger.log(Level.SEVERE, "saveで例外が発生しました。" + selFile, ex);
            ErrorDialog.showErrorDialog(ex);
        }
    }


    private void checkEnableSave() {
        boolean hasDataSource = dataViewTableModel.getDataListCount() > 0;
        setEnableSave(hasDataSource);
    }
    
    private boolean enableSave = false;
    public boolean isEnableSave() {
        return enableSave;
    }

    public void setEnableSave(boolean b) {
        boolean old = isEnableSave();
        this.enableSave = b;
        firePropertyChange("enableSave", old, isEnableSave());
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JRadioButtonMenuItem allRadioButtonMenuItem;
    private javax.swing.JMenu dataMenu;
    private textkeymatcher.ui.model.DataViewTableModel dataViewTableModel;
    private javax.swing.JScrollPane dataViewTableSP;
    private javax.swing.JTable dateViewTable;
    private javax.swing.JRadioButtonMenuItem existsLeftRadioButtonMenuItem;
    private javax.swing.JPopupMenu.Separator jSeparator1;
    private javax.swing.JPopupMenu.Separator jSeparator2;
    private javax.swing.ButtonGroup keyButtonGroup;
    private textkeymatcher.ui.model.KeyMatcherBinder keyMatcherBinder;
    private javax.swing.JMenu keyMenu;
    private javax.swing.JMenuItem loadDataMenuItem;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JRadioButtonMenuItem matchedRadioButtonMenuItem;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JMenuItem mergeMenuItem;
    private javax.swing.JRadioButtonMenuItem missingLeftRadioButtonMenuItem;
    private javax.swing.JMenuItem newMenuItem;
    private javax.swing.JRadioButtonMenuItem numberRadioButtonMenuItem;
    private javax.swing.JMenuItem openMenuItem;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JMenuItem removeMenuItem;
    private javax.swing.JMenuItem saveMenuItem;
    private javax.swing.JLabel statusAnimationLabel;
    private javax.swing.JLabel statusMessageLabel;
    private javax.swing.JPanel statusPanel;
    private javax.swing.JRadioButtonMenuItem textIgnoreCaseRadioButtonMenuItem;
    private javax.swing.JRadioButtonMenuItem textNormalRadioButtonMenuItem;
    private javax.swing.JRadioButtonMenuItem unmatchedRadioButtonMenuItem;
    private javax.swing.ButtonGroup viewButtonGroup;
    private javax.swing.JMenu viewMenu;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    private final Timer messageTimer;
    private final Timer busyIconTimer;
    private final Icon idleIcon;
    private final Icon[] busyIcons = new Icon[15];
    private int busyIconIndex = 0;

    private JDialog aboutBox;
}
