Handle hostnames with upper-case letters
[webmin.git] / file / MultiColumn.java
1 // MultiColumn
2 // A List box that supports multiple columns.
3 import java.awt.*;
4 import java.util.Vector;
5
6 public class MultiColumn extends BorderPanel implements CbScrollbarCallback
7 {
8         MultiColumnCallback callback;   // what to call back to 
9         String title[];                 // column titles
10         boolean adjustable = true;
11         boolean drawlines = true;
12         Color colors[][] = null;
13         boolean enabled = true;
14         boolean multiselect = false;
15         int cpos[];                     // column x positions
16         float cwidth[];                 // proportional column widths
17         Vector list[];                  // columns of the list
18         CbScrollbar sb;                 // scrollbar at the right side
19         int width, height;              // size, minus the scrollbar
20         Insets in;                      // used space around the border
21         int sbwidth;                    // width of the scrollbar
22         int th;                         // height of title bar
23         Image bim;                      // backing image
24         Graphics bg;                    // backing graphics
25         Font font = new Font("timesRoman", Font.PLAIN, 12);
26         FontMetrics fnm;                // drawing font size
27         int coldrag = -1;               // column being resized
28         int sel = -1;                   // selected row
29         int sels[] = new int[0];        // all selected rows
30         int top = 0;                    // first row displayed
31         long last;                      // last mouse click time
32         int rowh = 16;                  // row height
33         Event last_event;               // last event that triggered callback
34         int sortcol;                    // Column currently being sorted
35         int sortdir;                    // Sort direction (0=none, 1=up, 2=down)
36
37         // Create a new list with the given column titles
38         MultiColumn(String t[])
39         {
40         super(3, Util.dark_edge_hi, Util.body_hi);
41         title = new String[t.length];
42         for(int i=0; i<t.length; i++)
43                 title[i] = t[i];
44         list = new Vector[t.length];
45         for(int i=0; i<t.length; i++)
46                 list[i] = new Vector();
47         cwidth = new float[t.length];
48         for(int i=0; i<t.length; i++)
49                 cwidth[i] = 1.0f/t.length;
50         cpos = new int[t.length+1];
51         setLayout(null);
52         sb = new CbScrollbar(CbScrollbar.VERTICAL, this);
53         add(sb);
54         }
55
56         // Create a new list that calls back to the given object on
57         // single or double clicks.
58         MultiColumn(String t[], MultiColumnCallback c)
59         {
60         this(t);
61         callback = c;
62         }
63
64         // addItem
65         // Add a row to the list
66         void addItem(Object item[])
67         {
68         for(int i=0; i<title.length; i++)
69                 list[i].addElement(item[i]);
70         repaint();
71         compscroll();
72         }
73
74         // addItems
75         // Add several rows to the list
76         void addItems(Object item[][])
77         {
78         for(int i=0; i<item.length; i++)
79                 for(int j=0; j<title.length; j++)
80                         list[j].addElement(item[i][j]);
81         repaint();
82         compscroll();
83         }
84
85         // modifyItem
86         // Changes one row of the table
87         void modifyItem(Object item[], int row)
88         {
89         for(int i=0; i<title.length; i++)
90                 list[i].setElementAt(item[i], row);
91         repaint();
92         compscroll();
93         }
94
95         // getItem
96         // Returns the contents of a given row
97         Object []getItem(int n)
98         {
99         Object r[] = new Object[title.length];
100         for(int i=0; i<title.length; i++)
101                 r[i] = list[i].elementAt(n);
102         return r;
103         }
104
105         // selected
106         // Return the most recently selected row
107         int selected()
108         {
109         return sel;
110         }
111
112         // select
113         // Select some row
114         void select(int s)
115         {
116         sel = s;
117         sels = new int[1];
118         sels[0] = s;
119         repaint();
120         }
121
122         // select
123         // Select multiple rows
124         void select(int s[])
125         {
126         if (s.length == 0) {
127                 sel = -1;
128                 sels = new int[0];
129                 }
130         else {
131                 sel = s[0];
132                 sels = s;
133                 }
134         repaint();
135         }
136
137         // allSelected
138         // Returns all the selected rows
139         int[] allSelected()
140         {
141         return sels;
142         }
143
144         // scrollto
145         // Scroll to make some row visible
146         void scrollto(int s)
147         {
148         int r = rows();
149         if (s < top || s >= top+r) {
150                 top = s-1;
151                 if (top > list[0].size() - r)
152                         top = list[0].size() - r;
153                 sb.setValue(top);
154                 repaint();
155                 }
156         }
157
158         // deleteItem
159         // Remove one row from the list
160         void deleteItem(int n)
161         {
162         for(int i=0; i<title.length; i++)
163                 list[i].removeElementAt(n);
164         if (n == sel) {
165                 // De-select deleted file
166                 sel = -1;
167                 }
168         for(int i=0; i<sels.length; i++) {
169                 if (sels[i] == n) {
170                         // Remove from selection list
171                         int nsels[] = new int[sels.length-1];
172                         if (nsels.length > 0) {
173                                 System.arraycopy(sels, 0, nsels, 0, i);
174                                 System.arraycopy(sels, i+1, nsels, i,
175                                                  nsels.length-i);
176                                 sel = nsels[0];
177                                 }
178                         break;
179                         }
180                 }
181         repaint();
182         compscroll();
183         }
184
185         // clear
186         // Remove everything from the list
187         void clear()
188         {
189         for(int i=0; i<title.length; i++)
190                 list[i].removeAllElements();
191         sel = -1;
192         sels = new int[0];
193         top = 0;
194         repaint();
195         sb.setValues(0, 1, 0);
196         }
197
198         // setWidths
199         // Set the proportional widths of each column
200         void setWidths(float w[])
201         {
202         for(int i=0; i<title.length; i++)
203                 cwidth[i] = w[i];
204         respace();
205         repaint();
206         }
207
208         /**Turns on or off the user's ability to adjust column widths
209          * @param a     Can adjust or not?
210          */
211         void setAdjustable(boolean a)
212         {
213         adjustable = a;
214         }
215
216         /**Turns on or off the drawing of column lines
217          * @param d     Draw lines or not?
218          */
219         void setDrawLines(boolean d)
220         {
221         drawlines = d;
222         }
223
224         /**Sets the array of colors used to draw text items.
225          * @param c     The color array (in row/column order), or null to
226          *              use the default
227          */
228         void setColors(Color c[][])
229         {
230         colors = c;
231         repaint();
232         }
233
234         // Turns on or off multi-row selection with ctrl and shift
235         void setMultiSelect(boolean m)
236         {
237         multiselect = m;
238         }
239
240         // Enables the entire list
241         public void enable()
242         {
243         enabled = true;
244         sb.enable();
245         repaint();
246         }
247
248         // Disables the entire list
249         public void disable()
250         {
251         enabled = false;
252         sb.disable();
253         repaint();
254         }
255
256         // Sets or turns off the sort indication arrow for a column
257         // Direction 0 = None, 1 = Up arrow, 2 = Down arrow
258         public void sortingArrow(int col, int dir)
259         {
260         sortcol = col;
261         sortdir = dir;
262         repaint();
263         }
264
265         public void setFont(Font f)
266         {
267         font = f;
268         bim = null;
269         repaint();
270         }
271
272         // reshape
273         // Called when this component gets resized
274         public void reshape(int nx, int ny, int nw, int nh)
275         {
276         if (nw != width+sbwidth || nh != height) {
277                 in = insets();
278                 sbwidth = sb.minimumSize().width;
279                 width = nw-sbwidth - (in.left + in.right);
280                 height = nh - (in.top + in.bottom);
281                 sb.reshape(width+in.left, in.top, sbwidth, height);
282                 respace();
283
284                 // Force creation of a new backing image and re-painting
285                 bim = null;
286                 repaint();
287                 compscroll();
288                 }
289         super.reshape(nx, ny, nw, nh);
290         }
291
292         // respace
293         // Compute pixel column widths from proportional widths
294         void respace()
295         {
296         cpos[0] = 0;
297         for(int i=0; i<title.length; i++)
298                 cpos[i+1] = cpos[i] + (int)(width*cwidth[i]);
299         }
300
301         // paint
302         // Blit the backing image to the front
303         public void paint(Graphics g)
304         {
305         super.paint(g);
306         if (bim == null) {
307                 // This is the first rendering
308                 bim = createImage(width, height);
309                 bg = bim.getGraphics();
310                 bg.setFont(font);
311                 fnm = bg.getFontMetrics();
312                 th = fnm.getHeight() + 4;
313                 render();
314                 compscroll();
315                 }
316         g.drawImage(bim, in.left, in.top, this);
317         }
318
319         // update
320         // Called sometime after repaint()
321         public void update(Graphics g)
322         {
323         if (fnm != null) {
324                 render();
325                 paint(g);
326                 }
327         }
328
329         // render
330         // Re-draw the list into the backing image
331         void render()
332         {
333         int fh = fnm.getHeight(),       // useful font metrics
334             fd = fnm.getDescent(),
335             fa = fnm.getAscent();
336         int bot = Math.min(top+rows()-1, list[0].size()-1);
337
338         // Clear title section and list
339         bg.setColor(Util.body);
340         bg.fillRect(0, 0, width, th);
341         bg.setColor(Util.light_bg);
342         bg.fillRect(0, th, width, height-th);
343         Color lighterGray = Util.body_hi;
344
345         if (enabled) {
346                 // Mark the selected rows
347                 for(int i=0; i<sels.length; i++) {
348                         if (sels[i] >= top && sels[i] <= bot) {
349                                 bg.setColor(sels[i] == sel ? Util.body
350                                                            : lighterGray);
351                                 bg.fillRect(0, th+(sels[i]-top)*rowh,
352                                             width, rowh);
353                                 }
354                         }
355                 }
356
357         // Draw each column
358         for(int i=0; i<title.length; i++) {
359                 int x = cpos[i], w = cpos[i+1]-x-1;
360
361                 // Column title
362                 bg.setColor(Util.light_edge);
363                 bg.drawLine(x, 0, x+w, 0);
364                 bg.drawLine(x, 1, x+w-1, 1);
365                 bg.drawLine(x, 0, x, th-1);
366                 bg.drawLine(x+1, 0, x+1, th-2);
367                 bg.setColor(Util.dark_edge);
368                 bg.drawLine(x, th-1, x+w, th-1);
369                 bg.drawLine(x, th-2, x+w-1, th-2);
370                 bg.drawLine(x+w, th-1, x+w, 0);
371                 bg.drawLine(x+w-1, th-1, x+w-1, 1);
372                 int tw = fnm.stringWidth(title[i]);
373                 if (tw < w-6)
374                         bg.drawString(title[i], x+(w-tw)/2, th-fd-2);
375
376                 // Sorting arrow
377                 int as = th-8;
378                 if (sortcol == i && sortdir == 1) {
379                         bg.setColor(Util.light_edge);
380                         bg.drawLine(x+4, th-5, x+4+as, th-5);
381                         bg.drawLine(x+4+as, th-5, x+4+as/2, th-5-as);
382                         bg.setColor(Util.dark_edge);
383                         bg.drawLine(x+4+as/2, th-5-as, x+4, th-5);
384                         }
385                 else if (sortcol == i && sortdir == 2) {
386                         bg.setColor(Util.light_edge);
387                         bg.drawLine(x+4+as/2, th-5, x+4+as, th-5-as);
388                         bg.setColor(Util.dark_edge);
389                         bg.drawLine(x+4, th-5-as, x+4+as, th-5-as);
390                         bg.drawLine(x+4, th-5-as, x+4+as/2, th-5);
391                         }
392
393                 // Column items
394                 if (drawlines) {
395                         bg.setColor(Util.body);
396                         bg.drawLine(x+w-1, th, x+w-1, height);
397                         bg.setColor(Util.dark_edge);
398                         bg.drawLine(x+w, th, x+w, height);
399                         }
400                 for(int j=top; j<=bot; j++) {
401                         Object o = list[i].elementAt(j);
402                         if (o instanceof String) {
403                                 // Render string in column
404                                 String s = (String)o;
405                                 while(fnm.stringWidth(s) > w-3)
406                                         s = s.substring(0, s.length()-1);
407                                 if (!enabled)
408                                         bg.setColor(Util.body);
409                                 else if (colors != null)
410                                         bg.setColor(colors[j][i]);
411                                 bg.drawString(s, x+1, th+(j+1-top)*rowh-fd);
412                                 }
413                         else if (o instanceof Image) {
414                                 // Render image in column
415                                 Image im = (Image)o;
416                                 bg.drawImage(im, x+1, th+(j-top)*rowh, this);
417                                 }
418                         }
419                 }
420         }
421
422         // mouseDown
423         // Select a list item or a column to drag
424         public boolean mouseDown(Event e, int x, int y)
425         {
426         if (!enabled) {
427                 return true;
428                 }
429         x -= in.left;
430         y -= in.top;
431         coldrag = -1;
432         if (y < th) {
433                 // Click in title bar
434                 for(int i=0; i<title.length; i++) {
435                         if (adjustable && i > 0 && Math.abs(cpos[i] - x) < 3) {
436                                 // clicked on a column separator
437                                 coldrag = i;
438                                 }
439                         else if (x >= cpos[i] && x < cpos[i+1]) {
440                                 // clicked in a title
441                                 callback.headingClicked(this, i);
442                                 }
443                         }
444                 }
445         else {
446                 // Item chosen from list
447                 int row = (y-th)/rowh + top;
448                 if (row < list[0].size()) {
449                         // Double-click?
450                         boolean dclick = false;
451                         if (e.when-last < 1000 && sel == row)
452                                 dclick = true;
453                         else
454                                 last = e.when;
455
456                         if (e.shiftDown() && multiselect && sel != -1) {
457                                 // Select all from last selection to this one
458                                 int zero = sels[0];
459                                 if (zero < row) {
460                                         sels = new int[row-zero+1];
461                                         for(int i=zero; i<=row; i++)
462                                                 sels[i-zero] = i;
463                                         }
464                                 else {
465                                         sels = new int[zero-row+1];
466                                         for(int i=zero; i>=row; i--)
467                                                 sels[zero-i] = i;
468                                         }
469                                 }
470                         else if (e.controlDown() && multiselect) {
471                                 // Add this one to selection
472                                 int nsels[] = new int[sels.length + 1];
473                                 System.arraycopy(sels, 0, nsels, 0,sels.length);
474                                 nsels[sels.length] = row;
475                                 sels = nsels;
476                                 }
477                         else {
478                                 // Select one row only, and de-select others
479                                 sels = new int[1];
480                                 sels[0] = row;
481                                 }
482                         sel = row;
483                         repaint();
484                         last_event = e;
485                         if (callback != null) {
486                                 // Callback the right function
487                                 if (dclick) callback.doubleClick(this, row);
488                                 else        callback.singleClick(this, row);
489                                 }
490                         else {
491                                 // Send an event
492                                 getParent().postEvent(
493                                         new Event(this,
494                                                   Event.ACTION_EVENT,
495                                                   dclick?"Double":"Single"));
496                                 }
497                         }
498                 }
499         return true;
500         }
501
502         // mouseDrag
503         // If a column is selected, change it's width
504         public boolean mouseDrag(Event e, int x, int y)
505         {
506         if (!enabled) {
507                 return true;
508                 }
509         x -= in.left;
510         y -= in.top;
511         if (coldrag != -1) {
512                 if (x > cpos[coldrag-1]+3 && x < cpos[coldrag+1]-3) {
513                         cpos[coldrag] = x;
514                         cwidth[coldrag-1] = (cpos[coldrag]-cpos[coldrag-1]) /
515                                             (float)width;
516                         cwidth[coldrag] = (cpos[coldrag+1]-cpos[coldrag]) /
517                                             (float)width;
518                         repaint();
519                         }
520                 }
521         return true;
522         }
523
524         public void moved(CbScrollbar s, int v)
525         {
526         moving(s, v);
527         }
528
529         public void moving(CbScrollbar s, int v)
530         {
531         top = sb.getValue();
532         compscroll();
533         repaint();
534         }
535
536         // compscroll
537         // Re-compute the size of the scrollbar
538         private void compscroll()
539         {
540         if (fnm == null)
541                 return;         // not visible
542         int r = rows();
543         int c = list[0].size() - r;
544         sb.setValues(top, r==0?1:r, list[0].size());
545         }
546
547         // rows
548         // Returns the number of rows visible in the list
549         private int rows()
550         {
551         return Math.min(height/rowh - 1, list[0].size());
552         }
553
554         public Dimension minimumSize()
555         {
556         return new Dimension(400, 100);
557         }
558
559         public Dimension preferredSize()
560         {
561         return minimumSize();
562         }
563 }
564
565 // MultiColumnCallback
566 // Objects implementing this interface can be passed to the MultiColumn
567 // class, to have their singleClick() and doubleClick() functions called in
568 // response to single or double click in the list.
569 interface MultiColumnCallback
570 {
571         // singleClick
572         // Called on a single click on a list item
573         void singleClick(MultiColumn list, int num);
574
575         // doubleClick
576         // Called upon double-clicking on a list item
577         void doubleClick(MultiColumn list, int num);
578
579         // headingClicked
580         // Called when a column heading is clicked on
581         void headingClicked(MultiColumn list, int col);
582 }
583