I am using the SWT JFace TableViewer component to display a list of data. I have implemented the IColorProvider to provide a custom foreground and background colour. I provide black foreground text with a light background colour in most cases. However, in some cases I use a dark background colour with white foreground text. This causes a problem in Windows 7 in that the selection colour is a pale blue.
What is happening on Windows 7 is when a dark-coloured item in my table (row-select) is selected the background colour is the pale blue selection colour. However, the foreground colour remains white and you cannot see it (Refer to fig 1 below).
Figure 1: JFace SWT TableViewer - Row Selected in Windows 7
Firstly, am I doing something wrong here?
Secondly, I have attempted the following to fix this (any ideas about the HOT event painting?):
table.addListener(SWT.EraseItem, new Listener() {
public void handleEvent(Event event) {
System.out.println(event);
boolean selected = (event.detail & SWT.SELECTED) == 0;
event.detail &= ~SWT.HOT;
TableItem item = (TableItem) event.item;
int clientWidth = table.getClientArea().width;
GC gc = event.gc;
Color oldForeground = gc.getForeground();
Color oldBackground = gc.getBackground();
if (selected) {
gc.setForeground(ColourHelper.WHITE);
gc.setBackground(ColourHelper.WHITE);
gc.fillRectangle(0, event.y, clientWidth, event.height);
} else {
gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT));
gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_TITLE_BACKGROUND));
gc.fillGradientRectangle(0, event.y, clientWidth, event.height, true);
}
gc.setForeground(oldForeground);
gc.setBackground(oldBackground);
event.detail &= ~SWT.SELECTED;
}
});
When I re-run my test application I get the following. The selected row is fixed now but there is still a problem with the table-row that is "hovered" over as seen in figure 2. I have tried to capture the SWT.HOT event within the EraseItem handler, but it doesn't seem to do anything.
Figure 2: JFace SWT TableViewer - Added EraseItem Event Handler
I did have some luck adding the PaintItem event handler (as shown below), but I will have to place the same logic regarding cell-alignment in this code for it to render properly. In addition, my paint event fires and paints over the correctly rendering SELECTED Windows 7 Colour fix.
table.addListener(SWT.PaintItem, new Listener() {
public void handleEvent(Event event) {
boolean hot = (event.detail & SWT.HOT) == 0;
if (hot) System.out.println("HOT!");
if ((event.detail & SWT.HOT & ~SWT.SELECTED) == 0)
return;
event.detail &= ~SWT.HOT;
final int TEXT_MARGIN = 3;
GC gc = event.gc;
gc.setForeground(ColourHelper.BLACK);
gc.setBackground(ColourHelper.BLACK);
TableItem item = (TableItem) event.item;
item.setBackground(ColourHelper.BLACK);
item.setForeground(ColourHelper.BLACK);
String text = item.getText(event.index);
int yOffset = 0;
if (event.index == 1) {
Point size = event.gc.textExtent(text);
yOffset = Math.max(0, (event.height - size.y) / 2);
}
event.gc.drawText(text, event.x + TEXT_MARGIN, event.y + yOffset, true);
}
});
In summary, perhaps I am doing something wrong with the IColourProvider and it's that simple? Alternatively, there is a bug within the Table or TableViewer component. I have upgraded to the latest by downloading the RCP 3.6.1, but I have the same symptoms.
Some help would be much appreciated :-)
Related
The last column of my Table Viewer contains a check box only. The check box appears in the left side of the cell, and because the column name is pretty long it looks ugly as hell. How can I center the check box in the middle of the cell ? Is it possible without using images ? Here is how I create the column:
// third column - check box. temporary
TableColumn column = new TableColumn(viewer.getTable(), SWT.NONE);
column.setText("PrettyLongColumnName");
column.setWidth(100);
TableViewerColumn checkColumn = new TableViewerColumn(viewer, column);
checkColumn.setLabelProvider(new ColumnLabelProvider() {
// the checkboxes should be disposed and rebuilt when input changes
#Override
public void update(ViewerCell cell) {
MyObject system = (MyObject) cell.getElement();
TableItem item = (TableItem) cell.getItem();
Button button;
if (buttonsMap.containsKey(cell.getElement())) {
button = rightTableButtons.get(cell.getElement());
} else {
button = new Button((Composite) cell.getViewerRow().getControl(), SWT.CHECK);
button.setEnabled(true);
buttonsMap.put(cell.getElement(), button);
TableEditor editor = new TableEditor(item.getParent());
editor.grabHorizontal = true;
editor.grabVertical = true;
editor.setEditor(button, item, cell.getColumnIndex());
editor.layout();
}
}
}
});
TL;DR: Not available nativley, but can be implemented via epic hackery.
Checkboxes are actually an OS feature. As SWT is cross-platform, we rely on it being provided by OS.
AFIK the only thing provided by all OS's (Gtk/Win32/Cocoa) is a single checkbox on the first column.
Other
fancy functionality has to be implemented manually.
One way I've seen people do it is to draw custom icons and then update the icon when you click on it with event listeners.
One example on how to draw icons on the right is in this snippet. You'd have to add click listener to change the icon when clicked into checked/unchecked.
Note, this may cause your application to look inconsistent across platforms and themes (e.g dark theme).
To get around this, we have had some people that actually generate a native checkbox, then programatically take a screen shot, then draw it in the right side of a column. I think this is hackery at it's finest.
Let me know if you have questions.
I want my app to detect mouse clicks anywhere on the screen without having to have the app focused. I want it to detect mouse events universally even if its minimized. So far I've only been able to detect mouse events within a swing gui.
Autohotkey can detect mouse clicks and get the mouse's position at any time, how can I do this with java?
It is possible with a little trick. Should be 100% cross-platform (tested on Linux & Windows). Basically, you create a small JWindow, make it "alwaysOnTop" and move it around with the mouse using a timer.
Then, you can record the click, dismiss the window and forward the click to the actual receiver using the Robot class.
Short left and right clicks work completely fine in my tests.
You could also simulate dragging and click-and-hold, just forwarding that seems harder.
I have code for this, but it is in my Java extension (JavaX). JavaX does translate into Java source code, so you can check out the example here.
The code in JavaX:
static int windowSize = 11; // odd should look nice. Set to 1 for an invisible window
static int clickDelay = 0; // Delay in ms between closing window and forwarding click. 0 seems to work fine.
static int trackingSpeed = 10; // How often to move the window (ms)
p {
final new JWindow window;
window.setSize(windowSize, windowSize);
window.setVisible(true);
window.setAlwaysOnTop(true);
JPanel panel = singleColorPanel(Color.red);
window.setContentPane(panel);
revalidate(window);
final new Robot robot;
panel.addMouseListener(new MouseAdapter {
// public void mousePressed(final MouseEvent e) {}
public void mouseReleased(final MouseEvent e) {
print("release! " + e);
window.setVisible(false);
int b = e.getButton();
final int mod =
b == 1 ? InputEvent.BUTTON1_DOWN_MASK
: b == 2 ? InputEvent.BUTTON2_DOWN_MASK
: InputEvent.BUTTON3_DOWN_MASK;
swingLater(clickDelay, r {
print("clicking " + mod);
robot.mousePress(mod);
robot.mouseRelease(mod);
});
}
});
swingEvery(window, trackingSpeed, r {
Point p = getMouseLocation();
window.setLocation(p.x-windowSize/2, p.y-windowSize/2);
//print("moving");
});
}
I am trying to build an SWT Tree that has icons at the top level but not at the next level.
Is there any way to avoid the blank space which seems to have been left for the image which I'm not using? I tried using the following code snippets but neither did what I wanted.
SWT.MeasureItem:
tree.addListener(SWT.MeasureItem, new Listener()
{
#Override
public void handleEvent(Event event)
{
TreeItem item = (TreeItem)event.item;
Image image = item.getImage();
if (image == null)
{
event.x -= 40;
}
}
});
SWT.PaintItem:
tree.addListener(SWT.PaintItem, new Listener()
{
#Override
public void handleEvent(Event event) {
TreeItem item = (TreeItem)event.item;
Image image = item.getImage();
if (image == null)
{
event.x -= 40;
}
}
});
In both cases I was just hoping that the text could be drawn a bit further to the left.
This behavior comes from the native controls and is OS-specific (AFAIR, on Macs you won't see this problem). Alas, no easy fix but add some generic icon (or not adding icons at all).
I have done some more investigation myself. As per Eugene's answer this seems to be native behaviour. There are a couple of things worth noting.
If no items in the Tree have an icon then no space is left for icons. However, even a single item with an icon will cause all items to leave space for icons.
A hacky solution can be implemented as follows:
Use no icons so that the native control leaves no icon space
For items where you want an icon prefix some spaces to the text e.g. " " + text
Add a PaintItem listener that draws the icon you want into the space left by the text
This probably doesn't work well across platforms and across system fonts so I've just decided to live with having icons.
I have a JTable in my application. I have a custom renderer setup on the table (a JTextArea, with line-wrapping enabled) which allows for multi-line content. The contents of the JTable cells are expected to overflow the bounds of the cell in some cases. I want to do the following:
Instead of making the user drag the row border to resize the cell, I want the user to be able to double-click the row border, so when I detect this, I can automatically resize the height of the cell (height of the row) to show the entire contents of the cell.
My question is, what is the best way to detect a double-click on the row border? I have gotten this to work by setting up a MouseListener on the JTable with a mouseClicked method that looks like this:
public class MouseButtonInputListener extends MouseInputAdapter
{
private JTable fTable;
public MouseButtonInputListener(JTable parentTable)
{
fTable = parentTable;
}
#Override
public void mouseClicked(MouseEvent e)
{
// Some pre-processing here...
if(e.getClickCount() == 2 &&
fTable.getCursor().getType() == Cursor.N_RESIZE_CURSOR)
{
// Auto-adjust row height to fit contents...
}
}
}
While this works, i'm not very happy with the line:
fTable.getCursor().getType() == Cursor.N_RESIZE_CURSOR
Any suggestions on a better way to do this? Is the above a reliable approach on all platforms?
After some searching online, I found the following page:
http://java.sun.com/products/jlf/ed2/book/HIG.Behavior.html
amongst other things, it specifies mouse cursor behavior under different circumstances. According to the section on "Pointer Feedback", the cursor switches between a N_RESIZE_CURSOR and S_RESIZE_CURSOR depending on whether it is the upper or lower boundary of a component that is being hovered over. It's interesting to note that on 2 out of 3 platforms (Mac and Windows), these cursors are exactly the same. Anyway, it follows that my code should therefore read:
#Override
public void mouseClicked(MouseEvent e)
{
// Some pre-processing here. Determine row at mouse click location
int cursorType = fTable.getCursor().getType();
if(e.getClickCount == 2 &&
(cursorType == Cursor.N_RESIZE_CURSOR || cursorType == CURSOR.S_RESIZE_CURSOR))
{
// Resize row appropriately.
}
}
This will work on all platforms. Thanks for the inputs and comments everyone.
Suppose I have 4 squares colored blue, white, red and green (myComponent) associated with the mouse press event. At one point, the mouse is pressed over one of them - say, the yellow one - and the event is activated.
Now, the control flux is inside the event handling function. How do I get the MyComponent - the yellow square - that caused this from here?
EDIT
I have another question. Is there a way to tell the position of the component? My problem is a bit more complicated than what I said.
Basically, I have a grid full of squares. When I click one of the squares, I have to know which one it is, so I can update my matrix. The thing is, if I calculate it myself, it only works on a given resolution.
I have a GridBagLayout, and inside it are the myComponents. I have to know which one of the components exactly - like, component[2][2] - caused the interruption.
I mean, I can tell which one of the components did it, but not where in the matrix it is located.
MouseEvent.getSource() returns the object on which the event initially occurred.
I have a GridBagLayout, and inside it
are the myComponents. I have to know
which one of the components exactly -
like, component[2][2] - caused the
interruption.
You could store the indices, e.g. (2,2), inside each myComponent when you add them to the matrix. That way, given the component, you can always identify its position in the matrix.
class MyComponent extends JButton
{
final int i; // matrix row
final int j; // matrix col
// constructor
MyComponent(String text, int i, int j)
{
super(text);
this.i = i;
this.j = j;
}
...
}
By adding a MouseListener (or alternatively, a MouseAdapter, if you don't need to override all the MouseListener' methods) to each of your colored boxes, when an event such as a mouse click occurs, theMouseListenerwill be called with a [MouseEvent`]3, which can be used to obtain the component that was clicked.
For example:
final MyBoxComponent blueBox = // ... Initialize blue box
final MyBoxComponent whiteBox = // ... Initialize white box
final MyBoxComponent redBox = // ... Initialize red box
final MyBoxComponent greenBox = // ... Initialize green box
MouseListener myListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
// Obtain the Object which caused the event.
Object source = e.getSource();
if (source == blueBox)
{
System.out.println("Blue box clicked");
}
else if (source == whiteBox)
{
System.out.println("White box clicked");
}
// ... and so on.
}
};
blueBox.addMouseListener(myListener);
whiteBox.addMouseListener(myListener);
redBox.addMouseListener(myListener);
greenBox.addMouseListener(myListener);