Handle hostnames with upper-case letters
[webmin.git] / proc / 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         // Returns the total number of rows
273         public int count()
274         {
275         return list[0].size();
276         }
277
278         // reshape
279         // Called when this component gets resized
280         public void reshape(int nx, int ny, int nw, int nh)
281         {
282         if (nw != width+sbwidth || nh != height) {
283                 in = insets();
284                 sbwidth = sb.minimumSize().width;
285                 width = nw-sbwidth - (in.left + in.right);
286                 height = nh - (in.top + in.bottom);
287                 sb.reshape(width+in.left, in.top, sbwidth, height);
288                 respace();
289
290                 // Force creation of a new backing image and re-painting
291                 bim = null;
292                 repaint();
293                 compscroll();
294                 }
295         super.reshape(nx, ny, nw, nh);
296         }
297
298         // respace
299         // Compute pixel column widths from proportional widths
300         void respace()
301         {
302         cpos[0] = 0;
303         for(int i=0; i<title.length; i++)
304                 cpos[i+1] = cpos[i] + (int)(width*cwidth[i]);
305         }
306
307         // paint
308         // Blit the backing image to the front
309         public void paint(Graphics g)
310         {
311         super.paint(g);
312         if (bim == null) {
313                 // This is the first rendering
314                 bim = createImage(width, height);
315                 bg = bim.getGraphics();
316                 bg.setFont(font);
317                 fnm = bg.getFontMetrics();
318                 th = fnm.getHeight() + 4;
319                 render();
320                 compscroll();
321                 }
322         g.drawImage(bim, in.left, in.top, this);
323         }
324
325         // update
326         // Called sometime after repaint()
327         public void update(Graphics g)
328         {
329         if (fnm != null) {
330                 render();
331                 paint(g);
332                 }
333         }
334
335         // render
336         // Re-draw the list into the backing image
337         void render()
338         {
339         int fh = fnm.getHeight(),       // useful font metrics
340             fd = fnm.getDescent(),
341             fa = fnm.getAscent();
342         int bot = Math.min(top+rows()-1, list[0].size()-1);
343
344         // Clear title section and list
345         bg.setColor(Util.body);
346         bg.fillRect(0, 0, width, th);
347         bg.setColor(Util.light_bg);
348         bg.fillRect(0, th, width, height-th);
349         Color lighterGray = Util.body_hi;
350
351         if (enabled) {
352                 // Mark the selected rows
353                 for(int i=0; i<sels.length; i++) {
354                         if (sels[i] >= top && sels[i] <= bot) {
355                                 bg.setColor(sels[i] == sel ? Util.body
356                                                            : lighterGray);
357                                 bg.fillRect(0, th+(sels[i]-top)*rowh,
358                                             width, rowh);
359                                 }
360                         }
361                 }
362
363         // Draw each column
364         for(int i=0; i<title.length; i++) {
365                 int x = cpos[i], w = cpos[i+1]-x-1;
366
367                 // Column title
368                 bg.setColor(Util.light_edge);
369                 bg.drawLine(x, 0, x+w, 0);
370                 bg.drawLine(x, 1, x+w-1, 1);
371                 bg.drawLine(x, 0, x, th-1);
372                 bg.drawLine(x+1, 0, x+1, th-2);
373                 bg.setColor(Util.dark_edge);
374                 bg.drawLine(x, th-1, x+w, th-1);
375                 bg.drawLine(x, th-2, x+w-1, th-2);
376                 bg.drawLine(x+w, th-1, x+w, 0);
377                 bg.drawLine(x+w-1, th-1, x+w-1, 1);
378                 int tw = fnm.stringWidth(title[i]);
379                 if (tw < w-6)
380                         bg.drawString(title[i], x+(w-tw)/2, th-fd-2);
381
382                 // Sorting arrow
383                 int as = th-8;
384                 if (sortcol == i && sortdir == 1) {
385                         bg.setColor(Util.light_edge);
386                         bg.drawLine(x+4, th-5, x+4+as, th-5);
387                         bg.drawLine(x+4+as, th-5, x+4+as/2, th-5-as);
388                         bg.setColor(Util.dark_edge);
389                         bg.drawLine(x+4+as/2, th-5-as, x+4, th-5);
390                         }
391                 else if (sortcol == i && sortdir == 2) {
392                         bg.setColor(Util.light_edge);
393                         bg.drawLine(x+4+as/2, th-5, x+4+as, th-5-as);
394                         bg.setColor(Util.dark_edge);
395                         bg.drawLine(x+4, th-5-as, x+4+as, th-5-as);
396                         bg.drawLine(x+4, th-5-as, x+4+as/2, th-5);
397                         }
398
399                 // Column items
400                 if (drawlines) {
401                         bg.setColor(Util.body);
402                         bg.drawLine(x+w-1, th, x+w-1, height);
403                         bg.setColor(Util.dark_edge);
404                         bg.drawLine(x+w, th, x+w, height);
405                         }
406                 for(int j=top; j<=bot; j++) {
407                         Object o = list[i].elementAt(j);
408                         if (o instanceof String) {
409                                 // Render string in column
410                                 String s = (String)o;
411                                 while(fnm.stringWidth(s) > w-3)
412                                         s = s.substring(0, s.length()-1);
413                                 if (!enabled)
414                                         bg.setColor(Util.body);
415                                 else if (colors != null)
416                                         bg.setColor(colors[j][i]);
417                                 bg.drawString(s, x+1, th+(j+1-top)*rowh-fd);
418                                 }
419                         else if (o instanceof Image) {
420                                 // Render image in column
421                                 Image im = (Image)o;
422                                 bg.drawImage(im, x+1, th+(j-top)*rowh, this);
423                                 }
424                         }
425                 }
426         }
427
428         // mouseDown
429         // Select a list item or a column to drag
430         public boolean mouseDown(Event e, int x, int y)
431         {
432         if (!enabled) {
433                 return true;
434                 }
435         x -= in.left;
436         y -= in.top;
437         coldrag = -1;
438         if (y < th) {
439                 // Click in title bar
440                 for(int i=0; i<title.length; i++) {
441                         if (adjustable && i > 0 && Math.abs(cpos[i] - x) < 3) {
442                                 // clicked on a column separator
443                                 coldrag = i;
444                                 }
445                         else if (x >= cpos[i] && x < cpos[i+1]) {
446                                 // clicked in a title
447                                 callback.headingClicked(this, i);
448                                 }
449                         }
450                 }
451         else {
452                 // Item chosen from list
453                 int row = (y-th)/rowh + top;
454                 if (row < list[0].size()) {
455                         // Double-click?
456                         boolean dclick = false;
457                         if (e.when-last < 1000 && sel == row)
458                                 dclick = true;
459                         else
460                                 last = e.when;
461
462                         if (e.shiftDown() && multiselect && sel != -1) {
463                                 // Select all from last selection to this one
464                                 int zero = sels[0];
465                                 if (zero < row) {
466                                         sels = new int[row-zero+1];
467                                         for(int i=zero; i<=row; i++)
468                                                 sels[i-zero] = i;
469                                         }
470                                 else {
471                                         sels = new int[zero-row+1];
472                                         for(int i=zero; i>=row; i--)
473                                                 sels[zero-i] = i;
474                                         }
475                                 }
476                         else if (e.controlDown() && multiselect) {
477                                 // Add this one to selection
478                                 int nsels[] = new int[sels.length + 1];
479                                 System.arraycopy(sels, 0, nsels, 0,sels.length);
480                                 nsels[sels.length] = row;
481                                 sels = nsels;
482                                 }
483                         else {
484                                 // Select one row only, and de-select others
485                                 sels = new int[1];
486                                 sels[0] = row;
487                                 }
488                         sel = row;
489                         repaint();
490                         last_event = e;
491                         if (callback != null) {
492                                 // Callback the right function
493                                 if (dclick) callback.doubleClick(this, row);
494                                 else        callback.singleClick(this, row);
495                                 }
496                         else {
497                                 // Send an event
498                                 getParent().postEvent(
499                                         new Event(this,
500                                                   Event.ACTION_EVENT,
501                                                   dclick?"Double":"Single"));
502                                 }
503                         }
504                 }
505         return true;
506         }
507
508         // mouseDrag
509         // If a column is selected, change it's width
510         public boolean mouseDrag(Event e, int x, int y)
511         {
512         if (!enabled) {
513                 return true;
514                 }
515         x -= in.left;
516         y -= in.top;
517         if (coldrag != -1) {
518                 if (x > cpos[coldrag-1]+3 && x < cpos[coldrag+1]-3) {
519                         cpos[coldrag] = x;
520                         cwidth[coldrag-1] = (cpos[coldrag]-cpos[coldrag-1]) /
521                                             (float)width;
522                         cwidth[coldrag] = (cpos[coldrag+1]-cpos[coldrag]) /
523                                             (float)width;
524                         repaint();
525                         }
526                 }
527         return true;
528         }
529
530         public void moved(CbScrollbar s, int v)
531         {
532         moving(s, v);
533         }
534
535         public void moving(CbScrollbar s, int v)
536         {
537         top = sb.getValue();
538         compscroll();
539         repaint();
540         }
541
542         // compscroll
543         // Re-compute the size of the scrollbar
544         private void compscroll()
545         {
546         if (fnm == null)
547                 return;         // not visible
548         int r = rows();
549         int c = list[0].size() - r;
550         sb.setValues(top, r==0?1:r, list[0].size());
551         }
552
553         // rows
554         // Returns the number of rows visible in the list
555         private int rows()
556         {
557         return Math.min(height/rowh - 1, list[0].size());
558         }
559
560         public Dimension minimumSize()
561         {
562         return new Dimension(400, 100);
563         }
564
565         public Dimension preferredSize()
566         {
567         return minimumSize();
568         }
569 }
570
571 // MultiColumnCallback
572 // Objects implementing this interface can be passed to the MultiColumn
573 // class, to have their singleClick() and doubleClick() functions called in
574 // response to single or double click in the list.
575 interface MultiColumnCallback
576 {
577         // singleClick
578         // Called on a single click on a list item
579         void singleClick(MultiColumn list, int num);
580
581         // doubleClick
582         // Called upon double-clicking on a list item
583         void doubleClick(MultiColumn list, int num);
584
585         // headingClicked
586         // Called when a column heading is clicked on
587         void headingClicked(MultiColumn list, int col);
588 }
589