package com.vistatec.ocelot.tm.gui.match; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.List; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.table.TableCellEditor; import javax.swing.table.TableColumn; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter; import com.vistatec.ocelot.Ocelot; import com.vistatec.ocelot.segment.model.SegmentAtom; import com.vistatec.ocelot.segment.model.TextAtom; import com.vistatec.ocelot.tm.TmMatch; import com.vistatec.ocelot.tm.gui.AbstractDetachableTmPanel; import com.vistatec.ocelot.tm.gui.constants.TmIconsConst; /** * This panel implements the Concordance Search functionality. It is detachable, * i.e. by pressing the appropriate button the panel is detached from the Ocelot * main frame and it is displayed inside its own window. */ public class ConcordanceSearchPanel extends AbstractDetachableTmPanel { /** Search field width constant. */ private static final int SEARCH_TXT_WIDTH = 300; /** Search field height constant. */ private static final int SEARCH_TXT_HEIGHT = 25; /** Search button size. */ private static final int SEARCH_BTN_SIZE = 25; /** The search text field. */ private JTextField txtSearch; /** The search button. */ private JButton btnSearch; /** The table displaying the concordance mathces. */ private TmTable matchesTable; /** The loading label. */ private JLabel lblLoading; /** Label displayed when no matches are found. */ private JLabel lblNoResults; /** The info panel. */ private JPanel infoPanel; /** The table scroll panel. */ private JScrollPane scrollPanel; /** Cell editor for the target. */ private TableCellEditor targetEditor; /** The table model. */ private ConcordanceMatchTableModel tableModel; /** The table cell renderer assigned to the source column. */ private ConcordanceCellRenderer sourceColRenderer; /** * Constructor. * * @param controller * the controller. */ public ConcordanceSearchPanel(TmGuiMatchController controller) { super(controller); } /** * Builds the components displayed in this panel. */ private void buildComponents() { // Build and configure the search text field. txtSearch = new JTextField(); final Dimension txtDim = new Dimension(SEARCH_TXT_WIDTH, SEARCH_TXT_HEIGHT); txtSearch.setPreferredSize(txtDim); txtSearch.setMinimumSize(txtDim); txtSearch.setMaximumSize(txtDim); txtSearch.addActionListener(this); // Build and configure the search button. Toolkit kit = Toolkit.getDefaultToolkit(); ImageIcon icon = new ImageIcon(kit.createImage(Ocelot.class .getResource(TmIconsConst.FIND_ICO))); btnSearch = new JButton(icon); btnSearch.addActionListener(this); btnSearch.setToolTipText("Search"); final Dimension dim = new Dimension(SEARCH_BTN_SIZE, SEARCH_BTN_SIZE); btnSearch.setPreferredSize(dim); btnSearch.setMaximumSize(dim); btnSearch.setMinimumSize(dim); // configure the table configTable(); // Build and configure the info panel and its labels. lblLoading = new JLabel("Loading..."); lblNoResults = new JLabel("No results"); infoPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); infoPanel.add(lblLoading); } /** * Makes the concordance search panel. */ protected void makePanel() { // Creates the Concordance Search label. JLabel lblConcordance = new JLabel("Concordance Search"); lblConcordance.setFont(lblConcordance.getFont().deriveFont(Font.BOLD, 12)); // builds panel components. buildComponents(); // Create the panel. panel = new JPanel(); panel.setName("Concordance Search"); JPanel concordancePanel = (JPanel) panel; concordancePanel.setLayout(new GridBagLayout()); GridBagConstraints gridBag = new GridBagConstraints(); // Add the pin button gridBag.gridx = 0; gridBag.gridy = 0; gridBag.anchor = GridBagConstraints.NORTHWEST; gridBag.insets = new Insets(10, 5, 0, 0); concordancePanel.add(getPinComponent(), gridBag); // Add the concordance label gridBag.gridx = 1; gridBag.gridy = 0; gridBag.anchor = GridBagConstraints.NORTHWEST; gridBag.insets = new Insets(10, 0, 0, 0); concordancePanel.add(lblConcordance, gridBag); // Add the search text field. gridBag.gridx = 0; gridBag.gridy = 1; gridBag.gridwidth = 2; gridBag.insets = new Insets(10, 0, 0, 0); gridBag.anchor = GridBagConstraints.NORTHWEST; concordancePanel.add(txtSearch, gridBag); // add the search button gridBag.gridx = 2; gridBag.gridwidth = 1; gridBag.insets = new Insets(10, 0, 0, 10); gridBag.fill = GridBagConstraints.NONE; concordancePanel.add(btnSearch, gridBag); // Add the scroll panel containing the table. gridBag.gridx = 0; gridBag.gridy = 2; gridBag.gridwidth = 3; gridBag.weighty = 1; gridBag.weightx = 1; gridBag.insets = new Insets(0, 0, 0, 0); gridBag.fill = GridBagConstraints.BOTH; gridBag.anchor = GridBagConstraints.NORTH; scrollPanel = new JScrollPane(); concordancePanel.add(scrollPanel, gridBag); } /** * Configures the table. */ private void configTable() { // creates the data model tableModel = new ConcordanceMatchTableModel(null); // creates the table. matchesTable = new TmTable(); matchesTable.setModel(tableModel); // Configure the action "replaceTarget" for the keystroke ALT+R matchesTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( KeyStroke.getKeyStroke(KeyEvent.VK_R, Ocelot.getPlatformKeyMask()), "replaceTarget"); matchesTable.getActionMap().put("replaceTarget", new ReplaceTargetAction()); // Assign the match score renderer to the match score column, and set // size TableColumn scoreCol = matchesTable.getColumnModel().getColumn( tableModel.getMatchScoreColumnIdx()); scoreCol.setCellRenderer(new MatchScoreRenderer()); scoreCol.setPreferredWidth(50); scoreCol.setMaxWidth(50); // Set size to the TM column TableColumn tmCol = matchesTable.getColumnModel().getColumn( tableModel.getTmColumnIdx()); tmCol.setPreferredWidth(200); tmCol.setMaxWidth(200); // Assign the Concordance cell renderer to the source column TableColumn sourceCol = matchesTable.getColumnModel().getColumn( tableModel.getSourceColumnIdx()); sourceColRenderer = new ConcordanceCellRenderer(); sourceCol.setCellRenderer(sourceColRenderer); targetEditor = new ReadOnlyCellEditor(); TableColumn targetCol = matchesTable.getColumnModel().getColumn( tableModel.getTargetColumnIdx()); targetCol.setCellEditor(targetEditor); } /** * Performs the concordance search. */ private void performConcordanceSearch() { if (txtSearch.getText() != null && !txtSearch.getText().isEmpty()) { setLoading(); sourceColRenderer.setSearchedString(txtSearch.getText()); SegmentAtom text = new TextAtom(txtSearch.getText()); final List<TmMatch> results = controller .getConcordanceMatches(Arrays .asList(new SegmentAtom[] { text })); if (results != null && !results.isEmpty()) { targetEditor.stopCellEditing(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { tableModel.setModel(results); scrollPanel.setViewportView(matchesTable); scrollPanel.repaint(); matchesTable.getSelectionModel().setSelectionInterval( 0, 0); } }); } else { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { infoPanel.removeAll(); infoPanel.add(lblNoResults); infoPanel.repaint(); scrollPanel.setViewportView(infoPanel); scrollPanel.repaint(); } }); } } else { targetEditor.stopCellEditing(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { infoPanel.removeAll(); infoPanel.add(lblNoResults); infoPanel.repaint(); scrollPanel.setViewportView(infoPanel); scrollPanel.repaint(); } }); } } /** * Displays the info panel with the loading label. */ public void setLoading() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { scrollPanel.setViewportView(infoPanel); infoPanel.add(lblLoading); scrollPanel.repaint(); } }); } /* * (non-Javadoc) * * @see * com.vistatec.ocelot.tm.gui.AbstractDetachableTmPanel#actionPerformed( * java.awt.event.ActionEvent) */ @Override public void actionPerformed(ActionEvent e) { if (e.getSource().equals(btnSearch) || e.getSource().equals(txtSearch)) { performConcordanceSearch(); } else { super.actionPerformed(e); } } /** * Set the text into the search text field and then performs the concordance * search. * * @param text * the text form the concordance search. */ public void setTextAndPerformConcordanceSearch(final String text) { controller.selectConcordanceTab(); txtSearch.setText(text); performConcordanceSearch(); } /** * This class implements the "Replace Target" action. The target of the * segment currently selected in the main grid is replaced by the target of * the selected match in the concordance panel. */ public class ReplaceTargetAction extends AbstractAction { /** The serial version UID. */ private static final long serialVersionUID = 7013423666969632441L; /* * (non-Javadoc) * * @see * java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent * ) */ @Override public void actionPerformed(ActionEvent e) { final int selRow = matchesTable.getSelectedRow(); if (selRow != -1) { TmMatch selMatch = tableModel.getElementAtRow(selRow); controller.replaceTarget(selMatch.getTarget()); } } } /** * Makes the window that will contain the detached component. */ @Override protected void makeWindow() { window = new JFrame(); ((JFrame) window).setTitle("Concordance Search"); window.setPreferredSize(new Dimension(800, 400)); } } /** * Table cell renderer for concordance highlights. By setting the searched * string, this renderer highlights all words in the rendered column matching * the concordance string. */ class ConcordanceCellRenderer extends SegmentVariantCellRenderer { /** The serial version UID. */ private static final long serialVersionUID = -4227851946202241316L; /** The searched string. */ private String searchedString; /** The highlight painter. */ private DefaultHighlightPainter painter = new DefaultHighlightPainter( Color.yellow); /* * (non-Javadoc) * * @see com.vistatec.ocelot.tm.gui.match.SegmentVariantCellRenderer# * getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, * boolean, boolean, int, int) */ @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JTextPane textPane = (JTextPane) super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); String text = textPane.getText(); if (text != null && searchedString != null) { int strIndex = 0; while (strIndex != -1 && strIndex < text.length()) { strIndex = text.indexOf(searchedString, strIndex); if (strIndex != -1) { try { textPane.getHighlighter().addHighlight(strIndex, strIndex + searchedString.length(), painter); strIndex += searchedString.length(); } catch (BadLocationException e) { e.printStackTrace(); } } } } return textPane; } /** * Sets the searched string. * * @param searchedString * the searched string. */ public void setSearchedString(String searchedString) { this.searchedString = searchedString; } }