1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * ident "%Z%%M% %I% %E% SMI" 24 * 25 * Copyright (c) 2000 by Sun Microsystems, Inc. 26 * All rights reserved. 27 */ 28 29 /* 30 * Copyright (C) 1996 Active Software, Inc. 31 * All rights reserved. 32 * 33 * @(#) TextView.java 1.29 - last change made 08/12/97 34 */ 35 36 package sunsoft.jws.visual.rt.awt; 37 38 import sunsoft.jws.visual.rt.base.Global; 39 40 import java.awt.*; 41 import java.util.Vector; 42 import java.util.Hashtable; 43 44 public class TextView extends VJCanvas implements Scrollable { 45 private static final int RIGHT_MOUSE = 4; 46 47 protected Vector items; 48 49 protected int fontHeight, lineWidth, lineHeight; 50 protected FontMetrics fontMetrics; 51 protected int minrows = 10; 52 protected int mincolumns = 15; 53 protected int minWidth = 0; 54 55 static protected final int textIndent = 6; 56 static protected final int textBorder = 2; 57 static protected final int viewBorder = 0; 58 static protected final int viewIPad = 2; 59 60 private int selected[] = new int[0]; 61 private int numSelected; 62 63 private int scrollx = 0; 64 private int scrolly = 0; 65 private Image buffer; 66 private boolean multipleSelections; 67 private Hashtable stringWidthTable; 68 69 private boolean menuMode; 70 private CLChoice menuChoice; 71 boolean menuDrag; 72 73 private boolean gotEventInside; 74 private int prevMenuY; 75 TextView()76 public TextView() { 77 stringWidthTable = new Hashtable(); 78 // why was the background hardcoded to white? 79 // setBackground(Color.white); 80 } 81 82 // 83 // Accessor methods. These are forwarded from the TextList class. 84 // setMinimumRows(int num)85 public void setMinimumRows(int num) { 86 minrows = num; 87 } 88 getMinimumRows()89 public int getMinimumRows() { 90 return minrows; 91 } 92 setMinimumColumns(int num)93 public void setMinimumColumns(int num) { 94 mincolumns = num; 95 } 96 getMinimumColumns()97 public int getMinimumColumns() { 98 return mincolumns; 99 } 100 getRows()101 public int getRows() { 102 if (lineHeight == 0) 103 return 0; 104 105 Dimension size = size(); 106 int h = size.height - (viewBorder + viewIPad); 107 return ((h+lineHeight-1)/lineHeight); 108 } 109 updateView()110 public void updateView() { 111 for (int i = 0; i < numSelected; i++) { 112 if (selected[i] >= items.size()) { 113 shift(selected, i+1, numSelected, -1); 114 numSelected--; 115 } 116 } 117 118 cacheMinWidth(); 119 repaint(); 120 } 121 shift(int[] data, int offset, int length, int shift)122 private void shift(int[] data, int offset, int length, int shift) { 123 System.arraycopy(data, offset, data, offset+shift, 124 length-offset); 125 } 126 select(int index)127 public void select(int index) { 128 if (index >= items.size()) 129 return; 130 if (index < -1) 131 return; 132 133 if (!multipleSelections) { 134 if (index == -1) { 135 if (numSelected != 0) { 136 numSelected = 0; 137 repaint(); 138 } 139 } else { 140 if (numSelected == 0) { 141 selected = ensureCapacity(selected, numSelected, 1); 142 numSelected = 1; 143 selected[0] = -1; 144 } 145 if (selected[0] != index) { 146 selected[0] = index; 147 repaint(); 148 } 149 } 150 } else { 151 if (index == -1) 152 return; 153 154 boolean inserted = false; 155 for (int i = 0; i < numSelected; i++) { 156 if (index == selected[i]) { 157 inserted = true; 158 break; 159 } else if (index < selected[i]) { 160 inserted = true; 161 selected = ensureCapacity(selected, 162 numSelected, numSelected+1); 163 shift(selected, i, numSelected, 1); 164 selected[i] = index; 165 numSelected++; 166 repaint(); 167 break; 168 } 169 } 170 171 if (!inserted) { 172 selected = ensureCapacity(selected, numSelected, 173 numSelected+1); 174 selected[numSelected] = index; 175 numSelected++; 176 repaint(); 177 } 178 } 179 } 180 select(Object item)181 public void select(Object item) { 182 if (item != null) 183 select(items.indexOf(item)); 184 } 185 deselect(int index)186 public void deselect(int index) { 187 if (index < 0 || index >= items.size()) 188 return; 189 190 for (int i = 0; i < numSelected; i++) { 191 if (selected[i] == index) { 192 shift(selected, i+1, numSelected, -1); 193 numSelected--; 194 repaint(); 195 break; 196 } 197 } 198 } 199 deselectAll()200 public void deselectAll() { 201 if (numSelected != 0) { 202 numSelected = 0; 203 repaint(); 204 } 205 } 206 isSelected(int index)207 public boolean isSelected(int index) { 208 for (int i = 0; i < numSelected; i++) { 209 if (selected[i] == index) 210 return true; 211 } 212 213 return false; 214 } 215 setMultipleSelections(boolean v)216 public void setMultipleSelections(boolean v) { 217 multipleSelections = v; 218 } 219 allowsMultipleSelections()220 public boolean allowsMultipleSelections() { 221 return multipleSelections; 222 } 223 getSelectedIndex()224 public int getSelectedIndex() { 225 if (numSelected == 0) 226 return -1; 227 else 228 return selected[0]; 229 } 230 getSelectedIndexes()231 public int[] getSelectedIndexes() { 232 int[] data = new int[numSelected]; 233 System.arraycopy(selected, 0, data, 0, numSelected); 234 return data; 235 } 236 getSelectedItem()237 public Object getSelectedItem() { 238 if (numSelected == 0) 239 return null; 240 else 241 return items.elementAt(selected[0]); 242 } 243 getSelectedItems()244 public Object[] getSelectedItems() { 245 Object[] data = new Object[numSelected]; 246 for (int i = 0; i < numSelected; i++) 247 data[i] = items.elementAt(selected[i]); 248 return data; 249 } 250 ensureCapacity(int[] elementData, int elementCount, int minCapacity)251 private int[] ensureCapacity(int[] elementData, int elementCount, 252 int minCapacity) { 253 int oldCapacity = elementData.length; 254 if (minCapacity > oldCapacity) { 255 int oldData[] = elementData; 256 int newCapacity = oldCapacity * 2; 257 if (newCapacity < minCapacity) { 258 newCapacity = minCapacity; 259 } 260 elementData = new int[newCapacity]; 261 System.arraycopy(oldData, 0, elementData, 0, elementCount); 262 } 263 264 return elementData; 265 } 266 267 // 268 // Package private accessor methods 269 // items(Vector items)270 protected void items(Vector items) { 271 this.items = items; 272 } 273 274 // 275 // Component methods 276 // minimumSize()277 public Dimension minimumSize() { 278 int bd = getBD(); 279 return new Dimension(minWidth + bd, (minrows * lineHeight) 280 + bd); 281 } 282 preferredSize()283 public Dimension preferredSize() { 284 return minimumSize(); 285 } 286 287 // 288 // Scrollable methods 289 // scrollX(int x)290 public void scrollX(int x) { 291 scrollx = x; 292 repaint(); 293 } 294 scrollY(int y)295 public void scrollY(int y) { 296 scrolly = y; 297 repaint(); 298 } 299 scrollSize()300 public Dimension scrollSize() { 301 return new Dimension(minWidth, items.size()*lineHeight); 302 } 303 viewSize(Dimension size)304 public Dimension viewSize(Dimension size) { 305 int bd = getBD(); 306 size.width -= bd; 307 size.height -= bd; 308 return size; 309 } 310 lineHeight()311 public int lineHeight() { 312 return lineHeight; 313 } 314 getBD()315 private int getBD() { 316 return 2 * (viewBorder + viewIPad); 317 } 318 319 // 320 // Event handling for selections 321 // mouseDown(Event e, int x, int y)322 public boolean mouseDown(Event e, int x, int y) { 323 // 324 // On Windows95, we sometimes get bogus 325 // mouseDown events. This happens 326 // when the TextView is being used as a 327 // menu for the CLChoice component. 328 // The user presses the mouse over the CLChoice 329 // item in the list, causing 330 // the menu to be mapped. Then, without 331 // releasing the mouse, the user 332 // drags the mouse into the menu. This 333 // sometimes causes a bogus 334 // mouseDown event to be sent to the menu /* JSTYLED */ 335 // (actually to the TextView 336 // inside the menu). 337 // 338 // The workaround is to ignore mouseDown 339 // events that occur before there 340 // has been either a mouseDrag or a 341 // mouseMove event. This check is only 342 // made if menuChoice is not null /* JSTYLED */ 343 // (indicating that this TextView is being 344 // used as a CLChoice menu). 345 // 346 if (menuChoice != null && !menuDrag) 347 return true; 348 349 selectY(e, true); 350 351 menuMode = false; 352 menuChoice = null; 353 menuDrag = false; 354 355 return true; 356 } 357 mouseDrag(Event e, int x, int y)358 public boolean mouseDrag(Event e, int x, int y) { 359 // Workaround for bug observed on WindowsNT 360 // where you get spurious 361 // mouse drag events when pressing the mouse. 362 // The spurious event 363 // has coordinates x=-1 and y=-1. 364 if (!Global.isWindows() || e.y != -1) { 365 if (menuMode) { 366 menuDrag = true; 367 menuEvent(e); 368 } else if (!multipleSelections) { 369 selectY(e, true); 370 } 371 } 372 373 return true; 374 } 375 mouseUp(Event e, int x, int y)376 public boolean mouseUp(Event e, int x, int y) { 377 if (menuMode) 378 menuEvent(e); 379 380 return true; 381 } 382 mouseMove(Event e, int x, int y)383 public boolean mouseMove(Event e, int x, int y) { 384 if (menuMode) { 385 menuDrag = true; 386 menuEvent(e); 387 return true; 388 } else { 389 return false; 390 } 391 } 392 selectY(Event e, boolean doPost)393 private void selectY(Event e, boolean doPost) { 394 int evtX = e.x; 395 int evtY = e.y + scrolly - (viewBorder + viewIPad); 396 int index = evtY/lineHeight; 397 int size = items.size(); 398 int id; 399 400 if (size == 0) 401 return; 402 if (index >= size) 403 index = size-1; 404 if (index < 0) 405 index = 0; 406 407 if (multipleSelections) { 408 if (isSelected(index)) { 409 id = Event.LIST_DESELECT; 410 deselect(index); 411 } else { 412 id = Event.LIST_SELECT; 413 select(index); 414 } 415 416 if (doPost) { 417 Event evt = new Event(getParent(), id, 418 items.elementAt(index)); 419 if (menuChoice != null) 420 menuChoice.handleEvent(evt); 421 else 422 postEvent(evt); 423 } 424 } else { 425 id = Event.LIST_SELECT; 426 427 // 428 // Ignore double-clicks on Windows because 429 // they are sent spuriously. 430 // 431 if ((e.clickCount == 2 && !Global.isWindows()) || 432 e.modifiers == RIGHT_MOUSE) { 433 id = Event.ACTION_EVENT; 434 } 435 436 if (!isSelected(index)) { 437 select(index); 438 repaint(); 439 if (doPost) { 440 Event evt = new Event(getParent(), id, 441 items.elementAt(index)); 442 if (menuChoice != null) 443 menuChoice.handleEvent(evt); 444 else 445 postEvent(evt); 446 } 447 } else if (e.id == Event.MOUSE_DOWN || 448 e.id == Event.MOUSE_UP) { 449 if (doPost) { 450 Event evt = new Event(getParent(), id, 451 items.elementAt(index)); 452 if (menuChoice != null) 453 menuChoice.handleEvent(evt); 454 else 455 postEvent(evt); 456 } 457 } 458 } 459 } 460 461 // 462 // Painting 463 // reshape(int x, int y, int width, int height)464 public void reshape(int x, int y, int width, int height) { 465 super.reshape(x, y, width, height); 466 cacheLineWidth(); 467 468 if (width <= 0 || height <= 0) 469 return; 470 471 // Create the image used for double-buffering 472 if (buffer == null || 473 (width != buffer.getWidth(this) || 474 height != buffer.getHeight(this))) 475 buffer = createImage(width, height); 476 } 477 update(Graphics g)478 public void update(Graphics g) { 479 paint(g); 480 } 481 paint(Graphics g)482 public void paint(Graphics g) { 483 if (buffer == null) 484 return; 485 486 g = buffer.getGraphics(); 487 g.setFont(getFont()); 488 489 Dimension d = size(); 490 491 g.setColor(getBackground()); 492 g.fillRect(0, 0, d.width, d.height); 493 494 if (isEnabled()) 495 g.setColor(getForeground()); 496 else 497 g.setColor(getBackground().darker()); 498 drawItems(g); 499 500 g.setColor(getBackground()); 501 drawBorder(g); 502 503 g = getGraphics(); 504 g.drawImage(buffer, 0, 0, this); 505 } 506 drawItems(Graphics g)507 private void drawItems(Graphics g) { 508 Dimension d = size(); 509 int size = items.size(); 510 511 int viewTop, viewBottom, lineTop, lineBottom; 512 int bd = viewBorder + viewIPad; 513 int yoff; 514 515 viewTop = scrolly; 516 viewBottom = scrolly + d.height; 517 518 for (int i = 0; i < size; i++) { 519 lineTop = i*lineHeight; 520 lineBottom = lineTop + lineHeight; 521 522 if (lineTop > viewBottom || lineBottom < viewTop) 523 continue; 524 525 yoff = lineTop - viewTop + bd; 526 drawLine(g, i, -scrollx+bd, yoff); 527 } 528 } 529 drawLine(Graphics g, int index, int xoff, int yoff)530 protected void drawLine(Graphics g, int index, int xoff, int yoff) { 531 String name = (String)items.elementAt(index); 532 533 int x = textIndent; 534 int y = (lineHeight + fontHeight)/2 - 1; 535 536 if (isSelected(index)) { 537 g.setColor(new Color(0, 0, 128)); 538 g.fillRect(xoff, yoff, lineWidth, lineHeight); 539 g.setColor(Color.white); 540 } 541 542 // Useful for pixel debugging 543 // g.drawRect(xoff, yoff, lineWidth-1, lineHeight-1); 544 545 g.drawString(name, x+xoff, y+yoff); 546 547 if (isSelected(index)) { 548 g.setColor(getForeground()); 549 } 550 } 551 drawBorder(Graphics g)552 private void drawBorder(Graphics g) { 553 Dimension size = size(); 554 555 for (int i = 0; i < viewIPad; i++) 556 g.drawRect(viewBorder+i, viewBorder+i, 557 size.width-1-2*(i+viewBorder), 558 size.height-1-2*(i+viewBorder)); 559 } 560 addNotify()561 public void addNotify() { 562 super.addNotify(); 563 cacheAll(); 564 } 565 setFont(Font f)566 public void setFont(Font f) { 567 super.setFont(f); 568 569 stringWidthTable.clear(); 570 if (getPeer() != null) 571 cacheAll(); 572 } 573 cacheAll()574 private void cacheAll() { 575 cacheLineHeight(); 576 cacheMinWidth(); 577 } 578 579 // 580 // Need to call this when the list of items 581 // changes, the font changes, 582 // or the mincolumns changes. The mincolumns 583 // change should be followed 584 // by a call to updateView for the change 585 // to take effect. It is only 586 // necessary to call updateView if addNotify 587 // has not yet been called. 588 // cacheMinWidth()589 protected void cacheMinWidth() { 590 minWidth = mincolumns * getStringWidth(/* NOI18N */"0"); 591 592 int count = items.size(); 593 for (int i = 0; i < count; i++) 594 minWidth = Math.max(minWidth, 595 getStringWidth((String)items.elementAt(i))); 596 597 minWidth += textIndent * 2; 598 cacheLineWidth(); 599 } 600 getStringWidth(String s)601 protected int getStringWidth(String s) { 602 if (fontMetrics == null) 603 return 0; 604 605 Integer val = (Integer)stringWidthTable.get(s); 606 if (val == null) { 607 val = new Integer(fontMetrics.stringWidth(s)); 608 stringWidthTable.put(s, val); 609 } 610 611 return val.intValue(); 612 } 613 614 // 615 // Need to call this when the size 616 // changes and when the minWidth changes. 617 // cacheLineWidth()618 protected void cacheLineWidth() { 619 Dimension size = size(); 620 int bd = getBD(); 621 lineWidth = Math.max(minWidth, size.width-bd); 622 } 623 624 // 625 // Need to call this when the font changes. 626 // cacheLineHeight()627 protected void cacheLineHeight() { 628 lineHeight = 0; 629 Graphics g = getGraphics(); 630 if (g == null) 631 return; 632 633 Font f = getFont(); 634 if (f == null) 635 return; 636 637 fontMetrics = g.getFontMetrics(f); 638 fontHeight = fontMetrics.getMaxAscent(); 639 640 lineHeight = fontHeight + 2*textBorder; 641 } 642 643 // 644 // Methods used by CLChoice 645 // 646 menuMode(CLChoice choice)647 void menuMode(CLChoice choice) { 648 menuChoice = choice; 649 menuMode = true; 650 menuDrag = false; 651 gotEventInside = false; 652 prevMenuY = 0; 653 } 654 menuEvent(Event e)655 private void menuEvent(Event e) { 656 if (checkBounds(e)) { 657 selectY(e, (e.id == Event.MOUSE_UP)); 658 659 // Auto-scrolling 660 int bd = getBD(); 661 if (e.id != Event.MOUSE_MOVE && 662 ((e.y < bd && e.y < prevMenuY) || 663 (e.y > (size().height-bd) && e.y > prevMenuY))) { 664 ((TextList)getParent()).makeVisible(getSelectedIndex()); 665 } 666 667 prevMenuY = e.y; 668 } 669 } 670 checkBounds(Event e)671 private boolean checkBounds(Event e) { 672 if (!gotEventInside) { 673 Dimension d = size(); 674 int bd = getBD(); 675 if (e.x >= bd && e.y >= bd && 676 e.x <= (d.width-bd) && e.y <= (d.height-bd)) 677 gotEventInside = true; 678 } 679 680 return gotEventInside; 681 } 682 } 683