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)
273 // Called when this component gets resized
274 public void reshape(int nx, int ny, int nw, int nh)
276 if (nw != width+sbwidth || nh != height) {
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);
284 // Force creation of a new backing image and re-painting
289 super.reshape(nx, ny, nw, nh);
293 // Compute pixel column widths from proportional widths
297 for(int i=0; i<title.length; i++)
298 cpos[i+1] = cpos[i] + (int)(width*cwidth[i]);
302 // Blit the backing image to the front
303 public void paint(Graphics g)
307 // This is the first rendering
308 bim = createImage(width, height);
309 bg = bim.getGraphics();
311 fnm = bg.getFontMetrics();
312 th = fnm.getHeight() + 4;
316 g.drawImage(bim, in.left, in.top, this);
320 // Called sometime after repaint()
321 public void update(Graphics g)
330 // Re-draw the list into the backing image
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);
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;
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
351 bg.fillRect(0, th+(sels[i]-top)*rowh,
358 for(int i=0; i<title.length; i++) {
359 int x = cpos[i], w = cpos[i+1]-x-1;
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]);
374 bg.drawString(title[i], x+(w-tw)/2, th-fd-2);
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);
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);
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);
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);
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);
413 else if (o instanceof Image) {
414 // Render image in column
416 bg.drawImage(im, x+1, th+(j-top)*rowh, this);
423 // Select a list item or a column to drag
424 public boolean mouseDown(Event e, int x, int y)
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
439 else if (x >= cpos[i] && x < cpos[i+1]) {
440 // clicked in a title
441 callback.headingClicked(this, i);
446 // Item chosen from list
447 int row = (y-th)/rowh + top;
448 if (row < list[0].size()) {
450 boolean dclick = false;
451 if (e.when-last < 1000 && sel == row)
456 if (e.shiftDown() && multiselect && sel != -1) {
457 // Select all from last selection to this one
460 sels = new int[row-zero+1];
461 for(int i=zero; i<=row; i++)
465 sels = new int[zero-row+1];
466 for(int i=zero; i>=row; i--)
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;
478 // Select one row only, and de-select others
485 if (callback != null) {
486 // Callback the right function
487 if (dclick) callback.doubleClick(this, row);
488 else callback.singleClick(this, row);
492 getParent().postEvent(
495 dclick?"Double":"Single"));
503 // If a column is selected, change it's width
504 public boolean mouseDrag(Event e, int x, int y)
512 if (x > cpos[coldrag-1]+3 && x < cpos[coldrag+1]-3) {
514 cwidth[coldrag-1] = (cpos[coldrag]-cpos[coldrag-1]) /
516 cwidth[coldrag] = (cpos[coldrag+1]-cpos[coldrag]) /
524 public void moved(CbScrollbar s, int v)
529 public void moving(CbScrollbar s, int v)
537 // Re-compute the size of the scrollbar
538 private void compscroll()
541 return; // not visible
543 int c = list[0].size() - r;
544 sb.setValues(top, r==0?1:r, list[0].size());
548 // Returns the number of rows visible in the list
551 return Math.min(height/rowh - 1, list[0].size());
554 public Dimension minimumSize()
556 return new Dimension(400, 100);
559 public Dimension preferredSize()
561 return minimumSize();
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
572 // Called on a single click on a list item
573 void singleClick(MultiColumn list, int num);
576 // Called upon double-clicking on a list item
577 void doubleClick(MultiColumn list, int num);
580 // Called when a column heading is clicked on
581 void headingClicked(MultiColumn list, int col);