/* JMeld is a visual diff and merge tool. Copyright (C) 2007 Kees Kuip This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jmeld.ui; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import org.jmeld.JMeldException; import org.jmeld.diff.JMChunk; import org.jmeld.diff.JMDelta; import org.jmeld.diff.JMDiff; import org.jmeld.diff.JMRevision; import org.jmeld.model.LevenshteinTableModel; import org.jmeld.settings.JMeldSettings; import org.jmeld.ui.diffbar.DiffScrollComponent; import org.jmeld.ui.search.SearchCommand; import org.jmeld.ui.search.SearchHit; import org.jmeld.ui.search.SearchHits; import org.jmeld.ui.text.AbstractBufferDocument; import org.jmeld.ui.text.BufferDocumentIF; import org.jmeld.ui.text.JMDocumentEvent; import org.jmeld.ui.tree.DiffTree; import org.jmeld.util.StringUtil; import org.jmeld.util.conf.ConfigurationListenerIF; import org.jmeld.util.node.BufferNode; import org.jmeld.util.node.JMDiffNode; import javax.swing.*; import javax.swing.border.MatteBorder; import javax.swing.event.*; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import javax.swing.text.PlainDocument; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class BufferDiffPanel extends AbstractContentPanel implements ConfigurationListenerIF { public static final int LEFT = 0; public static final int MIDDLE = 1; //TODO: Usar el comparador del medio con dos JDiff public static final int RIGHT = 2; public static final int NUMBER_OF_PANELS = 3; private JMeldPanel mainPanel; private FilePanel[] filePanels; private JMDiffNode diffNode; int filePanelSelectedIndex = -1; private JMRevision currentRevision; private JMDelta selectedDelta; private int selectedLine; private ScrollSynchronizer scrollSynchronizer; private JMDiff diff; private JTable levensteinGraphTable; private DiffTree diffTree; private boolean showTree; private boolean showLevenstein; private JSplitPane splitPane; private JCheckBox checkSolutionPath; static Color selectionColor = Color.BLUE; static Color newColor = Color.CYAN; static Color mixColor = Color.WHITE; static { selectionColor = new Color(selectionColor.getRed() * newColor.getRed()/mixColor.getRed() ,selectionColor.getGreen() * newColor.getGreen()/mixColor.getGreen() ,selectionColor.getBlue() * newColor.getBlue()/mixColor.getBlue()); } BufferDiffPanel(JMeldPanel mainPanel) { this.mainPanel = mainPanel; readConfig(); JMeldSettings.getInstance().addConfigurationListener(this); diff = new JMDiff(); init(); setFocusable(true); } public boolean isShowTree() { return showTree; } public void setShowTree(boolean showTree) { this.showTree = showTree; } public boolean isShowLevenstein() { return showLevenstein; } public void setShowLevenstein(boolean showLevenstein) { this.showLevenstein = showLevenstein; } public void setDiffNode(JMDiffNode diffNode) { this.diffNode = diffNode; refreshDiffNode(); } public JMDiffNode getDiffNode() { return diffNode; } private void refreshDiffNode() { BufferNode bnLeft = getDiffNode().getBufferNodeLeft(); BufferNode bnRight = getDiffNode().getBufferNodeRight(); BufferDocumentIF leftDocument = bnLeft == null ? null : bnLeft.getDocument(); BufferDocumentIF rightDocument = bnRight == null ? null : bnRight.getDocument(); setBufferDocuments(leftDocument, rightDocument, getDiffNode().getDiff(), getDiffNode().getRevision()); } private void setBufferDocuments(BufferDocumentIF bd1, BufferDocumentIF bd2, JMDiff diff, JMRevision revision) { this.diff = diff; currentRevision = revision; if (bd1 != null) { filePanels[LEFT].setBufferDocument(bd1); } if (bd2 != null) { filePanels[RIGHT].setBufferDocument(bd2); } if (bd1 != null && bd2 != null) { filePanels[LEFT].updateFileLabel(bd1.getName(), bd2.getName()); filePanels[RIGHT].updateFileLabel(bd2.getName(), bd1.getName()); } if (bd1 != null && bd2 != null) { reDisplay(); } } private void reDisplay() { for (FilePanel fp : filePanels) { if (fp != null) { fp.reDisplay(); } } refreshTreeModel(); refreshLevensteinModel(); mainPanel.repaint(); } private void refreshTreeModel() { if (isShowTree()) { diffTree.setRevision(getCurrentRevision()); } } public String getTitle() { String title; BufferDocumentIF bd; List<String> titles; title = ""; titles = new ArrayList<String>(); for (FilePanel filePanel : filePanels) { if (filePanel == null) { continue; } bd = filePanel.getBufferDocument(); if (bd == null) { continue; } title = bd.getShortName(); if (StringUtil.isEmpty(title)) { continue; } titles.add(title); } title = ""; if (titles.size() == 1) { title = titles.get(0); } else { if (titles.get(0).equals(titles.get(1))) { title = titles.get(0); } else { title = titles.get(0) + "-" + titles.get(1); } } return title; } public boolean revisionChanged(JMDocumentEvent de) { FilePanel fp; BufferDocumentIF bd1; BufferDocumentIF bd2; if (currentRevision == null) { diff(); } else { fp = getFilePanel(de.getDocument()); if (fp == null) { return false; } bd1 = filePanels[LEFT].getBufferDocument(); bd2 = filePanels[RIGHT].getBufferDocument(); if (!currentRevision.update(bd1 != null ? bd1.getLines() : null, bd2 != null ? bd2.getLines() : null, fp == filePanels[LEFT], de .getStartLine(), de.getNumberOfLines())) { return false; } reDisplay(); } return true; } private FilePanel getFilePanel(AbstractBufferDocument document) { for (FilePanel fp : filePanels) { if (fp == null) { continue; } if (fp.getBufferDocument() == document) { return fp; } } return null; } public void diff() { BufferDocumentIF bd1; BufferDocumentIF bd2; bd1 = filePanels[LEFT].getBufferDocument(); bd2 = filePanels[RIGHT].getBufferDocument(); if (bd1 != null && bd2 != null) { try {currentRevision = diff.diff(bd1.getLines(), bd2.getLines() , getDiffNode().getIgnore()); reDisplay(); } catch (Exception ex) { ex.printStackTrace(); } } } private void init() { FormLayout layout; String columns; String rows; CellConstraints cc; columns = "3px, pref, 3px, 0:grow, 5px, min, 60px, 0:grow, 25px, min, 3px, pref, 3px"; rows = "6px, pref, 3px, fill:0:grow, pref"; setLayout(new BorderLayout()); if (splitPane != null) { remove(splitPane); } splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, buildFilePanel(columns, rows), buildBottonSplit()); add(splitPane); scrollSynchronizer = new ScrollSynchronizer(this, filePanels[LEFT], filePanels[RIGHT]); setSelectedPanel(filePanels[LEFT]); } private JComponent buildBottonSplit() { JScrollPane scrollTreePane = buildTreePane(); JComponent levensteinPanel = buildLevenstheinTable(); JComponent bottomSplit; if (scrollTreePane != null) { if (levensteinPanel != null) { bottomSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, scrollTreePane, levensteinPanel); } else { bottomSplit = scrollTreePane; } } else { bottomSplit = levensteinPanel; } return bottomSplit; } private JPanel buildFilePanel(String columns, String rows) { FormLayout layout; CellConstraints cc; JPanel filePanel = new JPanel(); layout = new FormLayout(columns, rows); cc = new CellConstraints(); filePanel.setLayout(layout); filePanels = new FilePanel[NUMBER_OF_PANELS]; filePanels[LEFT] = new FilePanel(this, BufferDocumentIF.ORIGINAL, LEFT); filePanels[RIGHT] = new FilePanel(this, BufferDocumentIF.REVISED, RIGHT); // panel for file1 filePanel.add(new RevisionBar(this, filePanels[LEFT], true), cc.xy(2, 4)); if (mainPanel.SHOW_FILE_TOOLBAR_OPTION.isEnabled()) { filePanel.add(filePanels[LEFT].getSaveButton(), cc.xy(2, 2)); filePanel.add(filePanels[LEFT].getFileLabel(), cc.xyw(4, 2, 3)); } filePanel.add(filePanels[LEFT].getScrollPane(), cc.xyw(4, 4, 3)); if (mainPanel.SHOW_FILE_STATUSBAR_OPTION.isEnabled()) { filePanel.add(filePanels[LEFT].getFilePanelBar(), cc.xyw(4, 5, 3)); } DiffScrollComponent diffScrollComponent = new DiffScrollComponent(this, LEFT, RIGHT); filePanel.add(diffScrollComponent, cc.xy(7, 4)); // panel for file2 filePanel.add(new RevisionBar(this, filePanels[RIGHT], false), cc.xy(12, 4)); if (mainPanel.SHOW_FILE_TOOLBAR_OPTION.isEnabled()) { filePanel.add(filePanels[RIGHT].getFileLabel(), cc.xyw(8, 2, 3)); } filePanel.add(filePanels[RIGHT].getScrollPane(), cc.xyw(8, 4, 3)); if (mainPanel.SHOW_FILE_TOOLBAR_OPTION.isEnabled()) { filePanel.add(filePanels[RIGHT].getSaveButton(), cc.xy(12, 2)); } if (mainPanel.SHOW_FILE_STATUSBAR_OPTION.isEnabled()) { filePanel.add(filePanels[RIGHT].getFilePanelBar(), cc.xyw(8, 5, 3)); } filePanels[RIGHT].getEditor().addKeyListener(diffScrollComponent.getKeyListener()); filePanels[LEFT].getEditor().addKeyListener(diffScrollComponent.getKeyListener()); filePanel.setMinimumSize(new Dimension(300,200)); return filePanel; } private JScrollPane buildTreePane() { JScrollPane scrollTreePane = null; if (isShowTree()) { scrollTreePane = new JScrollPane(); diffTree = new DiffTree(); diffTree.addMouseListener(new MouseAdapter() { private void showPopup(MouseEvent e) { int x = e.getX(); int y = e.getY(); JTree tree = (JTree)e.getSource(); TreePath path = tree.getPathForLocation(x, y); if (path == null) { return; } tree.setSelectionPath(path); DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); final Object userObject = node.getUserObject(); JPopupMenu popup = new JPopupMenu(); if (userObject instanceof JMDelta) { JMenuItem menuItem = buildDeleteItem(userObject); menuItem.setEnabled(enableDeleteAction(node)); popup.add(menuItem); } if (userObject instanceof JMRevision) { JMenuItem menuItem = buildInsertItem(); menuItem.setEnabled(enableInsertAction(node)); popup.add(menuItem); } popup.show(tree, x, y); } private boolean enableDeleteAction(DefaultMutableTreeNode node) { Object parentUserObject = ((DefaultMutableTreeNode) (node.getParent())).getUserObject(); return parentUserObject instanceof JMRevision || (parentUserObject instanceof JMDelta && ((JMDelta)parentUserObject).isChange()); } private JMenuItem buildDeleteItem(final Object userObject) { JMenuItem menuItem = new JMenuItem("Delete node"); menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { List<JMDelta> deltas = currentRevision.getDeltas(); for (JMDelta delta : deltas) { if (deleteDelta(deltas, delta)) { return; } if (delta.isChange()){ List<JMDelta> changeDeltas = delta.getChangeRevision().getDeltas(); for (JMDelta changeDelta : changeDeltas) { if (deleteDelta(changeDeltas, changeDelta)) { return; } } } } } private boolean deleteDelta(List<JMDelta> deltas, JMDelta delta) { if (delta.equals(userObject)) { deltas.remove(delta); reDisplay(); return true; } return false; } }); return menuItem; } private JMenuItem buildInsertItem() { JMenuItem menuItem; menuItem = new JMenuItem("Insert node"); menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int selectedRow = levensteinGraphTable.getSelectedRow(); int selectedColumn = levensteinGraphTable.getSelectedColumn(); int selectedRowCount = levensteinGraphTable.getSelectedRowCount(); int selectedColumnCount = levensteinGraphTable.getSelectedColumnCount(); if (selectedRowCount == 1) { selectedRowCount = 0; } if (selectedColumnCount == 1) { selectedColumnCount = 0; } try { JTextArea leftEditor = filePanels[LEFT].getEditor(); int firstLine = leftEditor.getLineOfOffset(selectedRow - 2); int endLine = leftEditor.getLineOfOffset(selectedRow - 2 + selectedRowCount - 1); JMChunk original = new JMChunk(firstLine, endLine - firstLine + 1); JTextArea rightEditor = filePanels[RIGHT].getEditor(); int firstCol = rightEditor.getLineOfOffset(selectedColumn - 2); int endCol = rightEditor.getLineOfOffset(selectedColumn - 2 + selectedColumnCount - 1); JMChunk revised = new JMChunk(firstCol, endCol - firstCol + 1); JMDelta newDdelta = new JMDelta(original, revised); newDdelta.setRevision(currentRevision); boolean createNew = true; List<JMDelta> deltas = currentRevision.getDeltas(); for (int i = 0; i < deltas.size(); i++) { JMDelta delta = deltas.get(i); if (delta.equals(newDdelta)) { newDdelta = delta; createNew = false; } } JMRevision changeRevision; if (createNew) { changeRevision = currentRevision.createChangeRevision(original, revised, false); newDdelta.setChangeRevision(changeRevision); deltas.add(newDdelta); } else { changeRevision = newDdelta.getChangeRevision(); } List<JMDelta> changeDeltas = changeRevision.getDeltas(); changeDeltas.add(new JMDelta(new JMChunk(selectedRow - 2 - leftEditor.getLineStartOffset(firstLine), selectedRowCount) , new JMChunk(selectedColumn - 2 - leftEditor.getLineStartOffset(firstCol), selectedColumnCount) )); reDisplay(); } catch (BadLocationException e1) { System.out.println("Error building delta: " + e1.getMessage()); e1.printStackTrace(); } } }); return menuItem; } private boolean enableInsertAction(DefaultMutableTreeNode node) { int selectedRow = levensteinGraphTable.getSelectedRow(); int selectedColumn = levensteinGraphTable.getSelectedColumn(); int selectedRowCount = levensteinGraphTable.getSelectedRowCount(); int selectedColumnCount = levensteinGraphTable.getSelectedColumnCount(); //TODO: if selection is next to a unchanged path boolean deltaDefined = selectedColumnCount > 0 || selectedRowCount > 0; return deltaDefined; } public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { showPopup(e); } } @Override public void mouseClicked(MouseEvent me) { TreePath tp = diffTree.getPathForLocation(me.getX(), me.getY()); if (tp != null) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)tp.getLastPathComponent(); DefaultMutableTreeNode root = (DefaultMutableTreeNode)node.getRoot(); while (node != root) { Object userObject = node.getUserObject(); if (userObject instanceof JMDelta) { Object parentUserObject = ((DefaultMutableTreeNode) node.getParent()).getUserObject(); JMDelta lineDelta = (JMDelta) userObject; JMDelta wordDelta = null; if (parentUserObject instanceof JMDelta) { wordDelta = lineDelta; lineDelta = (JMDelta) parentUserObject; } doSelection(lineDelta, wordDelta); break; } node = (DefaultMutableTreeNode)node.getParent(); } } } private void doSelection(JMDelta lineDelta, JMDelta wordDelta) { int firstLine = lineDelta.getOriginal().getAnchor(); int lines = lineDelta.getOriginal().getSize(); int firstCol = lineDelta.getRevised().getAnchor(); int cols = lineDelta.getRevised().getSize(); int lineStartOffset = 0; int lineEndOffset = 0; int colStartOffset = 0; int colEndOffset = 0; try { FilePanel leftFilePanel = filePanels[LEFT]; FilePanel rightFilePanel = filePanels[RIGHT]; int leftLineCount = leftFilePanel.getEditor().getLineCount(); int rightLineCount = rightFilePanel.getEditor().getLineCount(); if (firstLine == leftLineCount) { lineStartOffset = leftFilePanel.getEditor().getLineEndOffset(firstLine - 1); } else { lineStartOffset = leftFilePanel.getEditor().getLineStartOffset(firstLine); } lineEndOffset = leftFilePanel.getEditor().getLineEndOffset(firstLine + lines - 1); if (firstCol == rightLineCount) { colStartOffset = rightFilePanel.getEditor().getLineEndOffset(firstCol - 1); } else { colStartOffset = rightFilePanel.getEditor().getLineStartOffset(firstCol); } colEndOffset = rightFilePanel.getEditor().getLineEndOffset(firstCol + cols - 1); int lineOffset = 0; if (lineDelta.isAdd()) { lineOffset--; } int colOffset = 0; if (lineDelta.isDelete()) { colOffset--; } int startRow = lineStartOffset + 2 + lineOffset; int endRow = lineEndOffset + 2 - 1; int startCol = colStartOffset + 2 + colOffset; int endCol = colEndOffset + 2 - 1; if (wordDelta != null) { int offsetLine = 0; if (wordDelta.isChange() || wordDelta.isDelete()) { offsetLine--; } int offsetCol = 0; if (wordDelta.isChange() || wordDelta.isAdd()) { offsetCol--; } JMChunk wordDeltaOriginal = wordDelta.getOriginal(); JMChunk wordDeltaRevised = wordDelta.getRevised(); startRow += wordDeltaOriginal.getAnchor(); endRow = startRow + wordDeltaOriginal.getSize() +offsetLine; startCol += wordDeltaRevised.getAnchor(); endCol = startCol + wordDeltaRevised.getSize() +offsetCol; } levensteinGraphTable.clearSelection(); if (lineDelta.isAdd()) { HashMap<Point, MatteBorder> rowLineSelection = createRowLineSelection(startRow - 2); ((LevenshteinTableModel)levensteinGraphTable.getModel()).setBorderSelections(rowLineSelection); startRow = -1; endRow = -1; } if (lineDelta.isDelete()) { HashMap<Point, MatteBorder> columnLineSelection = createColumnLineSelection(startCol - 2); ((LevenshteinTableModel)levensteinGraphTable.getModel()).setBorderSelections(columnLineSelection); startCol = -1; endCol = -1; } levensteinGraphTable.changeSelection(startRow, startCol, false, false); levensteinGraphTable.changeSelection(endRow, endCol, false, true); levensteinGraphTable.repaint(); } catch (BadLocationException e) { System.err.printf("(%d, %d, %d, %d)%n", lineStartOffset, lineEndOffset, colStartOffset, colEndOffset); System.err.printf("(%d, %d, %d, %d)%n", firstLine, lines, firstCol, cols); e.printStackTrace(); } } }); scrollTreePane.setViewportView(diffTree); } return scrollTreePane; } private JComponent buildLevenstheinTable() { if (!isShowLevenstein()) { return null; } final JSpinner spinner = new JSpinner(new SpinnerNumberModel(15, 5, Integer.MAX_VALUE, 1)); levensteinGraphTable = new JTable() { /** * Falso si el tamaño de la tabla no es el tamaño del JScrollPane * @return */ public boolean getScrollableTracksViewportWidth() { boolean ok = false; if (autoResizeMode != AUTO_RESIZE_OFF) { if (getParent() instanceof JViewport) { int parentWidth = getParent().getWidth(); int tableWidth = getPreferredSize().width; ok = parentWidth > tableWidth; } } return ok; } @Override public void addColumn(TableColumn aColumn) { aColumn.setPreferredWidth((Integer) spinner.getValue()); super.addColumn(aColumn); } @Override public boolean isCellEditable(int row, int column) { return true; } }; spinner.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { TableColumnModel cm = levensteinGraphTable.getColumnModel(); Integer preferredWidth = (Integer) spinner.getValue(); for (int i = 0; i < cm.getColumnCount(); i++) { //TODO: Si es menor que el mininumWidth, no hace nada cm.getColumn(i).setPreferredWidth(preferredWidth); } } }); levensteinGraphTable.setTableHeader(null); levensteinGraphTable.setFillsViewportHeight(true); JPanel panelGraph = new JPanel(new BorderLayout()); panelGraph.add(new JScrollPane(levensteinGraphTable)); JPanel bPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); Box box = Box.createHorizontalBox(); bPanel.add(new JLabel("Min Column Width")); bPanel.add(spinner); box.add(bPanel); box.add(Box.createHorizontalGlue()); checkSolutionPath = new JCheckBox("Show solution path"); checkSolutionPath.setSelected(true); checkSolutionPath.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { ((LevenshteinTableModel) levensteinGraphTable.getModel()) .setShowSelectionPath(checkSolutionPath.isSelected()); levensteinGraphTable.repaint(); } }); box.add(checkSolutionPath); box.add(Box.createHorizontalGlue()); final JLabel cellSelected = new JLabel("(,)"); box.add(cellSelected); ListSelectionListener listSelectionListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { int rowOrigin = levensteinGraphTable.getSelectedRow() - 2; int rowCount = levensteinGraphTable.getSelectedRowCount() +1 - 2; int columnOrigin = levensteinGraphTable.getSelectedColumn() - 2; int columnCount = levensteinGraphTable.getSelectedColumnCount() +1 - 2; cellSelected.setText("(" + rowOrigin + "," + (rowOrigin + rowCount) + ")," + " (" + columnOrigin + "," + (columnOrigin + columnCount) + ")"); //TODO: Highlight position. levensteinGraphTable.repaint(); } }; levensteinGraphTable.getColumnModel().getSelectionModel() .addListSelectionListener(listSelectionListener); levensteinGraphTable.getSelectionModel() .addListSelectionListener(listSelectionListener); filePanels[LEFT].getEditor().addCaretListener(new CaretListener() { @Override public void caretUpdate(CaretEvent e) { int dot = e.getDot(); int mark = e.getMark(); HashMap<Point, MatteBorder> borderSelection; levensteinGraphTable.clearSelection(); if (dot == mark) { borderSelection = createRowLineSelection(dot); levensteinGraphTable.scrollRectToVisible(levensteinGraphTable.getCellRect(dot, 0, true)); } else { borderSelection = new HashMap<>(); if (mark > dot) { int temp = dot; dot = mark; mark = temp; } levensteinGraphTable.changeSelection(dot + 2 - 1, levensteinGraphTable.getSelectedColumn(), false, false); levensteinGraphTable.changeSelection(mark + 2, levensteinGraphTable.getSelectedColumn(), false, true); } ((LevenshteinTableModel) levensteinGraphTable.getModel()).setBorderSelections(borderSelection); levensteinGraphTable.repaint(); } }); filePanels[RIGHT].getEditor().addCaretListener(new CaretListener() { @Override public void caretUpdate(CaretEvent e) { int dot = e.getDot(); int mark = e.getMark(); HashMap<Point, MatteBorder> borderSelection; levensteinGraphTable.clearSelection(); if (dot == mark) { borderSelection = createColumnLineSelection(dot); levensteinGraphTable.scrollRectToVisible(levensteinGraphTable.getCellRect(0, dot, true)); } else { borderSelection = new HashMap<>(); if (mark > dot) { int temp = dot; dot = mark; mark = temp; } levensteinGraphTable.changeSelection(levensteinGraphTable.getSelectedRow(), dot + 2 - 1, false, false); levensteinGraphTable.changeSelection(levensteinGraphTable.getSelectedRow(), mark + 2, false, true); } ((LevenshteinTableModel) levensteinGraphTable.getModel()).setBorderSelections(borderSelection); levensteinGraphTable.repaint(); } }); panelGraph.add(box, BorderLayout.SOUTH); return panelGraph; } private HashMap<Point, MatteBorder> createRowLineSelection(int row) { HashMap<Point, MatteBorder> borderSelection = new HashMap<>(); for (int i = 0; i < filePanels[LEFT].getBufferDocument().getDocument().getLength(); i++) { borderSelection.put(new Point(row, i), BorderFactory.createMatteBorder(2, 0, 0, 0, selectionColor)); } return borderSelection; } private HashMap<Point, MatteBorder> createColumnLineSelection(int column) { HashMap<Point, MatteBorder> borderSelection = new HashMap<>(); for (int i = 0; i < filePanels[LEFT].getBufferDocument().getDocument().getLength(); i++) { borderSelection.put(new Point(i, column), BorderFactory.createMatteBorder(0, 2, 0, 0, selectionColor)); } return borderSelection; } private void refreshLevensteinModel() { if (isShowLevenstein()) { try { PlainDocument orgDoc = filePanels[LEFT].getBufferDocument().getDocument(); PlainDocument revDoc = filePanels[RIGHT].getBufferDocument().getDocument(); LevenshteinTableModel model = new LevenshteinTableModel(); model.setOrigin(orgDoc.getText(0, orgDoc.getLength())); model.setDestiny(revDoc.getText(0, revDoc.getLength())); //Add to renderer instead model.setCurrentRevision(currentRevision); model.setFilePanels(filePanels); model.setShowSelectionPath(checkSolutionPath.isSelected()); model.buildModel(); levensteinGraphTable.setModel(model); levensteinGraphTable.setDefaultRenderer(Object.class, model.getCellRenderer()); } catch (BadLocationException e) { } } } public void toNextDelta(boolean next) { if (next) { doDown(); } else { doUp(); } } public JMRevision getCurrentRevision() { return currentRevision; } @Override public boolean checkSave() { SavePanelDialog dialog; if (!isSaveEnabled()) { return true; } dialog = new SavePanelDialog(mainPanel); for (FilePanel filePanel : filePanels) { if (filePanel != null) { dialog.add(filePanel.getBufferDocument()); } } dialog.show(); if (dialog.isOK()) { dialog.doSave(); return true; } return false; } @Override public void doSave() { BufferDocumentIF document; for (FilePanel filePanel : filePanels) { if (filePanel == null) { continue; } if (!filePanel.isDocumentChanged()) { continue; } document = filePanel.getBufferDocument(); try { document.write(); } catch (JMeldException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(mainPanel, "Can't write file" + document.getName(), "Problem writing file", JOptionPane.ERROR_MESSAGE); } } } @Override public boolean isSaveEnabled() { for (FilePanel filePanel : filePanels) { if (filePanel != null && filePanel.isDocumentChanged()) { return true; } } return false; } @Override public void doStopSearch() { for (FilePanel filePanel : filePanels) { if (filePanel != null) { filePanel.doStopSearch(); } } } public SearchCommand getSearchCommand() { return mainPanel.getSearchCommand(); } @Override public SearchHits doSearch() { FilePanel fp; SearchHits searchHits; fp = getSelectedPanel(); if (fp == null) { return null; } searchHits = fp.doSearch(); scrollToSearch(fp, searchHits); return searchHits; } @Override public void doNextSearch() { FilePanel fp; SearchHits searchHits; fp = getSelectedPanel(); if (fp == null) { return; } searchHits = fp.getSearchHits(); searchHits.next(); fp.reDisplay(); scrollToSearch(fp, searchHits); } @Override public void doPreviousSearch() { FilePanel fp; SearchHits searchHits; fp = getSelectedPanel(); if (fp == null) { return; } searchHits = fp.getSearchHits(); searchHits.previous(); fp.reDisplay(); scrollToSearch(fp, searchHits); } @Override public void doRefresh() { diff(); } @Override public void doMergeMode(boolean mergeMode) { for (FilePanel fp : filePanels) { if (fp != null) { fp.getEditor().setFocusable(!mergeMode); } } } private void scrollToSearch(FilePanel fp, SearchHits searchHits) { SearchHit currentHit; int line; if (searchHits == null) { return; } currentHit = searchHits.getCurrent(); if (currentHit != null) { line = currentHit.getLine(); scrollSynchronizer.scrollToLine(fp, line); setSelectedLine(line); } } private FilePanel getSelectedPanel() { if (filePanelSelectedIndex >= 0 && filePanelSelectedIndex < filePanels.length) { return filePanels[filePanelSelectedIndex]; } return null; } void setSelectedPanel(FilePanel fp) { int index; index = -1; for (int i = 0; i < filePanels.length; i++) { if (filePanels[i] == fp) { index = i; } } if (index != filePanelSelectedIndex) { if (filePanelSelectedIndex != -1) { filePanels[filePanelSelectedIndex].setSelected(false); } filePanelSelectedIndex = index; if (filePanelSelectedIndex != -1) { filePanels[filePanelSelectedIndex].setSelected(true); } } } @Override public void checkActions() { mainPanel.checkActions(); } @Override public void doLeft(boolean shift) { runChange(RIGHT, LEFT, shift); } @Override public void doRight(boolean shift) { runChange(LEFT, RIGHT, shift); } public void runChange(int fromPanelIndex, int toPanelIndex, boolean shift) { JMDelta delta; BufferDocumentIF fromBufferDocument; BufferDocumentIF toBufferDocument; PlainDocument from; String s; int fromLine; int fromOffset; int toOffset; int size; JMChunk fromChunk; JMChunk toChunk; JTextComponent toEditor; delta = getSelectedDelta(); if (delta == null) { return; } // Some sanity checks. if (fromPanelIndex < 0 || fromPanelIndex >= filePanels.length) { return; } if (toPanelIndex < 0 || toPanelIndex >= filePanels.length) { return; } try { fromBufferDocument = filePanels[fromPanelIndex].getBufferDocument(); toBufferDocument = filePanels[toPanelIndex].getBufferDocument(); // TODO: delta and revision are not yet ready for 3-way merge! if (fromPanelIndex < toPanelIndex) { fromChunk = delta.getOriginal(); toChunk = delta.getRevised(); } else { fromChunk = delta.getRevised(); toChunk = delta.getOriginal(); } toEditor = filePanels[toPanelIndex].getEditor(); if (fromBufferDocument == null || toBufferDocument == null) { return; } fromLine = fromChunk.getAnchor(); size = fromChunk.getSize(); fromOffset = fromBufferDocument.getOffsetForLine(fromLine); if (fromOffset < 0) { return; } toOffset = fromBufferDocument.getOffsetForLine(fromLine + size); if (toOffset < 0) { return; } from = fromBufferDocument.getDocument(); s = from.getText(fromOffset, toOffset - fromOffset); fromLine = toChunk.getAnchor(); size = toChunk.getSize(); fromOffset = toBufferDocument.getOffsetForLine(fromLine); if (fromOffset < 0) { return; } toOffset = toBufferDocument.getOffsetForLine(fromLine + size); if (toOffset < 0) { return; } getUndoHandler().start("replace"); toEditor.setSelectionStart(fromOffset); toEditor.setSelectionEnd(toOffset); if (!shift) { toEditor.replaceSelection(s); } else { // toEditor.getDocument().insertString(fromOffset, s, null); toEditor.getDocument().insertString(toOffset, s, null); } getUndoHandler().end("replace"); setSelectedDelta(null); setSelectedLine(delta.getOriginal().getAnchor()); } catch (Exception ex) { ex.printStackTrace(); } } public void runDelete(int fromPanelIndex, int toPanelIndex) { JMDelta delta; BufferDocumentIF bufferDocument; PlainDocument document; String s; int fromLine; int fromOffset; int toOffset; int size; JMChunk chunk; JTextComponent toEditor; try { delta = getSelectedDelta(); if (delta == null) { return; } // Some sanity checks. if (fromPanelIndex < 0 || fromPanelIndex >= filePanels.length) { return; } if (toPanelIndex < 0 || toPanelIndex >= filePanels.length) { return; } bufferDocument = filePanels[fromPanelIndex].getBufferDocument(); if (fromPanelIndex < toPanelIndex) { chunk = delta.getOriginal(); } else { chunk = delta.getRevised(); } toEditor = filePanels[fromPanelIndex].getEditor(); if (bufferDocument == null) { return; } document = bufferDocument.getDocument(); fromLine = chunk.getAnchor(); size = chunk.getSize(); fromOffset = bufferDocument.getOffsetForLine(fromLine); if (fromOffset < 0) { return; } toOffset = bufferDocument.getOffsetForLine(fromLine + size); if (toOffset < 0) { return; } getUndoHandler().start("remove"); toEditor.setSelectionStart(fromOffset); toEditor.setSelectionEnd(toOffset); toEditor.replaceSelection(""); getUndoHandler().end("remove"); setSelectedDelta(null); setSelectedLine(delta.getOriginal().getAnchor()); } catch (Exception ex) { ex.printStackTrace(); } } @Override public void doDown() { JMDelta d; JMDelta sd; List<JMDelta> deltas; int index; if (currentRevision == null) { return; } deltas = currentRevision.getDeltas(); sd = getSelectedDelta(); index = deltas.indexOf(sd); if (index == -1 || sd.getOriginal().getAnchor() != selectedLine) { // Find the delta that would have been next to the // disappeared delta: d = null; for (JMDelta delta : deltas) { d = delta; if (delta.getOriginal().getAnchor() > selectedLine) { break; } } setSelectedDelta(d); } else { // Select the next delta if there is any. if (index + 1 < deltas.size()) { setSelectedDelta(deltas.get(index + 1)); } } showSelectedDelta(); } @Override public void doUp() { JMDelta d; JMDelta sd; JMDelta previousDelta; List<JMDelta> deltas; int index; if (currentRevision == null) { return; } deltas = currentRevision.getDeltas(); sd = getSelectedDelta(); index = deltas.indexOf(sd); if (index == -1 || sd.getOriginal().getAnchor() != selectedLine) { // Find the delta that would have been previous to the // disappeared delta: d = null; previousDelta = null; for (JMDelta delta : deltas) { d = delta; if (delta.getOriginal().getAnchor() > selectedLine) { if (previousDelta != null) { d = previousDelta; } break; } previousDelta = delta; } setSelectedDelta(d); } else { // Select the next delta if there is any. if (index - 1 >= 0) { setSelectedDelta(deltas.get(index - 1)); } } showSelectedDelta(); } public void doGotoDelta(JMDelta delta) { setSelectedDelta(delta); showSelectedDelta(); } public void doGotoLine(int line) { BufferDocumentIF bd; int offset; int startOffset; int endOffset; JViewport viewport; JTextComponent editor; Point p; FilePanel fp; Rectangle rect; setSelectedLine(line); fp = getFilePanel(0); bd = fp.getBufferDocument(); if (bd == null) { return; } offset = bd.getOffsetForLine(line); viewport = fp.getScrollPane().getViewport(); editor = fp.getEditor(); // Don't go anywhere if the line is already visible. rect = viewport.getViewRect(); startOffset = editor.viewToModel(rect.getLocation()); endOffset = editor.viewToModel(new Point(rect.x, rect.y + rect.height)); if (offset >= startOffset && offset <= endOffset) { return; } try { p = editor.modelToView(offset).getLocation(); p.x = 0; viewport.setViewPosition(p); } catch (BadLocationException ex) { } } @Override public void doZoom(boolean direction) { JTextComponent c; Font font; float size; Zoom zoom; for (FilePanel p : filePanels) { if (p == null) { continue; } c = p.getEditor(); zoom = (Zoom) c.getClientProperty("JMeld.zoom"); if (zoom == null) { // Save the orginal font because that's the font which will // give the derived font. zoom = new Zoom(); zoom.font = c.getFont(); c.putClientProperty("JMeld.zoom", zoom); } size = c.getFont().getSize() + (direction ? 1.0f : -1.0f); size = size > 100.0f ? 100.0f : size; size = size < 5.0f ? 5.0f : size; c.setFont(zoom.font.deriveFont(size)); } } @Override public void doGoToSelected() { showSelectedDelta(); } @Override public void doGoToFirst() { JMDelta d; List<JMDelta> deltas; if (currentRevision == null) { return; } deltas = currentRevision.getDeltas(); if (deltas.size() > 0) { setSelectedDelta(deltas.get(0)); showSelectedDelta(); } } @Override public void doGoToLast() { JMDelta d; List<JMDelta> deltas; if (currentRevision == null) { return; } deltas = currentRevision.getDeltas(); if (deltas.size() > 0) { setSelectedDelta(deltas.get(deltas.size() - 1)); showSelectedDelta(); } } @Override public void doGoToLine(int line) { FilePanel fp; fp = getSelectedPanel(); if (fp == null) { return; } scrollSynchronizer.scrollToLine(fp, line); setSelectedLine(line); } @Override public void configurationChanged() { readConfig(); init(); refreshDiffNode(); reDisplay(); diff(); } private void readConfig() { setShowTree(JMeldSettings.getInstance().getEditor().isShowTreeChunks()); setShowLevenstein(JMeldSettings.getInstance().getEditor().isShowLevenstheinEditor()); } class Zoom { Font font; } public void setSelectedDelta(JMDelta delta) { selectedDelta = delta; setSelectedLine(delta == null ? 0 : delta.getOriginal().getAnchor()); } private void setSelectedLine(int line) { selectedLine = line; } private void showSelectedDelta() { JMDelta delta; delta = getSelectedDelta(); if (delta == null) { return; } scrollSynchronizer.showDelta(delta); } public JMDelta getSelectedDelta() { List<JMDelta> deltas; if (currentRevision == null) { return null; } deltas = currentRevision.getDeltas(); if (deltas.size() == 0) { return null; } return selectedDelta; } public FilePanel getFilePanel(int index) { if (index < 0 || index > filePanels.length) { return null; } return filePanels[index]; } @Override public String getSelectedText() { FilePanel fp; fp = getSelectedPanel(); if (fp == null) { return null; } return fp.getSelectedText(); } }