2 // A List box that supports multiple columns.
4 import java.util.Vector;
6 public class MultiColumn extends BorderPanel implements CbScrollbarCallback
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)
37 // Create a new list with the given column titles
38 MultiColumn(String t[])
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++)
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];
52 sb = new CbScrollbar(CbScrollbar.VERTICAL, this);
56 // Create a new list that calls back to the given object on
57 // single or double clicks.
58 MultiColumn(String t[], MultiColumnCallback c)
65 // Add a row to the list
66 void addItem(Object item[])
68 for(int i=0; i<title.length; i++)
69 list[i].addElement(item[i]);
75 // Add several rows to the list
76 void addItems(Object item[][])
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]);
86 // Changes one row of the table
87 void modifyItem(Object item[], int row)
89 for(int i=0; i<title.length; i++)
90 list[i].setElementAt(item[i], row);
96 // Returns the contents of a given row
97 Object []getItem(int n)
99 Object r[] = new Object[title.length];
100 for(int i=0; i<title.length; i++)
101 r[i] = list[i].elementAt(n);
106 // Return the most recently selected row
123 // Select multiple rows
138 // Returns all the selected rows
145 // Scroll to make some row visible
149 if (s < top || s >= top+r) {
151 if (top > list[0].size() - r)
152 top = list[0].size() - r;
159 // Remove one row from the list
160 void deleteItem(int n)
162 for(int i=0; i<title.length; i++)
163 list[i].removeElementAt(n);
165 // De-select deleted file
168 for(int i=0; i<sels.length; i++) {
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,
186 // Remove everything from the list
189 for(int i=0; i<title.length; i++)
190 list[i].removeAllElements();
195 sb.setValues(0, 1, 0);
199 // Set the proportional widths of each column
200 void setWidths(float w[])
202 for(int i=0; i<title.length; i++)
208 /**Turns on or off the user's ability to adjust column widths
209 * @param a Can adjust or not?
211 void setAdjustable(boolean a)
216 /**Turns on or off the drawing of column lines
217 * @param d Draw lines or not?
219 void setDrawLines(boolean d)
224 /**Sets the array of colors used to draw text items.
225 * @param c The color array (in row/column order), or null to
228 void setColors(Color c[][])
234 // Turns on or off multi-row selection with ctrl and shift
235 void setMultiSelect(boolean m)
240 // Enables the entire list
248 // Disables the entire list
249 public void disable()
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)
265 public void setFont(Font f)
272 // Returns the total number of rows
275 return list[0].size();
279 // Called when this component gets resized
280 public void reshape(int nx, int ny, int nw, int nh)
282 if (nw != width+sbwidth || nh != height) {
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);
290 // Force creation of a new backing image and re-painting
295 super.reshape(nx, ny, nw, nh);
299 // Compute pixel column widths from proportional widths
303 for(int i=0; i<title.length; i++)
304 cpos[i+1] = cpos[i] + (int)(width*cwidth[i]);
308 // Blit the backing image to the front
309 public void paint(Graphics g)
313 // This is the first rendering
314 bim = createImage(width, height);
315 bg = bim.getGraphics();
317 fnm = bg.getFontMetrics();
318 th = fnm.getHeight() + 4;
322 g.drawImage(bim, in.left, in.top, this);
326 // Called sometime after repaint()
327 public void update(Graphics g)
336 // Re-draw the list into the backing image
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);
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;
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
357 bg.fillRect(0, th+(sels[i]-top)*rowh,
364 for(int i=0; i<title.length; i++) {
365 int x = cpos[i], w = cpos[i+1]-x-1;
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]);
380 bg.drawString(title[i], x+(w-tw)/2, th-fd-2);
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);
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);
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);
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);
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);
419 else if (o instanceof Image) {
420 // Render image in column
422 bg.drawImage(im, x+1, th+(j-top)*rowh, this);
429 // Select a list item or a column to drag
430 public boolean mouseDown(Event e, int x, int y)
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
445 else if (x >= cpos[i] && x < cpos[i+1]) {
446 // clicked in a title
447 callback.headingClicked(this, i);
452 // Item chosen from list
453 int row = (y-th)/rowh + top;
454 if (row < list[0].size()) {
456 boolean dclick = false;
457 if (e.when-last < 1000 && sel == row)
462 if (e.shiftDown() && multiselect && sel != -1) {
463 // Select all from last selection to this one
466 sels = new int[row-zero+1];
467 for(int i=zero; i<=row; i++)
471 sels = new int[zero-row+1];
472 for(int i=zero; i>=row; i--)
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;
484 // Select one row only, and de-select others
491 if (callback != null) {
492 // Callback the right function
493 if (dclick) callback.doubleClick(this, row);
494 else callback.singleClick(this, row);
498 getParent().postEvent(
501 dclick?"Double":"Single"));
509 // If a column is selected, change it's width
510 public boolean mouseDrag(Event e, int x, int y)
518 if (x > cpos[coldrag-1]+3 && x < cpos[coldrag+1]-3) {
520 cwidth[coldrag-1] = (cpos[coldrag]-cpos[coldrag-1]) /
522 cwidth[coldrag] = (cpos[coldrag+1]-cpos[coldrag]) /
530 public void moved(CbScrollbar s, int v)
535 public void moving(CbScrollbar s, int v)
543 // Re-compute the size of the scrollbar
544 private void compscroll()
547 return; // not visible
549 int c = list[0].size() - r;
550 sb.setValues(top, r==0?1:r, list[0].size());
554 // Returns the number of rows visible in the list
557 return Math.min(height/rowh - 1, list[0].size());
560 public Dimension minimumSize()
562 return new Dimension(400, 100);
565 public Dimension preferredSize()
567 return minimumSize();
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
578 // Called on a single click on a list item
579 void singleClick(MultiColumn list, int num);
582 // Called upon double-clicking on a list item
583 void doubleClick(MultiColumn list, int num);
586 // Called when a column heading is clicked on
587 void headingClicked(MultiColumn list, int col);