001 /* 002 * Copyright (c) Holger Pfaff - http://pfaff.ws 003 * 004 * This software maybe used for any purpose provided the 005 * above copyright notice is retained. It is supplied as is. 006 * No warranty expressed or implied - Use at your own risk. 007 */ 008 009 import java.awt.*; 010 import java.awt.event.*; 011 import java.util.*; 012 013 /** 014 * (#)MultiColumnList.java 015 * @author Holger Pfaff 016 * @version 3.2 19-Mar-2004<br><br> 017 * 018 * A widget for displaying Strings in rows and colums. Allows 019 * sorting, resizing and modifying the colums visible. Currently 020 * only single row selection is supportet 021 */ 022 023 public class MultiColumnList extends Awt { 024 025 /** 026 * titles per column (arraysize = no. of cols) 027 */ 028 protected String titles[] = null; 029 030 /** 031 * items per column (arraysize = no. of cols) 032 */ 033 protected Vector items[] = null; 034 035 /** 036 * show title ? 037 */ 038 protected boolean showTitle = true; 039 040 /** 041 * show lines ? 042 */ 043 protected boolean showLines = true; 044 045 /** 046 * user adjustable ? 047 */ 048 protected boolean adjustable = true; 049 050 /** 051 * sort normal or reversed ? 052 */ 053 protected boolean sortrev = false; 054 055 /** 056 * sort ignore case ? 057 */ 058 protected boolean sortign = false; 059 060 /** 061 * to determine minimal space required 062 */ 063 protected int minVisibleRows = 9; 064 065 /** 066 * minimal no. of rows that must be visible 067 */ 068 protected int minVisibleCols = 1; 069 070 /** 071 * no. of currently visible rows 072 */ 073 protected int visibleCols = 1; 074 075 /** 076 * selected row (-1 for no selection) 077 */ 078 protected int selrow = -1; 079 080 /** 081 * column being sorted (-1 for unsorted) 082 */ 083 protected int sortcol = -1; 084 085 /** 086 * column being resized (-1 for none) 087 */ 088 protected int dragcol = -1; 089 090 /** 091 * columns that should be unique 092 */ 093 protected int[] uniquecols = null; 094 095 /** 096 * border width around titles & col separators 097 */ 098 protected int borderw = 2; 099 100 /** 101 * mouse over column 102 */ 103 protected int moc = -1; 104 105 /** 106 * last mouse click time (to enable doubleclick) 107 */ 108 protected long lastclick = 0; 109 110 /** 111 * proportional column widths (sum = 1.0) 112 */ 113 protected float cwidth[] = null; 114 115 /** 116 * column x positions 117 */ 118 protected int cpos[] = null; 119 120 /** 121 * sorted positions 122 */ 123 protected int spos[] = null; 124 125 /** 126 * scrollbar at the right side 127 */ 128 protected Scrollbar scrollbar = null; 129 130 /** 131 * backing image for flickerfree drawing 132 */ 133 protected Image image = null; 134 135 /** 136 * Creates a new scrolling list. The number of cols is equal to 137 * t.length. The number of initially visible cols is specified 138 * via visibleCols. 139 * 140 * @param titles the column headers 141 * @param visibleCols no. initially visible columns 142 * @param minVisibleCols no. minimal visible columns 143 * @param minVisibleRows no. minimal visible rows 144 */ 145 public MultiColumnList(String tls[], int visCols, int minVisCols, int minVisRows) { 146 if(tls == null || tls.length == 0) { 147 titles = new String[1]; 148 } else { 149 titles = new String[tls.length]; 150 System.arraycopy(tls, 0, titles, 0, tls.length); 151 } 152 items = new Vector[titles.length]; 153 cwidth = new float[titles.length]; 154 cpos = new int[titles.length + 1]; 155 insets = new Insets(1,1,1,1); 156 innerInsets = new Insets(1,1,1,1); 157 158 visibleCols = visCols > 0 && visCols <= titles.length ? visCols : titles.length; 159 minVisibleCols = minVisCols > 0 && minVisCols <= titles.length ? minVisCols : 1; 160 161 if(minVisRows > 0) { 162 minVisibleRows = minVisRows; 163 } 164 165 for(int i = 0; i < titles.length; i++) { 166 items[i] = new Vector(); 167 cwidth[i] = 1.0f / titles.length; 168 } 169 170 if(titles[0] == null) { 171 showTitle = false; 172 } 173 174 addKeyListener(this); 175 addMouseListener(this); 176 addMouseMotionListener(this); 177 178 scrollbar = new Scrollbar(Scrollbar.VERTICAL, 0, 1, 0, 0); 179 scrollbar.addAdjustmentListener(this); 180 181 add(scrollbar); 182 } 183 184 /** 185 * Private version: Add a row to the list (no repaint) 186 * 187 * @param item row to add 188 */ 189 private void addRow(String item[]) { 190 if(item != null && item.length > 0) { 191 int max = items[0].size(); 192 int pos = max; 193 194 if(findItem(item, uniquecols) != -1) { 195 return; 196 } 197 if(sortcol > -1) { 198 String cmp = sortcol < item.length ? item[sortcol] : ""; 199 for(pos = 0; pos < max; ++pos) { 200 if(compare(cmp, (String) items[sortcol].elementAt(pos)) < 0) break; 201 } 202 } 203 for(int i = 0; i < getColumnCount(); i++) { 204 items[i].insertElementAt(i < item.length ? (item[i] == null ? "" : item[i]) : "", pos); 205 } 206 if(selrow > pos) selrow++; 207 } 208 } 209 210 /** 211 * Private version: Add a row to the list with just one col (no repaint) 212 * 213 * @param item row to add 214 */ 215 private void addRow(String item) { 216 if(item != null) { 217 String[] tmp = {item}; 218 addRow(tmp); 219 } 220 } 221 222 /** 223 * Add a item row to the list 224 * 225 * @param item row to add 226 */ 227 public void addItem(String item[]) { 228 addRow(item); 229 makeVisible(); 230 } 231 232 /** 233 * Add a item row to the list with just one col 234 * 235 * @param item row to add 236 */ 237 public void addItem(String item) { 238 addRow(item); 239 makeVisible(); 240 } 241 242 /** 243 * Add several rows to the list 244 * 245 * @param item rows to add 246 */ 247 public void addItems(String items[][]) { 248 if(items != null) { 249 for(int i = 0; i < items.length; i++) { 250 addRow(items[i]); 251 } 252 makeVisible(); 253 } 254 } 255 256 /** 257 * several rows to the list with just one col 258 * 259 * @param item rows to add 260 */ 261 public void addItems(String items[]) { 262 if(items != null) { 263 for(int i = 0; i < items.length; i++) { 264 addRow(items[i]); 265 } 266 makeVisible(); 267 } 268 } 269 270 /** 271 * Remove one row from the list 272 * 273 * @param item row to remove 274 */ 275 public void deleteItem(int row) { 276 if(isValidRow(row)) { 277 for(int i = 0; i < titles.length; i++) { 278 items[i].removeElementAt(row); 279 } 280 if(selrow == row) setSelectedIndex(-1); 281 if(selrow > row) selrow--; 282 makeVisible(); 283 } 284 } 285 286 /** 287 * Remove one row from the list (matching only first col) 288 * 289 * @param item row to remove 290 */ 291 public void deleteItem(String item) { 292 deleteItem(item, 0); 293 } 294 295 /** 296 * Remove one row from the list matching in col 297 * 298 * @param item item to find 299 * @param col col to search in 300 */ 301 public void deleteItem(String item, int col) { 302 deleteItem(findItem(item, col)); 303 } 304 305 /** 306 * Remove everything from the list 307 */ 308 public void clear() { 309 setSelectedIndex(-1); 310 for(int i = 0; i < getColumnCount(); i++) { 311 items[i].removeAllElements(); 312 } 313 sortcol = -1; 314 sortrev = false; 315 scrollbar.setValues(0, 1, 0, 0); 316 scroll(0); 317 } 318 319 /** 320 * Remove everything from the list 321 */ 322 public void removeAll() { 323 clear(); 324 } 325 326 /** 327 * Returns the contents of a given row (all cols) 328 * 329 * @param row row to return 330 */ 331 public String[] getItem(int row) { 332 if(isValidRow(row)) { 333 String r[] = new String[getColumnCount()]; 334 for(int i = 0; i < getColumnCount(); i++) { 335 r[i] = (String) items[i].elementAt(row); 336 } 337 return r; 338 } else { 339 return null; 340 } 341 } 342 343 /** 344 * Returns the contents of a given row (selected cols) 345 * 346 * @param row row to return 347 * @param col cols to return 348 */ 349 public String[] getItem(int row, int col[]) { 350 if(isValidRow(row)) { 351 String r[] = new String[col.length]; 352 for(int i = 0; i < col.length; i++) { 353 if(isValidColumn(col[i])) { 354 r[i] = (String) items[col[i]].elementAt(row); 355 } 356 } 357 return r; 358 } else { 359 return null; 360 } 361 } 362 363 /** 364 * Returns the contents of a given cell 365 * 366 * @param row row of item to return 367 * @param col col of item to return 368 */ 369 public String getItem(int row, int col) { 370 if(isValidRow(row) && isValidColumn(col)) { 371 return items[col].elementAt(row).toString(); 372 } else { 373 return null; 374 } 375 } 376 377 /** 378 * gets the contents of one given col as string array 379 * 380 * @param col col to return 381 */ 382 public String[] getItems(int col) { 383 if(isValidColumn(col)) { 384 String r[] = new String[getItemCount()]; 385 for(int i = 0; i < r.length; ++i) { 386 r[i] = getItem(i, col); 387 } 388 return r; 389 } else { 390 return null; 391 } 392 } 393 394 /** 395 * Gets the contents of given cols as string array 396 * 397 * @param col cols to return 398 */ 399 public String[][] getItems(int[] col) { 400 String r[][] = new String[getItemCount()][]; 401 for(int i = 0; i < r.length; ++i) { 402 r[i] = getItem(i, col); 403 } 404 return r; 405 } 406 407 /** 408 * Returns the all content 409 */ 410 public String[][] getItems() { 411 String r[][] = new String[getItemCount()][]; 412 for(int i = 0; i < r.length; i++) { 413 r[i] = getItem(i); 414 } 415 return r; 416 } 417 418 /** 419 * Sets the contents of a given cell 420 * 421 * @param row row of cell to set 422 * @param col col of cell to set 423 * @param value value to set 424 */ 425 public void setItem(int row, int col, String value) { 426 if(isValidRow(row) && isValidColumn(col)) { 427 items[col].setElementAt(value == null ? "" : value, row); 428 repaint(); 429 } 430 } 431 432 /** 433 * Return the selected item 434 */ 435 public String[] getSelectedItem() { 436 return selrow == -1 ? null : getItem(selrow); 437 } 438 439 /** 440 * Return the selected row 441 */ 442 public int getSelectedIndex() { 443 return selrow; 444 } 445 446 /** 447 * Set the selected row to index 448 * 449 * @param sel row to select 450 */ 451 public void setSelectedIndex(int sel) { 452 if(sel < -1) return; 453 if(sel >= getItemCount()) return; 454 455 if(selrow != sel) { 456 int oldrow = selrow; 457 selrow = sel; 458 makeVisible(); 459 460 // Send deselect event only if selection will be cleared 461 // java.awt.List behaves that way 462 463 if(selrow == -1) { 464 notifyItemListeners(new ItemEvent(this, oldrow, getItem(oldrow), ItemEvent.DESELECTED)); 465 } else { 466 notifyItemListeners(new ItemEvent(this, selrow, getItem(selrow), ItemEvent.SELECTED)); 467 } 468 } 469 } 470 471 /** 472 * See if item[] equals a specific row 473 * 474 * @param item each array elem represents one column 475 * @param row row to compare with 476 * @param cols cols of row to compare with item 477 */ 478 public boolean equalsItem(String[] item, int row, int[] cols) { 479 if(item != null && item.length > 0 && 480 cols != null && cols.length > 0 && 481 isValidRow(row)) { 482 for(int i = 0; i < cols.length; ++i) { 483 if(isValidColumn(cols[i]) == false || 484 cols[i] >= item.length || 485 getItem(row, cols[i]).equals(item[cols[i]]) == false) { 486 return false; 487 } 488 } 489 return true; 490 } 491 return false; 492 } 493 494 /** 495 * Find an item in column 0 - return row on succes ot -1 otherwis 496 * 497 * @param item item to find 498 */ 499 public int findItem(String item) { 500 return findItem(item, 0); 501 } 502 503 /** 504 * Find an item in a specific column - return row on succes ot -1 otherwise 505 * 506 * @param item item to find 507 * @param col col to search in 508 */ 509 public int findItem(String item, int col) { 510 if(isValidColumn(col)) { 511 for(int i = 0; i < getItemCount(); ++i) { 512 if(getItem(i, col).equals(item)) return i; 513 } 514 } 515 return -1; 516 } 517 518 /** 519 * Find an item in specific columns - return row on succes ot -1 otherwise 520 * 521 * @param item item to find - each array elem represents one column 522 * @param cols cols to search in 523 */ 524 public int findItem(String[] item, int[] cols) { 525 if(item != null && item.length > 0 && 526 cols != null && cols.length > 0 && 527 getItemCount() > 0) { 528 for(int i = 0; i < getItemCount(); ++i) { 529 if(equalsItem(item, i, cols) == true) { 530 return i; 531 } 532 } 533 } 534 return -1; 535 } 536 537 /** 538 * Return the item count 539 */ 540 public int getItemCount() { 541 return items[0].size(); 542 } 543 544 /** 545 * Return the column count 546 */ 547 public int getColumnCount() { 548 return items.length; 549 } 550 551 /** 552 * check if a row is valid 553 * 554 * @param row row to check 555 */ 556 public boolean isValidRow(int row) { 557 return row > -1 && row < getItemCount(); 558 } 559 560 /** 561 * check if a column is valid 562 * 563 * @param col col to check 564 */ 565 public boolean isValidColumn(int col) { 566 return col > -1 && col < getColumnCount(); 567 } 568 569 /** 570 * get the proportional widths of each column 571 */ 572 public float[] getWidths() { 573 return cwidth; 574 } 575 576 /** 577 * Set the proportional widths of each column 578 */ 579 public void setWidths(float w[]) { 580 for(int i = 0; i < getColumnCount(); i++) { 581 cwidth[i] = w[i]; 582 } 583 respace(); 584 repaint(); 585 } 586 587 /** 588 * can this lists columns be adjusted by user ? 589 */ 590 public boolean isAdjustable() { 591 return adjustable; 592 } 593 594 /** 595 * Turns on or off the user's ability to adjust column widths 596 * 597 * @param adjustable Can adjust or not? 598 */ 599 public void setAdjustable(boolean adjustable) { 600 this.adjustable = adjustable; 601 } 602 603 /** 604 * is a title shown ? 605 */ 606 public boolean isShowTitle() { 607 return showTitle; 608 } 609 610 /** 611 * show or hide a title 612 * 613 * @param showTitle show or not? 614 */ 615 public void setShowTitle(boolean showTitle) { 616 this.showTitle = showTitle; 617 scroll(0); 618 } 619 620 /** 621 * which columns are unique ? 622 */ 623 public int[] getUniqueColumns() { 624 return uniquecols; 625 } 626 627 /** 628 * set unique columns 629 * 630 * @param uc array of unique columns 631 */ 632 public void setUniqueColumns(int[] uc) { 633 uniquecols = uc; 634 } 635 636 /** 637 * reverse sort set ? 638 */ 639 public boolean isSortReverse() { 640 return sortrev; 641 } 642 643 /** 644 * set sort direction 645 * 646 * @param sortrev true=sort reverse false=sort normal 647 */ 648 public void setSortReverse(boolean sortrev) { 649 if(this.sortrev != sortrev) { 650 this.sortrev = sortrev; 651 if(sortcol > -1) { 652 reverse(); 653 if(selrow > -1) { 654 selrow = getItemCount() - selrow - 1; 655 } 656 makeVisible(); 657 } 658 } 659 } 660 661 /** 662 * which column to sort ? 663 */ 664 public int getSortColumn() { 665 return sortcol; 666 } 667 668 /** 669 * set column to sort 670 * 671 * @param sc sort column; -1 for none 672 */ 673 public void setSortColumn(int sc) { 674 if(sc < titles.length && sc > -2) { 675 if(sortcol != sc) { 676 sortcol = sc; 677 setSelectedIndex(-1);; 678 qsort(); 679 } 680 } 681 } 682 683 /** 684 * how many columns are currently visible? 685 */ 686 public int getVisibleCols() { 687 return visibleCols; 688 } 689 690 /** 691 * set number of visible columns 692 * 693 * @param cols how many columns should be visible 694 */ 695 public void setVisibleCols(int cols) { 696 if(cols < minVisibleCols) return; 697 if(cols > getColumnCount()) return; 698 visibleCols = cols; 699 respace(); 700 repaint(); 701 } 702 703 /** 704 * get all column titles 705 */ 706 public String[] getTitles() { 707 return titles; 708 } 709 710 /** 711 * Compute pixel column widths from proportional widths 712 */ 713 private void respace() { 714 float rsf = respacefactor(); 715 int vw = visibleWidth(); 716 cpos[0] = 0; 717 for(int i = 0; i < visibleCols; i++) { 718 cpos[i + 1] = cpos[i] + (int)(vw * cwidth[i] * rsf); 719 } 720 cpos[visibleCols] = vw - 1; 721 } 722 723 /** 724 * Compute pixel proportional widths from column pos 725 */ 726 private void revrespace() { 727 float rsf = respacefactor(); 728 int vw = visibleWidth(); 729 for(int i = 1; i < visibleCols; i++) { 730 cwidth[i -1] = (float)(cpos[i] - cpos[i - 1]) / (float) vw / rsf; 731 } 732 } 733 734 /** 735 * Compute average proportional widths 736 */ 737 private float respacefactor() { 738 float sum = 0; 739 for(int i = 0; i < visibleCols; ++i) { 740 sum += cwidth[i]; 741 } 742 return 1 / sum; 743 } 744 745 /** 746 * Called by the system when this component gets resized - intercept to do some layout 747 * @param x x-position 748 * @param y y-position 749 * @param w width 750 * @param h height 751 */ 752 public void setBounds(int x, int y, int w, int h) { 753 super.setBounds(x, y, w, h); 754 Rectangle ir = getInnerRectangle(); 755 scrollbar.setBounds(visibleWidth() + ir.x, titleHeight() + ir.y, scrollbarWidth(), visibleHeight()); 756 scrollbar.setUnitIncrement(Math.max(rowHeight(), 1)); 757 scrollbar.setBlockIncrement(Math.max(visibleHeight(), 1)); 758 respace(); 759 scroll(0); 760 } 761 762 /** 763 * Overwirte paint to do the actual artwork ;-) 764 * 765 * @param g Graphics object to use 766 */ 767 public void paint(Graphics g) { 768 Rectangle ir = getInnerRectangle(); 769 770 makeBim(); 771 772 int b = borderw; 773 int vh = visibleHeight(); 774 int vw = visibleWidth(); 775 int rh = rowHeight(); 776 int th = titleHeight(); 777 Color fg = getForeground(); 778 Color bg = getBackground(); 779 Color dbg = darker(bg); 780 Color bbg = brighter(bg); 781 782 Graphics tg = big.create(); 783 Graphics cg = big.create(); 784 785 Font font = getFont(); 786 787 tg.translate(ir.x, ir.y); 788 cg.translate(ir.x, ir.y); 789 790 tg.setClip(0, 0, ir.width, th); 791 cg.setClip(0, th, vw, vh); 792 793 paintBackground(big); 794 795 // highlight the selected row 796 if(isEnabled() && selrow > -1) { 797 drawRectangle(cg, fg, 0, row2y(selrow), vw, rh, 1, FILL); 798 } 799 800 // Draw each column 801 for(int i = 0; i < visibleCols; i++) { 802 int x = cpos[i]; 803 int y = row2y(firstRow()); 804 int w = cpos[i + 1] - x - 1; 805 int h = 0; 806 807 // visible Items 808 for(int j = firstRow(); y < getSize().height; j++, y += rh) { 809 drawCaption(cg, isEnabled() ? (j == selrow ? bbg : fg) : dbg, font, getItem(j, i), x + 2, y, w - 4, rh, LEFT, ""); 810 } 811 812 // draw column separators (lines) if requested 813 if(showLines && i < visibleCols - 1) { 814 drawLine(cg, dbg, x + w, th, x + w, th + vh, 1, 0); 815 drawLine(cg, bbg, x + w + 1, th, x + w + 1, th + vh, 1, 0); 816 } 817 818 // draw column title if requested 819 if(showTitle) { 820 drawRectangle(tg, bg, x, 0, i == visibleCols - 1 ? w + 1 : w, th - 1, 1, RAISED); 821 if(i == moc) { 822 drawRectangle(tg, dim(bg, -90), x + 1, 1, i == visibleCols - 1 ? w - 1 : w - 2, th - 3, 1, 0); 823 } 824 if(i == sortcol && w > th + b) { 825 drawTriangle(tg, bg, x + w - th + b + 2, b + 2, th - 2 * b - 4, th - 2 * b - 4, 1, sortrev ? SUNKEN | N : SUNKEN | S); 826 drawCaption(tg, isEnabled() ? fg : bg, bold(font), titles[i], x + b, 0, w - th - b, th, isEnabled() ? CENTER : CENTER | SUNKEN, ""); 827 } else { 828 drawCaption(tg, isEnabled() ? fg : bg, font, titles[i], x + b, 0, w - 2 * b, th, isEnabled() ? CENTER : CENTER | SUNKEN, ""); 829 } 830 } 831 832 // little arrow buttons for col +/- 833 if(i == 0) { 834 x = vw; 835 w = scrollbarWidth() / 2; // width of one box 836 h = th; // height of one box 837 838 drawRectangle(tg, bg, x, 0, w - 1, h - 1, 1, RAISED); 839 if(moc == -2) { 840 drawRectangle(tg, dim(bg, -90), x, 0, w - 1, h - 1, 1, 0); 841 } 842 drawRectangle(tg, bg, x + w, 0, w - 1, h - 1, 1, RAISED); 843 if(moc == -3) { 844 drawRectangle(tg, dim(bg, -90), x + w, 0, w - 1, h - 1, 1, 0); 845 } 846 847 w -= 2 * b; 848 h = 2 * w - 1; 849 y = th / 2 - h / 2; 850 851 if(isEnabled()) { 852 drawTriangle(tg, fg, x + b, y, w, h, visibleCols == getColumnCount() ? 1 : 99, LEFT); 853 drawTriangle(tg, fg, x + b + scrollbarWidth() / 2, y, w, h, visibleCols == minVisibleCols ? 1 : 99, RIGHT); 854 } 855 } 856 } 857 if(showLines) { 858 drawLine(cg, bbg, 0, th, 0, th + vh, 1, 0); 859 drawLine(cg, dbg, 0, th + vh - 1, vw, th + vh - 1, 1, 0); 860 } 861 862 super.paint(big); 863 paintBim(g); 864 } 865 866 /** 867 * Make sure selected item is visble 868 */ 869 public void makeVisible() { 870 if(selrow > -1) { 871 int y = row2y(selrow); 872 if(y < titleHeight() || y > visibleHeight() + 2 * borderw) { 873 scrollbar.setValue(Math.max(0, selrow * rowHeight() - visibleHeight() / 2)); 874 } 875 } 876 scroll(0); 877 } 878 879 /** 880 * implement ItemSelectable() 881 */ 882 public Object[] getSelectedObjects() { 883 return getSelectedItem(); 884 } 885 886 /** 887 * implement java.awt.event.MouseListener 888 * 889 */ 890 public void mouseExited(MouseEvent e) { 891 if(moc > -1) { 892 moc = -1; 893 repaint(); 894 } 895 } 896 897 /** 898 * implement java.awt.event.MouseListener 899 * Check if mouse is over a column separator & change cursor 900 */ 901 public void mouseMoved(MouseEvent e) { 902 if(isEnabled()) { 903 int x = e.getX(); 904 int y = e.getY(); 905 int h = titleHeight(); 906 int m = -1; 907 Cursor c = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); 908 909 if(x > visibleWidth() && y < h) { 910 if(x - visibleWidth() < scrollbarWidth() / 2) { 911 m=-2; 912 } else { 913 m=-3; 914 } 915 } else { 916 for(int i = 1; i <= visibleCols; i++) { 917 if(adjustable && Math.abs(cpos[i] - x) < borderw && i < visibleCols) { 918 c = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); 919 m = -1; 920 break; 921 } 922 if(y < h && x < cpos[i]) { 923 m = i - 1; 924 break; 925 } 926 } 927 } 928 setCursor(c); 929 if(m != moc) { 930 moc = m; 931 repaint(); 932 } 933 } 934 } 935 936 /** 937 * implement java.awt.event.MouseListener 938 * actively update screen while mouse drags column separator 939 */ 940 public void mouseDragged(MouseEvent e) { 941 if(dragcol > -1) { 942 if(e.getX() > cpos[dragcol - 1] + borderw && e.getX() < cpos[dragcol + 1] - borderw) { 943 cpos[dragcol] = e.getX(); 944 revrespace(); 945 repaint(); 946 } 947 } 948 } 949 950 /** 951 * implement java.awt.event.MouseListener 952 */ 953 public void mousePressed(MouseEvent e) { 954 if(isEnabled()) { 955 int x = e.getX(); 956 int y = e.getY(); 957 int w = visibleWidth(); 958 int rh = rowHeight(); 959 int r = y < titleHeight() ? -1 : (y - titleHeight() + scrollbar.getValue()) / rowHeight(); 960 ; 961 962 // request input focus for this widget 963 requestFocus(); 964 965 // dragging is definetly over after a mouse click 966 dragcol = -1; 967 968 // clicked right of bimage in title area ? 969 if(r == -1 && x > w) { 970 // So we hit the little column arrows, but which one? 971 if(x - w < scrollbarWidth() / 2) { 972 setVisibleCols(visibleCols + 1); 973 } else { 974 setVisibleCols(visibleCols - 1); 975 } 976 // click within bimage area ? 977 } else { 978 // find out what column 979 for(int c = 0; c < visibleCols; c++) { 980 // clicked on a column separator? -> start dragging 981 if(c > 0 && adjustable && Math.abs(cpos[c] - x) < borderw) { 982 dragcol = c; break; 983 } 984 // clicked on on this column ? 985 if(cpos[c] < x && cpos[c + 1] - borderw > x) { 986 // hit the column header ? 987 if(r == -1) { 988 // was this col sorted ? -> if yes reverse sort order 989 setSortReverse(getSortColumn() == c ? !isSortReverse() : false); 990 setSortColumn(c); 991 // not the header, the selection may change 992 } else { 993 // valid row ? 994 if(r < getItemCount()){ 995 // is this row already selected? -> may be a doubleclick? 996 if(r == selrow) { 997 if(e.getWhen() - lastclick < 200) { 998 notifyActionListeners(new ActionEvent(this, selrow, "")); 999 } 1000 1001 // just change selection and remember timestamp 1002 } else { 1003 setSelectedIndex(r); 1004 } 1005 lastclick = e.getWhen(); 1006 } 1007 } 1008 break; 1009 } 1010 } 1011 } 1012 } 1013 } 1014 1015 /** 1016 * implement java.awt.event.AdjustmentListener 1017 */ 1018 public void adjustmentValueChanged(AdjustmentEvent e) { 1019 repaint(); 1020 } 1021 1022 /** 1023 * manually scroll the list 1024 * 1025 * @param amount amount to scroll 1026 */ 1027 private void scroll(int amount) { 1028 scrollbar.setValue(scrollbar.getValue() + amount); 1029 scrollbar.setMaximum(getItemCount() * rowHeight()); 1030 scrollbar.setVisibleAmount(Math.max(1, visibleHeight())); 1031 repaint(); 1032 } 1033 1034 /** 1035 * Returns the internal rectangle - width 1036 */ 1037 private int visibleWidth() { 1038 return Math.max(getInnerRectangle().width - scrollbarWidth(), 0); 1039 } 1040 1041 /** 1042 * Returns the internal rectangle - height 1043 */ 1044 private int visibleHeight() { 1045 return Math.max(getInnerRectangle().height - titleHeight(), 0); 1046 } 1047 1048 /** 1049 * Returns the scrollbar - width 1050 */ 1051 private int scrollbarWidth() { 1052 return Math.max(scrollbar.getMinimumSize().width, 18); 1053 } 1054 1055 /** 1056 * Returns the Title height 1057 */ 1058 private int titleHeight() { 1059 return showTitle ? rowHeight() + 2 * borderw : 0; 1060 } 1061 1062 /** 1063 * Returns the height of a single row 1064 */ 1065 private int rowHeight() { 1066 return measureString("X", getFont()).height; 1067 } 1068 1069 /** 1070 * Returns the first row currently visible 1071 */ 1072 private int firstRow() { 1073 return (int) Math.floor(scrollbar.getValue() / rowHeight()); 1074 } 1075 1076 /** 1077 * Returns the position for a specific row 1078 * 1079 * @param r row 1080 */ 1081 private int row2y(int r) { 1082 return r * rowHeight() - scrollbar.getValue() + titleHeight(); 1083 } 1084 1085 /** 1086 * implement java.awt.event.KeyListener 1087 */ 1088 public void keyTyped(KeyEvent e) { 1089 int n = getItemCount(); 1090 int col = sortcol == -1 ? 0 : sortcol; 1091 char chr = e.getKeyChar(); 1092 for(int i = 0; i < n; ++i) { 1093 String item = getItem(i, col); 1094 if(item.length() > 0 && item.charAt(0) == chr) { 1095 setSelectedIndex(i); 1096 break; 1097 } 1098 } 1099 makeVisible(); 1100 }; 1101 1102 /** 1103 * implement java.awt.event.KeyListener 1104 */ 1105 public void keyPressed(KeyEvent e) { 1106 switch(e.getKeyCode()) { 1107 case KeyEvent.VK_HOME: setSelectedIndex(0); break; 1108 case KeyEvent.VK_END: setSelectedIndex(getItemCount() - 1); break; 1109 case KeyEvent.VK_UP: setSelectedIndex(selrow - 1); break; 1110 case KeyEvent.VK_DOWN: setSelectedIndex(selrow + 1); break; 1111 case KeyEvent.VK_PAGE_UP: scroll(-visibleHeight()); break; 1112 case KeyEvent.VK_PAGE_DOWN: scroll(+visibleHeight()); break; 1113 1114 case KeyEvent.VK_LEFT: setVisibleCols(visibleCols - 1); break; 1115 case KeyEvent.VK_RIGHT: setVisibleCols(visibleCols + 1); break; 1116 1117 case KeyEvent.VK_ENTER: notifyActionListeners(new ActionEvent(this, selrow, "")); break; 1118 case KeyEvent.VK_ESCAPE: if(selrow == -1) setSortColumn(-1); else setSelectedIndex(-1); break; 1119 } 1120 makeVisible(); 1121 } 1122 1123 /** 1124 * calculate required sizes 1125 */ 1126 public Dimension measure() { 1127 Dimension bd = super.measure(); 1128 Dimension md = new Dimension(visibleCols * 40, minVisibleRows * rowHeight() + titleHeight()); 1129 bd.width += md.width; 1130 bd.height += md.height; 1131 return bd; 1132 } 1133 1134 /** 1135 * sort this list if requested 1136 */ 1137 private void qsort() { 1138 if(sortcol > -1) { 1139 spos = new int[items[sortcol].size()]; 1140 qsort(items[sortcol], 0, items[sortcol].size() - 1); 1141 } 1142 repaint(); 1143 } 1144 1145 /** 1146 * implement quicksort 1147 * 1148 * @param v the Vector of the sortcolumn 1149 * @param first where to start in list 1150 * @param last where to stop in list 1151 */ 1152 private void qsort(Vector v, int first, int last) { 1153 int i, j; 1154 if(first >= last) return; 1155 for(i = first, j = last; ; j--) { 1156 while(i != j && compare(v, i, j)) j--; 1157 if(i == j) break; 1158 swap(i, j); 1159 do i++; while(i != j && compare(v, i, j)); 1160 if(i == j) break; 1161 swap(i, j); 1162 } 1163 qsort(v, first, i - 1); 1164 qsort(v, i + 1, last); 1165 } 1166 1167 /** 1168 * swap two rows - needed for qsort 1169 * 1170 * @param a row 1 1171 * @param b row 2 1172 */ 1173 private void swap(int a, int b) { 1174 for(int i = 0; i < getColumnCount(); i++) { 1175 Object obj = items[i].elementAt(a); 1176 items[i].setElementAt(items[i].elementAt(b), a); 1177 items[i].setElementAt(obj, b); 1178 } 1179 } 1180 1181 /** 1182 * compare two items - needed for qsort 1183 * 1184 * @param v the Vector of the sortcolumn 1185 * @param a row 1 1186 * @param b row 2 1187 */ 1188 private boolean compare(Vector v, int a, int b) { 1189 return compare((String) v.elementAt(a), (String) v.elementAt(b)) < 0; 1190 } 1191 1192 /** 1193 * compare two strings - needed for qsort<br> 1194 * sorts reverse if requested<br> 1195 * ignores case if requested 1196 * 1197 * @param A String 1 1198 * @param B String 2 1199 */ 1200 private int compare(String A, String B) { 1201 String a = sortrev ? B : A; 1202 String b = sortrev ? A : B; 1203 1204 int la = a.length(); 1205 int lb = b.length(); 1206 1207 for(int i = 0; i < la && i < lb; i++) { 1208 char ca = sortign ? Character.toLowerCase(a.charAt(i)) : a.charAt(i); 1209 char cb = sortign ? Character.toLowerCase(b.charAt(i)) : b.charAt(i); 1210 if(ca < cb) return -1; 1211 if(ca > cb) return 1; 1212 } 1213 if(la < lb) return -1; 1214 if(la > lb) return 1; 1215 return 0; 1216 } 1217 1218 /** 1219 * reverse current list - no specific sort order 1220 */ 1221 private void reverse() { 1222 for(int i = 0, j = getItemCount() - 1; i < j; ++i, --j) { 1223 swap(i, j); 1224 } 1225 } 1226 1227 /** 1228 * override Awt.addNotify() 1229 */ 1230 public void addNotify() { 1231 super.addNotify(); 1232 scrollbar.setBackground(getBackground()); 1233 } 1234 }