ca.dsrg.mirador.ui.MatchPanel.java Source code

Java tutorial

Introduction

Here is the source code for ca.dsrg.mirador.ui.MatchPanel.java

Source

/* --------------------------------------------------------------------------+
   MatchPanel.java - High-level description of module and place in system.
   DOCDO: Finish file description and details.
    
   Created by: Stephen Barrett
           Concordia University
           Montreal, Quebec
           ste_barr@encs.concorida.ca
    
   Licensed Material - Dependable Software Research Group
   --------------------------------------------------------------------------+
   Design rational, and module details that need highlighting.
   --------------------------------------------------------------------------*/
package ca.dsrg.mirador.ui;

import ca.dsrg.mirador.Debug;
import ca.dsrg.mirador.InvocationParser;
import ca.dsrg.mirador.Mirador;
import ca.dsrg.mirador.change.ChangeRecord.MergeSide;
import ca.dsrg.mirador.match.EclEvaluator;
import ca.dsrg.mirador.match.ElementMatcher;
import ca.dsrg.mirador.match.MatchStrategy;
import ca.dsrg.mirador.match.MeasureMatrix;
import ca.dsrg.mirador.match.SimilarityEvaluator;
import ca.dsrg.mirador.merge.MergeWorks;
import ca.dsrg.mirador.model.EcoreExtra;
import ca.dsrg.mirador.model.ModelRepository;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.factories.DefaultComponentFactory;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.ToolTipManager;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**                                                                       DOCDO: Provide class overview.
 *
 * @since   v0.5 - Jan 30, 2010
 * @author  Stephen Barrett
 */
public class MatchPanel extends WizardPanel {
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors
    /**
     *                                                                      DOCDO: Provide constructor overview.
     *
     * @param  wizard  Purpose of the argument.
     */
    public MatchPanel(MergeWizard wizard) {
        super(wizard, PANEL_INFO, HELP_FILE);
        initialize();
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ accessors
    /**
     * Gives left detail table GUI component.
     *
     * @return  Left detail table component.
     * @category  getter
     */
    public JTable getTableLeft() {
        return view_lf_tbl_;
    }

    /**
     * Gives right detail table GUI component.
     *
     * @return  Right detail table component.
     * @category  getter
     */
    public JTable getTableRight() {
        return view_rt_tbl_;
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ collections
    public void addStrategy(MatchStrategy to_add) {
        if (to_add != null && strategies_.size() < MAX_STRATEGIES)
            strategies_.add(to_add);
    }

    public void removeStrategy(MatchStrategy to_remove) {
        if (to_remove != null)
            strategies_.remove(to_remove);
    }

    public MatchStrategy getStrategy(int index) {
        return strategies_.get(index);
    }

    public MatchStrategy getStrategy(String label) {
        MatchStrategy rc = null;

        for (MatchStrategy strategy : strategies_) {
            if (label.equals(strategy.getLabel())) {
                rc = strategy;
                break;
            }
        }

        return rc;
    }

    public ListIterator<MatchStrategy> strategyIterator() {
        return strategies_.listIterator();
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ operations
    private void matchThreshold() { // TODO:3 Add a spinner for lower cutoff (i.e., eliminate from table view).
        if (merge_works_.getMasterSide() == MergeSide.LEFT) {
            ElementMatcher.matchThreshold(model_repo_.getDiffModelLeft().getRankMatrix(),
                    ((Number) limit_spn_.getValue()).floatValue());
        } else if (merge_works_.getMasterSide() == MergeSide.RIGHT) {
            ElementMatcher.matchThreshold(model_repo_.getDiffModelRight().getRankMatrix(),
                    ((Number) limit_spn_.getValue()).floatValue());
        }
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private void measureSimilarlities() {
        measure_lf_mtx_ = new MeasureMatrix(strategies_);
        model_repo_.getDiffModelLeft().setMatchMatrix(measure_lf_mtx_);

        ElementMatcher.measureSimilarities(model_repo_.getDiffModelLeft(), model_repo_.getDiffModelRight());

        measure_rt_mtx_ = new MeasureMatrix(strategies_);
        model_repo_.getDiffModelRight().setMatchMatrix(measure_rt_mtx_);

        ElementMatcher.measureSimilarities(model_repo_.getDiffModelRight(), model_repo_.getDiffModelLeft());
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private void rankSimilarlities() {
        ranking_lf_mtx_ = new MeasureMatrix(strategies_);
        model_repo_.getDiffModelLeft().setRankMatrix(ranking_lf_mtx_);

        ElementMatcher.rankSimilarlities(model_repo_.getDiffModelLeft(), ranking_lf_mtx_.getMatchStrategyIndex());

        Debug.dbg.println("\n\n\n\t    --- LEFT to RIGHT SIMILARITIES ---");
        ranking_lf_mtx_.dump();

        ranking_rt_mtx_ = new MeasureMatrix(strategies_);
        model_repo_.getDiffModelRight().setRankMatrix(ranking_rt_mtx_);

        ElementMatcher.rankSimilarlities(model_repo_.getDiffModelRight(), ranking_rt_mtx_.getMatchStrategyIndex());

        Debug.dbg.println("\n\n\n\t    --- RIGHT to LEFT SIMILARITIES ---");
        ranking_rt_mtx_.dump();
    }

    /**                                                                     DOCDO: Provide method overview.
     * Populate and render the trees. Fire table fill.
     *
     */
    private void syncViews() {
        // Detach renderers to avoid trying to render nothing.
        view_lf_tree_.setCellRenderer(null);
        view_rt_tree_.setCellRenderer(null);

        // Populated each tree's data model.
        model_lf_tree_.populate();
        model_rt_tree_.populate();

        // Assign populated data models to their respective tree views.
        view_lf_tree_.setModel(model_lf_tree_.getTreeModel());
        view_rt_tree_.setModel(model_rt_tree_.getTreeModel());

        // Assign cell renderer *after* the models are in place and populated.
        ModelTreeCellRenderer renderer = new ModelTreeCellRenderer(true);
        view_lf_tree_.setCellRenderer(renderer);
        view_rt_tree_.setCellRenderer(renderer);

        model_lf_tree_.expandRows();
        model_rt_tree_.expandRows();

        // Trigger filling of detail tables from current tree nodes.
        if (view_lf_tree_.getSelectionPath() != null)
            view_lf_tree_.setSelectionPath(view_lf_tree_.getSelectionPath());
        else
            view_lf_tree_.setSelectionRow(0);

        if (view_rt_tree_.getSelectionPath() != null)
            view_rt_tree_.setSelectionPath(view_rt_tree_.getSelectionPath());
        else
            view_rt_tree_.setSelectionRow(0);
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ initializers
    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private JPanel assembleControlMasterPanel() {
        // Instantiate the components.
        JPanel panel = new JPanel(new GridLayout(0, 1));
        ButtonGroup group = new ButtonGroup();
        master_lf_rbt_ = new JRadioButton("Left");
        master_rt_rbt_ = new JRadioButton("Right");

        // Set visual and behavioral aspects of the components.
        panel.setBorder(BorderFactory.createTitledBorder("Master side"));

        master_lf_rbt_.setMnemonic(KeyEvent.VK_L);
        master_lf_rbt_.setToolTipText("Merge from point of view of the \"left\" model.");
        master_rt_rbt_.setMnemonic(KeyEvent.VK_R);
        master_rt_rbt_.setToolTipText("Merge from point of view of the \"right\" model.");

        group.add(master_lf_rbt_);
        group.add(master_rt_rbt_);

        // Add components to the panel.
        panel.add(master_lf_rbt_);
        panel.add(master_rt_rbt_);

        return panel;
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private JPanel assembleControlPanel() {
        // Instantiate the components.
        JPanel master_pnl = assembleControlMasterPanel();
        match_btn_ = new JButton("Match");
        unmatch_btn_ = new JButton("Unmatch");

        // Set visual and behavioral aspects of the components.
        match_btn_.setMnemonic(KeyEvent.VK_M);
        match_btn_.setToolTipText("Pair the selected tree elements.");
        unmatch_btn_.setMnemonic(KeyEvent.VK_U);
        unmatch_btn_.setToolTipText("Unpair the selected tree elements.");

        // Set panel layout and constraints.
        String col_spec = "$rgap, pref, $rgap";
        String row_spec = "pref, $ugap, pref, 4dlu, pref";
        FormLayout layout = new FormLayout(col_spec, row_spec);

        // Initialize builder of the panel with the layout and a border.
        PanelBuilder builder = new PanelBuilder(layout);
        builder.setBorder(Borders.DLU2_BORDER);

        // Add components to the panel.
        CellConstraints cc = new CellConstraints();
        builder.add(master_pnl, cc.rcw(1, 1, 3));
        builder.add(match_btn_, cc.rc(3, 2));
        builder.add(unmatch_btn_, cc.rc(5, 2));

        return builder.getPanel();
    }

    /**
     * Instantiate the GUI's components, and populate its containers.
     */
    private void assembleGui() {
        // Assemble the constituent panels.
        weight_pnl_ = assembleWeightPanel();
        JPanel option_pnl = assembleOptionPanel();
        JPanel view_pnl = assembleViewPanel();

        // Set panel layout and constraints.
        String col_spec = "pref:grow";
        String row_spec = "pref, $ugap, pref, $ugap, fill:pref:grow";
        FormLayout layout = new FormLayout(col_spec, row_spec);

        // Initialize builder of the panel with the layout and a border.
        PanelBuilder builder = new PanelBuilder(layout, this);
        builder.setBorder(Borders.TABBED_DIALOG_BORDER);

        // Add components to the panel.
        CellConstraints cc = new CellConstraints();
        builder.add(weight_pnl_, cc.rc(1, 1));
        builder.add(option_pnl, cc.rc(3, 1));
        builder.add(view_pnl, cc.rc(5, 1));
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private JPanel assembleOptionPanel() {
        // Instantiate the components.
        JComponent separator = DefaultComponentFactory.getInstance().createSeparator("Set Model Matching Options",
                SwingConstants.CENTER);
        strategy_pnl_ = assembleOptionStrategyPanel();

        sync_cbx_ = new JCheckBox();
        InvocationParser parser = Mirador.getParser();
        float weight = new Float(
                (parser.optionValue("match_threshold") != null) ? parser.optionValue("match_threshold") : "0.0");
        limit_spn_ = new JSpinner(new SpinnerNumberModel(0, 0, 1, weight));
        limit_btn_ = new JButton("Show");
        limit_spn_.setEditor(new JSpinner.NumberEditor(limit_spn_, "0.00"));

        // Set visual and behavioral aspects of the components.
        sync_cbx_.setMnemonic(KeyEvent.VK_Y);
        sync_cbx_.setToolTipText("Check so selected element's match in opposite tree is also selected.");
        limit_spn_.setToolTipText("Set similarity limit at which elements will be automatically matched.");
        limit_btn_.setToolTipText("Highlight elements that will matched for the specified threshold.");

        // Set panel layout and constraints.
        String col_spec = "pref, $rgap, pref, fill:$ugap:grow, pref, $rgap," + "pref, $ugap, pref";
        String row_spec = "pref, $rgap, pref, $rgap, pref";
        FormLayout layout = new FormLayout(col_spec, row_spec);

        // Initialize builder of the panel with the layout and a border.
        PanelBuilder builder = new PanelBuilder(layout);
        builder.setBorder(Borders.EMPTY_BORDER);

        // Add components to the panel.
        CellConstraints cc = new CellConstraints();
        builder.add(separator, cc.rcw(1, 1, 9));
        builder.add(strategy_pnl_, cc.rcw(3, 1, 9));
        builder.add(sync_cbx_, cc.rc(5, 1));
        builder.addLabel("S&ynchronize tree node selections", cc.rc(5, 3));
        builder.add(limit_spn_, cc.rc(5, 5));
        builder.addLabel("Matching similarity threshold", cc.rc(5, 7));
        builder.add(limit_btn_, cc.rc(5, 9));

        return builder.getPanel();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private JPanel assembleOptionStrategyPanel() {
        boolean is_first = (strategy_pnl_ == null);
        int sz;

        // Instantiate the components.
        strategy_grp_ = new ButtonGroup();
        if (is_first) {
            strategy_pnl_ = new JPanel(new GridLayout(1, 0));
            sz = MAX_STRATEGIES;
        } else {
            strategy_pnl_.removeAll();
            sz = strategies_.size();
        }

        // Set visual and behavioral aspects of the components.
        strategy_pnl_.setBorder(BorderFactory.createTitledBorder("Automatic matching strategy"));

        // Add components to the panel.
        for (int i = 0; i < sz; ++i) {
            JRadioButton radio = (is_first) ? new JRadioButton("a Strategy") : strategies_.get(i).getRadio();
            strategy_grp_.add(radio);
            strategy_pnl_.add(radio);
        }

        return strategy_pnl_;
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private JPanel assembleViewPanel() {
        // Instantiate the components.
        JComponent separator = DefaultComponentFactory.getInstance().createSeparator("Alter Model Element Matches",
                SwingConstants.CENTER);

        JPanel control_pnl = assembleControlPanel();

        view_lf_tree_ = new JTree();
        view_lf_scl_ = new JScrollPane(view_lf_tree_);
        view_lf_tbl_ = new JTable();
        table_lf_scl_ = new JScrollPane(view_lf_tbl_);
        view_lf_spl_ = new JSplitPane(JSplitPane.VERTICAL_SPLIT, view_lf_scl_, table_lf_scl_);

        view_rt_tree_ = new JTree();
        view_rt_scl_ = new JScrollPane(view_rt_tree_);
        view_rt_tbl_ = new JTable();
        table_rt_scl_ = new JScrollPane(view_rt_tbl_);
        view_rt_spl_ = new JSplitPane(JSplitPane.VERTICAL_SPLIT, view_rt_scl_, table_rt_scl_);

        // Set visual and behavioral aspects of the components.
        DefaultTreeSelectionModel mode = new DefaultTreeSelectionModel();
        mode.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        view_lf_tree_.setSelectionModel(mode);
        view_lf_tree_.setToolTipText("Select an element to highlight its match"
                + "in the right tree, and show its details the table below.");

        mode = new DefaultTreeSelectionModel();
        mode.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        view_rt_tree_.setSelectionModel(mode);
        view_rt_tree_.setToolTipText("Select an element to highlight its match"
                + "in the left tree, and show its details the table below.");

        ToolTipManager.sharedInstance().registerComponent(view_lf_tree_);
        ToolTipManager.sharedInstance().registerComponent(view_rt_tree_);

        view_lf_tbl_.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        view_lf_tbl_.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        view_lf_tbl_.setRowSelectionAllowed(true);
        view_lf_tbl_.setToolTipText("Select a candidate to highlight its" + " potential match in the right tree.");

        view_rt_tbl_.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        view_rt_tbl_.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        view_rt_tbl_.setRowSelectionAllowed(true);
        view_rt_tbl_.setToolTipText("Select a candidate to highlight its" + " potential match in the left tree.");

        view_lf_spl_.setPreferredSize(view_lf_spl_.getMaximumSize());
        view_lf_spl_.setOneTouchExpandable(true);
        view_lf_spl_.setResizeWeight(0.7);

        view_rt_spl_.setPreferredSize(view_rt_spl_.getMaximumSize());
        view_rt_spl_.setOneTouchExpandable(true);
        view_rt_spl_.setResizeWeight(0.7);

        // Set panel layout and constraints.
        String col_spec = "center:160dlu:grow, pref, center:160dlu:grow";
        String row_spec = "pref, $rgap, pref, $rgap, fill:MIN(180dlu;pref):grow";
        FormLayout layout = new FormLayout(col_spec, row_spec);

        // Initialize builder of the panel with the layout and a border.
        PanelBuilder builder = new PanelBuilder(layout);
        builder.setBorder(Borders.EMPTY_BORDER);

        // Add components to the panel.
        CellConstraints cc = new CellConstraints();
        builder.add(separator, cc.rcw(1, 1, 3));
        builder.addLabel("Left model elements", cc.rc(3, 1));
        builder.add(view_lf_spl_, cc.rc(5, 1));
        builder.add(control_pnl, cc.rc(5, 2));
        builder.addLabel("Right model elements", cc.rc(3, 3));
        builder.add(view_rt_spl_, cc.rc(5, 3));

        return builder.getPanel();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private JPanel assembleWeightPanel() {
        // Instantiate the components.
        JComponent separator = DefaultComponentFactory.getInstance()
                .createSeparator("Set Relative Weights for Matching Strategies", SwingConstants.CENTER);

        update_btn_ = new JButton("Update");

        // Set visual and behavioral aspects of the components.
        update_btn_.setMnemonic(KeyEvent.VK_U);
        update_btn_.setToolTipText("Force similarity calculation using specified"
                + " weights, and redisplay new values in detail tables.");

        // Set panel layout and constraints.
        String col_spec = "pref:grow, $ugap, pref:grow, $ugap, pref:grow, $ugap,"
                + "pref:grow, $ugap, pref:grow, $ugap, pref:grow, $ugap,"
                + "pref:grow, $ugap, pref:grow, $ugap, pref";
        String row_spec = "pref, $rgap, pref, $rgap, pref";
        FormLayout layout = new FormLayout(col_spec, row_spec);

        // Initialize builder of the panel with the layout and a border.
        PanelBuilder builder;

        int sz;
        int col;

        if (weight_pnl_ == null) {
            builder = new PanelBuilder(layout);
            sz = MAX_STRATEGIES;
            col = 1;
        } else {
            weight_pnl_.removeAll();
            builder = new PanelBuilder(layout, weight_pnl_);
            sz = strategies_.size();
            col = 17 - (sz - 1) * 2;
        }
        builder.setBorder(Borders.EMPTY_BORDER);

        // Add components to the panel.
        CellConstraints cc = new CellConstraints();
        builder.add(separator, cc.rcw(1, 1, 17));
        for (int i = 1; i < sz; ++i) {
            if (weight_pnl_ == null) {
                builder.addLabel("a Strategy", cc.rcw(3, col, 2));
                builder.add(new JSpinner(), cc.rc(5, col));
            } else {
                builder.addLabel(strategies_.get(i).getLabel(), cc.rcw(3, col, 2));
                builder.add(strategies_.get(i).getSpinner(), cc.rc(5, col));
            }
            col += 2;
        }
        builder.add(update_btn_, cc.rc(5, col));

        return builder.getPanel();
    }

    /**
     * Attach observers to panel components.
     */
    private void attachHandlers() {
        match_btn_.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                onElementMatchClick(ev);
            }
        });

        unmatch_btn_.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                onElementUnmatchClick(ev);
            }
        });

        limit_btn_.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ev) {
                onLimitShowClick(ev);
            }
        });

        // Left master side radio button event handler.
        master_lf_rbt_.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent ev) {
                onMasterSideChange(ev);
            }
        });

        // Right master side radio button event handler.
        master_rt_rbt_.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent ev) {
                onMasterSideChange(ev);
            }
        });

        TreeMouseListener listener = new TreeMouseListener();
        view_lf_tree_.addMouseListener(listener);
        view_rt_tree_.addMouseListener(listener);

        table_lf_scl_.addAncestorListener(new DividerListener(view_lf_spl_, view_rt_spl_));
        table_rt_scl_.addAncestorListener(new DividerListener(view_rt_spl_, view_lf_spl_));
    }

    private void attachViewHandlers() {
        if (tree_lf_lsr_ != null)
            view_lf_tree_.removeTreeSelectionListener(tree_lf_lsr_);
        tree_lf_lsr_ = new TreeNodeListener(view_rt_tree_, measure_lf_tbl_);
        view_lf_tree_.addTreeSelectionListener(tree_lf_lsr_);

        if (tree_rt_lsr_ != null)
            view_rt_tree_.removeTreeSelectionListener(tree_rt_lsr_);
        tree_rt_lsr_ = new TreeNodeListener(view_lf_tree_, measure_rt_tbl_);
        view_rt_tree_.addTreeSelectionListener(tree_rt_lsr_);

        if (table_lf_lsr_ != null)
            view_lf_tbl_.getSelectionModel().removeListSelectionListener(table_lf_lsr_);
        view_lf_tbl_.getSelectionModel()
                .addListSelectionListener(new TableRowListener(measure_lf_tbl_, model_rt_tree_));

        if (table_rt_lsr_ != null)
            view_rt_tbl_.getSelectionModel().removeListSelectionListener(table_rt_lsr_);
        view_rt_tbl_.getSelectionModel()
                .addListSelectionListener(new TableRowListener(measure_rt_tbl_, model_lf_tree_));
    }

    private void buildViews() {
        // Create and initialize tree views.
        model_lf_tree_ = new ModelTree(model_repo_.getDiffModelLeft(), view_lf_tree_);
        model_rt_tree_ = new ModelTree(model_repo_.getDiffModelRight(), view_rt_tree_);

        // Create and initialize table views.
        measure_lf_tbl_ = new MeasureTable(model_repo_.getDiffModelLeft(), strategies_, view_lf_tbl_);
        measure_rt_tbl_ = new MeasureTable(model_repo_.getDiffModelRight(), strategies_, view_rt_tbl_);
    }

    /**
     * Constructs the panel and readys it for display.
     */
    private void initialize() {
        // Create and place the visual components on the panel.
        assembleGui();

        // Set state of components *prior* to establishing event links.
        if (Mirador.getParser().optionValue("master_side") == "right")
            master_rt_rbt_.setSelected(true);
        else
            master_lf_rbt_.setSelected(true);

        limit_btn_.setEnabled(true);
        limit_spn_.setValue(0.6f);
        sync_cbx_.setSelected(true);

        // Link observers to panel components for event handling.
        attachHandlers();
    }

    private void loadEvaluators() {
        strategies_.clear();
        addStrategy(new MatchStrategy(null, 1.0f, "Overall", null,
                "Automatically match elements based on overall similarity."));

        for (Iterator<SimilarityEvaluator> it = merge_works_.evaluatorIterator(); it.hasNext();) {
            SimilarityEvaluator evaluator = it.next();
            addStrategy(new MatchStrategy(evaluator, evaluator.getInitalWeight(), evaluator.getLabel(),
                    evaluator.getSpinToolTip(), evaluator.getRadioToolTip()));
        }

        setupEvaluators();
    }

    private void loadMatches() { // TODO:2 Prematched elements.
    }

    private void setupEvaluators() {
        MatchStrategy ecl_strategy = getStrategy("by ECL");

        if (ecl_strategy != null) {
            ((EclEvaluator) ecl_strategy.getEvaluator()).compare(model_repo_.getDiffModelLeft().getXmiModel(),
                    model_repo_.getDiffModelRight().getXmiModel(), null);
        }
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ event handlers
    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onCancelClick(ActionEvent ev) {
        // TODO:3 Save states and preferences.
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onClose(WindowEvent ev) {
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onElementMatchClick(ActionEvent ev) {
        TreePath path_lf = view_lf_tree_.getSelectionPath();
        ModelTreeNode node_lf = (ModelTreeNode) path_lf.getLastPathComponent();
        EcoreExtra element_lf = node_lf.getExtra();

        TreePath path_rt = view_rt_tree_.getSelectionPath();
        ModelTreeNode node_rt = (ModelTreeNode) path_rt.getLastPathComponent();
        EcoreExtra element_rt = node_rt.getExtra();

        // Match only like elements to like.
        if (element_lf.getEcoreType() == element_rt.getEcoreType()) {
            element_lf.setMatch(element_rt);
            element_lf.setKeepMatch();
            view_lf_tree_.repaint();
            view_rt_tree_.repaint();
            match_btn_.setEnabled(false);
            unmatch_btn_.setEnabled(true);
        }
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onElementUnmatchClick(ActionEvent ev) {
        TreePath path_lf = view_lf_tree_.getSelectionPath();
        ModelTreeNode node_lf = (ModelTreeNode) path_lf.getLastPathComponent();
        EcoreExtra element_lf = node_lf.getExtra();

        TreePath path_rt = view_rt_tree_.getSelectionPath();
        ModelTreeNode node_rt = (ModelTreeNode) path_rt.getLastPathComponent();
        EcoreExtra element_rt = node_rt.getExtra();

        // Unmatch only already matched elements.
        if (element_lf.getMatch() != null && element_lf.getMatch().equals(element_rt)) {
            element_lf.setMatch(null);
            element_lf.resetKeepMatch();
            view_lf_tree_.repaint();
            view_rt_tree_.repaint();
            match_btn_.setEnabled(true);
            unmatch_btn_.setEnabled(false);
        }
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onLimitShowClick(ActionEvent ev) {
        // Pair elements that meet or exceed new similarity threshold.
        matchThreshold();

        // Populate trees and tables based on latest measures of similarity.
        syncViews();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onMasterSideChange(ItemEvent ev) {
        if (ev.getStateChange() == ItemEvent.SELECTED) {
            if (ev.getItem() == master_lf_rbt_)
                merge_works_.setMasterSideLeft();
            else if (ev.getItem() == master_rt_rbt_)
                merge_works_.setMasterSideRight();

            // Pair elements w.r.t. new side that meet or exceed similarity threshold.
            matchThreshold();

            // Populate trees and tables based on latest measures of similarity.
            syncViews();
        }
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onOkayClick(ActionEvent ev) {
        wizard_.getActionBar().getForwardButton().doClick();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onStrategyChange(ActionEvent ev) {
        // Set index to new matching strategy.
        for (int i = 0; i < strategies_.size(); ++i) {
            if (strategies_.get(i).getLabel().equals(ev.getActionCommand())) {
                measure_lf_mtx_.setMatchStrategyIndex(i);
                measure_rt_mtx_.setMatchStrategyIndex(i);
                break;
            }
        }

        // Order measures of selected matching criteria from high to low.
        rankSimilarlities();

        // Pair elements w.r.t. new strategy that meet or exceed similarity threshold.
        matchThreshold();

        // Populate trees and tables based on latest measures of similarity.
        syncViews();
    }

    /**                                                                     DOCDO: Provide method overview.
     * Synchronize panel view with new inputs from model panel.
     *
     * @param  ev  The triggering event.
     */
    void onTabEnter(ChangeEvent ev) {
        // Complete construction of GUI.
        model_repo_ = wizard_.getModelRepository(); // Get latest model repository.
        merge_works_ = wizard_.getMergeWorks();
        loadEvaluators(); // Load similarity evaluators specified in model panel.

        assembleWeightPanel(); // Weight panel with new evaluators and...
        update_btn_.addActionListener(new ActionListener() { // ... event handlers.
            @Override
            public void actionPerformed(ActionEvent ev) {
                onWeightUpdateClick(ev);
            }
        });

        assembleOptionStrategyPanel(); // Strategy panel with new evaluators and...
        Enumeration<AbstractButton> strategies = strategy_grp_.getElements();
        while (strategies.hasMoreElements()) { // ... event handlers.
            JRadioButton strategy = (JRadioButton) strategies.nextElement();

            strategy.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ev) {
                    onStrategyChange(ev);
                }
            });
        }

        buildViews(); // Construct tree and table views.

        updateNavigation(); // Synchronize panel buttons with current GUI state.

        // Initialize models, and measure and match elements based on new settings.
        loadMatches(); // Load similarity evaluators specified in model panel.

        //??    ElementMatcher.matchCommon(model_repo_.getModelLeft().getElements(),
        //        model_repo_.getModelRight().getElements());  // FIXME:2 Common matches should be sticky.

        measureSimilarlities(); // Measure all possible element pairings.

        // Finalize state of GUI and event handlers.
        wizard_.getStatusBar().setText(PANEL_INFO); // Add panel status.

        attachViewHandlers(); // Add tree and table handlers.

        //Set master side without invoking any events.
        merge_works_.setMasterSideLeft();

        // Pick "Overall" scoring to rank and score elements, and fire syncViews().
        if (strategy_grp_.getElements().hasMoreElements())
            ((JRadioButton) strategy_grp_.getElements().nextElement()).doClick();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onTabExit(ChangeEvent ev) {
        // TODO:3 Output match report.

        matchThreshold();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     * @param  ev  The triggering event.
     */
    void onWeightUpdateClick(ActionEvent ev) {
        // Set strategy weights as specified by spinner boxes.
        for (int i = 1; i < strategies_.size(); ++i) {
            strategies_.get(i).setWeight(((Number) strategies_.get(i).getSpinner().getValue()).floatValue());
        }

        // Obtain measures for all possible element pairings using new weights.
        measureSimilarlities(); // TODO:2 Just multiple against nominal measures.

        // Order measures of selected matching criteria from high to low.
        rankSimilarlities();

        // Pair elements that meet or exceed similarity threshold.
        matchThreshold();

        // Populate trees and tables based on latest measures of similarity.
        syncViews();
    }

    /**                                                                     DOCDO: Provide method overview.
     *
     */
    private void updateNavigation() {
        TabActionBar action_bar = wizard_.getActionBar();
        action_bar.enableBackwardButton();
    }

    // Instance data ----------------------------------------------------------
    private JPanel weight_pnl_;
    private JPanel strategy_pnl_;
    private ButtonGroup strategy_grp_;

    private JRadioButton master_lf_rbt_;
    private JRadioButton master_rt_rbt_;

    private JButton match_btn_;
    private JButton unmatch_btn_;
    private JButton update_btn_;

    private JButton limit_btn_;
    private JSpinner limit_spn_;
    private JCheckBox sync_cbx_;

    private JTree view_lf_tree_;
    private JTree view_rt_tree_;

    private ModelTree model_lf_tree_;
    private ModelTree model_rt_tree_;

    private TreeNodeListener tree_lf_lsr_;
    private TreeNodeListener tree_rt_lsr_;

    private JSplitPane view_lf_spl_;
    private JSplitPane view_rt_spl_;

    private JScrollPane view_lf_scl_;
    private JScrollPane view_rt_scl_;

    private JTable view_lf_tbl_;
    private JTable view_rt_tbl_;

    private JScrollPane table_lf_scl_;
    private JScrollPane table_rt_scl_;

    private MeasureTable measure_lf_tbl_;
    private MeasureTable measure_rt_tbl_;

    private TableRowListener table_lf_lsr_;
    private TableRowListener table_rt_lsr_;

    private ModelRepository model_repo_;
    private MergeWorks merge_works_;

    private MeasureMatrix measure_lf_mtx_;
    private MeasureMatrix measure_rt_mtx_;
    private MeasureMatrix ranking_lf_mtx_;
    private MeasureMatrix ranking_rt_mtx_;

    private List<MatchStrategy> strategies_ = new ArrayList<MatchStrategy>();
    // End instance data ------------------------------------------------------

    // Class data -------------------------------------------------------------
    static private final String HELP_FILE = "element-match.html";
    static private final String PANEL_INFO = "Use this pane to establish correspondence between mode elements.";
    static public final int MAX_STRATEGIES = 9; // Overall plus eight more.
    // End class data ---------------------------------------------------------

    // Nested types -----------------------------------------------------------
    /**                                                                     DOCDO: Provide class overview.
     *
     * @since   v0.19 - Mar 10, 2010
     * @author  Stephen Barrett
     */
    private class DividerListener extends JScrollPane implements AncestorListener {
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors
        /**                                                                   DOCDO: Provide constructor overview.
         *
         * @param  master  Purpose of the argument.
         * @param  slave  Purpose of the argument.
         */
        public DividerListener(JSplitPane master, JSplitPane slave) {
            master_ = master;
            slave_ = slave;
        }

        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ event handlers
        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void ancestorAdded(AncestorEvent ev) {
        }

        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void ancestorMoved(AncestorEvent ev) {
            slave_.setDividerLocation(master_.getDividerLocation());
        }

        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void ancestorRemoved(AncestorEvent ev) {
        }

        // Instance data --------------------------------------------------------
        private JSplitPane master_;
        private JSplitPane slave_;
        // End instance data ----------------------------------------------------
    }

    /**                                                                     DOCDO: Provide class overview.
     *
     * @since   v0.19 - Mar 10, 2010
     * @author  Stephen Barrett
     */
    static private class TableRowListener implements ListSelectionListener {
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors
        public TableRowListener(MeasureTable source, ModelTree target) {
            source_ = source;
            target_ = target;
        }

        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ event handlers
        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void valueChanged(ListSelectionEvent ev) {
            if (ev.getValueIsAdjusting())
                return;

            int index = source_.getTable().getSelectedRow();
            if (index >= 0) {
                EcoreExtra source = (EcoreExtra) source_.getTable().getValueAt(index, 0);

                if (source != null) {
                    EcoreExtra target = target_.getMiradorModel().getExtra(source.getId());
                    target_.getTree().addSelectionPath(target.getTreePath());
                }
            }
        }

        // Instance data --------------------------------------------------------
        private MeasureTable source_;
        private ModelTree target_;
        // End instance data ----------------------------------------------------
    }

    /**                                                                     DOCDO: Provide class overview.
     *
     * @since   v0.19 - Mar 10, 2010
     * @author  Stephen Barrett
     */
    static private class TreeMouseListener extends MouseAdapter {
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ event handlers
        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void mouseEntered(MouseEvent ev) {
            ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
        }

        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void mouseExited(MouseEvent ev) {
            ToolTipManager.sharedInstance().setDismissDelay(DISMISS_MS);
        }

        // Class data -----------------------------------------------------------
        static private int DISMISS_MS = ToolTipManager.sharedInstance().getDismissDelay();
        // End class data -------------------------------------------------------
    }

    /**                                                                     DOCDO: Provide class overview.
     *
     * @since   v0.13 - Feb 26, 2010
     * @author  Stephen Barrett
     */
    private class TreeNodeListener implements TreeSelectionListener {
        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ constructors
        public TreeNodeListener(JTree slave, MeasureTable detail) {
            slave_ = slave;
            detail_ = detail;
        }

        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ event handlers
        /**                                                                   DOCDO: Provide method overview.
         *
         * @param  ev  The triggering event.
         */
        @Override
        public void valueChanged(TreeSelectionEvent ev) {
            ModelTreeNode selected_node = ((ModelTreeNode) ev.getPath().getLastPathComponent());

            EcoreExtra selected = selected_node.getExtra();

            // Update selected element's table details.
            detail_.fillTable(selected);

            // Selected element's matched candidate in the other tree.
            EcoreExtra matched = selected.getMatch();
            if (sync_cbx_.isSelected() && matched != null) {
                if (matched.getTreePath() != null)
                    slave_.addSelectionPath(matched.getTreePath()); // Fires event again.
            }

            // Update panel buttons.
            ModelTreeNode synched_node = ((ModelTreeNode) slave_.getLastSelectedPathComponent());

            if (synched_node != null) {
                EcoreExtra synched = synched_node.getExtra();

                if (synched == matched) {
                    match_btn_.setEnabled(false);
                    unmatch_btn_.setEnabled(true);
                } else if (selected.getEcoreType() == synched.getEcoreType()) {
                    match_btn_.setEnabled(true);
                    unmatch_btn_.setEnabled(false);
                } else {
                    match_btn_.setEnabled(false);
                    unmatch_btn_.setEnabled(false);
                }
            }
        }

        // Instance data --------------------------------------------------------
        private JTree slave_;
        private MeasureTable detail_;
        // End instance data ----------------------------------------------------
    }
    // End nested types -------------------------------------------------------
}