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