001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.diff.builtin.visualizer.editable;
042:
043: import org.netbeans.api.diff.Difference;
044: import org.netbeans.editor.*;
045: import org.openide.util.NbBundle;
046:
047: import javax.swing.*;
048: import javax.swing.text.StyledDocument;
049: import java.awt.*;
050: import java.awt.event.MouseMotionListener;
051: import java.awt.event.MouseEvent;
052: import java.awt.event.MouseListener;
053: import java.awt.geom.Rectangle2D;
054: import java.util.*;
055: import java.util.List;
056: import java.beans.PropertyChangeListener;
057: import java.beans.PropertyChangeEvent;
058:
059: /**
060: * Draws both line numbers and diff actions for a decorated editor pane.
061: *
062: * @author Maros Sandor
063: */
064: class LineNumbersActionsBar extends JPanel implements Scrollable,
065: MouseMotionListener, MouseListener, PropertyChangeListener {
066:
067: private static final int ACTIONS_BAR_WIDTH = 16;
068: private static final int LINES_BORDER_WIDTH = 4;
069: private static final Point POINT_ZERO = new Point(0, 0);
070:
071: private final Image insertImage = org.openide.util.Utilities
072: .loadImage("org/netbeans/modules/diff/builtin/visualizer/editable/insert.png"); // NOI18N
073: private final Image removeImage = org.openide.util.Utilities
074: .loadImage("org/netbeans/modules/diff/builtin/visualizer/editable/remove.png"); // NOI18N
075:
076: private final Image insertActiveImage = org.openide.util.Utilities
077: .loadImage("org/netbeans/modules/diff/builtin/visualizer/editable/insert_active.png"); // NOI18N
078: private final Image removeActiveImage = org.openide.util.Utilities
079: .loadImage("org/netbeans/modules/diff/builtin/visualizer/editable/remove_active.png"); // NOI18N
080:
081: private final DiffContentPanel master;
082: private final boolean actionsEnabled;
083: private final int actionIconsHeight;
084: private final int actionIconsWidth;
085:
086: private final String lineNumberPadding = " "; // NOI18N
087:
088: /**
089: * Rendering hints for annotations sidebar inherited from editor settings.
090: */
091: private Map renderingHints;
092:
093: private int linesWidth;
094: private int actionsWidth;
095:
096: private Color linesColor;
097: private int linesCount;
098: private int maxNumberCount;
099:
100: private Point lastMousePosition = POINT_ZERO;
101: private HotSpot lastHotSpot = null;
102:
103: private List<HotSpot> hotspots = new ArrayList<HotSpot>(0);
104:
105: public LineNumbersActionsBar(DiffContentPanel master,
106: boolean actionsEnabled) {
107: this .master = master;
108: this .actionsEnabled = actionsEnabled;
109: actionsWidth = actionsEnabled ? ACTIONS_BAR_WIDTH : 0;
110: actionIconsHeight = insertImage.getHeight(this );
111: actionIconsWidth = insertImage.getWidth(this );
112: setOpaque(true);
113: setToolTipText(""); // NOI18N
114: master.getMaster().addPropertyChangeListener(this );
115: addMouseMotionListener(this );
116: addMouseListener(this );
117: }
118:
119: public void addNotify() {
120: super .addNotify();
121: initUI();
122: }
123:
124: public void removeNotify() {
125: super .removeNotify();
126: }
127:
128: public void propertyChange(PropertyChangeEvent evt) {
129: repaint();
130: }
131:
132: private Font getLinesFont() {
133: Map coloringMap = EditorUIHelper.getSharedColoringMapFor(master
134: .getEditorPane().getEditorKit().getClass());
135:
136: Object colValue = coloringMap
137: .get(SettingsNames.LINE_NUMBER_COLORING);
138: Coloring col = null;
139: if (colValue != null && colValue instanceof Coloring) {
140: col = (Coloring) colValue;
141: } else {
142: col = SettingsDefaults.defaultLineNumberColoring;
143: }
144:
145: Font font = col.getFont();
146: if (font == null) {
147: font = ((Coloring) coloringMap
148: .get(SettingsNames.DEFAULT_COLORING)).getFont();
149: }
150: return font;
151: }
152:
153: private void initUI() {
154: Map coloringMap = EditorUIHelper.getSharedColoringMapFor(master
155: .getEditorPane().getEditorKit().getClass());
156:
157: Object colValue = coloringMap
158: .get(SettingsNames.LINE_NUMBER_COLORING);
159: Coloring col = null;
160: if (colValue != null && colValue instanceof Coloring) {
161: col = (Coloring) colValue;
162: } else {
163: col = SettingsDefaults.defaultLineNumberColoring;
164: }
165:
166: linesColor = col.getForeColor();
167: if (linesColor == null) {
168: linesColor = ((Coloring) coloringMap
169: .get(SettingsNames.DEFAULT_COLORING))
170: .getForeColor();
171: }
172: Color bg = col.getBackColor();
173: if (bg == null) {
174: bg = ((Coloring) coloringMap
175: .get(SettingsNames.DEFAULT_COLORING))
176: .getBackColor();
177: }
178: setBackground(bg);
179:
180: updateStateOnDocumentChange();
181: }
182:
183: private HotSpot getHotspotAt(Point p) {
184: for (HotSpot hotspot : hotspots) {
185: if (hotspot.getRect().contains(p)) {
186: return hotspot;
187: }
188: }
189: return null;
190: }
191:
192: public String getToolTipText(MouseEvent event) {
193: Point p = event.getPoint();
194: HotSpot spot = getHotspotAt(p);
195: if (spot == null)
196: return null;
197: Difference diff = spot.getDiff();
198: if (diff.getType() == Difference.ADD) {
199: return NbBundle.getMessage(LineNumbersActionsBar.class,
200: "TT_DiffPanel_Remove"); // NOI18N
201: } else if (diff.getType() == Difference.CHANGE) {
202: return NbBundle.getMessage(LineNumbersActionsBar.class,
203: "TT_DiffPanel_Replace"); // NOI18N
204: } else {
205: return NbBundle.getMessage(LineNumbersActionsBar.class,
206: "TT_DiffPanel_Insert"); // NOI18N
207: }
208: }
209:
210: private void performAction(HotSpot spot) {
211: master.getMaster().rollback(spot.getDiff());
212: }
213:
214: public void mouseClicked(MouseEvent e) {
215: if (!e.isPopupTrigger()) {
216: HotSpot spot = getHotspotAt(e.getPoint());
217: if (spot != null) {
218: performAction(spot);
219: }
220: }
221: }
222:
223: public void mousePressed(MouseEvent e) {
224: // not interested
225: }
226:
227: public void mouseReleased(MouseEvent e) {
228: // not interested
229: }
230:
231: public void mouseEntered(MouseEvent e) {
232: // not interested
233: }
234:
235: public void mouseExited(MouseEvent e) {
236: lastMousePosition = POINT_ZERO;
237: if (lastHotSpot != null) {
238: repaint(lastHotSpot.getRect());
239: }
240: lastHotSpot = null;
241: }
242:
243: public void mouseMoved(MouseEvent e) {
244: Point p = e.getPoint();
245: lastMousePosition = p;
246: HotSpot spot = getHotspotAt(p);
247: if (lastHotSpot != spot) {
248: repaint(lastHotSpot == null ? spot.getRect() : lastHotSpot
249: .getRect());
250: }
251: lastHotSpot = spot;
252: setCursor(spot != null ? Cursor
253: .getPredefinedCursor(Cursor.HAND_CURSOR) : Cursor
254: .getDefaultCursor());
255: }
256:
257: public void mouseDragged(MouseEvent e) {
258: // not interested
259: }
260:
261: void onUISettingsChanged() {
262: initUI();
263: updateStateOnDocumentChange();
264: repaint();
265: }
266:
267: private void updateStateOnDocumentChange() {
268: assert SwingUtilities.isEventDispatchThread();
269: StyledDocument doc = (StyledDocument) master.getEditorPane()
270: .getDocument();
271: int lastOffset = doc.getEndPosition().getOffset();
272: linesCount = org.openide.text.NbDocument.findLineNumber(doc,
273: lastOffset);
274:
275: Graphics g = getGraphics();
276: if (g != null)
277: checkLinesWidth(g);
278: maxNumberCount = getNumberCount(linesCount);
279: revalidate();
280: }
281:
282: private int oldLinesWidth;
283:
284: private boolean checkLinesWidth(Graphics g) {
285: FontMetrics fm = g.getFontMetrics(getLinesFont());
286: Rectangle2D rect = fm.getStringBounds(Integer
287: .toString(linesCount), g);
288: linesWidth = (int) rect.getWidth() + LINES_BORDER_WIDTH * 2;
289: if (linesWidth != oldLinesWidth) {
290: oldLinesWidth = linesWidth;
291: revalidate();
292: repaint();
293: return true;
294: }
295: return false;
296: }
297:
298: private int getNumberCount(int n) {
299: int nc = 0;
300: for (; n > 0; n /= 10, nc++)
301: ;
302: return nc;
303: }
304:
305: public Dimension getPreferredScrollableViewportSize() {
306: Dimension dim = master.getEditorPane()
307: .getPreferredScrollableViewportSize();
308: return new Dimension(getBarWidth(), dim.height);
309: }
310:
311: public int getScrollableUnitIncrement(Rectangle visibleRect,
312: int orientation, int direction) {
313: return master.getEditorPane().getScrollableUnitIncrement(
314: visibleRect, orientation, direction);//123
315: }
316:
317: public int getScrollableBlockIncrement(Rectangle visibleRect,
318: int orientation, int direction) {
319: return master.getEditorPane().getScrollableBlockIncrement(
320: visibleRect, orientation, direction);
321: }
322:
323: public boolean getScrollableTracksViewportWidth() {
324: return true;
325: }
326:
327: public boolean getScrollableTracksViewportHeight() {
328: return false;
329: }
330:
331: public Dimension getPreferredSize() {
332: return new Dimension(getBarWidth(), Integer.MAX_VALUE >> 2);
333: }
334:
335: private int getBarWidth() {
336: return actionsWidth + linesWidth;
337: }
338:
339: public void onDiffSetChanged() {
340: updateStateOnDocumentChange();
341: repaint();
342: }
343:
344: protected void paintComponent(Graphics gr) {
345: Graphics2D g = (Graphics2D) gr;
346: Rectangle clip = g.getClipBounds();
347: Stroke cs = g.getStroke();
348:
349: if (checkLinesWidth(gr))
350: return;
351:
352: if (renderingHints == null) {
353: DecoratedEditorPane jep = master.getEditorPane();
354: Class kitClass = jep.getEditorKit().getClass();
355: Object userSetHints = Settings.getValue(kitClass,
356: SettingsNames.RENDERING_HINTS);
357: renderingHints = (userSetHints instanceof Map && ((Map) userSetHints)
358: .size() > 0) ? (Map) userSetHints : null;
359: }
360: if (renderingHints != null) {
361: g.addRenderingHints(renderingHints);
362: }
363:
364: EditorUI editorUI = org.netbeans.editor.Utilities
365: .getEditorUI(master.getEditorPane());
366: int lineHeight = editorUI.getLineHeight();
367:
368: g.setColor(getBackground());
369: g.fillRect(clip.x, clip.y, clip.width, clip.height);
370:
371: g.setColor(Color.LIGHT_GRAY);
372: int x = master.isFirst() ? 0 : getBarWidth() - 1;
373: g.drawLine(x, clip.y, x, clip.y + clip.height - 1);
374:
375: DiffViewManager.DecoratedDifference[] diffs = master
376: .getMaster().getManager().getDecorations();
377:
378: int actionsYOffset = (lineHeight - actionIconsHeight) / 2;
379: int offset = linesWidth;
380:
381: int currentDifference = master.getMaster()
382: .getCurrentDifference();
383: List<HotSpot> newActionIcons = new ArrayList<HotSpot>();
384: if (master.isFirst()) {
385: int idx = 0;
386: for (DiffViewManager.DecoratedDifference dd : diffs) {
387: g.setColor(master.getMaster().getColorLines());
388: g.setStroke(currentDifference == idx ? master
389: .getMaster().getBoldStroke() : cs);
390: g.drawLine(0, dd.getTopLeft(), clip.width, dd
391: .getTopLeft());
392: if (dd.getBottomLeft() != -1) {
393: g.drawLine(0, dd.getBottomLeft(), clip.width, dd
394: .getBottomLeft());
395: }
396: if (actionsEnabled && dd.canRollback()) {
397: if (dd.getDiff().getType() != Difference.ADD) {
398: Rectangle hotSpot = new Rectangle(1, dd
399: .getTopLeft()
400: + actionsYOffset, actionIconsWidth,
401: actionIconsHeight);
402: if (hotSpot.contains(lastMousePosition)
403: || idx == currentDifference) {
404: g.drawImage(insertActiveImage, hotSpot.x,
405: hotSpot.y, this );
406: } else {
407: g.drawImage(insertImage, hotSpot.x,
408: hotSpot.y, this );
409: }
410: newActionIcons.add(new HotSpot(hotSpot, dd
411: .getDiff()));
412: }
413: }
414: idx++;
415: }
416: } else {
417: int idx = 0;
418: for (DiffViewManager.DecoratedDifference dd : diffs) {
419: g.setColor(master.getMaster().getColorLines());
420: g.setStroke(currentDifference == idx ? master
421: .getMaster().getBoldStroke() : cs);
422: g.drawLine(clip.x, dd.getTopRight(), clip.x
423: + clip.width, dd.getTopRight());
424: if (dd.getBottomRight() != -1) {
425: g.drawLine(clip.x, dd.getBottomRight(), clip.x
426: + clip.width, dd.getBottomRight());
427: }
428: if (actionsEnabled && dd.canRollback()) {
429: if (dd.getDiff().getType() == Difference.ADD) {
430: Rectangle hotSpot = new Rectangle(offset + 1,
431: dd.getTopRight() + actionsYOffset,
432: actionIconsWidth, actionIconsHeight);
433: if (hotSpot.contains(lastMousePosition)
434: || idx == currentDifference) {
435: g.drawImage(removeActiveImage, hotSpot.x,
436: hotSpot.y, this );
437: } else {
438: g.drawImage(removeImage, hotSpot.x,
439: hotSpot.y, this );
440: }
441: newActionIcons.add(new HotSpot(hotSpot, dd
442: .getDiff()));
443: }
444: }
445: idx++;
446: }
447: }
448:
449: hotspots = newActionIcons;
450:
451: int linesXOffset = master.isFirst() ? actionsWidth : 0;
452: linesXOffset += LINES_BORDER_WIDTH;
453:
454: g.setFont(getLinesFont());
455: g.setColor(linesColor);
456: int lineNumber = clip.y / lineHeight;
457: int yOffset = lineNumber * lineHeight;
458: yOffset -= lineHeight / 4; // baseline correction
459: int linesDrawn = clip.height / lineHeight + 3; // draw past clipping rectangle to avoid partially drawn numbers
460: int docLines = Utilities.getRowCount((BaseDocument) master
461: .getEditorPane().getDocument());
462: if (lineNumber + linesDrawn - 1 > docLines) {
463: linesDrawn = docLines - lineNumber + 1;
464: }
465: for (int i = 0; i < linesDrawn; i++) {
466: g.drawString(formatLineNumber(lineNumber), linesXOffset,
467: yOffset);
468: lineNumber++;
469: yOffset += lineHeight;
470: }
471: }
472:
473: private String formatLineNumber(int lineNumber) {
474: String strNumber = Integer.toString(lineNumber);
475: int nc = getNumberCount(lineNumber);
476: if (nc < maxNumberCount) {
477: StringBuilder sb = new StringBuilder(10);
478: sb.append(lineNumberPadding, 0, maxNumberCount - nc);
479: sb.append(strNumber);
480: return sb.toString();
481: } else {
482: return strNumber;
483: }
484: }
485:
486: private static class EditorUIHelper extends EditorUI {
487: /**
488: * Gets the coloring map that can be shared by the components
489: * with the same kit. Only the component coloring map is provided.
490: */
491: public static Map getSharedColoringMapFor(Class kitClass) {
492: return EditorUIHelper.getSharedColoringMap(kitClass);
493: }
494: }
495: }
|