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 }