I am trying to do something that should be absolutely trivial, namely add a product version number to an RCP application's splash screen. So my code goes like this:
class ARMSplashHandler extends EclipseSplashHandler {
private Font font;
private int heightInPx = 12;
private int fontHeightInPt(Display display, int heightInPx) {
return 72 * heightInPx / display.getDPI().y;
}
#Override void init(Shell splash) {
Product product = Platform.getProduct();
Display display = splash.getDisplay();
FontData fontData = splash.getFont.getFontData()[0];
fontData.setHeight(fontHeightInPt(display, fontHeightInPx));
font = new Font(display, fontData);
splash.setBackgroundMode(SWT.INHERIT_DEFAULT);
splash.setText(product.getName());
splash.setFont(font);
String version = "v"+product.getDefiningBundle().getHeaders().get("Bundle-Version");
String versionLocString = product.getProperty("versionLocation");
String versionColorString = product.getProperty("versionColor");
Rectangle versionLoc = StringConverter.asRectangle(versionLocString, new Rectangle(405, 260, 45, 12));
Color versionColor = new Color(display, StringConverter.asRGB(versionColorString, new RGB(193, 202, 212)));
CLabel versionLabel = new CLabel(splash, SWT.CENTER | SWT.BORDER);
versionLabel.setForeground(versionColor);
versionLabel.setFont(font);
versionLabel.setBounds(versionLoc);
versionLabel.setText(version);
splash.addDisposeListener(new DisposeListener {
void widgetDisposed(DisposeEvent e) {
versionColor.dispose();
font.dispose();
}
});
super.init(splash);
}
This works fine (except for a problem on Mac I'll ask about in a separate question; of course, the border is just for debugging):
But if I replace CLabel with Label, this happens:
It isn't centered, as expected. If I set alignment to RIGHT (which is what I really want), it disappears completely. Initially I thought the problem simply was that there isn't enough space, but increasing height of the rectangle doesn't fix the problem, and the width is certainly large enough:
Is this somehow expected behavior? Or an SWT bug (I certainly don't expect there to be one in something this basic)?
Related
I'm learning java through university and I've been taught the basics of making a java program and designing GUIs. Maximizing a window after running my program makes all the JFrame components stay in place while grey fills the rest of the space. Here's an example of how it looks like:
JFrame window normally, Maximized window before "fix".
After failing to find a solution I came up with a band-aid solution which is to get the component locations and just move them with hard-coded values when the jframe is maximized. This was not an elegant solution and every jframe in my java course project increased in the number of elements on screen. Is there any piece of code to make my components move and resize automatically and dynamically?
Here's what I've tried so far:
First I obtained the positions of components through 2D points:
Point managementLoginBtnLocation, empLogLocation, logoLocation, customerBtnLocation, welcomeLblLocation, contactBtnLocation, aboutBtnLocation, mainMenuBtnLocation;
//Constructor and rest of code...
public final void getOriginalComponentLocations()
{
managementLoginBtnLocation = managementLoginBtn.getLocation();
empLogLocation = empLoginBtn.getLocation();
logoLocation = shopLogo.getLocation();
customerBtnLocation = customerBtn.getLocation();
welcomeLblLocation = welcomeLbl.getLocation();
contactBtnLocation = contactBtn.getLocation();
aboutBtnLocation = aboutBtn.getLocation();
mainMenuBtnLocation = mainMenuBtn.getLocation();
}
//This method is called within the constructor.
I implemented the ComponentListener Interface and added a component listener to my jframe. Then I made it so when the jframe's size changes, it changes the size of the jlabel used for background art. And if the label's width is greater than 800 (the default I used while designing) it moves the components and doubles their size and font size. When the jframe is minimized the label will go back to the default size so I made a method to revert the font sizes, because I found the component sizes and locations reset automatically.
public void componentResized(ComponentEvent e)
{
//Resizing the background label and setting its icon to a resized version of its current icon.
backgroundMainArt.setSize(this.getWidth() - 16, this.getHeight() - 21);
ImageIcon icon = new ImageIcon("C:\\Program Files\\OMOClothingStore\\Resources\\Main menu\\main menu background art.jpg");
Image img = icon.getImage();
Image newImage = img.getScaledInstance(backgroundMainArt.getWidth(), backgroundMainArt.getHeight(), Image.SCALE_FAST);
icon = new ImageIcon(newImage);
backgroundMainArt.setIcon(icon);
if(backgroundMainArt.getWidth() > 800) //When the size of the label is greater than default
{
//I move the components, enlarge the buttons and zoom the font size
moveComponents();
enlargeBtns();
zoomBtnsFontSize();
}
else //When the label is back to its original size
{
//I revert the font sizes as button sizes and positions reset automatically
revertBtnsFontSize();
setLogoIconAndBackgroundArtAndWelcomeLbl();
}
}
public void moveComponents()
{
moveLogo();
moveManagementLoginBtn();
moveEmployeeLoginBtn();
moveCustomerBtn();
moveWelcomeLbl();
moveContactInfoBtn();
moveAboutBtn();
moveMainMenuBtn();
}
public void moveLogo()
{
ImageIcon logoIcon = new ImageIcon("C:\\Program Files\\OMOClothingStore\\Resources\\Shared resources\\OMO Clothing Store logo.png");
Image logoImg = logoIcon.getImage();
Image newLogoImage = logoImg.getScaledInstance(250, 250, Image.SCALE_DEFAULT);
logoIcon = new ImageIcon(newLogoImage);
shopLogo.setIcon(logoIcon);
Point newLogoLocation = new Point();
newLogoLocation.x = (logoLocation.x * 2) + 200;
newLogoLocation.y = (logoLocation.y * 2) + 30;
shopLogo.setLocation(newLogoLocation);
}
//The rest of the "moveX" methods follow the same pattern as moveLogo()
public void enlargeBtns()
{
managementLoginBtn.setSize(410, 94);
empLoginBtn.setSize(410, 94);
customerBtn.setSize(410, 94);
}
public void zoomBtnsFontSize()
{
customerBtn.setFont(sizeBtn.getFont());
//sizeBtn is a JButton that has a font size of 24. I found that just creating a new Font object with bigger size here made the font way larger for some reason.
empLoginBtn.setFont(sizeBtn.getFont());
managementLoginBtn.setFont(sizeBtn.getFont());
}
public void revertBtnsFontSize()
{
empLoginBtn.setFont(new Font("Segoe UI", Font.PLAIN, 14));
managementLoginBtn.setFont(new Font("Segoe UI", Font.PLAIN, 14));
customerBtn.setFont(new Font("Segoe UI", Font.PLAIN, 14));
}
I split the moving of the components into many methods inside other methods because I found it easier to keep up with.
This worked. Here's how it looks like when running the JFrame: Maximized window after "fix". But moving on to other JFrames, they are more intricate and have many more components - extra buttons, panels with other components in them, menu bars, etc.
Is there a better approach to fixing this? Or do I just remove the ability to resize and move on?
There is not a lot to explain. Just see the MCVE/image below:
public class FontExample extends JFrame {
private static final Font FONT = new Font("Calibri", Font.PLAIN, 14);
public FontExample() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setFont(FONT);
withoutHtml.setBorder(BorderFactory.createLineBorder(Color.red));
add(withoutHtml);
JLabel withHtml = new JLabel("<html><body style='vertical-align:top;'>hello stackoverflow");
withHtml.setBorder(BorderFactory.createLineBorder(Color.green));
withHtml.setFont(FONT);
add(withHtml);
setLocationByPlatform(true);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
//Make sure Calibri font is installed
if (!"Calibri".equals(FONT.getFamily())) {
System.err.println("Font calibri is not installed.");
System.exit(1);
}
new FontExample().setVisible(true);
});
}
}
The green one is with the <html> tag. Is there a way to fix it? And by fix, I mean to make it like the left one, without this stupid space?
It does not seem to happen with any other font (I tested 2-3 more). I am on Java 8 with Windows 7 and Windows 10.
I tried to add padding at bottom:
JLabel withHtml = new JLabel("<html><body style='padding-bottom:5px'>hello stackoverflow");
and as expected what I get is this:
which a) will screw the alignment of other components in the same container (bad for UI purposes) and b) I will have to hard code a lot of values since 5 since to be the proper for font size 14. But for other font size, it needs another value.
#Andrew Thomson in comments said to use the HTML format for all JLabels. But then, if they are next to another text-based component like a JTextField, I get this:
which obviously, is bad too.
UPDATE
Also, I tried to download Calibri font (among with variations like "Calibri Light", etc) somewhere from the web and install it as described in this question. I do not know if that "Overrides" the existing one, but I had the same result.
A line of text consists of 3 parts:
The ascent
The descent
The leading
To see more clearly, I used Calibri with size 50. The label without HTML is:
In HTML mode, things are different. The HTML renderer puts the leading first (for some reason):
This gives the unpleasant result you have observed.
Now you will ask "But why do I see that effect only with Calibri?" In fact the effect exists with all fonts, but it's usually much smaller, so you don't notice it.
Here is a program that outputs the metrics for some common Windows fonts:
import java.awt.*;
import javax.swing.JLabel;
public class FontInfo
{
static void info(String family, int size)
{
Font font = new Font(family, Font.PLAIN, size);
if(!font.getFamily().equals(family))
throw new RuntimeException("Font not available: "+family);
FontMetrics fm = new JLabel().getFontMetrics(font);
System.out.printf("%-16s %2d %2d %2d\n", family, fm.getAscent(), fm.getDescent(), fm.getLeading());
}
public static void main(String[] args)
{
String[] fonts = {"Arial", "Calibri", "Courier New", "Segoe UI", "Tahoma", "Times New Roman", "Verdana"};
System.out.printf("%-16s %s\n", "", " A D L");
for(String f : fonts)
info(f, 50);
}
}
For size 50, the results are:
A D L
Arial 46 11 2
Calibri 38 13 11
Courier New 42 15 0
Segoe UI 54 13 0
Tahoma 50 11 0
Times New Roman 45 11 2
Verdana 51 11 0
As you can see, the leading for Calibri is huge compared to the other fonts.
For size 14, the results are:
A D L
Arial 13 3 1
Calibri 11 4 3
Courier New 12 5 0
Segoe UI 16 4 0
Tahoma 14 3 0
Times New Roman 13 3 1
Verdana 15 3 0
The leading for Calibri is still 3 pixels. Other fonts have 0 or 1, which means the effect for them is invisible or very small.
It doesn't seem possible to change the behavior of the HTML renderer. However, if the goal is to align the baselines of adjacent components, then it is possible. The FlowLayout you have used has an alignOnBaseline property. If you enable it, it does align the components correctly:
UPDATE 1
Here's a JFixedLabel class that gives the same result, whether it contains HTML or plain text. It translates the Graphics by the leading value when in HTML mode:
import java.awt.Graphics;
import javax.swing.JLabel;
import javax.swing.plaf.basic.BasicHTML;
public class JFixedLabel extends JLabel
{
public JFixedLabel(String text)
{
super(text);
}
#Override
protected void paintComponent(Graphics g)
{
int dy;
if(getClientProperty(BasicHTML.propertyKey)!=null)
dy = getFontMetrics(getFont()).getLeading();
else
dy = 0;
g.translate(0, -dy);
super.paintComponent(g);
g.translate(0, dy);
}
}
Result:
UPDATE 2
The previous solution had an issue with icons, so here's a new one that handles both text and icons. Here we don't extend JLabel, instead we define a new UI class:
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.plaf.metal.MetalLabelUI;
public class FixedLabelUI extends MetalLabelUI
{
#Override
protected String layoutCL(JLabel label, FontMetrics fontMetrics, String text, Icon icon,
Rectangle viewR, Rectangle iconR, Rectangle textR)
{
String res = super.layoutCL(label, fontMetrics, text, icon, viewR, iconR, textR);
if(label.getClientProperty(BasicHTML.propertyKey)!=null)
textR.y -= fontMetrics.getLeading();
return res;
}
}
To assign the UI to a label, do like this:
JLabel label = new JLabel();
label.setUI(new FixedLabelUI());
Olivier's answer suggests to use flowLayout.setAlignOnBaseline(true); but it will not work in another Layoutmanagers, e.g GridLayout. However, it helped me a lot to find the exact solution I was looking for. Even if it is a messy/hacky one.
Here it is:
If you System.out.println(label.getFontMetrics(label.getFont())), you will see that the actual class of the FontMetrics is FontDesignMetrics. Luckily for us, the getters for the values ascent, descent and leading rely on the fields without some crazy calculations. Luckily for us vol.2, These font metrics are the same (equals) for the same font. That means, we have a single FontDesignMetrics instance of for each Font style-size combination (and obviously its family).
With other words:
private static final Font FONT = new Font("Calibri", Font.PLAIN, 50);
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setFont(FONT);
add(withoutHtml);
JLabel withHtml = new JLabel("<html>hello stackoverflow");
withHtml.setFont(FONT);
FontMetrics withHtmlFontMetrics = withHtml.getFontMetrics(withHtml.getFont());
FontMetrics withoutHtmlFontMetrics = withoutHtml.getFontMetrics(withoutHtml.getFont());
boolean equals = withHtmlFontMetrics.equals(withoutHtmlFontMetrics);
System.out.println(equals);
It prints true even if the getFontMetrics was called in different labels. If you withHtml.setFont(FONT.deriveFont(Font.BOLD)); you will see that it prints false. Because the font is different, we have different font metrics instance.
The fix
(Disclaimer: Desperate times call for desperate measures)
As I already mentioned, it's some sort of hacky and it relies on reflection. With reflection we can manipulate these 3 values. Something like:
FontMetrics fontMetrics = label.getFontMetrics(label.getFont());
Field descentField = fontMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(fontMetrics, 0);
But you are going to either hard code values for each font size/style, or you can do what I did.
What I did is to copy these values from other font's FontMetrics. It looks that in case of Calibri font, Tahoma is the one.
First, create the method that change the values in the fields, taken from Tahoma font metrics:
private static void copyTahomaFontMetricsTo(JComponent component) {
try {
FontMetrics calibriMetrics = component.getFontMetrics(component.getFont());
// Create a dummy JLabel with tahoma font, to obtain tahoma font metrics
JLabel dummyTahomaLabel = new JLabel();
dummyTahomaLabel.setFont(new Font("Tahoma", component.getFont().getStyle(), component.getFont().getSize()));
FontMetrics tahomaMetrics = dummyTahomaLabel.getFontMetrics(dummyTahomaLabel.getFont());
Field descentField = calibriMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(calibriMetrics, tahomaMetrics.getDescent());
Field ascentField = calibriMetrics.getClass().getDeclaredField("ascent");
ascentField.setAccessible(true);
ascentField.set(calibriMetrics, tahomaMetrics.getAscent());
Field leadingField = calibriMetrics.getClass().getDeclaredField("leading");
leadingField.setAccessible(true);
leadingField.set(calibriMetrics, tahomaMetrics.getLeading());
} catch (Exception e) {
e.printStackTrace();
}
}
Now, call it by: copyTahomaFontMetricsTo(withHtml); without caring if its the withHtml label or the withoutHtml, since they both have the same font.
The result (font size in frame title):
Even with other text-based components next to it:
As you see, it is works! Plus the layout alignment is not screwed.
It looks perfect, but it's not.
Again, as mentioned earlier, for each font (combination of family, size and style), there is one instance of FontMetrics. Changing one of these label's font to Font.BOLD will stop us from getting perfect alignment. Probably a one (or two) pixels miss. Plus we will have to copyTahomaFontMetricsTo for the Bold as well:
copyTahomaFontMetricsTo(withoutBoldFont);
copyTahomaFontMetricsTo(withBoldFont);
and the result (again font size on frame's title):
Look closer:
There is one pixel difference. But I guess I will take it since this is way (way) better than Swing's/Windows default Calibri-HTML behavior:
The complete example:
public class FontExample extends JFrame {
private static final Font FONT = new Font("Calibri", Font.PLAIN, 20);
public FontExample() {
super("Font: " + FONT.getSize());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JLabel withoutHtml = new JLabel("hello stackoverflow");
withoutHtml.setBorder(BorderFactory.createLineBorder(Color.GREEN));
withoutHtml.setFont(FONT.deriveFont(Font.BOLD));
add(withoutHtml);
JLabel withHtml = new JLabel("<html>hello stackoverflow");
withHtml.setBorder(BorderFactory.createLineBorder(Color.RED));
withHtml.setFont(FONT);
copyTahomaFontMetricsTo(withoutHtml);
copyTahomaFontMetricsTo(withHtml);
add(withHtml);
setLocationByPlatform(true);
pack();
}
private static void copyTahomaFontMetricsTo(JLabel label) {
try {
FontMetrics calibriMetrics = label.getFontMetrics(label.getFont());
// Create a dummy JLabel with tahoma font, to obtain tahoma font metrics
JLabel dummyTahomaLabel = new JLabel();
dummyTahomaLabel.setFont(new Font("Tahoma", label.getFont().getStyle(), label.getFont().getSize()));
FontMetrics tahomaMetrics = dummyTahomaLabel.getFontMetrics(dummyTahomaLabel.getFont());
Field descentField = calibriMetrics.getClass().getDeclaredField("descent");
descentField.setAccessible(true);
descentField.set(calibriMetrics, tahomaMetrics.getDescent());
Field ascentField = calibriMetrics.getClass().getDeclaredField("ascent");
ascentField.setAccessible(true);
ascentField.set(calibriMetrics, tahomaMetrics.getAscent());
Field leadingField = calibriMetrics.getClass().getDeclaredField("leading");
leadingField.setAccessible(true);
leadingField.set(calibriMetrics, tahomaMetrics.getLeading());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new FontExample().setVisible(true);
});
}
}
Two ways you can probably handle this, add
html {
margin:0;
}
or add padding to both bits of text. :)
Of course you can try
<html style="margin:0;">
<body style='vertical-align:text-bottom;' worked for me, but if I'm misunderstanding your question, you can find other values at https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align
I have seen this thread which asked the exact same question I have now, but find the answers a bit unsatisfactory:
Android's LinearLayout for Swing
I created a class WeightedPanel like so:
public class WeightedPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 6844740568601141924L;
private boolean mVertical;
private double mLastWeight = 1;
private GridBagConstraints mConstraints;
private int mLastGrid = 0;
public WeightedPanel(boolean vertical) {
mVertical = vertical;
mConstraints = new GridBagConstraints();
setLayout(new GridBagLayout());
}
#Override
public Component add(Component comp) {
return add(comp, mLastWeight);
}
public Component add(Component comp, double weight) {
if (mVertical) {
mConstraints.weighty = weight;
mConstraints.weightx = 1;
mConstraints.gridy = mLastGrid;
mConstraints.gridx = 0;
} else {
mConstraints.weightx = weight;
mConstraints.weighty = 1;
mConstraints.gridx = mLastGrid;
mConstraints.gridy = 0;
}
mConstraints.fill = GridBagConstraints.BOTH;
add(comp, mConstraints);
mLastWeight = weight;
mLastGrid += weight;
return comp;
}
public Component add(Component comp, int weight) {
return add(comp, (double) weight);
}
}
This kind of works, but I have two problems with it:
1) In my application, I have a login screen:
#Override
protected void addComponents(WeightedPanel jPanel) {
mUpdateListener = new UpdateListener() {
#Override
public void onUpdate() {
LoginFrame.this.onUpdate();
}
};
WeightedPanel panel = getUserPanel();
jPanel.add(panel);
panel = getPasswordPanel();
jPanel.add(panel);
mLoginButton = getLoginButton();
jPanel.add(mLoginButton);
}
private WeightedPanel getPasswordPanel() {
WeightedPanel result = new WeightedPanel(false);
JLabel label = new JLabel("Password");
result.add(label);
mPasswordField = new PasswordField(mUpdateListener);
result.add(mPasswordField);
return result;
}
private WeightedPanel getUserPanel() {
WeightedPanel result = new WeightedPanel(false);
JLabel label = new JLabel("User");
result.add(label);
mUserTextField = new TextField(mUpdateListener);
result.add(mUserTextField);
return result;
}
which in practice looks like this:
Click to view
Why aren't the labels and text fields all the same size here? I figure it's got something to do with the fact that "Password" is a longer string than "User", but that's obviously not what I want!
2) My second problem is this. I have another screen like so:
#Override
protected void addComponents(WeightedPanel jPanel) {
WeightedPanel scrollPanePanel = getOrdersScrollPane();
jPanel.add(scrollPanePanel);
WeightedPanel buttonPanel = getButtonPanel();
jPanel.add(buttonPanel);
}
private WeightedPanel getOrdersScrollPane() {
WeightedPanel result = new WeightedPanel(true);
JPanel filterPanel = getFilterPanel();
result.add(filterPanel, 1);
mTableModel = new OrdersTableModel();
mTable = new JTable(mTableModel);
mTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent arg0) {
checkEnabled();
}
});
JScrollPane scrollPane = new JScrollPane(mTable);
result.add(scrollPane, 40);
return result;
}
It really doesn't look bad in practice:
Click to view
But have a look at the getOrdersScrollPane() function. The call to functions result.add(filterPanel, 1); and result.add(scrollPane, 50); say that the proportion between the filter panel and the scroll pane should be 1:50, but looking at the scroll pane, it's definitely not 50 times the size of the filter panel. Obviously, I am exaggerating to make my point, I don't really want a proportion of 1:50; it just strikes me that it makes no difference whether I do result.add(scrollPane, 10); or result.add(scrollPane, 50);
Both questions stem from an incorrect understanding of GridBagLayout. A bit more reading and experimenting should help) To answer the question at hand:
1) The problem here is that you want a single GridBagLayout, but instead are adding 2 independent panels.
The result: The columns in the top grid bag are independent of the columns in the bottom grid bag.
To rectify this, there are 2 things you can try:
Add both labels and both text fields to a single GridBag panel. That way the columns will align.
Make a minimum and preferred size for the labels so that their width matches and set their weightx to 0 (and weightx of text fields non-zero). That way you are making the GridBags allocate the same amount of space for the labels and text fields.
The first method is preferred, but not always possible. The second method is hacky and will likely break as soon as you change the label string, a user set a different default font etc, etc.
2) Here you are misunderstanding what weighty does.
It does not make your components of the specified proportion. That should be clear enough since you can mix 0 and non-0 weight components in a single layout.
What it does, is it allocates the preferred (or minimum) sizes for components, and distributes the remaining space in that proportion. Which means if you make your panel 100 pixels higher by resizing the window, 2 will go to the top panel adding spacing, and 98 will go to the table.
What you likely wanted is to make the weighty of the top filter 0 (so that there is no awkward spacings in large windows) and control its actual height with setPreferred and setMinimum size (or by setting those on the embedded components).
EDIT
As docs for Linear Layout state, to achieve a fixed proportion of sizes of components (the initial problem), one has to set their preferred sizes to 0, and then set weights (then all space is remaining space, and is distributed according to weights only). This also works for the GridBag variant.
I have created a Chart shows Questions (X) / Time (Y) answered during a test.
You can see the first question here for details.
But now I need to show the chart bullet values correctly, at the moment shows the milliseconds value but i need to show the custom hh:mm:ss value like I've done with the Y-Axis label and somehow customize the Y-Axis area to show the full values correctly.
Below is a screenshot of how the chart looks like now.
[EDIT]
With Dan's help I almost got what I want. It's just a little problem.
Check in the screenshot below where the chart values now appearing.
I updated to 1.1.0 from the AChartEngine repository.
addNotations is on the TimeSeries objects. I copy paste my code below where adding data to my TimeSeries instance.
myQuestionsTimeSeries.add(i, DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
xyMultipleSeriesRenderer.addYTextLabel(DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()),
String.valueOf(answer.getEstimatedAnswerTime()));
myQuestionsTimeSeries.addAnnotation(String.valueOf(answer.getEstimatedAnswerTime()), i,
DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
The code results to this Chart:
[EDIT]
This is basically the whole class:
private void initQuestionsTimeChart() {
xyMultipleSeriesDataset = new XYMultipleSeriesDataset();
xyMultipleSeriesRenderer = new XYMultipleSeriesRenderer();
questionsTimeChart = ChartFactory.getLineChartView(getActivity(), xyMultipleSeriesDataset, xyMultipleSeriesRenderer);
rootView.addView(questionsTimeChart);
initSeriesData();
}
private void initSeriesData() {
createMyQuestionsSeries();
addSeriesAndRenderer(myQuestionsTimeSeries, myQuestionsRenderer);
xyMultipleSeriesRenderer.setYTitle("Questions Time");
xyMultipleSeriesRenderer.setXTitle("Questions Number");
xyMultipleSeriesRenderer.setMarginsColor(Color.argb(0, 255, 255, 255));
xyMultipleSeriesRenderer.setAxesColor(Color.BLACK);
xyMultipleSeriesRenderer.setLabelsColor(Color.BLACK);
xyMultipleSeriesRenderer.setXLabelsColor(Color.BLACK);
xyMultipleSeriesRenderer.setYLabelsColor(0, Color.BLACK);
xyMultipleSeriesRenderer.setAxisTitleTextSize(16);
xyMultipleSeriesRenderer.setLabelsTextSize(15);
xyMultipleSeriesRenderer.setYLabelsAlign(Paint.Align.RIGHT);
xyMultipleSeriesRenderer.setSelectableBuffer(20);
xyMultipleSeriesRenderer.setYLabels(0);
xyMultipleSeriesRenderer.setMargins(new int[]{ 80, 80, 80, 80 });
}
private void addSeriesAndRenderer(XYSeries series, XYSeriesRenderer renderer) {
xyMultipleSeriesDataset.addSeries(series);
xyMultipleSeriesRenderer.addSeriesRenderer(renderer);
}
private void createMyQuestionsSeries() {
myQuestionsTimeSeries = new TimeSeries("My Questions/Time");
myQuestionsRenderer = new XYSeriesRenderer();
myQuestionsRenderer.setColor(Color.BLUE);
myQuestionsRenderer.setLineWidth(3f);
myQuestionsRenderer.setPointStyle(PointStyle.CIRCLE);
myQuestionsRenderer.setFillPoints(true);
myQuestionsRenderer.setChartValuesSpacing(10f);
}
private void fillData() {
int i = 0;
for (Answer answer : getAnswers()) {
i++;
if (answer.getEstimatedAnswerTime() != null) {
myQuestionsTimeSeries.add(i, DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
xyMultipleSeriesRenderer.addYTextLabel(DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()),
String.valueOf(answer.getEstimatedAnswerTime()));
myQuestionsTimeSeries.addAnnotation(String.valueOf(answer.getEstimatedAnswerTime()), i,
DateTimeHelper.getMillisFromTime(answer.getEstimatedAnswerTime()));
}
}
}
Thank you in advance!
First of all, hide the chart values:
renderer.setDisplayChartValues(false);
Then, for each chart value, add an annotation:
renderer.addAnnotation("text", x, y);
For the Y axis labels to be visible, just align them to LEFT:
renderer.setYLabelsAlign(Align.LEFT);
Or you can increase the margins:
renderer.setMargins(margins);
Make sure you are using the latest ACE build available here.
Everything is in the title.
In my application, depending on a selection made by the user I fill up a combobox with a list which can sometime be small (1 element) sometimes be large (150 elements).
What I would like is not to have a fixed height set at startup to a given value but to set the maximumRowCount to the height of my JFrame or to the height of my screen and I don't know how to determine the number of rows that would match my application height or my screen height. This should be dynamical (at runtime) so when I change the combobox font size the maximumRowCount also adapts itself.
Can anyone help me?
So you need to figure out how big each row in the drop-down list is. To do this, the easy way is to create a cell renderer, populate it, and ask it for its preferred height.
final DefaultListCellRenderer dlcr = new DefaultListCellRenderer();
dlcr.setText("one of my combo items");
int numRows = (int)( (1.0f * frame.getHeight()) / dlcr.getPreferredSize().height );
setMaximumRowCount(numRows);
Note that I used frame.getHeight() here, but at least in some look-and-feels, the drop-down list starts below the combo box, so you'd have to make some adjustment for that. And there are ways to get the size of a screen, too, such as those in http://docs.oracle.com/javase/6/docs/api/java/awt/Toolkit.html.
Below is a code snippet which sets the rowCount dynamically
The basic steps
get the pref size of the rendering component
get the location of the combo relative to the context you want (the example takes the screen) and the available space below
calculate the number of rows which fit into the available space
do all this in a PopupMenuListener to be really dynamical
The code (which is obviously not production quality, just to give you something to play with :-)
final JComboBox box = new JComboBox(new Object[] {1, 2, 3, 4, 5, 6, 6, 34,3,3});
PopupMenuListener l = new PopupMenuListener() {
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
int pref = getRowHeight(box);
int available = getAvailableScreenHeightBelow(box);
int count = available / pref;
box.setMaximumRowCount(count);
}
private int getRowHeight(final JComboBox box) {
// note: here we assume the rendering comp's pref is the same for all rows
ComboPopup popup = (ComboPopup) box.getAccessibleContext().getAccessibleChild(0);
ListCellRenderer renderer = box.getRenderer();
Component comp = renderer.getListCellRendererComponent(popup.getList(), 1, 0, false, false);
int pref = comp.getPreferredSize().height;
return pref;
}
private int getAvailableScreenHeightBelow(final JComboBox box) {
// note: this is crude - f.i. doesn't take taskbar into account
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Point location = box.getLocationOnScreen();
location.y += box.getHeight();
int available = screen.height - location.y;
return available;
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
#Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
};
box.addPopupMenuListener(l);