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    }