1 /* 2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.awt.X11; 27 28 import java.awt.*; 29 import java.awt.event.MouseEvent; 30 import java.awt.event.MouseWheelEvent; 31 import java.awt.event.AdjustmentEvent; 32 import java.util.ArrayList; 33 import java.util.Iterator; 34 import sun.util.logging.PlatformLogger; 35 36 // FIXME: implement multi-select 37 /* 38 * Class to paint a list of items, possibly with scrollbars 39 * This class paints all items with the same font 40 * For now, this class manages the list of items and painting thereof, but not 41 * posting of Item or ActionEvents 42 */ 43 public class ListHelper implements XScrollbarClient { 44 private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.ListHelper"); 45 46 private final int FOCUS_INSET = 1; 47 48 private final int BORDER_WIDTH; // Width of border drawn around the list 49 // of items 50 private final int ITEM_MARGIN; // Margin between the border of the list 51 // of items and and item's bg, and between 52 // items 53 private final int TEXT_SPACE; // Space between the edge of an item and 54 // the text 55 56 private final int SCROLLBAR_WIDTH; // Width of a scrollbar 57 58 private java.util.List items; // List of items 59 60 // TODO: maybe this would be better as a simple int[] 61 private java.util.List selected; // List of selected items 62 private boolean multiSelect; // Can multiple items be selected 63 // at once? 64 private int focusedIndex; 65 66 private int maxVisItems; // # items visible without a vsb 67 private XVerticalScrollbar vsb; // null if unsupported 68 private boolean vsbVis; 69 private XHorizontalScrollbar hsb; // null if unsupported 70 private boolean hsbVis; 71 72 private Font font; 73 private FontMetrics fm; 74 75 private XWindow peer; // So far, only needed for painting 76 // on notifyValue() 77 private Color[] colors; // Passed in for painting on notifyValue() 78 79 // Holds the true if mouse is dragging outside of the area of the list 80 // The flag is used at the moment of the dragging and releasing mouse 81 // See 6243382 for more information 82 boolean mouseDraggedOutVertically = false; 83 private volatile boolean vsbVisibilityChanged = false; 84 85 /* 86 * Comment 87 */ 88 public ListHelper(XWindow peer, 89 Color[] colors, 90 int initialSize, 91 boolean multiSelect, 92 boolean scrollVert, 93 boolean scrollHoriz, 94 Font font, 95 int maxVisItems, 96 int SPACE, 97 int MARGIN, 98 int BORDER, 99 int SCROLLBAR) { 100 this.peer = peer; 101 this.colors = colors; 102 this.multiSelect = multiSelect; 103 items = new ArrayList(initialSize); 104 selected = new ArrayList(1); 105 selected.add(Integer.valueOf(-1)); 106 107 this.maxVisItems = maxVisItems; 108 if (scrollVert) { 109 vsb = new XVerticalScrollbar(this); 110 vsb.setValues(0, 0, 0, 0, 1, maxVisItems - 1); 111 } 112 if (scrollHoriz) { 113 hsb = new XHorizontalScrollbar(this); 114 hsb.setValues(0, 0, 0, 0, 1, 1); 115 } 116 117 setFont(font); 118 TEXT_SPACE = SPACE; 119 ITEM_MARGIN = MARGIN; 120 BORDER_WIDTH = BORDER; 121 SCROLLBAR_WIDTH = SCROLLBAR; 122 } 123 124 public Component getEventSource() { 125 return peer.getEventSource(); 126 } 127 128 /**********************************************************************/ 129 /* List management methods */ 130 /**********************************************************************/ 131 132 public void add(String item) { 133 items.add(item); 134 updateScrollbars(); 135 } 136 137 public void add(String item, int index) { 138 items.add(index, item); 139 updateScrollbars(); 140 } 141 142 public void remove(String item) { 143 // FIXME: need to clean up select list, too? 144 items.remove(item); 145 updateScrollbars(); 146 // Is vsb visible now? 147 } 148 149 public void remove(int index) { 150 // FIXME: need to clean up select list, too? 151 items.remove(index); 152 updateScrollbars(); 153 // Is vsb visible now? 154 } 155 156 public void removeAll() { 157 items.removeAll(items); 158 updateScrollbars(); 159 } 160 161 public void setMultiSelect(boolean ms) { 162 multiSelect = ms; 163 } 164 165 /* 166 * docs.....definitely docs 167 * merely keeps internal track of which items are selected for painting 168 * dealing with target Components happens elsewhere 169 */ 170 public void select(int index) { 171 if (index > getItemCount() - 1) { 172 index = (isEmpty() ? -1 : 0); 173 } 174 if (multiSelect) { 175 assert false : "Implement ListHelper.select() for multiselect"; 176 } 177 else if (getSelectedIndex() != index) { 178 selected.remove(0); 179 selected.add(Integer.valueOf(index)); 180 makeVisible(index); 181 } 182 } 183 184 /* docs */ 185 public void deselect(int index) { 186 assert(false); 187 } 188 189 /* docs */ 190 /* if called for multiselect, return -1 */ 191 public int getSelectedIndex() { 192 if (!multiSelect) { 193 Integer val = (Integer)selected.get(0); 194 return val.intValue(); 195 } 196 return -1; 197 } 198 199 int[] getSelectedIndexes() { assert(false); return null;} 200 201 /* 202 * A getter method for XChoicePeer. 203 * Returns vsbVisiblityChanged value and sets it to false. 204 */ 205 public boolean checkVsbVisibilityChangedAndReset(){ 206 boolean returnVal = vsbVisibilityChanged; 207 vsbVisibilityChanged = false; 208 return returnVal; 209 } 210 211 public boolean isEmpty() { 212 return items.isEmpty(); 213 } 214 215 public int getItemCount() { 216 return items.size(); 217 } 218 219 public String getItem(int index) { 220 return (String) items.get(index); 221 } 222 223 /**********************************************************************/ 224 /* GUI-related methods */ 225 /**********************************************************************/ 226 227 public void setFocusedIndex(int index) { 228 focusedIndex = index; 229 } 230 231 public boolean isFocusedIndex(int index) { 232 return index == focusedIndex; 233 } 234 235 public void setFont(Font newFont) { 236 if (newFont != font) { 237 font = newFont; 238 fm = Toolkit.getDefaultToolkit().getFontMetrics(font); 239 // Also cache stuff like fontHeight? 240 } 241 } 242 243 /* 244 * Returns width of the text of the longest item 245 */ 246 public int getMaxItemWidth() { 247 int m = 0; 248 int end = getItemCount(); 249 for(int i = 0 ; i < end ; i++) { 250 int l = fm.stringWidth(getItem(i)); 251 m = Math.max(m, l); 252 } 253 return m; 254 } 255 256 /* 257 * Height of an item (this doesn't include ITEM_MARGIN) 258 */ 259 int getItemHeight() { 260 return fm.getHeight() + (2*TEXT_SPACE); 261 } 262 263 public int y2index(int y) { 264 if (log.isLoggable(PlatformLogger.Level.FINE)) { 265 log.fine("y=" + y +", firstIdx=" + firstDisplayedIndex() +", itemHeight=" + getItemHeight() 266 + ",item_margin=" + ITEM_MARGIN); 267 } 268 // See 6243382 for more information 269 int newIdx = firstDisplayedIndex() + ((y - 2*ITEM_MARGIN) / (getItemHeight() + 2*ITEM_MARGIN)); 270 return newIdx; 271 } 272 273 /* write these 274 int index2y(int); 275 public int numItemsDisplayed() {} 276 */ 277 278 public int firstDisplayedIndex() { 279 if (vsbVis) { 280 return vsb.getValue(); 281 } 282 return 0; 283 } 284 285 public int lastDisplayedIndex() { 286 // FIXME: need to account for horiz scroll bar 287 if (hsbVis) { 288 assert false : "Implement for horiz scroll bar"; 289 } 290 291 return vsbVis ? vsb.getValue() + maxVisItems - 1: getItemCount() - 1; 292 } 293 294 /* 295 * If the given index is not visible in the List, scroll so that it is. 296 */ 297 public void makeVisible(int index) { 298 if (vsbVis) { 299 if (index < firstDisplayedIndex()) { 300 vsb.setValue(index); 301 } 302 else if (index > lastDisplayedIndex()) { 303 vsb.setValue(index - maxVisItems + 1); 304 } 305 } 306 } 307 308 // FIXME: multi-select needs separate focused index 309 public void up() { 310 int curIdx = getSelectedIndex(); 311 int numItems = getItemCount(); 312 int newIdx; 313 314 assert curIdx >= 0; 315 316 if (curIdx == 0) { 317 newIdx = numItems - 1; 318 } 319 else { 320 newIdx = --curIdx; 321 } 322 // focus(newIdx); 323 select(newIdx); 324 } 325 326 public void down() { 327 int newIdx = (getSelectedIndex() + 1) % getItemCount(); 328 select(newIdx); 329 } 330 331 public void pageUp() { 332 // FIXME: for multi-select, move the focused item, not the selected item 333 if (vsbVis && firstDisplayedIndex() > 0) { 334 if (multiSelect) { 335 assert false : "Implement pageUp() for multiSelect"; 336 } 337 else { 338 int selectionOffset = getSelectedIndex() - firstDisplayedIndex(); 339 // the vsb does bounds checking 340 int newIdx = firstDisplayedIndex() - vsb.getBlockIncrement(); 341 vsb.setValue(newIdx); 342 select(firstDisplayedIndex() + selectionOffset); 343 } 344 } 345 } 346 public void pageDown() { 347 if (vsbVis && lastDisplayedIndex() < getItemCount() - 1) { 348 if (multiSelect) { 349 assert false : "Implement pageDown() for multiSelect"; 350 } 351 else { 352 int selectionOffset = getSelectedIndex() - firstDisplayedIndex(); 353 // the vsb does bounds checking 354 int newIdx = lastDisplayedIndex(); 355 vsb.setValue(newIdx); 356 select(firstDisplayedIndex() + selectionOffset); 357 } 358 } 359 } 360 public void home() {} 361 public void end() {} 362 363 364 public boolean isVSBVisible() { return vsbVis; } 365 public boolean isHSBVisible() { return hsbVis; } 366 367 public XVerticalScrollbar getVSB() { return vsb; } 368 public XHorizontalScrollbar getHSB() { return hsb; } 369 370 public boolean isInVertSB(Rectangle bounds, int x, int y) { 371 if (vsbVis) { 372 assert vsb != null : "Vert scrollbar is visible, yet is null?"; 373 int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH : bounds.height; 374 return (x <= bounds.width) && 375 (x >= bounds.width - SCROLLBAR_WIDTH) && 376 (y >= 0) && 377 (y <= sbHeight); 378 } 379 return false; 380 } 381 382 public boolean isInHorizSB(Rectangle bounds, int x, int y) { 383 if (hsbVis) { 384 assert hsb != null : "Horiz scrollbar is visible, yet is null?"; 385 386 int sbWidth = vsbVis ? bounds.width - SCROLLBAR_WIDTH : bounds.width; 387 return (x <= sbWidth) && 388 (x >= 0) && 389 (y >= bounds.height - SCROLLBAR_WIDTH) && 390 (y <= bounds.height); 391 } 392 return false; 393 } 394 395 public void handleVSBEvent(MouseEvent e, Rectangle bounds, int x, int y) { 396 int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH : bounds.height; 397 398 vsb.handleMouseEvent(e.getID(), 399 e.getModifiers(), 400 x - (bounds.width - SCROLLBAR_WIDTH), 401 y); 402 } 403 404 /* 405 * Called when items are added/removed. 406 * Update whether the scrollbar is visible or not, scrollbar values 407 */ 408 void updateScrollbars() { 409 boolean oldVsbVis = vsbVis; 410 vsbVis = vsb != null && items.size() > maxVisItems; 411 if (vsbVis) { 412 vsb.setValues(vsb.getValue(), getNumItemsDisplayed(), 413 vsb.getMinimum(), items.size()); 414 } 415 416 // 6405689. If Vert Scrollbar gets disappeared from the dropdown menu we should repaint whole dropdown even if 417 // no actual resize gets invoked. This is needed because some painting artifacts remained between dropdown items 418 // but draw3DRect doesn't clear the area inside. Instead it just paints lines as borders. 419 vsbVisibilityChanged = (vsbVis != oldVsbVis); 420 // FIXME: check if added item makes a hsb necessary (if supported, that of course) 421 } 422 423 public int getNumItemsDisplayed() { 424 return items.size() > maxVisItems ? maxVisItems : items.size(); 425 } 426 427 public void repaintScrollbarRequest(XScrollbar sb) { 428 Graphics g = peer.getGraphics(); 429 Rectangle bounds = peer.getBounds(); 430 if ((sb == vsb) && vsbVis) { 431 paintVSB(g, XComponentPeer.getSystemColors(), bounds); 432 } 433 else if ((sb == hsb) && hsbVis) { 434 paintHSB(g, XComponentPeer.getSystemColors(), bounds); 435 } 436 g.dispose(); 437 } 438 439 public void notifyValue(XScrollbar obj, int type, int v, boolean isAdjusting) { 440 if (obj == vsb) { 441 int oldScrollValue = vsb.getValue(); 442 vsb.setValue(v); 443 boolean needRepaint = (oldScrollValue != vsb.getValue()); 444 // See 6243382 for more information 445 if (mouseDraggedOutVertically){ 446 int oldItemValue = getSelectedIndex(); 447 int newItemValue = getSelectedIndex() + v - oldScrollValue; 448 select(newItemValue); 449 needRepaint = needRepaint || (getSelectedIndex() != oldItemValue); 450 } 451 452 // FIXME: how are we going to paint!? 453 Graphics g = peer.getGraphics(); 454 Rectangle bounds = peer.getBounds(); 455 int first = v; 456 int last = Math.min(getItemCount() - 1, 457 v + maxVisItems); 458 if (needRepaint) { 459 paintItems(g, colors, bounds, first, last); 460 } 461 g.dispose(); 462 463 } 464 else if ((XHorizontalScrollbar)obj == hsb) { 465 hsb.setValue(v); 466 // FIXME: how are we going to paint!? 467 } 468 } 469 470 public void updateColors(Color[] newColors) { 471 colors = newColors; 472 } 473 474 /* 475 public void paintItems(Graphics g, 476 Color[] colors, 477 Rectangle bounds, 478 Font font, 479 int first, 480 int last, 481 XVerticalScrollbar vsb, 482 XHorizontalScrollbar hsb) { 483 */ 484 public void paintItems(Graphics g, 485 Color[] colors, 486 Rectangle bounds) { 487 // paint border 488 // paint items 489 // paint scrollbars 490 // paint focus? 491 492 } 493 public void paintAllItems(Graphics g, 494 Color[] colors, 495 Rectangle bounds) { 496 paintItems(g, colors, bounds, 497 firstDisplayedIndex(), lastDisplayedIndex()); 498 } 499 public void paintItems(Graphics g, 500 Color[] colors, 501 Rectangle bounds, 502 int first, 503 int last) { 504 peer.flush(); 505 int x = BORDER_WIDTH + ITEM_MARGIN; 506 int width = bounds.width - 2*ITEM_MARGIN - 2*BORDER_WIDTH - (vsbVis ? SCROLLBAR_WIDTH : 0); 507 int height = getItemHeight(); 508 int y = BORDER_WIDTH + ITEM_MARGIN; 509 510 for (int i = first; i <= last ; i++) { 511 paintItem(g, colors, getItem(i), 512 x, y, width, height, 513 isItemSelected(i), 514 isFocusedIndex(i)); 515 y += height + 2*ITEM_MARGIN; 516 } 517 518 if (vsbVis) { 519 paintVSB(g, XComponentPeer.getSystemColors(), bounds); 520 } 521 if (hsbVis) { 522 paintHSB(g, XComponentPeer.getSystemColors(), bounds); 523 } 524 peer.flush(); 525 // FIXME: if none of the items were focused, paint focus around the 526 // entire list. This is how java.awt.List should work. 527 } 528 529 /* 530 * comment about what is painted (i.e. the focus rect 531 */ 532 public void paintItem(Graphics g, 533 Color[] colors, 534 String string, 535 int x, int y, int width, int height, 536 boolean selected, 537 boolean focused) { 538 //System.out.println("LP.pI(): x="+x+" y="+y+" w="+width+" h="+height); 539 //g.setColor(colors[BACKGROUND_COLOR]); 540 541 // FIXME: items shouldn't draw into the scrollbar 542 543 if (selected) { 544 g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]); 545 } 546 else { 547 g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]); 548 } 549 g.fillRect(x, y, width, height); 550 551 if (focused) { 552 //g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]); 553 g.setColor(Color.BLACK); 554 g.drawRect(x + FOCUS_INSET, 555 y + FOCUS_INSET, 556 width - 2*FOCUS_INSET, 557 height - 2*FOCUS_INSET); 558 } 559 560 if (selected) { 561 g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]); 562 } 563 else { 564 g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]); 565 } 566 g.setFont(font); 567 //Rectangle clip = g.getClipBounds(); 568 //g.clipRect(x, y, width, height); 569 //g.drawString(string, x + TEXT_SPACE, y + TEXT_SPACE + ITEM_MARGIN); 570 571 int fontAscent = fm.getAscent(); 572 int fontDescent = fm.getDescent(); 573 574 g.drawString(string, x + TEXT_SPACE, y + (height + fm.getMaxAscent() - fm.getMaxDescent())/2); 575 //g.clipRect(clip.x, clip.y, clip.width, clip.height); 576 } 577 578 boolean isItemSelected(int index) { 579 Iterator itr = selected.iterator(); 580 while (itr.hasNext()) { 581 Integer val = (Integer)itr.next(); 582 if (val.intValue() == index) { 583 return true; 584 } 585 } 586 return false; 587 } 588 589 public void paintVSB(Graphics g, Color colors[], Rectangle bounds) { 590 int height = bounds.height - 2*BORDER_WIDTH - (hsbVis ? (SCROLLBAR_WIDTH-2) : 0); 591 Graphics ng = g.create(); 592 593 g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]); 594 try { 595 ng.translate(bounds.width - BORDER_WIDTH - SCROLLBAR_WIDTH, 596 BORDER_WIDTH); 597 // Update scrollbar's size 598 vsb.setSize(SCROLLBAR_WIDTH, bounds.height); 599 vsb.paint(ng, colors, true); 600 } finally { 601 ng.dispose(); 602 } 603 } 604 605 public void paintHSB(Graphics g, Color colors[], Rectangle bounds) { 606 607 } 608 609 /* 610 * Helper method for Components with integrated scrollbars. 611 * Pass in the vertical and horizontal scroll bar (or null for none/hidden) 612 * and the MouseWheelEvent, and the appropriate scrollbar will be scrolled 613 * correctly. 614 * Returns whether or not scrolling actually took place. This will indicate 615 * whether or not repainting is required. 616 */ 617 static boolean doWheelScroll(XVerticalScrollbar vsb, 618 XHorizontalScrollbar hsb, 619 MouseWheelEvent e) { 620 XScrollbar scroll = null; 621 int wheelRotation; 622 623 // Determine which, if any, sb to scroll 624 if (vsb != null) { 625 scroll = vsb; 626 } 627 else if (hsb != null) { 628 scroll = hsb; 629 } 630 else { // Neither scrollbar is showing 631 return false; 632 } 633 634 wheelRotation = e.getWheelRotation(); 635 636 // Check if scroll is necessary 637 if ((wheelRotation < 0 && scroll.getValue() > scroll.getMinimum()) || 638 (wheelRotation > 0 && scroll.getValue() < scroll.getMaximum()) || 639 wheelRotation != 0) { 640 641 int type = e.getScrollType(); 642 int incr; 643 if (type == MouseWheelEvent.WHEEL_BLOCK_SCROLL) { 644 incr = wheelRotation * scroll.getBlockIncrement(); 645 } 646 else { // type is WHEEL_UNIT_SCROLL 647 incr = e.getUnitsToScroll() * scroll.getUnitIncrement(); 648 } 649 scroll.setValue(scroll.getValue() + incr); 650 return true; 651 } 652 return false; 653 } 654 655 /* 656 * Helper method for XChoicePeer with integrated vertical scrollbar. 657 * Start or stop vertical scrolling when mouse dragged in / out the area of the list if it's required 658 * Restoring Motif behavior 659 * See 6243382 for more information 660 */ 661 void trackMouseDraggedScroll(int mouseX, int mouseY, int listWidth, int listHeight){ 662 663 if (!mouseDraggedOutVertically){ 664 if (vsb.beforeThumb(mouseX, mouseY)) { 665 vsb.setMode(AdjustmentEvent.UNIT_DECREMENT); 666 } else { 667 vsb.setMode(AdjustmentEvent.UNIT_INCREMENT); 668 } 669 } 670 671 if(!mouseDraggedOutVertically && (mouseY < 0 || mouseY >= listHeight)){ 672 mouseDraggedOutVertically = true; 673 vsb.startScrollingInstance(); 674 } 675 676 if (mouseDraggedOutVertically && mouseY >= 0 && mouseY < listHeight && mouseX >= 0 && mouseX < listWidth){ 677 mouseDraggedOutVertically = false; 678 vsb.stopScrollingInstance(); 679 } 680 } 681 682 /* 683 * Helper method for XChoicePeer with integrated vertical scrollbar. 684 * Stop vertical scrolling when mouse released in / out the area of the list if it's required 685 * Restoring Motif behavior 686 * see 6243382 for more information 687 */ 688 void trackMouseReleasedScroll(){ 689 690 if (mouseDraggedOutVertically){ 691 mouseDraggedOutVertically = false; 692 vsb.stopScrollingInstance(); 693 } 694 695 } 696 }