1 /* 2 * $Id: EditorRuler.java,v 1.2 2009-11-24 12:00:28 gaudenz Exp $ 3 * Copyright (c) 2001-2005, Gaudenz Alder 4 * 5 * All rights reserved. 6 * 7 * See LICENSE file for license details. If you are unable to locate 8 * this file please contact info (at) jgraph (dot) com. 9 */ 10 package com.mxgraph.examples.swing.editor; 11 12 import java.awt.Color; 13 import java.awt.Dimension; 14 import java.awt.Font; 15 import java.awt.Graphics; 16 import java.awt.Graphics2D; 17 import java.awt.Point; 18 import java.awt.Rectangle; 19 import java.awt.RenderingHints; 20 import java.awt.dnd.DropTarget; 21 import java.awt.dnd.DropTargetDragEvent; 22 import java.awt.dnd.DropTargetDropEvent; 23 import java.awt.dnd.DropTargetEvent; 24 import java.awt.dnd.DropTargetListener; 25 import java.awt.event.MouseEvent; 26 import java.awt.event.MouseMotionListener; 27 import java.awt.geom.AffineTransform; 28 import java.awt.geom.Point2D; 29 import java.text.NumberFormat; 30 import java.util.TooManyListenersException; 31 32 import javax.swing.BorderFactory; 33 import javax.swing.JComponent; 34 35 import com.mxgraph.swing.mxGraphComponent; 36 import com.mxgraph.util.mxEvent; 37 import com.mxgraph.util.mxEventObject; 38 import com.mxgraph.util.mxPoint; 39 import com.mxgraph.util.mxEventSource.mxIEventListener; 40 import com.mxgraph.view.mxGraph; 41 42 /** 43 * Component that displays a ruler for a JGraph component. 44 */ 45 public class EditorRuler extends JComponent implements MouseMotionListener, 46 DropTargetListener 47 { 48 49 /** 50 * 51 */ 52 private static final long serialVersionUID = -6310912355878668096L; 53 54 /** 55 * Defines the constants for horizontal and vertical orientation. 56 */ 57 public static int ORIENTATION_HORIZONTAL = 0, ORIENTATION_VERTICAL = 1; 58 59 /** 60 * Internal constant used to describe the screen resolution (DPI). Default 61 * is 72. 62 */ 63 protected static int INCH = 72; 64 65 /** 66 * Internal constant used to describe the screen resolution (DPI). Default 67 * is 72. 68 */ 69 protected static int DEFAULT_PAGESCALE = 1; 70 71 /** 72 * Internal constant used to describe the screen resolution (DPI). Default 73 * is 72. 74 */ 75 protected static boolean DEFAULT_ISMETRIC = true; 76 77 /** 78 * Holds the shared number formatter. 79 * 80 * @see NumberFormat#getInstance() 81 */ 82 public static final NumberFormat numberFormat = NumberFormat.getInstance(); 83 84 /** 85 * Configuers the number format. 86 */ 87 static 88 { 89 numberFormat.setMaximumFractionDigits(2); 90 } 91 92 /** 93 * Defines the inactive background border. Default is a not-so-dark gray. 94 */ 95 protected Color inactiveBackground = new Color(170, 170, 170); 96 97 /** 98 * Specifies the orientation. 99 */ 100 protected int orientation = ORIENTATION_HORIZONTAL; 101 102 /** 103 * Specified that start and length of the active region, ie the region to 104 * paint with the background border. This is used for example to indicate 105 * the printable region of a graph. 106 */ 107 protected int activeoffset, activelength; 108 109 /** 110 * Specifies the scale for the metrics. Default is 111 * {@link JGraphEditorDiagramPane#DEFAULT_PAGESCALE}. 112 */ 113 protected double scale = DEFAULT_PAGESCALE; 114 115 /** 116 * Specifies the unit system. Default is 117 * {@link JGraphEditorDiagramPane#DEFAULT_ISMETRIC}. 118 */ 119 protected boolean metric = DEFAULT_ISMETRIC; 120 121 /** 122 * 123 */ 124 protected Font labelFont = new Font("Tahoma", Font.PLAIN, 9); 125 126 /** 127 * Specifies height or width of the ruler. Default is 15 pixels. 128 */ 129 protected int rulerSize = 16; 130 131 /** 132 * Specifies the minimum distance between two major ticks. Default is 30. 133 */ 134 protected int tickDistance = 30; 135 136 /** 137 * Reference to the attached graph. 138 */ 139 protected mxGraphComponent graphComponent; 140 141 /** 142 * Holds the current and first mouse point. 143 */ 144 protected Point mouse = new Point(); 145 146 /** 147 * Parameters to control the display. 148 */ 149 protected double increment, units; 150 151 /** 152 * 153 */ 154 protected transient mxIEventListener repaintHandler = new mxIEventListener() 155 { 156 public void invoke(Object source, mxEventObject evt) 157 { 158 repaint(); 159 } 160 }; 161 162 /** 163 * Constructs a new ruler for the specified graph and orientation. 164 * 165 * @param graph 166 * The graph to create the ruler for. 167 * @param orientation 168 * The orientation to use for the ruler. 169 */ EditorRuler(mxGraphComponent graphComponent, int orientation)170 public EditorRuler(mxGraphComponent graphComponent, int orientation) 171 { 172 this.orientation = orientation; 173 this.graphComponent = graphComponent; 174 updateIncrementAndUnits(); 175 176 graphComponent.getGraph().getView().addListener( 177 mxEvent.SCALE, repaintHandler); 178 graphComponent.getGraph().getView().addListener( 179 mxEvent.TRANSLATE, repaintHandler); 180 graphComponent.getGraph().getView().addListener( 181 mxEvent.SCALE_AND_TRANSLATE, repaintHandler); 182 183 graphComponent.getGraphControl().addMouseMotionListener(this); 184 185 DropTarget dropTarget = graphComponent.getDropTarget(); 186 187 try 188 { 189 if (dropTarget != null) 190 { 191 dropTarget.addDropTargetListener(this); 192 } 193 } 194 catch (TooManyListenersException tmle) 195 { 196 // should not happen... swing drop target is multicast 197 } 198 199 setBorder(BorderFactory.createLineBorder(Color.black)); 200 } 201 202 /** 203 * Sets the start of the active region in pixels. 204 * 205 * @param offset 206 * The start of the active region. 207 */ setActiveOffset(int offset)208 public void setActiveOffset(int offset) 209 { 210 activeoffset = (int) (offset * scale); 211 } 212 213 /** 214 * Sets the length of the active region in pixels. 215 * 216 * @param length 217 * The length of the active region. 218 */ setActiveLength(int length)219 public void setActiveLength(int length) 220 { 221 activelength = (int) (length * scale); 222 } 223 224 /** 225 * Returns true if the ruler uses metric units. 226 * 227 * @return Returns if the ruler is metric. 228 */ isMetric()229 public boolean isMetric() 230 { 231 return metric; 232 } 233 234 /** 235 * Sets if the ruler uses metric units. 236 * 237 * @param isMetric 238 * Whether to use metric units. 239 */ setMetric(boolean isMetric)240 public void setMetric(boolean isMetric) 241 { 242 this.metric = isMetric; 243 updateIncrementAndUnits(); 244 repaint(); 245 } 246 247 /** 248 * Returns the ruler's horizontal or vertical size. 249 * 250 * @return Returns the rulerSize. 251 */ getRulerSize()252 public int getRulerSize() 253 { 254 return rulerSize; 255 } 256 257 /** 258 * Sets the ruler's horizontal or vertical size. 259 * 260 * @param rulerSize 261 * The rulerSize to set. 262 */ setRulerSize(int rulerSize)263 public void setRulerSize(int rulerSize) 264 { 265 this.rulerSize = rulerSize; 266 } 267 268 /** 269 * 270 */ setTickDistance(int tickDistance)271 public void setTickDistance(int tickDistance) 272 { 273 this.tickDistance = tickDistance; 274 } 275 276 /** 277 * 278 */ getTickDistance()279 public int getTickDistance() 280 { 281 return tickDistance; 282 } 283 284 /** 285 * Returns the preferred size by replacing the respective component of the 286 * graph's preferred size with {@link #rulerSize}. 287 * 288 * @return Returns the preferred size for the ruler. 289 */ getPreferredSize()290 public Dimension getPreferredSize() 291 { 292 Dimension dim = graphComponent.getGraphControl().getPreferredSize(); 293 294 if (orientation == ORIENTATION_VERTICAL) 295 { 296 dim.width = rulerSize; 297 } 298 else 299 { 300 dim.height = rulerSize; 301 } 302 303 return dim; 304 } 305 306 /* 307 * (non-Javadoc) 308 * @see java.awt.dnd.DropTargetListener#dragEnter(java.awt.dnd.DropTargetDragEvent) 309 */ dragEnter(DropTargetDragEvent arg0)310 public void dragEnter(DropTargetDragEvent arg0) 311 { 312 // empty 313 } 314 315 /* 316 * (non-Javadoc) 317 * @see java.awt.dnd.DropTargetListener#dragExit(java.awt.dnd.DropTargetEvent) 318 */ dragExit(DropTargetEvent arg0)319 public void dragExit(DropTargetEvent arg0) 320 { 321 // empty 322 } 323 324 /* 325 * (non-Javadoc) 326 * @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent) 327 */ dragOver(final DropTargetDragEvent arg0)328 public void dragOver(final DropTargetDragEvent arg0) 329 { 330 updateMousePosition(arg0.getLocation()); 331 } 332 333 /* 334 * (non-Javadoc) 335 * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent) 336 */ drop(DropTargetDropEvent arg0)337 public void drop(DropTargetDropEvent arg0) 338 { 339 // empty 340 } 341 342 /* 343 * (non-Javadoc) 344 * @see java.awt.dnd.DropTargetListener#dropActionChanged(java.awt.dnd.DropTargetDragEvent) 345 */ dropActionChanged(DropTargetDragEvent arg0)346 public void dropActionChanged(DropTargetDragEvent arg0) 347 { 348 // empty 349 } 350 351 /* 352 * (non-Javadoc) 353 */ mouseMoved(MouseEvent e)354 public void mouseMoved(MouseEvent e) 355 { 356 updateMousePosition(e.getPoint()); 357 } 358 359 /* 360 * (non-Javadoc) 361 */ mouseDragged(MouseEvent e)362 public void mouseDragged(MouseEvent e) 363 { 364 updateMousePosition(e.getPoint()); 365 } 366 367 /** 368 * Repaints the mouse position. 369 */ updateMousePosition(Point pt)370 protected void updateMousePosition(Point pt) 371 { 372 Point old = mouse; 373 mouse = pt; 374 repaint(old.x, old.y); 375 repaint(mouse.x, mouse.y); 376 } 377 378 /** 379 * Updates the local variables used for painting based on the current scale 380 * and unit system. 381 */ updateIncrementAndUnits()382 protected void updateIncrementAndUnits() 383 { 384 double graphScale = graphComponent.getGraph().getView().getScale(); 385 386 if (metric) 387 { 388 units = INCH / 2.54; // 2.54 dots per centimeter 389 units *= graphComponent.getPageScale() * graphScale; 390 increment = units; 391 } 392 else 393 { 394 units = INCH; 395 units *= graphComponent.getPageScale() * graphScale; 396 increment = units / 2; 397 } 398 } 399 400 /** 401 * Repaints the ruler between the specified 0 and x or y depending on the 402 * orientation. 403 * 404 * @param x 405 * The endpoint for repainting a horizontal ruler. 406 * @param y 407 * The endpoint for repainting a vertical ruler. 408 */ repaint(int x, int y)409 public void repaint(int x, int y) 410 { 411 if (orientation == ORIENTATION_VERTICAL) 412 { 413 repaint(0, y, rulerSize, 1); 414 } 415 else 416 { 417 repaint(x, 0, 1, rulerSize); 418 } 419 } 420 421 /** 422 * Paints the ruler. 423 * 424 * @param g 425 * The graphics to paint the ruler to. 426 */ paintComponent(Graphics g)427 public void paintComponent(Graphics g) 428 { 429 mxGraph graph = graphComponent.getGraph(); 430 Rectangle clip = g.getClipBounds(); 431 updateIncrementAndUnits(); 432 433 // Fills clipping area with background. 434 if (activelength > 0 && inactiveBackground != null) 435 { 436 g.setColor(inactiveBackground); 437 } 438 else 439 { 440 g.setColor(getBackground()); 441 } 442 443 g.fillRect(clip.x, clip.y, clip.width, clip.height); 444 445 // Draws the active region. 446 g.setColor(getBackground()); 447 Point2D p = new Point2D.Double(activeoffset, activelength); 448 449 if (orientation == ORIENTATION_HORIZONTAL) 450 { 451 g.fillRect((int) p.getX(), clip.y, (int) p.getY(), clip.height); 452 } 453 else 454 { 455 g.fillRect(clip.x, (int) p.getX(), clip.width, (int) p.getY()); 456 } 457 458 double left = clip.getX(); 459 double top = clip.getY(); 460 double right = left + clip.getWidth(); 461 double bottom = top + clip.getHeight(); 462 463 // Fetches some global display state information 464 mxPoint trans = graph.getView().getTranslate(); 465 double scale = graph.getView().getScale(); 466 double tx = trans.getX() * scale; 467 double ty = trans.getY() * scale; 468 469 // Sets the distance of the grid lines in pixels 470 double stepping = increment; 471 472 if (stepping < tickDistance) 473 { 474 int count = (int) Math 475 .round(Math.ceil(tickDistance / stepping) / 2) * 2; 476 stepping = count * stepping; 477 } 478 479 // Creates a set of strokes with individual dash offsets 480 // for each direction 481 ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 482 RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 483 g.setFont(labelFont); 484 g.setColor(Color.black); 485 486 int smallTick = rulerSize - rulerSize / 3; 487 int middleTick = rulerSize / 2; 488 489 // TODO: Merge into a single drawing loop for both orientations 490 if (orientation == ORIENTATION_HORIZONTAL) 491 { 492 double xs = Math.floor((left - tx) / stepping) * stepping + tx; 493 double xe = Math.ceil(right / stepping) * stepping; 494 xe += (int) Math.ceil(stepping); 495 496 for (double x = xs; x <= xe; x += stepping) 497 { 498 // FIXME: Workaround for rounding errors when adding stepping to 499 // xs or ys multiple times (leads to double grid lines when zoom 500 // is set to eg. 121%) 501 double xx = Math.round((x - tx) / stepping) * stepping + tx; 502 503 int ix = (int) Math.round(xx); 504 g.drawLine(ix, rulerSize, ix, 0); 505 506 String text = format((x - tx) / increment); 507 g.drawString(text, ix + 2, labelFont.getSize()); 508 509 ix += (int) Math.round(stepping / 4); 510 g.drawLine(ix, rulerSize, ix, smallTick); 511 512 ix += (int) Math.round(stepping / 4); 513 g.drawLine(ix, rulerSize, ix, middleTick); 514 515 ix += (int) Math.round(stepping / 4); 516 g.drawLine(ix, rulerSize, ix, smallTick); 517 } 518 } 519 else 520 { 521 double ys = Math.floor((top - ty) / stepping) * stepping + ty; 522 double ye = Math.ceil(bottom / stepping) * stepping; 523 ye += (int) Math.ceil(stepping); 524 525 for (double y = ys; y <= ye; y += stepping) 526 { 527 // FIXME: Workaround for rounding errors when adding stepping to 528 // xs or ys multiple times (leads to double grid lines when zoom 529 // is set to eg. 121%) 530 y = Math.round((y - ty) / stepping) * stepping + ty; 531 532 int iy = (int) Math.round(y); 533 g.drawLine(rulerSize, iy, 0, iy); 534 535 String text = format((y - ty) / increment); 536 537 // Rotates the labels in the vertical ruler 538 AffineTransform at = ((Graphics2D) g).getTransform(); 539 ((Graphics2D) g).rotate(-Math.PI / 2, 0, iy); 540 g.drawString(text, 1, iy + labelFont.getSize()); 541 ((Graphics2D) g).setTransform(at); 542 543 iy += (int) Math.round(stepping / 4); 544 g.drawLine(rulerSize, iy, smallTick, iy); 545 546 iy += (int) Math.round(stepping / 4); 547 g.drawLine(rulerSize, iy, middleTick, iy); 548 549 iy += (int) Math.round(stepping / 4); 550 g.drawLine(rulerSize, iy, smallTick, iy); 551 } 552 } 553 554 // Draw Mouseposition 555 g.setColor(Color.green); 556 557 if (orientation == ORIENTATION_HORIZONTAL) 558 { 559 g.drawLine(mouse.x, rulerSize, mouse.x, 0); 560 } 561 else 562 { 563 g.drawLine(rulerSize, mouse.y, 0, mouse.y); 564 } 565 } 566 567 /** 568 * Fixes the formatting of -0. 569 */ format(double value)570 private final String format(double value) 571 { 572 String text = numberFormat.format(value); 573 574 if (text.equals("-0")) 575 { 576 text = "0"; 577 } 578 579 return text; 580 } 581 582 }