Java JTable detect Column re-sized by user - java

I have a JTable that is using a TableColumnModelListener() to detect when the column has been re-sized and I have some code I want to execute in the columnMarginChanged() method.
How do I determine whether the column was re-sized by the user or as a result of other code?
I am thinking I have to start with ChangeEvent.getSource() but I don't know where to go from there.
Thank you.

I can give you one possible approach. I was trying to solve the same problem, because I wanted to serialize information about column widths to disk, so that the next time the table opened up in my application, I could restore the column widths appropriately. Here goes:
Step 1 - Override your JTable and add a boolean property to it
class MyTable extends JTable {
private boolean isColumnWidthChanged;
public boolean getColumnWidthChanged() {
return isColumnWidthChanged;
}
public void setColumnWidthChanged(boolean widthChanged) {
isColumnWidthChanged = widthChanged;
}
}
Step 2 - Add a TableColumnModelListener() to the table
private class TableColumnWidthListener implements TableColumnModelListener
{
#Override
public void columnMarginChanged(ChangeEvent e)
{
/* columnMarginChanged is called continuously as the column width is changed
by dragging. Therefore, execute code below ONLY if we are not already
aware of the column width having changed */
if(!tableObj.hasColumnWidthChanged())
{
/* the condition below will NOT be true if
the column width is being changed by code. */
if(tableObj.getTableHeader.getResizingColumn() != null)
{
// User must have dragged column and changed width
tableObj.setColumnWidthChanged(true);
}
}
}
#Override
public void columnMoved(TableColumnModelEvent e) { }
#Override
public void columnAdded(TableColumnModelEvent e) { }
#Override
public void columnRemoved(TableColumnModelEvent e) { }
#Override
public void columnSelectionChanged(ListSelectionEvent e) { }
}
Step 3 - Add a mouse listener to the table header
private class TableHeaderMouseListener extends MouseAdapter
{
#Override
public void mouseReleased(MouseEvent e)
{
/* On mouse release, check if column width has changed */
if(tableObj.getColumnWidthChanged())
{
// Do whatever you need to do here
// Reset the flag on the table.
tableObj.setColumnWidthChanged(false);
}
}
}
NOTE: In my application, the TableHeaderMouseListener and TableColumnWidthListener classes were private inner classes of my main application class. My main application class held on to a reference of the table being observed. Therefore, these inner classes had access to the table instance. Obviously, depending on your setup, you need to do the appropriate thing to make the table instance available to these other classes. Hope this helps!

Typically you want to be notified of a completed column drag, or a completed column resize. E.g.
interface SGridModel {
public void columnMoved(int oldLocation, int newLocation);
public void columnResized(int column, int newWidth);
}
This isn't pretty but it will do what you want:
class SColumnListener extends MouseAdapter implements TableColumnModelListener {
private final Logger log = Logger.getLogger(getClass());
private final SGridModel model;
private int oldIndex = -1;
private int newIndex = -1;
private boolean dragging = false;
private boolean resizing = false;
private int resizingColumn = -1;
private int oldWidth = -1;
SColumnListener(SGridModel model){
this.model = model;
}
#Override
public void mousePressed(MouseEvent e) {
// capture start of resize
if(e.getSource() instanceof JTableHeader) {
TableColumn tc = ((JTableHeader)e.getSource()).getResizingColumn();
if(tc != null) {
resizing = true;
resizingColumn = tc.getModelIndex();
oldWidth = tc.getPreferredWidth();
} else {
resizingColumn = -1;
oldWidth = -1;
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
// column moved
if(dragging && oldIndex != newIndex) {
model.columnMoved(oldIndex, newIndex);
log.info("column moved: " +oldIndex+" -> "+newIndex);
}
dragging = false;
oldIndex = -1;
newIndex = -1;
// column resized
if(resizing) {
if(e.getSource() instanceof JTableHeader) {
TableColumn tc = ((JTableHeader)e.getSource()).getColumnModel().getColumn(resizingColumn);
if(tc != null) {
int newWidth = tc.getPreferredWidth();
if(newWidth != oldWidth) {
model.columnResized(resizingColumn, newWidth);
log.info("column resized: " +resizingColumn+" -> "+newWidth);
}
}
}
}
resizing = false;
resizingColumn = -1;
oldWidth = -1;
}
#Override
public void columnAdded(TableColumnModelEvent e) {
}
#Override
public void columnRemoved(TableColumnModelEvent e) {
}
#Override
public void columnMoved(TableColumnModelEvent e) {
// capture dragging
dragging = true;
if(oldIndex == -1){
oldIndex = e.getFromIndex();
}
newIndex = e.getToIndex();
}
#Override
public void columnMarginChanged(ChangeEvent e) {
}
#Override
public void columnSelectionChanged(ListSelectionEvent e) {
}
}
add it to the table as follows:
table.getColumnModel().addColumnModelListener(cl);
table.getTableHeader().addMouseListener(cl);

A simpler solution might just be to listen on the mouse release event (which happens only once in this user interaction) and check if the column-sizes changed in the meantime ? I'm using the code below to listen for column reordering and size changes.
getTableHeader().addMouseListener( new MouseAdapter() {
public void mouseReleased(MouseEvent arg0)
{
String colNamesAndSizes = "";
for( int i=0;i<getColumnModel().getColumnCount();i++ ) {
if( i>0 ) colNamesAndSizes += ",";
TableColumn column = getColumnModel().getColumn(i);
colNamesAndSizes += column.getHeaderValue();
colNamesAndSizes += ":";
colNamesAndSizes += column.getWidth();
}
// check if changed, if yes, persist...
}});

You can also add PropertyChangeListener on each TableColumn to capture changes on width and preferredWidth properties.

Related

Expanding one field while others are collapsed in blackberry java

I am developing an app in which i am adding and deleting fields based on setting flag to true or false.but what i am trying to do is if i click on field that particular field should expand while others are collapsed(even if it is expanded)
i googled it but i didnt get solution please help me i am new to blackberry java
i have used below code
public final class MyScreen extends MainScreen implements
FieldChangeListener
{
/**
* Creates a new MyScreen object
*/
private VerticalFieldManager main_manager;
private HorizontalFieldManager parentNodes;
private LabelField parent_lables[];
private Bitmap bitmap,upbitmap;
private BitmapField bitmap_field[];
private VerticalFieldManager submanager[];
private int sizeOfParentNodes=3;
private int sizeOfChildNodes=5;
private static boolean flag[];
public MyScreen()
{
// Set the displayed title of the screen
bitmap=Bitmap.getBitmapResource("arrow.png");
upbitmap=Bitmap.getBitmapResource("uparrow.png");
main_manager=new
VerticalFieldManager(Manager.VERTICAL_SCROLL|VERTICAL_SCROLLBAR){
protected void sublayout(int maxWidth, int maxHeight) {
super.sublayout(Display.getWidth(), Display.getHeight());
setExtent(Display.getWidth(), Display.getHeight());
};
};
parent_lables=new LabelField[sizeOfParentNodes];
flag=new boolean[sizeOfParentNodes];
submanager=new VerticalFieldManager[sizeOfParentNodes];
bitmap_field=new BitmapField[sizeOfParentNodes];
for(int i=0;i<sizeOfParentNodes;i++)
{
submanager[i]=new VerticalFieldManager();
updateGUI(i);
main_manager.add(submanager[i]);
}
add(main_manager);
}
public void fieldChanged(Field field, int context) {
// TODO Auto-generated method stub
synchronized (UiApplication.getEventLock()) {
for(int i=0;i<sizeOfParentNodes;i++)
{ if(field==parent_lables[i])
{
if(flag[i]==true){
flag[i]=false;
submanager[i].deleteAll();
updateGUI(i);
parent_lables[i].setFocus();
}else{
flag[i]=true;
bitmap_field[i].setBitmap(upbitmap);
submanager[i].invalidate();
}
}
}
}
}
public void updateGUI(int index)
{
parentNodes=new HorizontalFieldManager(USE_ALL_WIDTH);
bitmap_field[index]=new BitmapField(bitmap);
parentNodes.add(bitmap_field[index]);
parent_lables[index]=new LabelField("Day"+index,Field.FOCUSABLE){
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(1);
return true;
};
};
parentNodes.add(parent_lables[index]);
parent_lables[index].setChangeListener(this);
submanager[index].add(parentNodes);
}
}
I knocked this together, and will likely need some tweaks by you to get it exactly how you want it, but it should be something you can work from. Assuming I've understood your question correctly.
You'll need to create a base field, a helper able to manage a list of them, and a callback. The fact that you need a base field is the biggest downfall, because it immediately excludes all other widgets you have, as they need to be made from scratch with the paint method. You can use a VerticalFieldManager instead of field by extending VerticalFieldManager instead of Field.
On to the java classes.
My CollapsableField.java looks as follows:
public abstract class CollapsableField extends Field
{
// We make use of a different listener than the FieldChangeListener, since you can only attach one and we will most likely want to do so, we can't "occupy" the listener.
private CollapseListener listener;
private boolean collapsed;
protected int collapsedWidth;
protected int collapsedHeight;
public CollapsableField()
{
collapsed = true;
// Field is completely collapsed by default
collapsedWidth = 0;
collapsedHeight = 0;
}
public void setCollapseListener(CollapseListener listener)
{
this.listener = listener;
}
public final boolean isCollapsed()
{
return collapsed;
}
public final void collapse()
{
this.collapsed = true;
if (listener != null)
{
listener.onCollapse(this);
}
fieldChangeNotify(0); // Notify that the field has changed, so that attached field change listeners will fire
updateLayout(); // re-call layout
}
public final void expand()
{
this.collapsed = false;
if (listener != null)
{
listener.onExpand(this);
}
fieldChangeNotify(0); // Notify that the field has changed, so that attached field change listeners will fire
updateLayout(); // re-call layout
}
protected void layout(int width, int height)
{
if (collapsed)
{
// Set dimensions to collapsed
setExtent(collapsedWidth, collapsedHeight);
}
else
{
// Set dimensions to what the extending class specified
setExtent(width, height);
}
}
protected final void paint(Graphics graphics)
{
if (collapsed)
{
paintCollapsed(graphics);
}
else
{
paintExpanded(graphics);
}
}
protected abstract void paintCollapsed(Graphics graphics);
protected abstract void paintExpanded(Graphics graphics);
}
The Group then takes a list of these, applies a listener to each field as it's added. When a field signals that it has expanded, the group will tell all other fields to collapse themselves.
CollapsableGroup.java:
public class CollapsableGroup
{
private Vector fields;
private CollapseListener listener;
public CollapsableGroup()
{
fields = new Vector();
listener = new CollapseListener()
{
public void onExpand(CollapsableField source)
{
for (int i = 0; i < fields.size(); i++)
{
CollapsableField field = (CollapsableField) fields.elementAt(i);
if ((!field.isCollapsed()) && (field != source))
{
field.collapse();
}
}
}
public void onCollapse(CollapsableField source)
{
// Don't need to handle this. Method is here just for completeness
}
};
}
public void add(CollapsableField field)
{
field.setCollapseListener(listener);
fields.addElement(field);
}
public void remove(CollapsableField field)
{
field.setCollapseListener(null);
fields.removeElement(field);
}
/**
* Returns the currently expanded field. Returns <b>null</b> if all fields are collapsed.
*
* #return
*/
public CollapsableField getExpandedField()
{
for (int i = 0; i < fields.size(); i++)
{
CollapsableField field = (CollapsableField) fields.elementAt(i);
if (!field.isCollapsed())
{
return field;
}
}
return null;
}
public void expand(CollapsableField field)
{
field.expand(); // Listeners should take care of the rest
}
public void collapseAll()
{
for (int i = 0; i < fields.size(); i++)
{
CollapsableField field = (CollapsableField) fields.elementAt(i);
if (!field.isCollapsed())
{
field.collapse();
}
}
}
}
And finally the listener interface.
CollapseListener.java:
interface CollapseListener
{
public void onExpand(CollapsableField source);
public void onCollapse(CollapsableField source);
}
Those three classes should be all that you need. The classes that follow are my example/test classes.
TestLabel.java is an example of an extended class:
public class TestLabel extends CollapsableField
{
private String text;
private String collapsedText;
public TestLabel(String text, String collapsedText)
{
this.text = text;
this.collapsedText = collapsedText;
// Tells the layout method to collapse to the size of this text
collapsedWidth = getFont().getAdvance(collapsedText);
collapsedHeight = getFont().getHeight();
}
public int getPreferredWidth()
{
return getFont().getAdvance(text);
}
public int getPreferredHeight()
{
return getFont().getHeight();
}
protected void layout(int width, int height)
{
super.layout(getPreferredWidth(), getPreferredHeight());
}
protected void paintCollapsed(Graphics graphics)
{
// Paints only the collapsedText
graphics.drawText(collapsedText, 0, 0);
}
protected void paintExpanded(Graphics graphics)
{
// Paints the full Text
graphics.drawText(text, 0, 0);
}
protected boolean touchEvent(TouchEvent message)
{
// Toggle on mouse press
if (message.getEvent() == TouchEvent.CLICK)
{
if (isCollapsed())
{
expand();
}
else
{
collapse();
}
return true;
}
return super.touchEvent(message);
}
}
The following screen contains some of the fields to show that both the widgets themselves and the group can manipulate the fields.
MyScreen.java:
public final class MyScreen extends MainScreen
{
public MyScreen()
{
// Set the displayed title of the screen
setTitle("MyTitle");
final CollapsableGroup group = new CollapsableGroup();
final TestLabel label1 = new TestLabel("Label1", "L1");
label1.setBackground(BackgroundFactory.createSolidBackground(0x999999));
group.add(label1);
final TestLabel label2 = new TestLabel("Label2", "L2");
label2.setBackground(BackgroundFactory.createSolidBackground(0xBBBBBB));
group.add(label2);
final TestLabel label3 = new TestLabel("Label3", "L3");
label3.setBackground(BackgroundFactory.createSolidBackground(0xDDDDDD));
group.add(label3);
ButtonField collapseAll = new ButtonField("Collapse All")
{
protected boolean navigationClick(int status, int time)
{
group.collapseAll();
return true;
}
};
add(collapseAll);
ButtonField expand1 = new ButtonField("Expand1")
{
protected boolean navigationClick(int status, int time)
{
group.expand(label1);
return true;
}
};
add(expand1);
ButtonField expand2 = new ButtonField("Expand2")
{
protected boolean navigationClick(int status, int time)
{
group.expand(label2);
return true;
}
};
add(expand2);
ButtonField expand3 = new ButtonField("Expand3")
{
protected boolean navigationClick(int status, int time)
{
group.expand(label3);
return true;
}
};
add(expand3);
add(label1);
add(label2);
add(label3);
}
}

A custom JComponent subclass as a JTable cell-editor does not get mouse/keyboard events

Recently I have created two very simple, yet very useful components called AttributesEditableView and AttributesEditor. AttributesEditableView is designed to just show arbitrary number of attributes, while the AttributesEditor makes it possible to edit attributes.
So, say we have three attributes that can take values 'Y', 'N' and '?' (where by '?' we mean "unspecified"). The view looks something like [[Y][N][?]]:
In the editor, user can use arrow-keys to jump left-right between attributes, and by pressing the SPACE key toggle the value of particular (currently selected) attribute.
(I guess those of you who are familiar with JTable and its peculiarities already understand when am I getting to right now).
It all works well in a form, but when I want to make a TableCellEditor out of this AttributesEditor, I get stuck, because JTable either "eats" all events, or there is something that I am doing wrong so my AttributesEditor simply does not get events... :(
My logic is the following: how come DefaultCellEditor when the editor component is JTextField gets those events (left-key or right-key presses for example), while my cell-editor does not?
I know I am not giving all the classes here, but I believe the two classes below should give those who have already encountered this problem enough information to be able to tell me how to make this cell editor work as expected?
UPDATE: I managed to fix the problem by refactoring the AttributesEditor class. Now it uses key-bindings instead of the KeyListener and everything works as expected. Lesson learned...
You can see the AttributesEditableView, AttributesEditor and AttributesCellEditor in action on the following screen shot (I also have the renderer, but could not bother to put it on, it looks like AttributesEditableView anyway):
In the screenshot above, first attribute may have 'Y', 'N' and '?' values, second one 'A', 'B', 'C', and 'D', and the third attribute may have 'T' and 'F' as values.
Here are the classes:
AttributesCellEditor
/**
* Authors: Dejan Lekic , http://dejan.lekic.org
* License: MIT
*/
public class AttributesCellEditor extends AbstractCellEditor
implements TableCellEditor, TreeCellEditor {
private AttributesEditor attributesEditor;
private AttributesColumnModel attrColModel;
private AttributesModel model;
private int clickCountToStart = 2;
DefaultCellEditor dfe;
public AttributesCellEditor(AttributesColumnModel argModel) {
super();
attrColModel = argModel;
model = new AttributesModel(attrColModel.getAllowedValues());
model.setDefaultValues(attrColModel.getDefaultValues());
attributesEditor = new AttributesEditor(model);
attributesEditor.setBorder(new BevelBorder(BevelBorder.LOWERED));
}
// ::::: CellEditor method implementations :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public Object getCellEditorValue() {
System.out.println("getCellEditorValue()");
// before we return the value, we should also modify the attributes column model and set the
// selected attribute index to the index of what we last edited.
attrColModel.setSelectedAttributeIndex(model.getSelectedAttributeIndex());
return model.getAttributes();
}
// ::::: TableCellEditor method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row,
int column) {
String val = (value == null) ? "" : value.toString();
model.setAttributes(val);
model.setSelectedAttributeIndex(attrColModel.getSelectedAttributeIndex());
attributesEditor.setModel(model);
return attributesEditor;
}
// ::::: TreeCellEditor method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// TODO: implement these
#Override
public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected,
boolean expanded, boolean leaf, int row) {
//To change body of generated methods, choose Tools | Templates.
throw new UnsupportedOperationException("Not supported yet.");
}
// ::::: AbstractCellEditor method overrides ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public boolean isCellEditable(EventObject e) {
if (e instanceof MouseEvent) {
return ((MouseEvent) e).getClickCount() >= clickCountToStart;
}
return true;
}
/**
* {#inheritDoc}
* #return
*/
#Override
public boolean stopCellEditing() {
boolean ret = super.stopCellEditing();
return ret;
}
} // AttributesCellEditor class
AttributesEditor
/**
* Authors: Dejan Lekic , http://dejan.lekic.org
* License: MIT
*/
public class AttributesEditor
extends AttributesEditableView
implements PropertyChangeListener, FocusListener, KeyListener {
public AttributesEditor() {
super();
editorComponent = true;
setFocusable(true);
setBackground(Color.white);
// Let's copy the border from the TextField
Border b = (Border) UIManager.getLookAndFeelDefaults().get("TextField.border");
setBorder(b);
// Let's copy the insets from the TextField
Insets insets = (Insets) UIManager.getLookAndFeelDefaults().get("TextField.margin");
getInsets().set(insets.top, insets.left, insets.bottom, insets.right);
addKeyListener(this);
// listeners...
addFocusListener(this);
}
public AttributesEditor(AttributesModel argModel) {
this();
setOpaque(true);
setFocusable(true);
setBackground(Color.white);
repaint();
setModel(argModel);
}
// :::: PropertyChangeListener method implementations :::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void propertyChange(PropertyChangeEvent evt) {
String str = (String) evt.getNewValue();
System.out.println("CALL: AttributesEditor.propertyChange() : " + str);
updateView();
}
// :::: FocusListener method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void focusGained(FocusEvent e) {
int sel = model.getSelectedAttributeIndex();
if (sel < 0) {
model.setSelectedAttributeIndex(0);
}
sel = model.getSelectedAttributeIndex();
labels[sel].setBackground(model.getSelectedBackgroundColor());
}
#Override
public void focusLost(FocusEvent e) {
model.setSelectedAttributeIndex(-1);
updateView();
}
// :::: KeyListener method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void keyTyped(KeyEvent e) {
// Weird thing is, arrow keys do not trigger keyTyped() to be called,
// so we have to use keyPressed here, or keyReleased
}
/**
* Weird thing is, arrow keys do not trigger keyTyped() to be called, so we have to use keyPressed/keyReleased here.
* #param e
*/
#Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
switch (key) {
case KeyEvent.VK_UP:
model.previous(model.getSelectedAttributeIndex());
break;
case KeyEvent.VK_DOWN:
case KeyEvent.VK_SPACE:
model.next(model.getSelectedAttributeIndex());
break;
case KeyEvent.VK_LEFT:
model.previousAttribute();
updateView();
break;
case KeyEvent.VK_RIGHT:
model.nextAttribute();
updateView();
break;
default:
int idx = model.getSelectedAttributeIndex();
char chr = Character.toUpperCase(e.getKeyChar());
if (model.isValid(idx, chr)) {
model.setValue(idx, chr);
}
} // switch
} // keyPressed() method
#Override
public void keyReleased(KeyEvent e) {
// nothing
}
} // AttributesEditor class
AttributesEditableView
/**
* Authors: Dejan Lekic , http://dejan.lekic.org
* License: MIT
*/
public class AttributesEditableView
extends JComponent
implements PropertyChangeListener, MouseListener {
protected AttributesModel model;
protected JLabel[] labels;
protected boolean editorComponent;
protected boolean inTable;
public AttributesEditableView() {
super();
FlowLayout fl = new FlowLayout(FlowLayout.LEFT);
fl.setVgap(0);
setLayout(fl);
editorComponent = false;
inTable = false;
}
public AttributesEditableView(AttributesModel argModel) {
this();
setModel(argModel);
}
// :::: JComponent method overrides :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
/**
* I had to do this because it is a common problem with JComponent subclasses.
* More about it: http://docs.oracle.com/javase/tutorial/uiswing/painting/problems.html
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(getForeground());
}
// :::: PropertyChangeListener mthod implementations ::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void propertyChange(PropertyChangeEvent evt) {
String str = (String) evt.getNewValue();
updateView();
}
// :::: <Interface> method implementations ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
#Override
public void mouseClicked(MouseEvent e) {
// labels have names in form of "attributelbl-3", we need to extract the ID from the name
String num = e.getComponent().getName().split("-")[1];
int idx = Integer.parseInt(num);
if (editorComponent) {
model.setSelectedAttributeIndex(idx);
}
model.next(idx);
} // mouseClicked() method
#Override
public void mousePressed(MouseEvent e) {
// do nothing
}
#Override
public void mouseReleased(MouseEvent e) {
// do nothing
}
#Override
public void mouseEntered(MouseEvent e) {
// labels have names in form of "attributelbl-3", we need to extract the ID from the name
String num = e.getComponent().getName().split("-")[1];
int idx = Integer.parseInt(num);
model.setSelectedAttributeIndex(idx);
updateView();
}
#Override
public void mouseExited(MouseEvent e) {
// labels have names in form of "attributelbl-3", we need to extract the ID from the name
String num = e.getComponent().getName().split("-")[1];
int idx = Integer.parseInt(num);
if (!editorComponent) {
model.setSelectedAttributeIndex(-1);
}
updateView();
}
/**
* Use this method to forcefully highlight specific attribute.
* #param argIndex
*/
public final void highlight(int argIndex) {
for (int i = 0; i < model.getNumberOfAttributes(); i++) {
if (i == argIndex) {
labels[i].setBackground(model.getSelectedBackgroundColor());
} else {
labels[i].setBackground(model.getBackgroundColor());
}
} // for
} // highlight() method
/**
* Extremely important method. Here we set the model, and generate JLabel objects for the attributes.
* We also set the values, and initialise the listeners.
* #param argModel
*/
public final void setModel(AttributesModel argModel) {
if (model != null) {
model.removePropertyChangeListener(this);
}
labels = null;
removeAll();
labels = new JLabel[argModel.getNumberOfAttributes()];
int w = 0;
int h = 0;
for (int i = 0; i < argModel.getNumberOfAttributes(); i++) {
String txt = "" + argModel.getValue(i);
System.out.println(txt);
JLabel lbl = new JLabel(txt);
labels[i] = lbl;
lbl.setName("attributelbl-" + i); // very important, because we will use the name to get the idx
lbl.setHorizontalAlignment(SwingConstants.CENTER);
lbl.setOpaque(true);
lbl.setBackground(argModel.getBackgroundColor());
if (isInTable()) {
lbl.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY));
}
int nh = lbl.getPreferredSize().height;
int nw = nh; // make the width to be equal to the height
lbl.setPreferredSize(new Dimension(nw, nh));
lbl.addMouseListener(this);
h = Math.max(h, lbl.getPreferredSize().height);
w = w + lbl.getPreferredSize().width;
add(lbl);
} // for
Dimension ps = new Dimension(w, h);
model = argModel;
model.addPropertyChangeListener(this);
} // setModel() method
public final AttributesModel getModel() {
return model;
}
public boolean isInTable() {
return inTable;
}
public void setInTable(boolean inTable) {
this.inTable = inTable;
}
/**
* Updates the view
*/
protected void updateView() {
String attrs = model.getAttributes();
for (int i = 0; i < model.getNumberOfAttributes(); i++) {
labels[i].setText("" + model.getValue(i));
if (model.getSelectedAttributeIndex() == i) {
labels[i].setBackground(model.getSelectedBackgroundColor());
} else {
labels[i].setBackground(model.getBackgroundColor());
}
}
}
} // AttributesEditableView class

Java Window pulse-fader works once then breaks

Ok, I've got some code I setup to create a simple little overlay window to use as an alert message for a program I'm working on. Everything works fine the first run through, but trying to run through it again, it freezes the whole thing, forcing me to terminate it via the debugger or task manager. I know I'm doing something wrong, I'm just not sure what, due to my limited experience with Java.
Below is the code I use to setup my window and place it in the lower-right corner above the taskbar:
private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public static JWindow alertWindow() {
JWindow newWin = new JWindow();
JPanel panel = new JPanel();
BufferedImage img = null;
try {
img = ImageIO.read(Main.class.getResource("/images/test.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
JLabel imgLbl = new JLabel(new ImageIcon(img));
panel.add(imgLbl);
newWin.setContentPane(panel);
newWin.pack();
Insets scnMax = Toolkit.getDefaultToolkit().getScreenInsets(newWin.getGraphicsConfiguration());
int taskBar = scnMax.bottom;
int x = screenSize.width - newWin.getWidth();
int y = screenSize.height - taskBar - newWin.getHeight();
newWin.setLocation(x,y);
newWin.setVisible(true);
final PulseWindow pulseWin = new PulseWindow(newWin);
pulseWin.getWindow().addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent click) {
if(SwingUtilities.isRightMouseButton(click)) {
pulseWin.stopPulsing();
pulseWin.destroyPulse();
} else {
System.out.println(pulseWin.isPulsing());
if(pulseWin.isPulsing()) {pulseWin.stopPulsing();}
else {pulseWin.startPulse();}
}
}
#Override
public void mouseEntered(MouseEvent arg0) {}
#Override
public void mouseExited(MouseEvent arg0) {}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
});
pulseWin.startPulsing();
return newWin;
}
And below is the code I've setup to make it pulse to draw the user's attention:
import javax.swing.JWindow;
public class PulseWindow {
private boolean pulse = true;
private boolean doPulse = true;
private Float floor = 0.50f;
private JWindow win;
public PulseWindow(JWindow win) {
this.win = win;
}
public void startPulsing() {
pulse = true;
boolean decreasing = true;
double inc2 = 0.03;
double current = win.getOpacity();
while(pulse) {
if(doPulse) {
if(decreasing) {
current = current - inc2;
if((float) current <= floor) {
current = floor;
win.setOpacity((float) current);
decreasing = false;
} else {
win.setOpacity((float) current);
}
} else {
current = current + inc2;
if((float) current >= 1.0f) {
current = 1.0;
win.setOpacity((float) current);
decreasing = true;
} else {
win.setOpacity((float) current);
}
}
} else {
current = 1.0;
win.setOpacity(1.0f);
decreasing = true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
win.setOpacity(1.0f);
}
public void destroyPulse() {
pulse = false;
win.dispose();
}
public boolean isPulsing() { return doPulse; }
public void setFloor(float floor) { this.floor = floor; }
public void stopPulsing() { doPulse = false; }
public void startPulse() { doPulse = true; }
public JWindow getWindow() { return win; }
}
Anyway, like I mentioned, it works fine for the first use, but as soon as you close the window via the right-click then attempt to re-run it later (whether by calling the startPulsing() method or by completely reinitializing the whole class with a new JWindow by calling alertWindow() again), the whole program freezes. Any ideas why this is?
Like I said, I'm still a bit of a newbie to Java, so if you see anything else I'm doing wrong/inefficiently, as well, feel free to point it out so I can do it correctly.
Edit:
I'm starting to think the issue is with JWindows, now. I setup some other code for a different method of displaying the alert and, while it doesn't freeze this time, it doesn't work as intended, either.
public class AlertWindow extends JWindow {
private static Border compound = BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), BorderFactory.createLoweredBevelBorder());
private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public AlertWindow() {
JPanel panel = new JPanel();
panel.setBorder(compound);
panel.setBackground(Color.RED);
JLabel imgLbl = new JLabel("Enter Alert Msg Here!");
imgLbl.setFont(new Font(null,Font.BOLD,16));
panel.add(imgLbl);
setContentPane(panel);
pack();
this.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent click) {
if(SwingUtilities.isLeftMouseButton(click)) {
scrollOff();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scrollOn();
}
}
#Override
public void mouseEntered(MouseEvent arg0) {}
#Override
public void mouseExited(MouseEvent arg0) {}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
});
scrollOn();
}
public void scrollOn() {
Insets scnMax = Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration());
int taskBar = scnMax.bottom;
int x = screenSize.width - getWidth();
int yEnd = screenSize.height - taskBar - getHeight();
int yStart = screenSize.height;
setLocation(x,yStart);
setVisible(true);
int current = yStart;
while(current > yEnd) {
current-=2;
System.out.println(current);
setLocation(x,current);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void scrollOff() {
int x = screenSize.width - getWidth();
int yEnd = screenSize.height;
int yStart = this.getBounds().y;
setLocation(x,yStart);
int current = yStart;
while(current < yEnd) {
current+=2;
setLocation(x,current);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
setVisible(false);
}
}
Just like the pulsing window issue, it works as intended the first time, then breaks on subsequent uses. In this case, the only thing that breaks is the scrollOn() command. It scrolls on while invisible, then becomes visible once it reaches its destination. The console output of the position clearly shows that it's moving, but you can't see it until it stops moving.
Edit 2:
And back to feeling dumb... I found the issue (actually found it some time ago but forgot to update this...). The issue ended up being that I was only using the runnable and not placing it inside of a new Thread() object. For some reason I was thinking runnable objects created their own new threads, but once I figured out my mistake, it was an easy fix. Obviously I still have a long ways to go in learning Java...
Edit:
Ok, now I'm annoyed... apparently it still breaks if you attempt to run it from an action listener of some kind. My most recent version of the PulseAlert class (below) that calls into the PulseWindow class shown in the original answer further below:
public class PulseAlert {
private static Border compound = BorderFactory.createCompoundBorder(BorderFactory.createRaisedBevelBorder(), BorderFactory.createLoweredBevelBorder());
private static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public void runAlert() throws InterruptedException {
final PulseWindow pulseWin = new PulseWindow(alertWindow());
pulseWin.getWindow().addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent click) {
if(SwingUtilities.isRightMouseButton(click)) {
pulseWin.stopPulsing();
pulseWin.destroyPulse();
} else if(SwingUtilities.isLeftMouseButton(click) && pulseWin.isPulsing()) {
pulseWin.stopPulsing();
} else if(SwingUtilities.isLeftMouseButton(click) && !pulseWin.isPulsing()) {
pulseWin.startPulsing();
}
}
#Override
public void mouseEntered(MouseEvent arg0) {}
#Override
public void mouseExited(MouseEvent arg0) {}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
});
try {
pulseWin.startPulse();
} catch (Exception e) {
e.printStackTrace();
}
while(pulseWin.pulserActive()) {
Thread.sleep(100);
}
System.out.println("done with second SW");
}
public static JWindow alertWindow() {
System.out.println("Start");
JWindow newWin = new JWindow();
JPanel panel = new JPanel();
panel.setBorder(compound);
panel.setBackground(Color.RED);
JLabel imgLbl = new JLabel("Enter Alert Msg Here!");
imgLbl.setFont(new Font(null,Font.BOLD,16));
panel.add(imgLbl);
newWin.setContentPane(panel);
newWin.pack();
Insets scnMax = Toolkit.getDefaultToolkit().getScreenInsets(newWin.getGraphicsConfiguration());
int taskBar = scnMax.bottom;
int x = screenSize.width - newWin.getWidth();
int y = screenSize.height - taskBar - newWin.getHeight();
newWin.setLocation(x,y);
newWin.setVisible(true);
return newWin;
}
}
And below is how I can call up the alert window - repeatedly, if I like, as long as it's outside of an action listener.
PulseAlert alertPulse = new PulseAlert();
alertPulse.runAlert();
The above code works flawlessly until placed into an action listener of some kind such as:
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
alertPulse.runAlert();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
});
Once the runAlert() method is called from an action listener, the whole thing freezes like it did previously. Runs perfectly fine until then. Any ideas what is causing this? Is this a bug in Java or am I doing something wrong?
Original Answer:
Ok, I feel pretty dumb, now. All I had to do to fix the issue was place the startPulsing() contents into a new runnable and it all works, and as many times as I need it to.
public void startPulsing() throws Exception {
new Runnable() {
#Override
public void run() {
pulse = true;
win.setVisible(true);
boolean decreasing = true;
double inc = 0.05;
double current = win.getOpacity();
while(pulse) {
if(doPulse) {
if(decreasing) {
current = current - inc;
if((float) current <= floor) {
current = floor;
win.setOpacity((float) current);
decreasing = false;
} else {
win.setOpacity((float) current);
}
} else {
current = current + inc;
if((float) current >= 1.0f) {
current = 1.0;
win.setOpacity((float) current);
decreasing = true;
} else {
win.setOpacity((float) current);
}
}
} else {
current = 1.0;
win.setOpacity(1.0f);
decreasing = true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
win.setOpacity(1.0f);
}
}.run();
}

CheckBoxRenderer

I Have developed an application in which i take data from the database and populate in jtable.But the problem is in rendering checkbox.i have coded for select all checkbox but it is giving error in UIManager.Tried a lot but of no use.
the code is:-
public jtable1_1() throws Exception {
DBEngine dbengine = new DBEngine();
data = dbengine.getcandidatereport();
//create header for the table
header = new Vector<String>();
header.add("check");
header.add("Name");
header.add("UserID"); //Empid
header.add("EName"); // employee position
header.add("LeadName");
initComponents();
TableColumn tc = jTable1.getColumnModel().getColumn(0);
tc.setHeaderRenderer(new CheckBoxHeader(new MyItemListener()));
tc.setCellEditor(jTable1.getDefaultEditor(Boolean.class));
tc.setCellRenderer(jTable1.getDefaultRenderer(Boolean.class));}
private void jTable1MouseClicked(java.awt.event.MouseEvent evt)
{
// TODO add your handling code here:
int row = jTable1.getSelectedRow();
jTextField1.setText(jTable1.getModel().getValueAt(row,1).toString());
jTextField2.setText(jTable1.getModel().getValueAt(row,2).toString());
jTextField3.setText(jTable1.getModel().getValueAt(row,3).toString());
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
try
{
new jtable1_1().setVisible(true);
}catch(Exception e){e.printStackTrace();}
}
});
}
public class MyItemListener implements ItemListener {
public void itemStateChanged(ItemEvent e) {
Object source = e.getSource();
if (source instanceof AbstractButton == false) {
return;
}
boolean checked = e.getStateChange() == ItemEvent.SELECTED;
for (int x = 0, y = jTable1.getRowCount(); x < y; x++) {
jTable1.setValueAt(new Boolean(checked), x, 0);
}
}
}
public class CheckBoxHeader extends JCheckBox implements TableCellRenderer, MouseListener {
protected CheckBoxHeader rendererComponent;
protected int column;
protected boolean mousePressed = false;
public CheckBoxHeader(ItemListener itemListener) {
rendererComponent = this;
rendererComponent.addItemListener(itemListener);
}
public Component getTableCellRendererComponent(JTable jTable1,
Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
if (jTable1 != null) {
JTableHeader header = jTable1.getTableHeader();
if (header != null) {
rendererComponent.setForeground(header.getForeground());
rendererComponent.setBackground(header.getBackground());
rendererComponent.setFont(header.getFont());
header.addMouseListener(rendererComponent);
}
}
setColumn(column);
rendererComponent.setText("Check All");
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
return rendererComponent;
}
protected void setColumn(int column) {
this.column = column;
}
public int getColumn() {
return column;
}
protected void handleClickEvent(MouseEvent e) {
if (mousePressed) {
mousePressed = false;
JTableHeader header = (JTableHeader) (e.getSource());
JTable tableView = header.getTable();
TableColumnModel columnModel = tableView.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = tableView.convertColumnIndexToModel(viewColumn);
if (viewColumn == this.column && e.getClickCount() == 1 && column != -1) {
doClick();
}
}
}
public void mouseClicked(MouseEvent e) {
handleClickEvent(e);
((JTableHeader) e.getSource()).repaint();
}
public void mousePressed(MouseEvent e) {
mousePressed = true;
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
}
public class CheckBoxHeader extends JCheckBox implements
TableCellRenderer, MouseListener {
this code, whole class is uselles for todays Java, Swing and JTable (its renderer & editor), please read Oracles JTable tutorial
put only Boolean value to the model, not JComponents
have to override ColumnClass in XxxTableModel
These two lines are not required. JTable by default does that for you.
tc.setCellEditor(jTable1.getDefaultEditor(Boolean.class));
tc.setCellRenderer(jTable1.getDefaultRenderer(Boolean.class));
Only thing you have to do is to override getColumnClass(..) in the table model.
private Class<?> getColumnClass(int columnIndex) {
switch(columnIndex) {
// This is for your case.
case 0:
return Boolean.class;
default:
return String.class;
}
}

hyperlinks in JEditorPane in a JTable

I swear... i hope this is the last question I have to ask like this, but I'm about to go crazy.
I've got a JTable using a custom TableCellRenderer which uses a JEditorPane to display html in the individual cells of the JTable. How do I process clicking on the links displayed in the JEditorPane?
I know about HyperlinkListener but no mouse events get through the JTable to the EditorPane for any HyperlinkEvents to be processed.
How do I process Hyperlinks in a JEditorPane within a JTable?
The EditorPane isn't receiving any events because the component returned from the TableCellRenderer is only allowed to display, and not intercept events, making it pretty much the same as an image, with no behaviour allowed on it. Hence even when listeners are registered, the returned component is never 'aware' of any events. The work-around for this is to register a MouseListener on the JTable, and intercept all relevant events from there.
Here's some classes I created in the past for allowing JButton roll-over to work in a JTable, but you should be able to re-use most of this for your problem too. I had a separate JButton for every cell requiring it. With that, this ActiveJComponentTableMouseListener works out in which cell the mouse event occurs in, and dispatches an event to the corresponding component. It's the job of the ActiveJComponentTableCellRenderer to keep track of the components via a Map.
It's smart enough to know when it's already fired events, so you don't get a backlog of redundant events. Implementing this for hypertext shouldn't be that different, and you may still want roll-over too. Here are the classes
public class ActiveJComponentTableMouseListener extends MouseAdapter implements MouseMotionListener {
private JTable table;
private JComponent oldComponent = null;
private TableCell oldTableCell = new TableCell();
public ActiveJComponentTableMouseListener(JTable table) {
this.table = table;
}
#Override
public void mouseMoved(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
if (alreadyVisited(cell)) {
return;
}
save(cell);
if (oldComponent != null) {
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_EXITED), oldComponent);
oldComponent = null;
}
JComponent component = getComponent(cell);
if (component == null) {
return;
}
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_ENTERED), component);
saveComponent(component);
save(cell);
}
#Override
public void mouseExited(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
if (alreadyVisited(cell)) {
return;
}
if (oldComponent != null) {
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_EXITED), oldComponent);
oldComponent = null;
}
}
#Override
public void mouseEntered(MouseEvent e) {
forwardEventToComponent(e);
}
private void forwardEventToComponent(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
save(cell);
JComponent component = getComponent(cell);
if (component == null) {
return;
}
dispatchEvent(e, component);
saveComponent(component);
}
private void dispatchEvent(MouseEvent componentEvent, JComponent component) {
MouseEvent convertedEvent = (MouseEvent) SwingUtilities.convertMouseEvent(table, componentEvent, component);
component.dispatchEvent(convertedEvent);
// This is necessary so that when a button is pressed and released
// it gets rendered properly. Otherwise, the button may still appear
// pressed down when it has been released.
table.repaint();
}
private JComponent getComponent(TableCell cell) {
if (rowOrColumnInvalid(cell)) {
return null;
}
TableCellRenderer renderer = table.getCellRenderer(cell.row, cell.column);
if (!(renderer instanceof ActiveJComponentTableCellRenderer)) {
return null;
}
ActiveJComponentTableCellRenderer activeComponentRenderer = (ActiveJComponentTableCellRenderer) renderer;
return activeComponentRenderer.getComponent(cell);
}
private int getColumn(MouseEvent e) {
TableColumnModel columnModel = table.getColumnModel();
int column = columnModel.getColumnIndexAtX(e.getX());
return column;
}
private int getRow(MouseEvent e) {
int row = e.getY() / table.getRowHeight();
return row;
}
private boolean rowInvalid(int row) {
return row >= table.getRowCount() || row < 0;
}
private boolean rowOrColumnInvalid(TableCell cell) {
return rowInvalid(cell.row) || columnInvalid(cell.column);
}
private boolean alreadyVisited(TableCell cell) {
return oldTableCell.equals(cell);
}
private boolean columnInvalid(int column) {
return column >= table.getColumnCount() || column < 0;
}
private MouseEvent createMouseEvent(MouseEvent e, int eventID) {
return new MouseEvent((Component) e.getSource(), eventID, e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e.getButton());
}
private void save(TableCell cell) {
oldTableCell = cell;
}
private void saveComponent(JComponent component) {
oldComponent = component;
}}
public class TableCell {
public int row;
public int column;
public TableCell() {
}
public TableCell(int row, int column) {
this.row = row;
this.column = column;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TableCell other = (TableCell) obj;
if (this.row != other.row) {
return false;
}
if (this.column != other.column) {
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + this.row;
hash = 67 * hash + this.column;
return hash;
}}
public class ActiveJComponentTableCellRenderer<T extends JComponent> extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private Map<TableCell, T> components;
private JComponentFactory<T> factory;
public ActiveJComponentTableCellRenderer() {
this.components = new HashMap<TableCell, T>();
}
public ActiveJComponentTableCellRenderer(JComponentFactory<T> factory) {
this();
this.factory = factory;
}
public T getComponent(TableCell key) {
T component = components.get(key);
if (component == null && factory != null) {
// lazy-load component
component = factory.build();
initialiseComponent(component);
components.put(key, component);
}
return component;
}
/**
* Override this method to provide custom component initialisation code
* #param component passed in component from getComponent(cell)
*/
protected void initialiseComponent(T component) {
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return getComponent(new TableCell(row, column));
}
#Override
public Object getCellEditorValue() {
return null;
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
return getComponent(new TableCell(row, column));
}
public void setComponentFactory(JComponentFactory factory) {
this.factory = factory;
}}
public interface JComponentFactory<T extends JComponent> {
T build();
}
To use it, you want to register the listener to as mouse and motion listener on the table, and register the renderer on the appropriate cells. If you want to intercept actionPerformed type events, override ActiveJComponentTableCellRenderer.initialiseComponent() like so:
protected void initialiseComponent(T component) {
component.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
}
If you register a MouseListener on the JTable, you could easily get the text at the mouse click point. This would be done by generating a Point object from the MouseEvent, using event.getX() and event.getY(). You then pass that Point into JTable's rowAtPoint(pt) and columnAtPoint(pt). From there, you can get the text via JTable.getValueAt(row, column). Now you have the value of your cell, so you can determine whether it is a link or not and do what you'd like with the result.
To solve the same problem, instead of trying have the JEditorPane produce the event, I instead processed the MouseEvent produced by the JTable, had the listener figure out when we were clicking on a link or not.
Here's code. It keeps a map of JEditorPanes, so do make sure you don't have memory leaks, and that you clear this map appropriately if the data in the table can change. It's slightly modified from the code I actually used - in the version I actually used, it only only produced JEditorPane when actually links were in the html, and didn't bother with JEditorPanes when no such links existed...
public class MessageWithPossibleHtmlLinksRenderer extends DefaultTableCellRenderer {
private final Map<Integer, JEditorPane> editorPanes = new HashMap<>();
public MessageWithPossibleHtmlLinksRenderer(JTable table) {
// register mouseAdapter to table for link-handling
table.addMouseMotionListener(this.mouseAdapter);
table.addMouseListener(this.mouseAdapter);
}
private JEditorPane getOrCreateEditorPane(int row, int col) {
final int key = combine(row, col);
JEditorPane jep = editorPanes.get(key);
if (jep == null) {
jep = new JEditorPane();
jep.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
jep.setContentType("text/html");
jep.setEditable(false);
jep.setOpaque(true);
editorPanes.put(key, jep);
}
return jep;
}
private static int combine(int row, int col) {
return row * 10 + col; // works for up to 10 columns
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
// modify here if you want JEditorPane only when links exist
if (value instanceof String && ((String) value).startsWith("<html>")) {
final JEditorPane jep = getOrCreateEditorPane(row, column);
jep.setText((String) value);
// code to adjust row height
return jep;
} else {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
private AttributeSet anchorAt(MouseEvent e) {
// figure out the JEditorPane we clicked on, or moved over, if any
final JTable table = (JTable) e.getSource();
final Point p = e.getPoint();
final int row = table.rowAtPoint(p);
final int col = table.columnAtPoint(p);
final int key = combine(row, col);
final JEditorPane pane = this.editorPanes.get(key);
if (pane == null) {
return null;
}
// figure out the exact link, if any
final Rectangle r = table.getCellRect(row, col, false);
final Point relativePoint = new Point((int) (p.getX() - r.x), (int) (p.getY() - r.y));
pane.setSize(r.getSize()); // size the component to the size of the cell
final int pos = pane.viewToModel(relativePoint);
if (pos >= 0) {
final Document doc = pane.getDocument();
if (doc instanceof HTMLDocument) {
final Element el = ((HTMLDocument) doc).getCharacterElement(pos);
return (AttributeSet) el.getAttributes().getAttribute(HTML.Tag.A);
}
}
return null;
}
private final MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
final AttributeSet anchor = anchorAt(e);
final Cursor cursor = anchor == null
? Cursor.getDefaultCursor()
: Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
final JTable table = (JTable) e.getSource();
if (table.getCursor() != cursor) {
table.setCursor(cursor);
}
}
#Override
public void mouseClicked(MouseEvent e) {
if (! SwingUtilities.isLeftMouseButton(e)) {
return;
}
final AttributeSet anchor = anchorAt(e);
if (anchor != null) {
try {
String href = (String) anchor.getAttribute(HTML.Attribute.HREF);
Desktop.getDesktop().browse(new URL(href).toURI());
} catch (Exception ex) {
// ignore
}
}
}
};
}

Categories