1 /*
   2  * Copyright (c) 1999, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package test.java.awt.event.helpers.lwcomponents;
  25 
  26 import java.awt.*;
  27 import java.awt.event.*;
  28 import java.util.Vector;
  29 import java.util.Enumeration;
  30 
  31 /**
  32  * Remarks : Source for LeightWeight component - List.
  33  *
  34  * Scroll bar support is not available for this component, so if the
  35  * items exceeds visibility those items will be truncated. Also, here
  36  * double buffering is not used so there will be little bit flickering
  37  * while it repaints. Item listener support is not enabled in this
  38  * component. Listeners handled were Mouse, Key and Focus.
  39  *
  40  * @author R.Govindarajan (govind@siptech.co.in), G.N.V.Sekhar (sekharv@siptech.co.in)
  41  */
  42 
  43 public class LWList extends LWComponent implements ItemSelectable {
  44 
  45   // Constants used for component size
  46   private final int MIN_WIDTH   = 100;
  47   private final int MIN_HEIGHT  = 100;
  48   private final int PREF_WIDTH  = 100;
  49   private final int PREF_HEIGHT = 100;
  50 
  51   // Constants used for setting color for component
  52   private final Color BACK_COLOR          = Color.white;
  53   private final Color FRONT_COLOR         = Color.black;
  54   private final Color BORDER_COLOR        = Color.darkGray;
  55   private final Color FOCUS_COLOR         = Color.blue;
  56   private final Color FOCUS_FORECOLOR     = Color.white;
  57   private final Color FOCUS_ENABLED_COLOR = Color.red;
  58   private final int BORDER_WIDTH = 2;
  59 
  60   private Vector stringList;  // List of items
  61   private Vector selList;     // List of selected items
  62   private int rows;           // Visible rows
  63   private int focusIndex, prevfocusIndex;
  64   private Dimension minSize;
  65   private Dimension prefSize;
  66   private boolean pressed, eventOccurred, focusEnabled;
  67   private boolean multipleMode;
  68 
  69   // Listeners handled for this component
  70   private ActionListener actionListener;
  71   private KeyListener    keyListener;
  72   private FocusListener  focusListener;
  73   private ItemListener   itemListener;
  74 
  75   private static int nameCounter = 0;
  76 
  77   /**
  78    * Creates a new list.
  79    */
  80   public LWList() {
  81     this(0);
  82   }
  83 
  84   /**
  85    * Creates a new list with the specified number of rows; 
  86    * multiple selection mode is disabled.
  87    *
  88    * @param i  the number of rows
  89    */
  90   public LWList(int i) {
  91     this(i, false);
  92   }
  93 
  94   /**
  95    * Creates a new list with the specified number of rows and multiple selection mode.
  96    * 
  97    * @param rows  the number of rows
  98    * @param flag  determines whether the list allows multiple selections
  99    */
 100   public LWList(int rows, boolean flag) {
 101     multipleMode        = flag;
 102     this.rows           = rows;
 103     minSize             = new Dimension(MIN_WIDTH, MIN_HEIGHT);
 104     prefSize            = new Dimension(PREF_WIDTH, PREF_HEIGHT);
 105     stringList          = new Vector();
 106     selList             = new Vector();
 107     selList.addElement(0);
 108     focusIndex          = -1;
 109     prevfocusIndex      = focusIndex;
 110     enableEvents(AWTEvent.MOUSE_EVENT_MASK);
 111     enableEvents(AWTEvent.KEY_EVENT_MASK);
 112     enableEvents(AWTEvent.FOCUS_EVENT_MASK);
 113     enableEvents(AWTEvent.ITEM_EVENT_MASK);
 114     setName(makeComponentName()); // set the name to the component
 115   }
 116 
 117   String makeComponentName() {
 118     String s = "LWList" + nameCounter++;
 119     return s;
 120   }
 121 
 122   /**
 123    * Set whether the component is enabled or not.
 124    * @param enabled  if {@code true}, the component is to be enabled
 125    */
 126   @Override
 127   public void setEnabled(boolean enabled) {
 128     super.setEnabled(enabled);
 129 
 130     if (enabled) {
 131       enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
 132     } else {
 133       disableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
 134     }
 135     repaint(1);
 136   }
 137 
 138   /**
 139    * Set the selection mode.
 140    *
 141    * @param flag  determines whether the list allows multiple selections
 142    */
 143   public void setSelectionMode(boolean flag) {
 144     multipleMode = flag;
 145   }
 146 
 147   /**
 148    * Check if the list allows multiple selections.
 149    *
 150    * @return  {@code true} if the list allows multiple selections
 151    */
 152   public boolean isMultipleMode() {
 153     return multipleMode;
 154   }
 155 
 156   /**
 157    * Add the specified item.
 158    *
 159    * @param listItem  the item
 160    */
 161   public void add(String listItem) {
 162     stringList.addElement(listItem);
 163     invalidate();
 164     repaint();
 165   }
 166 
 167   /**
 168    * Get minimum dimension for the list.
 169    *
 170    * @return  the minimum dimensions for displaying
 171    */
 172   @Override
 173   public Dimension getMinimumSize() {
 174     return minSize;
 175   }
 176 
 177   /**
 178    * Get the preferred size of the list.
 179    *
 180    * @return  the preferred dimensions for displaying
 181    */
 182   @Override
 183   public Dimension getPreferredSize() {
 184     return prefSize;
 185   }
 186 
 187   /**
 188    * Get the background color for the component.
 189    *
 190    * @return  the background color for the component
 191    */
 192   @Override
 193   public Color getBackground() {
 194     return BACK_COLOR;
 195   }
 196 
 197   /**
 198    * Get the foreground color for the component.
 199    *
 200    * @return  the foreground color for the component
 201    */
 202   @Override
 203   public Color getForeground() {
 204     return FRONT_COLOR;
 205   }
 206 
 207   /**
 208    * Get the border color for the component.
 209    *
 210    * @return  the border color for the component
 211    */
 212   public Color getBorder() {
 213     return BORDER_COLOR;
 214   }
 215 
 216   /**
 217    * Get background color for the selected item.
 218    *
 219    * @return  the color for the selected item
 220    */
 221   public Color getFocusColor() {
 222     return FOCUS_COLOR;
 223   }
 224 
 225   /**
 226    * Get foreground color for the selected item.
 227    *
 228    * @return  the foreground color for the selected item
 229    */
 230   public Color getFocusForeColor() {
 231     return FOCUS_FORECOLOR;
 232   }
 233 
 234   /**
 235    * Get a "focus enabled" color - a small rectangle around the item 
 236    * should be drawn when the component got the focus.
 237    *
 238    * @return  the "focus enabled" color
 239    */
 240   public Color getFocusEnabledColor() {
 241     return FOCUS_ENABLED_COLOR;
 242   }
 243 
 244   /**
 245    * Get border width.
 246    *
 247    * @return  the border width
 248    */
 249   public int getBorderWidth() {
 250     return BORDER_WIDTH;
 251   }
 252     
 253   /**
 254    * Get the list item count.
 255    *
 256    * @return  the count of items
 257    */
 258   public int getItemCount() {
 259     return stringList.size();
 260   }
 261 
 262   /**
 263    * Get the specified item from the list.
 264    *
 265    * @param index  the index
 266    * @return  the item string
 267    */
 268   public String getItem(int index) {
 269     return (String)stringList.elementAt(index);
 270   }
 271 
 272   /**
 273    * Get array of items from the list.
 274    *
 275    * @return  the array of item strings
 276    */
 277   public String[] getItems() {
 278     String str[] = new String[getItemCount()];
 279     int count = 0;
 280     for (Enumeration e = stringList.elements(); e.hasMoreElements(); ) {
 281       str[count++] = (String)e.nextElement();
 282     }
 283     return str;
 284   }
 285 
 286   /**
 287    * Check whether the component can be a focus owner (explicitly enabled here).
 288    *
 289    * @return {@code true} if the component is focusable
 290    */
 291   @Override
 292   public boolean isFocusTraversable() {
 293     return true;
 294   }
 295 
 296   /**
 297    * Check whether mouse click point lies within the list of items.
 298    *
 299    * @param pt  the click point
 300    * @return  {@code true} if the click point lies within the list of items
 301    */
 302   @Override
 303   public boolean contains(Point pt) {
 304     Rectangle rect = new Rectangle();
 305     Dimension d = getSize();
 306     rect.x = getBorderWidth();
 307     rect.y = getBorderWidth();
 308     rect.width  = d.width  - (getBorderWidth() * 2);
 309     rect.height = d.height - (getBorderWidth() * 2);
 310     return rect.contains(pt);
 311   }
 312 
 313   /**
 314    * Given a click point the item that has to be selected is found from the list
 315    * and focusIndex variable is set accordingly.
 316    *
 317    * @param pt  the click point
 318    */
 319   private void findSelectedIndex(Point pt) {
 320     Font f = getFont();
 321     FontMetrics fm = getFontMetrics(f);
 322     focusIndex = pt.y / fm.getHeight() - 1;
 323     if (multipleMode) {
 324       Integer fi = focusIndex;
 325       if (selList.contains(fi)) {
 326         int i = selList.indexOf(fi);
 327         selList.removeElementAt(i);
 328       } else {
 329         selList.addElement(fi);
 330       }
 331     }
 332   }
 333 
 334   /**
 335    * Set index of the selected item.
 336    *
 337    * @param index  the index
 338    */
 339   public void setSelectedIndex(int index) {
 340     prevfocusIndex = focusIndex;
 341     focusIndex = index;
 342   }
 343 
 344   /**
 345    * Get the selected item index.
 346    *
 347    * @return  the selected item index.
 348    */
 349   public int getSelectedIndex() {
 350     return focusIndex;
 351   }
 352 
 353   /**
 354    * Get an array of the selected Objects.
 355    *
 356    * @return  array of the Objects
 357    */
 358   @Override
 359   public Object[] getSelectedObjects() {
 360     int ai[] = getSelectedIndexes();
 361     Object aobj[] = new Object[selList.size()];
 362     for (int i = 0; i < selList.size(); i++) {
 363       aobj[i] = stringList.elementAt(ai[i]);
 364     }
 365     return aobj;
 366   }
 367 
 368   /**
 369    * Get an array of the selected item indices.
 370    *
 371    * @return  the array of the indices
 372    */
 373   public int[] getSelectedIndexes() {
 374     int ai[] = new int[selList.size()];
 375     for (int i = 0; i < selList.size(); i++) {
 376       ai[i] = ((Integer)selList.elementAt(i));
 377     }
 378     return ai;
 379   }
 380 
 381   /**
 382    * Add the specified item listener to receive item events from the list.
 383    *
 384    * @param itemlistener  the item listener
 385    */
 386   @Override
 387   public synchronized void addItemListener(ItemListener itemlistener) {
 388     itemListener = AWTEventMulticaster.add(itemListener, itemlistener);
 389     enableEvents(AWTEvent.ITEM_EVENT_MASK);
 390   }
 391 
 392   /**
 393    * Remove the specified item listener so
 394    * that it no longer receives item events from this list.
 395    *
 396    * @param itemlistener  the item listener
 397    */
 398   @Override
 399   public synchronized void removeItemListener(ItemListener itemlistener) {
 400     itemListener = AWTEventMulticaster.remove(itemListener, itemlistener);
 401   }
 402 
 403   /**
 404    * Add the specified action listener to receive action events from this list.
 405    *
 406    * @param listener  the action listener
 407    */
 408   public synchronized void addActionListener(ActionListener listener) {
 409     actionListener = AWTEventMulticaster.add(actionListener, listener);
 410     enableEvents(AWTEvent.MOUSE_EVENT_MASK);
 411   }
 412 
 413   /**
 414    * Remove the specified action listener so
 415    * that it no longer receives action events from this list.
 416    *
 417    * @param listener  the action listener
 418    */
 419   public synchronized void removeActionListener(ActionListener listener) {
 420     actionListener = AWTEventMulticaster.remove(actionListener, listener);
 421   }
 422 
 423   /**
 424    * Add the specified key listener to receive key events from this component.
 425    *
 426    * @param listener  the key listener
 427    */
 428   @Override
 429   public synchronized void addKeyListener(KeyListener listener) {
 430     keyListener = AWTEventMulticaster.add(keyListener, listener);
 431     enableEvents(AWTEvent.KEY_EVENT_MASK);
 432   }
 433 
 434   /**
 435    * Remove the specified key listener so 
 436    * that it no longer receives key events from this component.
 437    *
 438    * @param listener  the key listener
 439    */
 440   @Override
 441   public synchronized void removeKeyListener(KeyListener listener) {
 442     keyListener = AWTEventMulticaster.remove(keyListener, listener);
 443   }
 444 
 445   /**
 446    * Add the specified focus listener to receive focus events 
 447    * from this component when it gains input focus.
 448    *
 449    * @param listener  the focus listener
 450    */
 451   @Override
 452   public synchronized void addFocusListener(FocusListener listener) {
 453     focusListener = AWTEventMulticaster.add(focusListener, listener);
 454     enableEvents(AWTEvent.FOCUS_EVENT_MASK);
 455   }
 456 
 457   /**
 458    * Remove the specified focus listener so 
 459    * that it no longer receives focus events from this component.
 460    *
 461    * @param listener  the focus listener
 462    */
 463   @Override
 464   public synchronized void removeFocusListener(FocusListener listener) {
 465     focusListener = AWTEventMulticaster.remove(focusListener, listener);
 466   }
 467 
 468   @Override
 469   protected void processEvent(AWTEvent awtevent) {
 470 
 471     if (awtevent instanceof FocusEvent) {
 472       processFocusEvent((FocusEvent)awtevent);
 473     } else if (awtevent instanceof ItemEvent) {
 474       processItemEvent((ItemEvent)awtevent);
 475     } else if (awtevent instanceof KeyEvent) {
 476       processKeyEvent((KeyEvent)awtevent);
 477     } else if (awtevent instanceof MouseEvent) {
 478       switch (awtevent.getID()) {
 479       case MouseEvent.MOUSE_CLICKED:
 480       case MouseEvent.MOUSE_PRESSED:
 481       case MouseEvent.MOUSE_RELEASED:
 482       case MouseEvent.MOUSE_ENTERED:
 483       case MouseEvent.MOUSE_EXITED:
 484     processMouseEvent((MouseEvent)awtevent);
 485     break;
 486 
 487       case MouseEvent.MOUSE_MOVED:
 488       case MouseEvent.MOUSE_DRAGGED:
 489     super.processEvent((MouseEvent)awtevent);
 490     break;
 491       }
 492     } else {
 493       if (awtevent instanceof ComponentEvent)
 494     super.processComponentEvent((ComponentEvent)awtevent);
 495       else
 496     super.processEvent(awtevent);
 497     }
 498   }
 499 
 500   protected void processItemEvent(ItemEvent itemevent) {
 501     if (itemListener != null) {
 502       itemListener.itemStateChanged(itemevent);
 503     }
 504   }
 505 
 506   @Override
 507   protected void processFocusEvent(FocusEvent e) {
 508     switch (e.getID()) {
 509     case FocusEvent.FOCUS_GAINED:
 510       if (focusListener != null) { focusListener.focusGained(e); }
 511       if (getSelectedIndex() == -1) { setSelectedIndex(0); }
 512       focusEnabled = true;
 513       repaint();
 514       break;
 515     case FocusEvent.FOCUS_LOST:
 516       if (focusListener != null) {
 517         focusListener.focusLost(e);
 518       }
 519       focusEnabled = false;
 520       repaint();
 521       break;
 522     }
 523     super.processFocusEvent(e);
 524   }
 525 
 526   @Override
 527   protected void processKeyEvent(KeyEvent e) {
 528     rows = getItemCount();
 529 
 530     switch (e.getID()) {
 531 
 532     case KeyEvent.KEY_TYPED:
 533       if (keyListener != null) {
 534         keyListener.keyTyped(e);
 535       }
 536       break;
 537 
 538     case KeyEvent.KEY_PRESSED:
 539       if (keyListener != null) {
 540         keyListener.keyPressed(e);
 541       }
 542       if (e.getKeyCode() == KeyEvent.VK_DOWN) {
 543         prevfocusIndex = focusIndex;
 544         int index = getSelectedIndex() + 1;
 545         if (index > rows) { break; }
 546         setSelectedIndex(index);
 547         processItemEvent(new ItemEvent(this, 0, index, 0));
 548         eventOccurred = true;
 549         repaint();
 550       } else if (e.getKeyCode() == KeyEvent.VK_UP) {
 551         int index = getSelectedIndex()-1;
 552         if (index >= 0) {
 553           setSelectedIndex(index);
 554           if (e.getID() != 400) {
 555             processItemEvent(new ItemEvent(this, 0, index, 0));
 556           }
 557           eventOccurred = true;
 558           repaint();
 559         }
 560       }
 561       break;
 562 
 563     case KeyEvent.KEY_RELEASED:
 564       if (keyListener != null) {
 565         keyListener.keyReleased(e);
 566       }
 567       if (e.getKeyCode() == KeyEvent.VK_ENTER) {
 568         eventOccurred = true;
 569 
 570         // ActionEvent is fired here
 571         if (actionListener != null) {
 572           actionListener.actionPerformed( new ActionEvent(
 573               this, ActionEvent.ACTION_PERFORMED, null));
 574         }
 575         repaint();
 576       }
 577       break;
 578     } // switch
 579     super.processKeyEvent(e);
 580   }
 581 
 582   @Override
 583   protected void processMouseEvent(MouseEvent e) {
 584     switch (e.getID()) {
 585     case MouseEvent.MOUSE_PRESSED:
 586       pressed = true;
 587       if (contains(e.getPoint())) {
 588         findSelectedIndex(e.getPoint());
 589         processItemEvent(new ItemEvent(this, 0, focusIndex, 0));
 590         eventOccurred = true;
 591       }
 592       repaint();
 593       break;
 594 
 595     case MouseEvent.MOUSE_RELEASED:
 596       if (pressed) { requestFocus(); }
 597 
 598       if (contains(e.getPoint())) {
 599         findSelectedIndex(e.getPoint());
 600         eventOccurred = true;
 601       }
 602       // ActionEvent is fired here
 603       if (actionListener != null) {
 604         actionListener.actionPerformed(new ActionEvent(
 605             this, ActionEvent.ACTION_PERFORMED, null));
 606       }
 607 
 608       if (pressed) {
 609         pressed = false;
 610         repaint();
 611       }
 612       break;
 613     }
 614     super.processMouseEvent(e);
 615   }
 616 
 617   @Override
 618   /**
 619    * Paint the list.
 620    *
 621    * @param g  the graphics context to be used for testing
 622    */
 623   public void paint(Graphics g) {
 624     super.paint(g);
 625     restrictGraphicsToClientArea(g);
 626 
 627     Point     loc = getClientLocation();
 628     Dimension dim = getClientSize();
 629     Color prevColor = g.getColor();
 630 
 631     // List border is drawn here
 632     g.setColor(getBackground());
 633     g.fillRect(0, 0, dim.width - 2, dim.height - 2);
 634     g.setColor(getBorder());
 635     g.drawRect(0, 0, dim.width - 2, dim.height - 2);
 636 
 637     if (getItemCount() > 0) {
 638       Font f = getFont();
 639       if (f != null) {
 640         String str[] = getItems();
 641         FontMetrics fm = getFontMetrics(f);
 642         int drawRow = loc.x + getBorderWidth() + fm.getAscent();
 643         int drawCol = loc.y + getBorderWidth();
 644         int rectRow = loc.y + getBorderWidth();
 645         int i = 0;
 646 
 647         // Draw items (if the items exceeds visibility those items will be truncated
 648         // as scrollbar support is not enabled
 649 
 650         for (;
 651              i < str.length && drawRow < (dim.height - getBorderWidth());
 652              i++) {
 653                if (fm.stringWidth(str[i]) < (dim.width - (getBorderWidth() * 2))) {
 654                  drawItem(g, i, drawCol, drawRow, rectRow, fm);
 655                  drawRow += fm.getHeight();
 656                  rectRow += fm.getHeight();
 657                } else {
 658                  LWComponent.errorMsg("string width exceeds list width");
 659                  LWComponent.errorMsg("Horizontal scrollbar support is not available");
 660                }
 661             } // for
 662 
 663         if ( (drawRow > (dim.height - getBorderWidth())) && (str.length > i) ) {
 664           //LWComponent.errorMsg("no of strings exceeds list height");
 665           //LWComponent.errorMsg("Vertical scrollbar support is not available");
 666         }
 667       } else { LWComponent.errorMsg("Font not available.."); }
 668     }
 669 
 670     eventOccurred = false;
 671     g.setColor(prevColor);
 672     unrestrictGraphicsFromClientArea(g);
 673   }
 674 
 675   // Draw String items
 676   private void drawItem(Graphics g, int listIndex, int drawCol,
 677       int drawRow, int rectRow, FontMetrics fm) {
 678     Point     loc = getClientLocation();
 679     Dimension dim = getClientSize();
 680     String    str = getItem(listIndex);
 681     if (multipleMode) {
 682       for (int i1 = 0; i1 < selList.size(); i1++) {
 683         if (listIndex == ((Integer)selList.elementAt(i1))) {
 684           g.setColor(getFocusColor());
 685           g.fillRect(loc.x + getBorderWidth(),
 686                      rectRow,
 687                      dim.width - getBorderWidth() * 2,
 688                      fm.getHeight());
 689           g.setColor(getFocusEnabledColor());
 690           g.drawRect(loc.x + getBorderWidth(),
 691                      rectRow,
 692                      dim.width - getBorderWidth() * 2,
 693                      fm.getHeight());
 694         }
 695       } // for
 696     } else {
 697       if (listIndex == getSelectedIndex() && !multipleMode) {
 698         g.setColor(getFocusColor());
 699         g.fillRect(loc.x + getBorderWidth(),
 700                    rectRow,
 701                    dim.width - getBorderWidth() * 2,
 702                    fm.getHeight());
 703         g.setColor(getFocusForeColor());
 704       }
 705       if ((listIndex == prevfocusIndex) && (prevfocusIndex != getSelectedIndex()) && !multipleMode) {
 706         g.setColor(getBackground());
 707         g.fillRect(loc.x + getBorderWidth(),
 708                    rectRow,
 709                    dim.width - getBorderWidth() * 2,
 710                    fm.getHeight());
 711         prevfocusIndex = getSelectedIndex();
 712       }
 713       if (focusEnabled && listIndex == getSelectedIndex() && !multipleMode) {
 714         g.setColor(getFocusEnabledColor());
 715         g.drawRect(loc.x + getBorderWidth(),
 716                    rectRow,
 717                    dim.width - getBorderWidth() * 2,
 718                    fm.getHeight());
 719       }
 720     }
 721     g.setColor(getForeground());
 722     g.drawString(str,drawCol,drawRow);
 723   }
 724 
 725 }
 726