1 19 package org.netbeans.modules.versioning.diff; 20 21 import org.netbeans.editor.*; 22 import org.netbeans.editor.Utilities; 23 import org.netbeans.api.editor.fold.FoldHierarchy; 24 import org.netbeans.api.editor.fold.FoldHierarchyListener; 25 import org.netbeans.api.editor.fold.FoldHierarchyEvent; 26 import org.netbeans.api.diff.Difference; 27 import org.netbeans.api.diff.Diff; 28 import org.netbeans.api.diff.StreamSource; 29 import org.netbeans.api.diff.DiffView; 30 import org.netbeans.spi.diff.DiffProvider; 31 import org.netbeans.modules.editor.errorstripe.privatespi.MarkProvider; 32 import org.netbeans.modules.versioning.spi.OriginalContent; 33 import org.openide.ErrorManager; 34 import org.openide.awt.UndoRedo; 35 import org.openide.windows.TopComponent; 36 import org.openide.util.Lookup; 37 import org.openide.util.RequestProcessor; 38 import org.openide.util.HelpCtx; 39 import org.openide.util.NbBundle; 40 import org.openide.util.lookup.Lookups; 41 42 import javax.swing.*; 43 import javax.swing.event.DocumentListener ; 44 import javax.swing.event.DocumentEvent ; 45 import javax.swing.text.*; 46 import java.awt.event.*; 47 import java.awt.*; 48 import java.beans.PropertyChangeListener ; 49 import java.beans.PropertyChangeEvent ; 50 import java.util.*; 51 import java.util.List ; 52 import java.util.logging.Logger ; 53 import java.util.logging.Level ; 54 import java.io.*; 55 import java.text.ChoiceFormat ; 56 import java.text.MessageFormat ; 57 58 63 class DiffSidebar extends JComponent implements DocumentListener , ComponentListener, PropertyChangeListener , FoldHierarchyListener { 64 65 private static final int BAR_WIDTH = 9; 66 67 private final JTextComponent textComponent; 68 69 private final EditorUI editorUI; 70 private final FoldHierarchy foldHierarchy; 71 private final BaseDocument document; 72 73 private boolean annotated; 74 private Difference [] currentDiff; 75 private DiffMarkProvider markProvider; 76 77 private Color colorAdded = new Color(150, 255, 150); 78 private Color colorChanged = new Color(160, 200, 255); 79 private Color colorRemoved = new Color(255, 160, 180); 80 private Color colorBorder = new Color(102, 102, 102); 81 82 private final OriginalContent originalContent; 83 84 private int originalContentSerial; 85 private int originalContentBufferSerial = -1; 86 private String originalContentBuffer; 87 88 private RequestProcessor.Task refreshDiffTask; 89 90 public DiffSidebar(JTextComponent target, OriginalContent content) { 91 this.textComponent = target; 92 this.originalContent = content; 93 this.editorUI = Utilities.getEditorUI(target); 94 this.foldHierarchy = FoldHierarchy.get(editorUI.getComponent()); 95 this.document = editorUI.getDocument(); 96 this.markProvider = new DiffMarkProvider(); 97 setToolTipText(""); refreshDiffTask = DiffSidebarManager.getInstance().createDiffSidebarTask(new RefreshDiffTask()); 99 setMaximumSize(new Dimension(BAR_WIDTH, Integer.MAX_VALUE)); 100 } 101 102 JTextComponent getTextComponent() { 103 return textComponent; 104 } 105 106 Difference[] getCurrentDiff() { 107 return currentDiff; 108 } 109 110 public String getToolTipText(MouseEvent event) { 111 Difference diff = getDifferenceAt(event); 112 return getShortDescription(diff); 113 } 114 115 static String getShortDescription(Difference diff) { 116 if (diff == null) return null; 117 int n; 118 switch (diff.getType()) { 119 case Difference.ADD: 120 n = diff.getSecondEnd() - diff.getSecondStart() + 1; 121 return MessageFormat.format(new ChoiceFormat (NbBundle.getMessage(DiffSidebar.class, "TT_LinesAdded")).format(n), n); 122 case Difference.CHANGE: 123 n = diff.getFirstEnd() - diff.getFirstStart() + 1; 124 return MessageFormat.format(new ChoiceFormat (NbBundle.getMessage(DiffSidebar.class, "TT_LinesChanged")).format(n), n); 125 case Difference.DELETE: 126 n = diff.getFirstEnd() - diff.getFirstStart() + 1; 127 return MessageFormat.format(new ChoiceFormat (NbBundle.getMessage(DiffSidebar.class, "TT_LinesDeleted")).format(n), n); 128 default: 129 throw new IllegalStateException ("Unknown difference type: " + diff.getType()); } 131 } 132 133 protected void processMouseEvent(MouseEvent event) { 134 super.processMouseEvent(event); 135 Difference diff = getDifferenceAt(event); 136 if (diff == null) return; 137 if (event.getID() == MouseEvent.MOUSE_CLICKED) { 138 onClick(event, diff); 139 } 140 } 141 142 private void onClick(MouseEvent event, Difference diff) { 143 Point p = new Point(event.getPoint()); 144 SwingUtilities.convertPointToScreen(p, this); 145 Point p2 = new Point(p); 146 SwingUtilities.convertPointFromScreen(p2, textComponent); 147 showTooltipWindow(new Point(p.x - p2.x, p.y), diff); 148 } 149 150 private void showTooltipWindow(Point p, Difference diff) { 151 DiffActionTooltipWindow ttw = new DiffActionTooltipWindow(this, diff); 152 ttw.show(new Point(p.x, p.y)); 153 } 154 155 private Difference getDifferenceAt(MouseEvent event) { 156 if (currentDiff == null) return null; 157 int line = getLineFromMouseEvent(event); 158 if (line == -1) return null; 159 Difference diff = getDifference(line + 1); 160 if (diff == null) { 161 diff = getDifference(line); 163 if (diff != null && diff.getType() != Difference.DELETE) diff = null; 164 } 165 return diff; 166 } 167 168 void onDiff(Difference diff) { 169 try { 170 DiffView view = Diff.getDefault().createDiff(new SidebarStreamSource(true), new SidebarStreamSource(false)); 171 JComponent c = (JComponent) view.getComponent(); 172 DiffTopComponent tc = new DiffTopComponent(c); 173 tc.setName(originalContent.getWorkingCopy().getName() + " [Diff]"); 174 tc.open(); 175 tc.requestActive(); 176 view.setCurrentDifference(getDiffIndex(diff)); 177 } catch (IOException e) { 178 ErrorManager.getDefault().notify(e); 179 } 180 } 181 182 private int getDiffIndex(Difference diff) { 183 for (int i = 0; i < currentDiff.length; i++) { 184 if (diff == currentDiff[i]) return i; 185 } 186 return -1; 187 } 188 189 void onRollback(Difference diff) { 190 try { 191 if (diff.getType() == Difference.ADD) { 192 int start = Utilities.getRowStartFromLineOffset(document, diff.getSecondStart() - 1); 193 int end = Utilities.getRowStartFromLineOffset(document, diff.getSecondEnd()); 194 document.remove(start, end - start); 195 } else if (diff.getType() == Difference.CHANGE) { 196 int start = Utilities.getRowStartFromLineOffset(document, diff.getSecondStart() - 1); 197 int end = Utilities.getRowStartFromLineOffset(document, diff.getSecondEnd()); 198 document.replace(start, end - start, diff.getFirstText(), null); 199 } else { 200 int start = Utilities.getRowStartFromLineOffset(document, diff.getSecondStart()); 201 document.insertString(start, diff.getFirstText(), null); 202 } 203 refreshDiff(); 204 } catch (BadLocationException e) { 205 ErrorManager.getDefault().notify(e); 206 } 207 } 208 209 boolean canRollback(Difference diff) { 210 if (!(document instanceof GuardedDocument)) return true; 211 int start, end; 212 if (diff.getType() == Difference.DELETE) { 213 start = end = Utilities.getRowStartFromLineOffset(document, diff.getSecondStart()); 214 } else { 215 start = Utilities.getRowStartFromLineOffset(document, diff.getSecondStart() - 1); 216 end = Utilities.getRowStartFromLineOffset(document, diff.getSecondEnd()); 217 } 218 MarkBlockChain mbc = ((GuardedDocument) document).getGuardedBlockChain(); 219 return (mbc.compareBlock(start, end) & MarkBlock.OVERLAP) == 0; 220 } 221 222 void onPrevious(Difference diff) { 223 diff = currentDiff[getDiffIndex(diff) - 1]; 224 Point location = scrollToDifference(diff); 225 showTooltipWindow(location, diff); 226 textComponent.repaint(); 227 } 228 229 void onNext(Difference diff) { 230 diff = currentDiff[getDiffIndex(diff) + 1]; 231 Point location = scrollToDifference(diff); 232 showTooltipWindow(location, diff); 233 textComponent.repaint(); 234 } 235 236 private Point scrollToDifference(Difference diff) { 237 int lineStart = diff.getSecondStart() - 1; 238 int lineEnd = diff.getSecondEnd() - 1; 239 if (diff.getType() == Difference.DELETE) { 240 lineEnd = lineStart; 241 } 242 try { 243 int visibleBorder = editorUI.getLineHeight() * 5; 244 int startOffset = Utilities.getRowStartFromLineOffset((BaseDocument) textComponent.getDocument(), lineStart); 245 int endOffset = Utilities.getRowStartFromLineOffset((BaseDocument) textComponent.getDocument(), lineEnd); 246 Rectangle startRect = textComponent.getUI().modelToView(textComponent, startOffset); 247 Rectangle endRect = textComponent.getUI().modelToView(textComponent, endOffset); 248 Rectangle visibleRect = new Rectangle(startRect.x - visibleBorder, startRect.y - visibleBorder, 249 startRect.x, endRect.y - startRect.y + endRect.height + visibleBorder * 2); 250 textComponent.scrollRectToVisible(visibleRect); 251 252 Point p = new Point(endRect.x, endRect.y + endRect.height + 1); 253 SwingUtilities.convertPointToScreen(p, textComponent); 254 return p; 255 } catch (BadLocationException e) { 256 Logger.getLogger(DiffSidebar.class.getName()).log(Level.WARNING, "scrollToDifference", e); } 258 return null; 259 } 260 261 String getMimeType() { 262 if (textComponent instanceof JEditorPane) { 263 return ((JEditorPane) textComponent).getContentType(); 264 } 265 return "text/plain"; } 267 268 private static class DiffTopComponent extends TopComponent { 269 270 private JComponent diffView; 271 272 public DiffTopComponent() { 273 } 274 275 public DiffTopComponent(JComponent c) { 276 this.diffView = c; 277 setLayout(new BorderLayout()); 278 c.putClientProperty(TopComponent.class, this); 279 add(c, BorderLayout.CENTER); 280 } 283 284 public UndoRedo getUndoRedo() { 285 UndoRedo undoredo = (UndoRedo) diffView.getClientProperty(UndoRedo.class); 286 return undoredo == null ? UndoRedo.NONE : undoredo; 287 } 288 289 public int getPersistenceType(){ 290 return TopComponent.PERSISTENCE_NEVER; 291 } 292 293 protected String preferredID(){ 294 return "DiffSidebarTopComponent"; } 296 297 public HelpCtx getHelpCtx() { 298 return new HelpCtx(getClass()); 299 } 300 } 301 302 private int getLineFromMouseEvent(MouseEvent e){ 303 int line = -1; 304 if (editorUI != null) { 305 try{ 306 JTextComponent component = editorUI.getComponent(); 307 BaseTextUI textUI = (BaseTextUI)component.getUI(); 308 int clickOffset = textUI.viewToModel(component, new Point(0, e.getY())); 309 line = Utilities.getLineOffset(document, clickOffset); 310 }catch (BadLocationException ble){ 311 Logger.getLogger(DiffSidebar.class.getName()).log(Level.WARNING, "getLineFromMouseEvent", ble); } 313 } 314 return line; 315 } 316 317 public void setSidebarVisible(boolean visible) { 318 if (visible) { 319 showSidebar(); 320 } else { 321 hideSidebar(); 322 } 323 } 324 325 private void showSidebar() { 326 annotated = true; 327 328 document.addDocumentListener(this); 329 textComponent.addComponentListener(this); 330 editorUI.addPropertyChangeListener(this); 331 foldHierarchy.addFoldHierarchyListener(this); 332 originalContent.addPropertyChangeListener(this); 333 334 refreshDiff(); 335 revalidate(); } 337 338 private void hideSidebar() { 339 annotated = false; 340 341 originalContent.removePropertyChangeListener(this); 342 foldHierarchy.removeFoldHierarchyListener(this); 343 editorUI.removePropertyChangeListener(this); 344 textComponent.removeComponentListener(this); 345 document.removeDocumentListener(this); 346 347 refreshDiff(); 348 revalidate(); 349 } 350 351 private Reader getDocumentReader() { 352 JTextComponent component = editorUI.getComponent(); 353 if (component == null) return null; 354 355 Document doc = component.getDocument(); 356 try { 357 return new StringReader(doc.getText(0, doc.getLength())); 358 } catch (BadLocationException e) { 359 return null; 360 } 361 } 362 363 private void refreshDiff() { 364 refreshDiffTask.schedule(50); 365 } 366 367 MarkProvider getMarkProvider() { 368 return markProvider; 369 } 370 371 private static void copyStreamsCloseAll(Writer writer, Reader reader) throws IOException { 372 char [] buffer = new char[2048]; 373 int n; 374 while ((n = reader.read(buffer)) != -1) { 375 writer.write(buffer, 0, n); 376 } 377 writer.close(); 378 reader.close(); 379 } 380 381 static void copyStreamsCloseAll(OutputStream writer, InputStream reader) throws IOException { 382 byte [] buffer = new byte[2048]; 383 int n; 384 while ((n = reader.read(buffer)) != -1) { 385 writer.write(buffer, 0, n); 386 } 387 writer.close(); 388 reader.close(); 389 } 390 391 public Dimension getPreferredSize() { 392 Dimension dim = textComponent.getSize(); 393 dim.width = annotated ? BAR_WIDTH : 0; 394 return dim; 395 } 396 397 public void paintComponent(Graphics g) { 398 super.paintComponent(g); 399 400 Rectangle clip = g.getClipBounds(); 401 if (clip.y >= 16) { 402 clip.y -= 16; 404 clip.height += 16; 405 } 406 407 JTextComponent component = editorUI.getComponent(); 408 if (component == null) return; 409 410 BaseTextUI textUI = (BaseTextUI)component.getUI(); 411 View rootView = Utilities.getDocumentView(component); 412 if (rootView == null) return; 413 414 g.setColor(backgroundColor()); 415 g.fillRect(clip.x, clip.y, clip.width, clip.height); 416 417 Difference [] paintDiff = currentDiff; 418 if (paintDiff == null || paintDiff.length == 0) return; 419 420 try{ 421 int startPos = textUI.getPosFromY(clip.y); 422 int startViewIndex = rootView.getViewIndex(startPos,Position.Bias.Forward); 423 int rootViewCount = rootView.getViewCount(); 424 425 if (startViewIndex >= 0 && startViewIndex < rootViewCount) { 426 Rectangle rec = textUI.modelToView(component, rootView.getView(startViewIndex).getStartOffset()); 428 int y = (rec == null) ? 0 : rec.y; 429 int [] yCoords = new int[3]; 430 431 int clipEndY = clip.y + clip.height; 432 Element rootElem = textUI.getRootView(component).getElement(); 433 434 View view = rootView.getView(startViewIndex); 435 int line = rootElem.getElementIndex(view.getStartOffset()); 436 line++; if (line == 1 && paintDiff[0].getSecondStart() == 0 && paintDiff[0].getType() == Difference.DELETE) { 438 g.setColor(getColor(paintDiff[0])); 439 yCoords[0] = y - editorUI.getLineAscent() / 2; 440 yCoords[1] = y; 441 yCoords[2] = y + editorUI.getLineAscent() / 2; 442 g.fillPolygon(new int [] { 0, BAR_WIDTH, 0 }, yCoords, 3); 443 } 444 445 for (int i = startViewIndex; i < rootViewCount; i++){ 446 view = rootView.getView(i); 447 line = rootElem.getElementIndex(view.getStartOffset()); 448 line++; Difference ad = getDifference(line); 450 if (ad != null) { 451 g.setColor(getColor(ad)); 452 if (ad.getType() == Difference.DELETE) { 453 yCoords[0] = y + editorUI.getLineAscent(); 454 yCoords[1] = y + editorUI.getLineAscent() * 3 / 2; 455 yCoords[2] = y + editorUI.getLineAscent() * 2; 456 g.fillPolygon(new int [] { 2, BAR_WIDTH, 2 }, yCoords, 3); 457 g.setColor(colorBorder); 458 g.drawLine(2, yCoords[0], 2, yCoords[2] - 1); 459 } else { 460 g.fillRect(3, y, BAR_WIDTH - 3, editorUI.getLineHeight()); 461 g.setColor(colorBorder); 462 g.drawLine(2, y, 2, y + editorUI.getLineHeight()); 463 if (ad.getSecondStart() == line) { 464 g.drawLine(2, y, BAR_WIDTH - 1, y); 465 } 466 if (ad.getSecondEnd() == line) { 467 g.drawLine(2, y + editorUI.getLineHeight(), BAR_WIDTH - 1, y + editorUI.getLineHeight()); 468 } 469 } 470 } 471 y += editorUI.getLineHeight(); 472 if (y >= clipEndY) { 473 break; 474 } 475 } 476 } 477 } catch (BadLocationException ble){ 478 ErrorManager.getDefault().notify(ble); 479 } 480 } 481 482 private Color getColor(Difference ad) { 483 if (ad.getType() == Difference.ADD) return colorAdded; 484 if (ad.getType() == Difference.CHANGE) return colorChanged; 485 return colorRemoved; 486 } 487 488 private Difference getDifference(int line) { 489 if (line < 0) return null; 490 for (int i = 0; i < currentDiff.length; i++) { 491 Difference difference = currentDiff[i]; 492 if (line < difference.getSecondStart()) return null; 493 if (difference.getType() == Difference.DELETE && line == difference.getSecondStart()) return difference; 494 if (line <= difference.getSecondEnd()) return difference; 495 } 496 return null; 497 } 498 499 private Color backgroundColor() { 500 if (textComponent != null) { 501 return textComponent.getBackground(); 502 } 503 504 return Color.WHITE; 505 } 506 507 public void insertUpdate(DocumentEvent e) { 508 refreshDiff(); 509 } 510 511 public void removeUpdate(DocumentEvent e) { 512 refreshDiff(); 513 } 514 515 public void changedUpdate(DocumentEvent e) { 516 refreshDiff(); 517 } 518 519 public void componentResized(ComponentEvent e) { 520 revalidate(); 521 } 522 523 public void componentMoved(ComponentEvent e) { 524 } 525 526 public void componentShown(ComponentEvent e) { 527 } 528 529 public void componentHidden(ComponentEvent e) { 530 } 531 532 public void propertyChange(PropertyChangeEvent evt) { 533 String id = evt.getPropertyName(); 534 if (EditorUI.COMPONENT_PROPERTY.equals(id)) { if (evt.getNewValue() == null){ 536 hideSidebar(); 537 } 538 } else if (OriginalContent.PROP_CONTENT_CHANGED.equals(id)) { 539 originalContentSerial++; 540 refreshDiff(); 541 } 542 } 543 544 public void foldHierarchyChanged(FoldHierarchyEvent evt) { 545 repaint(); 546 } 547 548 551 private class DiffMarkProvider extends MarkProvider { 552 553 private List <DiffMark> marks; 554 555 public DiffMarkProvider() { 556 marks = getMarksForDifferences(); 557 } 558 559 public List getMarks() { 560 return marks; 561 } 562 563 void refresh() { 564 List <DiffMark> oldMarks = marks; 565 marks = getMarksForDifferences(); 566 firePropertyChange(PROP_MARKS, oldMarks, marks); 567 } 568 569 private List <DiffMark> getMarksForDifferences() { 570 if (currentDiff == null) return Collections.emptyList(); 571 List <DiffMark> marks = new ArrayList<DiffMark>(currentDiff.length); 572 for (int i = 0; i < currentDiff.length; i++) { 573 Difference difference = currentDiff[i]; 574 marks.add(new DiffMark(difference, getColor(difference))); 575 } 576 return marks; 577 } 578 } 579 580 583 public class RefreshDiffTask implements Runnable { 584 585 public void run() { 586 computeDiff(); 587 repaint(); 588 markProvider.refresh(); 589 } 590 591 private void computeDiff() { 592 if (!annotated) { 593 currentDiff = null; 594 return; 595 } 596 fetchOriginalContent(); 597 if (originalContentBuffer == null) { 598 currentDiff = null; 599 return; 600 } 601 Reader working = getDocumentReader(); 602 if (working == null) { 603 return; 605 } 606 DiffProvider diff = Lookup.getDefault().lookup(DiffProvider.class); 607 if (diff == null) { 608 currentDiff = null; 609 return; 610 } 611 try { 612 currentDiff = diff.computeDiff(new StringReader(originalContentBuffer), working); 613 } catch (IOException e) { 614 currentDiff = null; 615 } 616 } 617 618 private void fetchOriginalContent() { 619 int serial = originalContentSerial; 620 if (originalContentBuffer != null && originalContentBufferSerial == serial) return; 621 originalContentBufferSerial = serial; 622 623 Reader r = originalContent.getText(); 624 if (r == null) return; 625 626 StringWriter w = new StringWriter(2048); 627 try { 628 copyStreamsCloseAll(w, r); 629 originalContentBuffer = w.toString(); 630 } catch (IOException e) { 631 } 633 } 634 } 635 636 private class SidebarStreamSource extends StreamSource { 637 638 private final boolean isFirst; 639 640 public SidebarStreamSource(boolean isFirst) { 641 this.isFirst = isFirst; 642 } 643 644 public boolean isEditable() { 645 return !isFirst; 646 } 647 648 public Lookup getLookup() { 649 if (isFirst) return super.getLookup(); 650 return Lookups.fixed(document); 651 } 652 653 public String getName() { 654 return originalContent.getWorkingCopy().getName(); 655 } 656 657 public String getTitle() { 658 if (isFirst) { 659 return NbBundle.getMessage(DiffSidebar.class, "LBL_DiffPane_Original"); } else { 661 return NbBundle.getMessage(DiffSidebar.class, "LBL_DiffPane_WorkingCopy"); } 663 } 664 665 public String getMIMEType() { 666 return getMimeType(); 667 } 668 669 public Reader createReader() throws IOException { 670 return isFirst ? new StringReader(originalContentBuffer) : getDocumentReader(); 671 } 672 673 public Writer createWriter(Difference[] conflicts) throws IOException { 674 return null; 675 } 676 } 677 } 678 | Popular Tags |