GWT Custom Button with Icon above Text - gwt

I want to create a button with an icon and text, where icon is above text.
Looking for information I've found this answer from Juri.
With it I've created my own SquareButton class like below:
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Image;
/**
* Square Button class
* #author julio.palma.vazquez
*
*/
public class SquareButton extends Button {
/** Text to show. */
private String text;
/**
* Constructor.
*/
public SquareButton() {
super();
}
public SquareButton(String text, ClickHandler clickHandler, ImageResource imageResource) {
super(text, clickHandler);
setResource(imageResource);
setPixelSize(60, 60);
}
/**
* Set image resource.
* #param imageResource image resource
*/
public void setResource(ImageResource imageResource){
Image img = new Image(imageResource);
String definedStyles = img.getElement().getAttribute("style");
img.getElement().setAttribute("style", definedStyles + "; vertical-align:top;");
DOM.insertBefore(getElement(), img.getElement(), DOM.getFirstChild(getElement()));
}
/**
* Set text.
* #param text text to show
*/
#Override
public void setText(String text) {
this.text = text;
Element span = DOM.createElement("span");
span.setInnerText(text);
span.setAttribute("style", "vertical-align:bottom; text-align: center;");
DOM.insertChild(getElement(), span, 0);
}
/**
* Get text.
* #return text to show
*/
#Override
public String getText() {
return this.text;
}
}
Unfortunatelly it doesn't work as I expect and output text above image.
Could you please give me a hand?

You can set HTML in Button. You can try following:
Button button = new Button();
String html = "<div><center><img src = '"+GWT.getModuleBaseURL()+"/images/img1.png' height = '10px' width = '10px'></img></center><label>Text</label></br></div>";
button.setHTML(html);
Give proper size to your button as well as image.

You can see SquareButton class below, thanks for your help.
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Button;
/**
* Square Button class
* #author julio.palma.vazquez
*
*/
public class SquareButton extends Button {
public SquareButton(String text, ClickHandler clickHandler, ImageResource imageResource) {
super();
String html = "<div><center><img src = '" + imageResource.getSafeUri().asString() + "' height = '32px' width = '32px'></img></center><label>" + text + "</label></br></div>";
addClickHandler(clickHandler);
setHTML(html);
setPixelSize(60, 60);
}
}

You don't need a custom button. You can do this with a GWT Button, ImageResource, and CSS.
The trick is to set the padding properly. For example,
Button btnHelp=new Button("Help");
btnHelp.setStyleName("buttonImageText");
// Separate style for each image resource
btnHelp.addStyleName("bgImageHelp");
In your stylesheet:
(Note: padding-top is at least as large as your image height.)
.buttonImageText{
background-repeat: no-repeat;
background-position: 50% 0%;
background-size: 20px 20px;
background-color: transparent;
border: none;
margin: 0 15px;
padding: 20px 0 0 0;
text-align: center;
}
.bgImageHelp {
background-image: helpUrl;
}
And you've also defined in your CSS file:
#url helpUrl help;
Finally, in your Resources class you have defined:
#Source("resources/images/help.png");
ImageResource help();

Related

iText 7 Add Annotation at a specific position

I convert HTML to PDF using iText7 and need to add Text Markup Annotations for specific text in the HTML. I am using CustomTagWorkers as explained in this link and then I am using the annotation examples given here.
I am able to successfully add Link Annotation by replacing the qr tag with a link annotation. However my requirement is to add a Text Markup Annotation. The Text Markup annotation can only be drawn by giving the specific coordinates of the page (rectangle object) which I do not know in the code. I tried to give Rectangle(0, 0) hoping that iText will render this in place of tag. However I am unable to add the Text Markup Annotation to paragraph object which is the return object of public IPropertyContainer getElementResult() {.
Here is my entire code:
/**
* Converts an HTML file to a PDF document, introducing a custom tag to create a
* QR Code involving a custom TagWorker and a custom CssApplier.
*/
public class C05E04_QRCode2 {
/**
* The path to the resulting PDF file.
*/
public static final String DEST = "C:\\Samples\\itext-annotation\\qrcode.pdf";
/**
* The path to the source HTML file.
*/
public static final String SRC = "C:\\Samples\\itext-annotation\\qrcode.html";
/**
* The main method of this example.
*
* #param args no arguments are needed to run this example.
* #throws IOException signals that an I/O exception has occurred.
*/
public static void main(String[] args) throws IOException {
File file = new File(DEST);
file.getParentFile().mkdirs();
C05E04_QRCode2 app = new C05E04_QRCode2();
System.out.println("pdf");
app.createPdf(SRC, DEST);
}
/**
* Creates the PDF file.
*
* #param src the path to the source HTML file
* #param dest the path to the resulting PDF
* #throws IOException signals that an I/O exception has occurred.
*/
public void createPdf(String src, String dest) throws IOException {
ConverterProperties properties = new ConverterProperties();
properties.setCssApplierFactory(new QRCodeTagCssApplierFactory())
.setTagWorkerFactory(new QRCodeTagWorkerFactory());
HtmlConverter.convertToPdf(new File(src), new File(dest), properties);
}
/**
* A factory for creating QRCodeTagCssApplier objects.
*/
class QRCodeTagCssApplierFactory extends DefaultCssApplierFactory {
/**
* Gets the custom css applier.
*
* #param tag the tag
* #return the custom css applier
*/
/*
* (non-Javadoc)
*
* #see com.itextpdf.html2pdf.css.apply.impl.DefaultCssApplierFactory#
* getCustomCssApplier(com.itextpdf.html2pdf.html.node.IElementNode)
*/
#Override
public ICssApplier getCustomCssApplier(IElementNode tag) {
if (tag.name().equals("qr")) {
return new BlockCssApplier();
}
return null;
}
}
/**
* A factory for creating QRCodeTagWorker objects.
*/
class QRCodeTagWorkerFactory extends DefaultTagWorkerFactory {
/**
* Gets the custom tag worker.
*
* #param tag the tag
* #param context the context
* #return the custom tag worker
*/
#Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if (tag.name().equals("qr")) {
return new QRCodeTagWorker(tag, context);
}
return null;
}
}
/**
* The custom ITagWorker implementation for the qr-tag.
*/
static class QRCodeTagWorker implements ITagWorker {
/** The p. */
private Paragraph p;
/** The annotation. */
private PdfLinkAnnotation linkAnnotation;
/**
* Instantiates a new QR code tag worker.
*
* #param element the element
* #param context the context
*/
public QRCodeTagWorker(IElementNode element, ProcessorContext context) {
}
/**
* Process content.
*
* #param content the content
* #param context the context
* #return true, if successful
*/
#Override
public boolean processContent(String content, ProcessorContext context) {
return true;
}
/**
* Process tag child.
*
* #param childTagWorker the child tag worker
* #param context the context
* #return true, if successful
*/
#Override
public boolean processTagChild(ITagWorker childTagWorker, ProcessorContext context) {
return false;
}
/**
* Process end.
*
* #param element the element
* #param context the context
*/
#Override
public void processEnd(IElementNode element, ProcessorContext context) {
//Link Annotation
linkAnnotation = new PdfLinkAnnotation(new Rectangle(0, 0)).setAction(PdfAction.createURI(
"https://kb.itextpdf.com/"));
Link link = new Link("here", linkAnnotation);
p = new Paragraph("The example of link annotation. Click ").add(link.setUnderline())
.add(" to learn more...");
//Line Annotation
float[] floatArray = new float[] { 169, 790, 105, 790, 169, 800, 105, 800 };
PdfAnnotation lineAnnotation = PdfTextMarkupAnnotation.createHighLight(new Rectangle(0, 0), floatArray);
lineAnnotation.setTitle(new PdfString("You are here:"));
lineAnnotation.setContents("Cambridge Innovation Center");
lineAnnotation.setColor(ColorConstants.YELLOW);
//Text Markup Annotation
PdfAnnotation ann = PdfTextMarkupAnnotation.createHighLight(
new Rectangle(105, 790, 64, 10),
new float[]{169, 790, 105, 790, 169, 800, 105, 800})
.setColor(Color.YELLOW)
.setTitle(new PdfString("Hello!"))
.setContents(new PdfString("I'm a popup."))
.setTitle(new PdfString("iText"))
.setOpen(true)
.setRectangle(new PdfArray(new float[]{100, 600, 200, 100}));
}
/**
* Gets the element result.
*
* #return the element result
*/
#Override
public IPropertyContainer getElementResult() {
return p;
}
}
}
HTML File:
<html>
<head>
<meta charset="UTF-8">
<title>QRCode Example</title>
<link rel="stylesheet" type="text/css" href="css/qrcode.css"/>
</head>
<body>
<span> The example of <qr> text markup </qr> annotation. </span>
</body>
</html>
CSS File:
qr {
border: solid 1px red;
height: 200px;
width: 200px;
}
Illustration of the final output
The easiest pragmatic way to proceed is make your <qr> tag behave as inline-block. This will make sure that we will not have the text from the tag wrap to the next line (in this case annotation is tricky to define), and also we will have a natural grouping element that we will be able to fetch the coordinates from.
I have modified the input HTML slightly, to get rid of <qr> tag in favor of <annot> and add the above mentioned display: inline-block behavior:
<html>
<head>
<style>
annot {
display: inline-block;
}
</style>
</head>
<body>
<span> The example of <annot> text markup </annot> annotation. </span>
</body>
</html>
We proceed with the definition of the custom tag worker factory and custom tag worker in the similar fashion as in the original post:
private static class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
#Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if ("annot".equals(tag.name())) {
return new AnnotTagWorker(tag, context);
}
return super.getCustomTagWorker(tag, context);
}
}
private static class AnnotTagWorker extends PTagWorker {
public AnnotTagWorker(IElementNode element, ProcessorContext context) {
super(element, context);
}
#Override
public IPropertyContainer getElementResult() {
IPropertyContainer baseResult = super.getElementResult();
if (baseResult instanceof Paragraph) {
((Paragraph) baseResult).setNextRenderer(new AnnotTagRenderer((Paragraph) baseResult));
}
return baseResult;
}
}
Now the meaty part will be implemented in AnnotTagRenderer. Notice how we set the renderer to the resultant element in AnnotTagWorker. The renedrer implementation is aware of the physical coordinates of the text block corresponding to our <annot> tag on the PDF page. We can now fetch the coordinates from this.getOccupiedArea() in draw() method and this does the main trick for us - all we need to do as a remainder is create the right annotation object and add it to the page. Here is the implementation:
private static class AnnotTagRenderer extends ParagraphRenderer {
public AnnotTagRenderer(Paragraph modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new AnnotTagRenderer((Paragraph) modelElement);
}
#Override
public void draw(DrawContext drawContext) {
super.draw(drawContext);
Rectangle occupiedArea = this.getOccupiedAreaBBox();
float[] quadPoints = new float[] {occupiedArea.getLeft(), occupiedArea.getTop(), occupiedArea.getRight(), occupiedArea.getTop(),
occupiedArea.getLeft(), occupiedArea.getBottom(), occupiedArea.getRight(), occupiedArea.getBottom()};
PdfAnnotation ann = PdfTextMarkupAnnotation.createHighLight(
new Rectangle(occupiedArea), quadPoints)
.setColor(ColorConstants.YELLOW)
.setTitle(new PdfString("Hello!"))
.setContents(new PdfString("I'm a popup."))
.setTitle(new PdfString("iText"));
drawContext.getDocument().getPage(this.getOccupiedArea().getPageNumber()).addAnnotation(ann);
}
}
Visual result looks as follows:
Finally, don't forget to plug the custom tag worker factory into the converter properties:
ConverterProperties properties = new ConverterProperties().setTagWorkerFactory(new CustomTagWorkerFactory());
HtmlConverter.convertToPdf(new File(sourceHTML), new File(targetPDF), properties);

Problems with Eclipse Window Builder Composite

I am experiencing some problems with Window Builder example:
https://developers.google.com/web-toolkit/tools/gwtdesigner/features/custom_composites
When I copy the codes to my Eclipse, I have an error in the code:
customComposite.setFirstFieldBackground(SWTResourceManager
.getColor(SWT.COLOR_YELLOW));
Eclipse tells me that there's no SWTResourceManager.
When I try to exhibit the form in design mode, another error occours in the code:
final Label thirdFieldLabel = new Label(customComposite.getComposite(),
SWT.NONE);
thirdFieldLabel.setText("Third Field");
Error:
org.eclipse.wb.internal.core.utils.exception.DesignerException: 2005 (Null as parent argument.).
Considering that this example is very common in Web, even in the Help Files of the own WindowBuilder, I supose that I hanven't the correct settings/files to my application.
What would I do differently? Thanks!
What is happening is that SWTResourceManager is a class that is created in the plugin when you use the design view to set things like color, it is specific to your plugin. So when you copy code into another plugin that references that class you will get a class not found exception. Easiest thing to do,is copy the code. Delete the set color, set the color in the editor in your new plugin and a SWTResourceManager wil be auto created for that plugin. The second NPE not sure sounds environment specific but as a rule of thumb swt resource manager is bundle specific.
The SWTResourceManager class is usually generated in a com.swtdesigner package when you create a new WindowBuilder project using Eclipse wizard (File > New > Other > WindowBuilder> SWTDesigner).
Try to generate your project using this wizard.
If you still don't have this class generated here is its code :
package com.swtdesigner;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
/**
* Utility class for managing OS resources associated with SWT controls such as colors, fonts, images, etc.
* <p>
* !!! IMPORTANT !!! Application code must explicitly invoke the <code>dispose()</code> method to release the
* operating system resources managed by cached objects when those objects and OS resources are no longer
* needed (e.g. on application shutdown)
* <p>
* This class may be freely distributed as part of any application or plugin.
* <p>
* #author scheglov_ke
* #author Dan Rubel
*/
public class SWTResourceManager {
////////////////////////////////////////////////////////////////////////////
//
// Color
//
////////////////////////////////////////////////////////////////////////////
private static Map<RGB, Color> m_colorMap = new HashMap<RGB, Color>();
/**
* Returns the system {#link Color} matching the specific ID.
*
* #param systemColorID
* the ID value for the color
* #return the system {#link Color} matching the specific ID
*/
public static Color getColor(int systemColorID) {
Display display = Display.getCurrent();
return display.getSystemColor(systemColorID);
}
/**
* Returns a {#link Color} given its red, green and blue component values.
*
* #param r
* the red component of the color
* #param g
* the green component of the color
* #param b
* the blue component of the color
* #return the {#link Color} matching the given red, green and blue component values
*/
public static Color getColor(int r, int g, int b) {
return getColor(new RGB(r, g, b));
}
/**
* Returns a {#link Color} given its RGB value.
*
* #param rgb
* the {#link RGB} value of the color
* #return the {#link Color} matching the RGB value
*/
public static Color getColor(RGB rgb) {
Color color = m_colorMap.get(rgb);
if (color == null) {
Display display = Display.getCurrent();
color = new Color(display, rgb);
m_colorMap.put(rgb, color);
}
return color;
}
/**
* Dispose of all the cached {#link Color}'s.
*/
public static void disposeColors() {
for (Color color : m_colorMap.values()) {
color.dispose();
}
m_colorMap.clear();
}
////////////////////////////////////////////////////////////////////////////
//
// Image
//
////////////////////////////////////////////////////////////////////////////
/**
* Maps image paths to images.
*/
private static Map<String, Image> m_imageMap = new HashMap<String, Image>();
/**
* Returns an {#link Image} encoded by the specified {#link InputStream}.
*
* #param stream
* the {#link InputStream} encoding the image data
* #return the {#link Image} encoded by the specified input stream
*/
protected static Image getImage(InputStream stream) throws IOException {
try {
Display display = Display.getCurrent();
ImageData data = new ImageData(stream);
if (data.transparentPixel > 0) {
return new Image(display, data, data.getTransparencyMask());
}
return new Image(display, data);
} finally {
stream.close();
}
}
/**
* Returns an {#link Image} stored in the file at the specified path.
*
* #param path
* the path to the image file
* #return the {#link Image} stored in the file at the specified path
*/
public static Image getImage(String path) {
Image image = m_imageMap.get(path);
if (image == null) {
try {
image = getImage(new FileInputStream(path));
m_imageMap.put(path, image);
} catch (Exception e) {
image = getMissingImage();
m_imageMap.put(path, image);
}
}
return image;
}
/**
* Returns an {#link Image} stored in the file at the specified path relative to the specified class.
*
* #param clazz
* the {#link Class} relative to which to find the image
* #param path
* the path to the image file, if starts with <code>'/'</code>
* #return the {#link Image} stored in the file at the specified path
*/
public static Image getImage(Class<?> clazz, String path) {
String key = clazz.getName() + '|' + path;
Image image = m_imageMap.get(key);
if (image == null) {
try {
image = getImage(clazz.getResourceAsStream(path));
m_imageMap.put(key, image);
} catch (Exception e) {
image = getMissingImage();
m_imageMap.put(key, image);
}
}
return image;
}
private static final int MISSING_IMAGE_SIZE = 10;
/**
* #return the small {#link Image} that can be used as placeholder for missing image.
*/
private static Image getMissingImage() {
Image image = new Image(Display.getCurrent(), MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE);
//
GC gc = new GC(image);
gc.setBackground(getColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, MISSING_IMAGE_SIZE, MISSING_IMAGE_SIZE);
gc.dispose();
//
return image;
}
/**
* Style constant for placing decorator image in top left corner of base image.
*/
public static final int TOP_LEFT = 1;
/**
* Style constant for placing decorator image in top right corner of base image.
*/
public static final int TOP_RIGHT = 2;
/**
* Style constant for placing decorator image in bottom left corner of base image.
*/
public static final int BOTTOM_LEFT = 3;
/**
* Style constant for placing decorator image in bottom right corner of base image.
*/
public static final int BOTTOM_RIGHT = 4;
/**
* Internal value.
*/
protected static final int LAST_CORNER_KEY = 5;
/**
* Maps images to decorated images.
*/
#SuppressWarnings("unchecked")
private static Map<Image, Map<Image, Image>>[] m_decoratedImageMap = new Map[LAST_CORNER_KEY];
/**
* Returns an {#link Image} composed of a base image decorated by another image.
*
* #param baseImage
* the base {#link Image} that should be decorated
* #param decorator
* the {#link Image} to decorate the base image
* #return {#link Image} The resulting decorated image
*/
public static Image decorateImage(Image baseImage, Image decorator) {
return decorateImage(baseImage, decorator, BOTTOM_RIGHT);
}
/**
* Returns an {#link Image} composed of a base image decorated by another image.
*
* #param baseImage
* the base {#link Image} that should be decorated
* #param decorator
* the {#link Image} to decorate the base image
* #param corner
* the corner to place decorator image
* #return the resulting decorated {#link Image}
*/
public static Image decorateImage(final Image baseImage, final Image decorator, final int corner) {
if (corner <= 0 || corner >= LAST_CORNER_KEY) {
throw new IllegalArgumentException("Wrong decorate corner");
}
Map<Image, Map<Image, Image>> cornerDecoratedImageMap = m_decoratedImageMap[corner];
if (cornerDecoratedImageMap == null) {
cornerDecoratedImageMap = new HashMap<Image, Map<Image, Image>>();
m_decoratedImageMap[corner] = cornerDecoratedImageMap;
}
Map<Image, Image> decoratedMap = cornerDecoratedImageMap.get(baseImage);
if (decoratedMap == null) {
decoratedMap = new HashMap<Image, Image>();
cornerDecoratedImageMap.put(baseImage, decoratedMap);
}
//
Image result = decoratedMap.get(decorator);
if (result == null) {
Rectangle bib = baseImage.getBounds();
Rectangle dib = decorator.getBounds();
//
result = new Image(Display.getCurrent(), bib.width, bib.height);
//
GC gc = new GC(result);
gc.drawImage(baseImage, 0, 0);
if (corner == TOP_LEFT) {
gc.drawImage(decorator, 0, 0);
} else if (corner == TOP_RIGHT) {
gc.drawImage(decorator, bib.width - dib.width, 0);
} else if (corner == BOTTOM_LEFT) {
gc.drawImage(decorator, 0, bib.height - dib.height);
} else if (corner == BOTTOM_RIGHT) {
gc.drawImage(decorator, bib.width - dib.width, bib.height - dib.height);
}
gc.dispose();
//
decoratedMap.put(decorator, result);
}
return result;
}
/**
* Dispose all of the cached {#link Image}'s.
*/
public static void disposeImages() {
// dispose loaded images
{
for (Image image : m_imageMap.values()) {
image.dispose();
}
m_imageMap.clear();
}
// dispose decorated images
for (int i = 0; i < m_decoratedImageMap.length; i++) {
Map<Image, Map<Image, Image>> cornerDecoratedImageMap = m_decoratedImageMap[i];
if (cornerDecoratedImageMap != null) {
for (Map<Image, Image> decoratedMap : cornerDecoratedImageMap.values()) {
for (Image image : decoratedMap.values()) {
image.dispose();
}
decoratedMap.clear();
}
cornerDecoratedImageMap.clear();
}
}
}
////////////////////////////////////////////////////////////////////////////
//
// Font
//
////////////////////////////////////////////////////////////////////////////
/**
* Maps font names to fonts.
*/
private static Map<String, Font> m_fontMap = new HashMap<String, Font>();
/**
* Maps fonts to their bold versions.
*/
private static Map<Font, Font> m_fontToBoldFontMap = new HashMap<Font, Font>();
/**
* Returns a {#link Font} based on its name, height and style.
*
* #param name
* the name of the font
* #param height
* the height of the font
* #param style
* the style of the font
* #return {#link Font} The font matching the name, height and style
*/
public static Font getFont(String name, int height, int style) {
return getFont(name, height, style, false, false);
}
/**
* Returns a {#link Font} based on its name, height and style. Windows-specific strikeout and underline
* flags are also supported.
*
* #param name
* the name of the font
* #param size
* the size of the font
* #param style
* the style of the font
* #param strikeout
* the strikeout flag (warning: Windows only)
* #param underline
* the underline flag (warning: Windows only)
* #return {#link Font} The font matching the name, height, style, strikeout and underline
*/
public static Font getFont(String name, int size, int style, boolean strikeout, boolean underline) {
String fontName = name + '|' + size + '|' + style + '|' + strikeout + '|' + underline;
Font font = m_fontMap.get(fontName);
if (font == null) {
FontData fontData = new FontData(name, size, style);
if (strikeout || underline) {
try {
Class<?> logFontClass = Class.forName("org.eclipse.swt.internal.win32.LOGFONT"); //$NON-NLS-1$
Object logFont = FontData.class.getField("data").get(fontData); //$NON-NLS-1$
if (logFont != null && logFontClass != null) {
if (strikeout) {
logFontClass.getField("lfStrikeOut").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$
}
if (underline) {
logFontClass.getField("lfUnderline").set(logFont, Byte.valueOf((byte) 1)); //$NON-NLS-1$
}
}
} catch (Throwable e) {
System.err
.println("Unable to set underline or strikeout" + " (probably on a non-Windows platform). " + e); //$NON-NLS-1$ //$NON-NLS-2$
}
}
font = new Font(Display.getCurrent(), fontData);
m_fontMap.put(fontName, font);
}
return font;
}
/**
* Returns a bold version of the given {#link Font}.
*
* #param baseFont
* the {#link Font} for which a bold version is desired
* #return the bold version of the given {#link Font}
*/
public static Font getBoldFont(Font baseFont) {
Font font = m_fontToBoldFontMap.get(baseFont);
if (font == null) {
FontData fontDatas[] = baseFont.getFontData();
FontData data = fontDatas[0];
font = new Font(Display.getCurrent(), data.getName(), data.getHeight(), SWT.BOLD);
m_fontToBoldFontMap.put(baseFont, font);
}
return font;
}
/**
* Dispose all of the cached {#link Font}'s.
*/
public static void disposeFonts() {
// clear fonts
for (Font font : m_fontMap.values()) {
font.dispose();
}
m_fontMap.clear();
// clear bold fonts
for (Font font : m_fontToBoldFontMap.values()) {
font.dispose();
}
m_fontToBoldFontMap.clear();
}
////////////////////////////////////////////////////////////////////////////
//
// Cursor
//
////////////////////////////////////////////////////////////////////////////
/**
* Maps IDs to cursors.
*/
private static Map<Integer, Cursor> m_idToCursorMap = new HashMap<Integer, Cursor>();
/**
* Returns the system cursor matching the specific ID.
*
* #param id
* int The ID value for the cursor
* #return Cursor The system cursor matching the specific ID
*/
public static Cursor getCursor(int id) {
Integer key = Integer.valueOf(id);
Cursor cursor = m_idToCursorMap.get(key);
if (cursor == null) {
cursor = new Cursor(Display.getDefault(), id);
m_idToCursorMap.put(key, cursor);
}
return cursor;
}
/**
* Dispose all of the cached cursors.
*/
public static void disposeCursors() {
for (Cursor cursor : m_idToCursorMap.values()) {
cursor.dispose();
}
m_idToCursorMap.clear();
}
////////////////////////////////////////////////////////////////////////////
//
// General
//
////////////////////////////////////////////////////////////////////////////
/**
* Dispose of cached objects and their underlying OS resources. This should only be called when the cached
* objects are no longer needed (e.g. on application shutdown).
*/
public static void dispose() {
disposeColors();
disposeImages();
disposeFonts();
disposeCursors();
}
}

Multiple pages tutorial in Google Web Toolkit (GWT)

I just started learning Google Web Toolkit (GWT). How do I make different HTML pages in my GWT application?
For example, I want to create an application for a book store. In this application I'll have three pages:
Home pages where I will welcome the user and offer the user books
Page to browse books by categories and view details (use GWT widgets)
Check out books online.
Of course there could be other pages like the user's details, add new book, etc.
So, what is the best way of making different pages in GWT and how can I make navigation from page to page? Are there any examples or tutorials? Or do I even need to create different pages when I can create a whole application in one page?
What I usually do in situations like this is design the webpage framework first. I'll have a div for the header, side menu and footer. I'll also have a div in my HTML for the main content.
Example:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name='gwt:module' content='org.project.package.Core=org.project.package.Core'>
</head>
<body>
<!-- Load the JavaScript code for GWT -->
<script language="javascript" src="ui/org.project.package.ui.Core.nocache.js"></script>
<!-- For some unknown reason in Internet Explorer you have to have cellpadding/spacing ON THE ELEMENT and not on the STYLE if it is in the body tag like this -->
<table id="wrapper" cellpadding="0" cellspacing="0" style="width: 100%;height: 100%;">
<!-- Header row -->
<tr style="height: 25%;">
<td colspan="2" id="header"></td>
</tr>
<!-- Body row and left nav row -->
<tr style="height: 65%;">
<td id="leftnav"></td>
<td id="content"></td>
</tr>
<!-- Footer row -->
<tr style="height: 10%;">
<td colspan="2" id="footer"></td>
</tr>
</table>
<!-- This iframe handles history -->
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
</body>
</html>
(If you like <div> based layouts, feel free to use those instead.)
Then you build your entry point (in my case Core.java) as you normally would, setting each of the elements as need be.
RootPanel.get("header").add(new Header());
RootPanel.get("leftnav").add(new NavigationMenu());
RootPanel.get("footer").add(new Footer());
It is, of course, possible to have a static footer and header, but that's neither here nor there.
I also have an abstract class called "Content". Content objects extend "Composite" and will have various methods for simplifying the creation and layout of a new page. Every page that I build for this application, be it a help screen, search screen, shopping cart, or anything else, is of type Content.
Now, what I do is create a class called "ContentContainer". This is a singleton that is responsible for managing the "content" element. It has one method "setContent" that accepts objects of type "Content". It then basically removes anything within the "content" <td> and replaces it with whatever widget (Composite) you assign via the "setContent" method. The setContent method also handles history and title bar management. Basically the ContentContainer serves to aggregate all the various points of binding that you might have to make if each page content had to "know" about all the functions it must perform.
Finally, you need a way to get to that page, right? That's simple:
ContentContainer.getInstance().setContent(new Search());
Put the above in an on-click event somewhere and you're golden.
The only things that your other widgets need to be bound to is the ContentContainer and the type of Content that they are adding.
The downsides that I can see to ChrisBo's approach are that you've got a list that has to be maintained of tokens -> pages. The other downside I can see is that I don't see how you can have an actual history system with this method.
One thing it does offer over my approach is that all page choices are pretty centralized. I'd use some sort of Enum or at least a static class with String values to prevent myself from mongling up links.
In either case, I think the point can be summed up as this: swap the content of some central page element based on what your user clicks actions your user(s) perform.
I would use the HyperLink and History class. The good thing about the Hyperlink class is, that it sets this token(e.g.#foobar), and all you have to do, is catch the event, that is fired when the value of the token is changed(ValueChangeEvent). In the eventHandler you would then replace the pages.
Example:
address of welcome Page: www.yourpage.com/#home
on this page would be a link to the "browse book"-page, when the link is clicked the new address would be something like this: www.yourpage.com/#browse
And here is the code:
public class MainEntryPoint implements EntryPoint, ValueChangeHandler {
VerticalPanel panel = new VerticalPanel();
Label label=new Label();
public void onModuleLoad() {
Hyperlink link1 = new Hyperlink("books", "browse");
Hyperlink link2 = new Hyperlink("user details", "details");
panel.add(link1);
panel.add(link2);
panel.add(label);
RootPanel.get().add(panel);
History.addValueChangeHandler(this);
//when there is no token, the "home" token is set else changePage() is called.
//this is useful if a user has bookmarked a site other than the homepage.
if(History.getToken().isEmpty()){
History.newItem("home");
} else {
changePage(History.getToken());
}
}
public void onValueChange(ValueChangeEvent event) {
changePage(History.getToken());
}
public void changePage(String token) {
if(History.getToken().equals("browse")) {
label.setText("Here would be some books");
} else if (History.getToken().equals("details")) {
label.setText("Here would be the user details");
} else {
label.setText("Welcome page");
}
}
}
Awesome! I combined Chris R.'s answer with Chris Boesing's to come up with this:
This is the 'index' start page
public class Index implements EntryPoint, ValueChangeHandler<String> {
public void onModuleLoad() {
History.addValueChangeHandler(this);
if (History.getToken().isEmpty()) History.newItem("index");
Composite c = new Login();
FlowControl.go(c);
}
public void onValueChange(ValueChangeEvent<String> e) {
FlowControl.go(History.getToken());
}
}
This is the controller, or ContentContainer according to Chris R.
public class FlowControl {
private static FlowControl instance;
private FlowControl() {}
public static void go(Composite c) {
if (instance == null) instance = new FlowControl(); // not sure why we need this yet since everything is static.
RootPanel.get("application").clear();
RootPanel.get("application").getElement().getStyle().setPosition(Position.RELATIVE); // not sure why, but GWT throws an exception without this. Adding to CSS doesn't work.
// add, determine height/width, center, then move. height/width are unknown until added to document. Catch-22!
RootPanel.get("application").add(c);
int left = Window.getClientWidth() / 2 - c.getOffsetWidth() / 2; // find center
int top = Window.getClientHeight() / 2 - c.getOffsetHeight() / 2;
RootPanel.get("application").setWidgetPosition(c, left, top);
History.newItem(c.getTitle()); // TODO: need to change and implement (or override) this method on each screen
}
public static void go(String token) {
if (token == null) go(new Login());
if (token.equals("cart")) go(new Cart());
if (token.equals("login")) go(new Login());
// Can probably make these constants in this class
}
Then you can pepper Hyperlinks and Buttons anywhere throughout your code. (Have not tried Hyperlinks yet.)
Button submit = new Button("Submit");
submit.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
FlowControl.go(new MyScreen());
}
});
I added a div to my HTML
<!-- This is where the application will reside within. It is controlled by FlowControl class. -->
<div id="application"></div>
And now all screens must call initWidget() in the constructor instead of adding to RootPanel, since it is a Composite class now, like
initWidget(myPanel); // all composites must call this in constructor
If you want it to be FULL AJAXified (like a desktop app) of course you'd only need one page. Then just change the contents of the body depending on the link.
Also, there is a google group for GWT that is very very active, and I know this has been asked before there, you just need to use the "search" feature.
GWT Multipage - simple framework for multi-page-GWT-applications.
You can use MVP patern.
Here is mi simple library https://code.google.com/p/gwt-simple-mvp/wiki/GettingStarted .
And you can split code to more js files. https://code.google.com/p/gwt-spliting/
I used Chloe S. answer (combining Chris R.'s answer with Chris Boesing's) to build this App Controller for a working GWT Web App. The version in production is tested (and working %100) but this redacted version below will need to be modified to integrate with your own app (start by renaming the page keys to your menu items).
AppController.java:
/**
* This App Controller utilizes two static inner-classes (Pages and External)
* to manage and server multiple pages with multiple sub-page (through their presenters)
* via String key constants which also serve as the literal text for the menu items.
*
* Pages are added as menu commands in their respective views:
* // Add menu items to the menu with commands:
* menuItems.put(Pages.PAGE1, mainMenu.addItem(Pages.PAGE1, new Command() {
* public void execute() {
* History.newItem(Pages.PAGE1);
* }
* }));
*
* Pages are fired as History tokens (from entry point java class):
*
* **
* * Receives history events and pushes them to the AppController using a deferred command.
* * Changes the cursor to show waiting.
* * #param the value change token
* *
* public void onValueChange(ValueChangeEvent<String> e) {
* // check token to cover first historical "back" navigation:
* if(!History.getToken().isEmpty()) {
* AppController.waitCursor.execute(); // cursor is reset in page attach method
* }
* Scheduler.get().scheduleDeferred(new ScheduledCommand() {
* public void execute() {
* AppController.go(History.getToken());
* }
* });
* }
*
* Wait cursors are implemented as CSS:
*
* body.wait, body.wait * {
* cursor: wait !important;
* }
*
* NOTE: This page swapping implementation technique (based on the StackOverflow solution
* found here: [http://stackoverflow.com/questions/1061705/multiple-pages-tutorial-in-google-web-toolkit-gwt][1])
* differs from the obtuse and ancient 2010 GWT framework documentation in that the App Controller manages / handles
* adding the widget to the container, and therefore all the Presenters must implement the
* "AppControlPresenter" or "AppControlContainerPresenter" interface to give it access to their containers.
* (thus eliminating "public void go(final HasWidgets container);" method in all presenter architecture except for 'MainAppPresenter')
* There is also no event bus; static method calls are used for any needed interactivity.
*
* Includes a popup for pages still under construction.
*/
package com.;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DecoratedPopupPanel;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.UIObject;
/**
*
*/
public class AppController {
/** */
public final static String DEFAULT_INITIAL_PAGE1_SUB_PAGE = Pages.PAGE_1A;
/** Singleton instance for the AppController */
private static AppController instance = new AppController();
/** Presenter for the main app */
private static MainAppPresenter mainAppPresenter;
/** container for the different views */
private static LayoutPanel container;
/** sub-container for the different sub-views */
private static LayoutPanel page1Container;
/** */
private static DecoratedPopupPanel popup;
/** constant for Style-Dependent names for menu items (see menu-style.css) */
public final static String MENU_ACTIVE_STYLE = "active";
/** constant for Style-Dependent class name in css */
public final static String CURSOR_WAIT_CLASS = "wait";
/** */
public final static String POPUP_DEMO_ID = "popupDemo";
/** */
public final static int DEMOP_POPUP_VERTICAL_OFFSET = 0;
/** */
public final static String POPUP_DEMO_STATEMENT = "<span class='text'>This page is under construction</span>"
+ "<span class='char'>…</span>";
/** */
public static ScheduledCommand waitCursor = new ScheduledCommand() {
#Override
public void execute() {
AppController.waitCursor(true);
}
};
/** */
public static ScheduledCommand normalCursor = new ScheduledCommand() {
#Override
public void execute() {
AppController.waitCursor(false);
}
};
/** Flag for determining if the page was reloaded */
private static boolean reloaded = false;
private static final LoginServiceAsync loginRpcService = GWT.create(LoginService.class);
/**
* Called on the resize event to set the position of the demo popup
* window to be adjusted to the correct dimensions (size and positoin)
* regardless of screen size.
*/
private static ScheduledCommand resetPopupDimensions = new ScheduledCommand() {
#Override
public void execute() {
if(!UNDER_CONSTRUCTION || popup == null) {
return;
}
int demoWidth = Math.round(Window.getClientWidth() / MainApp.PHI),
demoYPosition = Window.getClientHeight() / 2 - Math.round(popup.getOffsetHeight() / 2);
popup.setWidth(String.valueOf(demoWidth) + "px");
if(popup.getOffsetWidth() >= Window.getClientWidth()) {
popup.setWidth("100%");
popup.setPopupPosition(0, demoYPosition);
} else {
popup.setPopupPosition(Window.getClientWidth() / 2 - (popup.getOffsetWidth() / 2), demoYPosition);
}
}
};
/** */
private static final String LOGIN_OBJECT_NAME = "Login Presenter Object";
/**
* static inner-class for external websites
*/
public static class External {
/** The frame to contain the website */
private static Frame frame;
/** */
public static final String EXTERNAL_URL_1 = "http://";
/** */
public static final String EXTERNAL_URL_2 = "http://";
/**
* #returns true if the name of the token is equal to one of the URLs
* #param token the name to check
*/
public static boolean has(String token) {
return token.equalsIgnoreCase(EXTERNAL_URL_1) ||
token.equalsIgnoreCase(EXTERNAL_URL_2);
}
/**
* Gets the external Frame object
* #param url
* #return Frame
*/
public static Frame get(String url) {
if(frame == null) {
frame = new Frame(url);
frame.addAttachHandler(new AttachEvent.Handler() {
#Override
public void onAttachOrDetach(AttachEvent event) {
// hide the popup:
showPopup(false);
Scheduler.get().scheduleFinally(resetPopupDimensions);
Scheduler.get().scheduleFinally(normalCursor);
}
});
}
else if(!frame.getUrl().equalsIgnoreCase(url)) {
frame.setUrl(url);
}
return frame;
}
}
/**
* static inner-class for holding pages activated by the app's main menu commands
*/
public static class Pages {
/** */
public static final String PAGE1 = "foo";
/** */
public static final String PAGE2 = "bar";
/** */
public static final String PAGE_1A = "baz";
/** */
public static final String PAGE_1B = "qux";
/** */
public static String lastPage;
/** */
public static String lastPage1SubPage;
/** */
public static String unsavedMessage;
/** */
private static HashMap<String, AppControlPresenter> pageMap;
/** */
private static AppControlPresenter presenter;
/** */
private static Composite view;
/**
* initializes the hashmap of pages
*/
public static void init() {
pageMap = new HashMap<String, AppControlPresenter>();
}
/**
* #returns true if the name of the token is equal to one of the pages
* #param token the name to check
*/
public static boolean has(String token) {
return token.equalsIgnoreCase(PAGE1) ||
token.equalsIgnoreCase(PAGE2) ||
token.equalsIgnoreCase(PAGE_1A);
}
/**
* Gets the correct page container to display as a Composite
* #param page the token name of the page
* #return Composite page
*/
public static Composite get(String page) {
view = null;
presenter = null;
if(page.equalsIgnoreCase(PAGE1)) {
if(pageMap.get(PAGE1) == null) {
pageMap.put(PAGE1, new Page1Presenter(PAGE1));
page1Container = ((AppControlContainerPresenter) pageMap.get(PAGE1)).getContentPane();
}
presenter = pageMap.get(PAGE1);
lastPage = page;
mainAppPresenter.setCurrentMenuItem(page);
}
else if(page.equalsIgnoreCase(PAGE_1A) ||
page.equalsIgnoreCase(PAGE_1B) {
if(pageMap.get(PAGE1) == null) {
pageMap.put(PAGE1, new Page1Presenter(PAGE1));
page1Container = ((AppControlContainerPresenter) pageMap.get(PAGE1)).getContentPane();
}
presenter = pageMap.get(PAGE1);
lastPage1SubPage = page;
view = ((AppControlContainerPresenter)presenter).setCurrentPage(page);
}
else if(page.equalsIgnoreCase(PAGE2)) {
if(pageMap.get(PAGE2) == null) {
pageMap.put(PAGE2, new Page2Presenter(PAGE2));
}
presenter = pageMap.get(PAGE2);
lastPage = PAGE2;
mainAppPresenter.setCurrentMenuItem(page);
}
else if(External.has(page)) {
throw new Error("App Controller Error -- Use 'External' inner-class for: " + page);
}
else {
throw new Error("App Controller Error -- Page name not found: " + page);
}
if(view == null) {
view = (Composite)presenter.view();
}
view.addAttachHandler(new AttachEvent.Handler() {
#Override
public void onAttachOrDetach(AttachEvent event) {
AppController.showPopup(false);
presenter.updateAttachOrDetach(event);
Scheduler.get().scheduleFinally(resetPopupDimensions);
Scheduler.get().scheduleFinally(normalCursor);
}
});
return view;
}
/**
* Gets the current AppControlPresenter for the last page.
* #returns the current AppControlPresenter
*/
public static AppControlPresenter getCurrentPresenter() {
return presenter;
}
/**
* Gets an AppControlPresenter from the pageMap.
* #param token the name of the presenter
* #returns the AppControlPresenter
*/
public static AppControlPresenter getPresenter(String token) {
return pageMap.get(token);
}
/**
* Returns true if the page is already loaded.
* #param token name of the page
*/
public static boolean alreadyLoaded(String token) {
MainApp.debug(1, "[already loaded: " + presenter.toString() + " (token: " + token + ")");
return presenter.toString().equalsIgnoreCase(token);
}
/**
* Returns true if the page is visible
* #param page the token name of the page
*/
public static boolean isVisible(String page) {
UIObject component = pageMap.get(page).view();
return !(component.getOffsetHeight() == 0 && component.getOffsetWidth() == 0);
}
/**
* Returns true if the page is visible
* #param presenter the AppControlPresenter instance
*/
public static boolean isVisible(AppControlPresenter presenter) {
UIObject component = presenter.view();
return !(component.getOffsetHeight() == 0 && component.getOffsetWidth() == 0);
}
/**
* Returns true if the application has unsaved data.
* Iterates through all the pages and checks each presenter.
*/
public static boolean unsavedData() {
if(pageMap.isEmpty()) return false;
boolean unsaved = false;
for(Map.Entry<String, AppControlPresenter> entry : pageMap.entrySet()) {
AppControlPresenter presenter = entry.getValue();
if(presenter != null && presenter.unsavedData()) {
MainApp.debug(1, "(!) " + presenter.toString() + " has unsaved data");
unsavedMessage = presenter.dataDescription();
unsaved = true;
break; // just need to know one exists for now (window closing event)
}
}
return unsaved;
}
/**
* Called on a resize event on the window. Iterates through all the pages
* and tells their presenters to resize their content.
*/
public static void resize() {
for(Map.Entry<String, AppControlPresenter> entry : pageMap.entrySet()) {
AppControlPresenter presenter = entry.getValue();
if(presenter != null && isVisible(presenter)) {
presenter.resize();
}
}
}
} //end class Pages
/**
* #returns true if the history token is equal to any of the pages in the app
*/
public static boolean hasHistory() {
String token = History.getToken();
return External.has(token) || Pages.has(token);
}
/**
* Starts the login view at the root layout level
*/
public static void goLoginScreen() {
//check for reload:
if(hasHistory()) {
MainApp.debug(1, "(!) AppController has History on Login");
reloaded = true;
}
else {
reloaded = false;
}
RootLayoutPanel.get().clear();
RootLayoutPanel.get().add(new LoginPresenter(LOGIN_OBJECT_NAME).view());
}
/**
* #returns the last "Page1" page
*/
public static String getLastPage1Page() {
if(Pages.lastPage1SubPage == null || Pages.lastPage1SubPage.isEmpty()) {
Pages.lastPage1SubPage = DEFAULT_INITIAL_PAGE1_SUB_PAGE;
}
return Pages.lastPage1SubPage;
}
/**
* Tells the app to start with the Page1 page.
* #param username the username of the person logged-in
*/
public static void goMainApp(String username) {
//hide the login background:
RootPanel.getBodyElement().getStyle().setProperty("background", "none");
mainAppPresenter = new MainAppPresenter(username);
RootLayoutPanel.get().clear();
mainAppPresenter.go(RootLayoutPanel.get());
//get the center panel:
container = mainAppPresenter.getContainer();
//check for reload:
//NOTE: the token will be empty if the user refreshes
// and navigates all the way back to the zero-state
// from the login screen.
//NOTE: this logic may change after user-persistence is implemented
if(hasHistory() || History.getToken().isEmpty()) {
// reset the reloaded flag:
reloaded = false;
if(History.getToken().isEmpty()) {
//land on the first page:
History.newItem(AppController.Pages.PAGE1);
}
else {
MainApp.debug(2, "(!) AppController has History on reload: " + History.getToken());
History.fireCurrentHistoryState();
}
}
else {
//land on the first page:
History.newItem(AppController.Pages.PAGE1);
}
}
/**
*
*/
public static void checkIfSessionActive() {
loginRpcService.loginFromSession(new AsyncCallback<LoginSummary>() {
#Override
public void onFailure(Throwable throwable) {
goLoginScreen();
}
#Override
public void onSuccess(LoginSummary loginSummary) {
if (loginSummary.getErrorString() != null)
goLoginScreen();
else
goMainApp(loginSummary.getUser().getName());
}
});
}
/**
*
*/
public static void sessionLogout() {
DialogBoxWidget.confirm(200,
"Logout",
"Are you sure you want to log out?",
new ConfirmDialogCallback() {
#Override
public void onAffirmative() {
loginRpcService.logout(new AsyncCallback<Void>() {
#Override
public void onFailure(Throwable throwable) {
goLoginScreen();
}
#Override
public void onSuccess(Void aVoid) {
goLoginScreen();
}
});
}
#Override
public void onCancel() {
}
});
}
/**
* Shows or hides the "Under Construction" popup if UNDER_CONSTRUCION is true.
* #param show true to show and false to hide
*/
public static void showPopup(boolean show) {
if(MainApp.UNDER_CONSTRUCTION && popup != null) {
if(show) {
popup.show();
}
else {
popup.hide();
}
}
}
/**
* Called by every history event fired (including the back and forward buttons).
* Ignores the login and empty index historically.
* #param token the name of the page to load
*/
public static void go(String token) {
if(reloaded) {
normalCursor.execute();
}
if(token == null || token.isEmpty() || reloaded == true) return;
MainApp.debug("<history changed> - AppController.go()-> " + token);
// build the popup message for all unfinished pages:
if(MainApp.UNDER_CONSTRUCTION) {
if(popup == null) {
popup = new DecoratedPopupPanel(false);
popup.ensureDebugId(POPUP_DEMO_ID);
popup.addStyleDependentName(POPUP_DEMO_ID);
popup.setWidget(new HTML(new Image("images/workingman.png") + POPUP_DEMO_STATEMENT + new Image("images/workingmanFLIP.png")));
}
}
// check token for which page to return:
if(token.equalsIgnoreCase(External.EXTERNAL_URL_1)) {
MainAppPresenter.clearActiveMenuItems();
setExternalContentURL(External.get(token));
}
else if(token.equalsIgnoreCase(External.EXTERNAL_URL_2)) {
MainAppPresenter.clearActiveMenuItems();
setExternalContentURL(External.get(token));
}
else if(token.equalsIgnoreCase(Pages.PAGE1)) {
setContent(Pages.get(Pages.PAGE1));
setPage1Content(Pages.get(getLastPage1Page()));
}
else if(token.equalsIgnoreCase(Pages.PAGE_1A) ||
token.equalsIgnoreCase(Pages.PAGE_1B)) {
setContent(Pages.get(Pages.PAGE1));
setPage1Content(Pages.get(token));
}
else if(token.equalsIgnoreCase(Pages.PAGE2)) {
setContent(Pages.get(Pages.PAGE2));
}
else { // default behavior for a page not described:
MainApp.debug(2, "(!) Unknown page: " + token);
setContent(Pages.get(token));
}
}
/**
* Called by MainApp on a window resize event.
* #param e the ResizeEvent
*/
public static void resize(ResizeEvent e) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
if(mainAppPresenter != null) {
mainAppPresenter.resize();
}
Pages.resize();
Scheduler.get().scheduleFinally(resetPopupDimensions);
}
});
}
/**
* Changes the cursor to "wait" or "auto" depending on the parameter
* #param wait true to set the cursor to waiting
*/
private static void waitCursor(Boolean wait) {
if(wait) {
RootPanel.getBodyElement().addClassName(CURSOR_WAIT_CLASS);
}
else {
RootPanel.getBodyElement().removeClassName(CURSOR_WAIT_CLASS);
}
}
/**
* Private Constructor which initializes the Pages object.
*/
private AppController() {
Pages.init();
}
/**
* Sets the content of the main app container to one of the "Pages."
* #param c the Composite widget to be added
*/
private static void setContent(Composite c) {
container.clear();
container.add(c.asWidget());
}
/**
* Sets the content of the main app container an external URL.
* #param f the Frame by which external web sites are added
*/
private static void setExternalContentURL(Frame f) {
container.clear();
container.add(f);
// must reset the width and height every time:
f.getElement().getStyle().setWidth(100, Unit.PCT);
f.getElement().getStyle().setHeight(100, Unit.PCT);
}
/**
* Sets the content of the Page1 container to one of the sub pages.
* #param c the Composite widget to be added
*/
private static void setPage1Content(Composite c) {
page1Container.clear();
page1Container.add(c.asWidget());
}
}
AppControlPresenter.java:
package com.*;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.user.client.ui.Composite;
/**
* Base interface for all 'Presenters' used by AppController.java
* NOTE: classes that implement this interface do not launch the presenter's view
* into the provided container; rather, the view is retrieved and used by the
* AppController instance by calling the 'view()' method
*/
public interface AppControlPresenter {
/**
* Gets the view (for use in AppController.java)
*/
public Composite view();
/**
* Indicates if current search data is present and unsaved.
* #returns true to if a search is still active
*/
public boolean unsavedData();
/**
* Called on resize event to notify presenters with visible
* components that need resizing for different screen sizes.
* #returns true if elements were resized
*/
public boolean resize();
/**
* Called on attachEvents to tell the presenter to update.
* #param event the AttachEvent
*/
public void updateAttachOrDetach(AttachEvent event);
/**
* Gets the message to display for unsaved data.
* #returns a message String describing the data
*/
public String dataDescription();
/**
* Gets a fully qualified name for use in comparisons
* #return the name of this presenter used by the <code>AppController</code>
*/
public String toString();
}
AppControlContainerPresenter.java:
package com.*;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.LayoutPanel;
/**
*/
public interface AppControlContainerPresenter extends AppControlPresenter {
/**
*
* #return
*/
public LayoutPanel getContentPane();
/**
*
* #param pageName
* #return
*/
public Composite setCurrentPage(String pageName);
}
Add a module for each page you have that needs the GWT functionality. Reuse your components.

GWT Close button in title bar of DialogBox

Is there a non JSNI way to add a close button to the title bar area of a DialogBox?
We used GWT-ext from the begining in our project. It was a bad idea. They have lots of cool widgets, but they are not GWT widgets AND they have no compatibility with GWT widgets. Once you choose GWT-Ext, everything, even the event mechanism, must be in the GWT-Ext way, not in the GWT way. This library will not be updated for the newest version of GWT, because the javascript library Ext is no more free. We are removing GWT-Ext from our project now.
It´s not possible to add a different widget int the GWT DialogBox caption, but you can extend "DecoratedPanel" (it is the DialogBox parent). Look at the DialogBox source to learn the techniques, specially how it adds the Caption object to the panel and how the window drag is implemented.
That´s what we have done here, and it works very well. We´ve made our own Caption class that extends FocusablePanel (a SimplePanel that captures all mouse events) and we added a HorizontalPanel to it, with buttons and text. We had to override onAttach() and onDetach() just by calling the super method (they are protected).
I believe I am not allowed to put our source code in here, so I just can give you these tips.
You can do it by adding a button to the center panel of the DialogBox:
Image closeButton = new Image("");
closeButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
registerBox.hide();
}
});
closeButton.setStyleName("TopRight");
Then position it with CSS:
.TopRight {
float:right;
margin-top:-22px;
width:16px;
height:16px;
display:block;
background-image: url(images/cancel_16.png);
}
I created this caption class:
public class DialogBoxCaptionWithCancel extends Composite
implements Caption, HasClickHandlers {
#UiField
HTMLPanel mainPanel;
#UiField
HTML captionLabel;
#UiField
PushButton cancelButton;
private HandlerManager handlerManager = null;
private static final Binder binder = GWT.create(Binder.class);
interface Binder extends UiBinder<Widget, DialogBoxCaptionWithCancel> {
}
public DialogBoxCaptionWithCancel() {
initWidget(binder.createAndBindUi(this));
mainPanel.setStyleName("Caption");
Image upImage = new Image("images/closeWindow.png");
Image hoverImage = new Image("images/closeWindowFocus.png");
cancelButton.getUpFace().setImage(upImage);
cancelButton.getUpHoveringFace().setImage(hoverImage);
cancelButton.setStylePrimaryName("none");
}
/*
* (non-Javadoc)
*
* #see com.google.gwt.user.client.ui.Widget#onLoad()
*/
#Override
protected void onLoad() {
super.onLoad();
handlerManager = new HandlerManager(this);
}
#UiHandler("cancelButton")
public void cancelButtonOnClick(ClickEvent event) {
handlerManager.fireEvent(event);
}
#Override
public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
return handlerManager.addHandler(MouseDownEvent.getType(), handler);
}
#Override
public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
return handlerManager.addHandler(MouseUpEvent.getType(), handler);
}
#Override
public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
return handlerManager.addHandler(MouseOutEvent.getType(), handler);
}
#Override
public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
return handlerManager.addHandler(MouseOverEvent.getType(), handler);
}
#Override
public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
return handlerManager.addHandler(MouseMoveEvent.getType(), handler);
}
#Override
public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) {
return handlerManager.addHandler(MouseWheelEvent.getType(), handler);
}
#Override
public String getHTML() {
return "";
}
#Override
public void setHTML(String html) {
}
#Override
public String getText() {
return this.captionLabel.getText();
}
#Override
public void setText(String text) {
this.captionLabel.setText(text);
}
#Override
public void setHTML(SafeHtml html) {
}
#Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return handlerManager.addHandler(ClickEvent.getType(), handler);
}
}
The images are just captured from the behavior of IE8 when you mouse over the cancel button.
Here is the UiBinder code:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:style>
.htmlField {
width: 100%;
}
.pushButton {
border: none;
padding: 0px;
width: 49px;
height: 21px;
}
</ui:style>
<g:HTMLPanel ui:field="mainPanel">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td width="100%">
<g:HTML ui:field="captionLabel" addStyleNames="{style.htmlField}"></g:HTML>
</td>
<td>
<g:PushButton ui:field="cancelButton" addStyleNames="{style.pushButton}"></g:PushButton>
</td>
</tr>
</table>
</g:HTMLPanel>
</ui:UiBinder>
Then my class that extends DialogBox has the following:
public class MyDialogBox extends DialogBox implements ClickHandler {
...
// instantiate the caption with the cancel button
private static DialogBoxCaptionWithCancel caption = new DialogBoxCaptionWithCancel();
...
public MyDialogBox() {
// construct the dialog box with the custom caption
super(false, false, caption);
setWidget(binder.createAndBindUi(this));
// set the caption's text
caption.setText("My Caption");
}
....
protected void onLoad() {
super.onLoad();
// let us react to the captions cancel button
caption.addClickHandler(this);
}
...
#Override
public void onClick(ClickEvent event) {
// the caption's cancel button was clicked
this.hide();
}
A more simplier solution is to use gwt-ext (http://code.google.com/p/gwt-ext/). It is free and easy to use and integrate.
You can see their showcase http://www.gwt-ext.com/demo/.
I think that what you want is the MessageBox or Layout Window (they are on the Windows category of the showcase).
Regards.
You can try this out, slightly improved solution by fungus1487:
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.HasDirection;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.*;
/**
* #author Andrey Talnikov
*/
public class ClosablePopup extends DialogBox {
private Anchor closeAnchor;
/**
* Instantiates new closable popup.
*
* #param title the title
* #param defaultClose it {#code true}, hide popup on 'x' click
*/
public ClosablePopup(String title, boolean defaultClose) {
super(true);
closeAnchor = new Anchor("x");
FlexTable captionLayoutTable = new FlexTable();
captionLayoutTable.setWidth("100%");
captionLayoutTable.setText(0, 0, title);
captionLayoutTable.setWidget(0, 1, closeAnchor);
captionLayoutTable.getCellFormatter().setHorizontalAlignment(0, 1,
HasHorizontalAlignment.HorizontalAlignmentConstant.endOf(HasDirection.Direction.LTR));
HTML caption = (HTML) getCaption();
caption.getElement().appendChild(captionLayoutTable.getElement());
caption.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
EventTarget target = event.getNativeEvent().getEventTarget();
Element targetElement = (Element) target.cast();
if (targetElement == closeAnchor.getElement()) {
closeAnchor.fireEvent(event);
}
}
});
if (defaultClose) {
addCloseHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
hide();
}
});
}
}
public void addCloseHandler(ClickHandler handler) {
closeAnchor.addClickHandler(handler);
}
}
Yes there is
No there isn't - at least not without fiddling with GWT's DialogBox class itself or by recreating the DialogBox using common widgets. This is a known issue in GWT, aka issue 1405 (Star it to show your interest).
However; DialogBox doesn't give us the tools to do this so we need to extend it - Edit: this doesn't work.
If you want to make a drop-in replacement for DialogBox you can name your class DialogBox and import it instead of the one that's included in GWT. This thread on the GWT forum gives better details on how this can be done (outdated, uses listeners) Outdated, the internals of DialogBox have been changed a lot since this thread - it doesn't work.
Here's some code I hacked to get the same results (used the linked thread for guidance). This doesn't work:
MyDialogBox:
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
public class MyDialogBox extends DialogBox {
private class crossHandler implements ClickHandler, MouseOverHandler, MouseOutHandler
{
#Override
public void onClick(ClickEvent event) {
hide();
Window.alert("Click!");
}
#Override
public void onMouseOver(MouseOverEvent event) {
DOM.setStyleAttribute(cross.getElement(), "font-weight", "bold");
}
#Override
public void onMouseOut(MouseOutEvent event) {
DOM.setStyleAttribute(cross.getElement(), "font-weight", "normal");
}
}
Label cross = new Label("X"); // The close button
crossHandler crosshandler = new crossHandler();
HTML caption = new HTML(); // The caption aka title
HorizontalPanel captionPanel = new HorizontalPanel(); // Contains caption and cross
/**
* Creates an empty dialog box. It should not be shown until its child widget
* has been added using {#link #add(Widget)}.
*/
public MyDialogBox()
{
this(false);
}
/**
* Creates an empty dialog box specifying its "auto-hide" property. It should
* not be shown until its child widget has been added using
* {#link #add(Widget)}.
*
* #param autoHide <code>true</code> if the dialog should be automatically
* hidden when the user clicks outside of it
*/
public MyDialogBox(boolean autoHide) {
this(autoHide, true);
}
/**
* Creates an empty dialog box specifying its "auto-hide" property. It should
* not be shown until its child widget has been added using
* {#link #add(Widget)}.
*
* #param autoHide <code>true</code> if the dialog should be automatically
* hidden when the user clicks outside of it
* #param modal <code>true</code> if keyboard and mouse events for widgets not
* contained by the dialog should be ignored
*/
public MyDialogBox(boolean autoHide, boolean modal)
{
super(autoHide, modal);
cross.addClickHandler(crosshandler);
cross.addMouseOutHandler(crosshandler);
cross.addMouseOverHandler(crosshandler);
captionPanel.add(caption);
captionPanel.add(cross);
captionPanel.setStyleName("caption");
Element td = getCellElement(0, 1); // Get the cell element that holds the caption
td.setInnerHTML(""); // Remove the old caption
td.appendChild(captionPanel.getElement());
}
#Override
public void setText(String text)
{
caption.setText(text);
}
public String getText()
{
return caption.getText();
}
public void setHtml(String html)
{
caption.setHTML(html);
}
public String getHtml()
{
return caption.getHTML();
}
Note: This code doesn't work. The ClickEvent isn't sent from cross but instead from MyDialogBox regardless of whether you add ClickHandlers to the cross or not, IOW the MyDialogBox is the sender/source and therefor not possible to check against cross. When cross is clicked it doesn't fire the ClickEvent for some reasons.
Edit:
It appears this cannot be done without hacks unless you either write your own DialogBox (almost) from scratch or fix issue 1405. Of course there are number of existing libraries that have already solved this problem, i.e. SmartGWT and GWT-Ext, but their implementation is made mostly from scratch.
So to answer your question in one sentence: Yes there is a way, but you're not gonna like it :)
I guess a simple answer to this is to instantiate a widget to replace the standard Caption widget from DialogBox.
I created a caption that has a button at right and you can pick a reference to it.
Then you can add any click event you desire.
In GWT 2.4 I used the following solution:
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.DialogBox.Caption;
/**
* #author Cristiano Sumariva
*/
public class ButtonCaption extends HorizontalPanel implements Caption
{
protected InlineLabel text;
protected PushButton closeDialog;
/**
* #return the button at caption
*/
public PushButton getCloseButton()
{
return closeDialog;
}
public ButtonCaption( String label )
{
super();
setWidth( "100%" );
setStyleName( "Caption" ); // so you have same styling as standard caption widget
closeDialog = new PushButton();
add( text = new InlineLabel( label ) );
add( closeDialog );
setCellWidth( closeDialog, "1px" ); // to make button cell minimal enough to it
}
/* (non-Javadoc)
* #see com.google.gwt.event.dom.client.HasMouseDownHandlers#addMouseDownHandler(com.google.gwt.event.dom.client.MouseDownHandler)
*/
#Override
public HandlerRegistration addMouseDownHandler( MouseDownHandler handler )
{
return addMouseDownHandler( handler );
}
/* (non-Javadoc)
* #see com.google.gwt.event.dom.client.HasMouseUpHandlers#addMouseUpHandler(com.google.gwt.event.dom.client.MouseUpHandler)
*/
#Override
public HandlerRegistration addMouseUpHandler( MouseUpHandler handler )
{
return addMouseUpHandler( handler );
}
/* (non-Javadoc)
* #see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler)
*/
#Override
public HandlerRegistration addMouseOutHandler( MouseOutHandler handler )
{
return addMouseOutHandler( handler );
}
/* (non-Javadoc)
* #see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler)
*/
#Override
public HandlerRegistration addMouseOverHandler( MouseOverHandler handler )
{
return addMouseOverHandler( handler );
}
/* (non-Javadoc)
* #see com.google.gwt.event.dom.client.HasMouseMoveHandlers#addMouseMoveHandler(com.google.gwt.event.dom.client.MouseMoveHandler)
*/
#Override
public HandlerRegistration addMouseMoveHandler( MouseMoveHandler handler )
{
return addMouseMoveHandler( handler );
}
/* (non-Javadoc)
* #see com.google.gwt.event.dom.client.HasMouseWheelHandlers#addMouseWheelHandler(com.google.gwt.event.dom.client.MouseWheelHandler)
*/
#Override
public HandlerRegistration addMouseWheelHandler( MouseWheelHandler handler )
{
return addMouseWheelHandler( handler );
}
/* (non-Javadoc)
* #see com.google.gwt.user.client.ui.HasHTML#getHTML()
*/
#Override
public String getHTML()
{
return getElement().getInnerHTML();
}
/* (non-Javadoc)
* #see com.google.gwt.user.client.ui.HasHTML#setHTML(java.lang.String)
*/
#Override
public void setHTML( String html )
{
remove( text );
insert( text, 1 );
}
/* (non-Javadoc)
* #see com.google.gwt.user.client.ui.HasText#getText()
*/
#Override
public String getText()
{
return text.getText();
}
/* (non-Javadoc)
* #see com.google.gwt.user.client.ui.HasText#setText(java.lang.String)
*/
#Override
public void setText( String text )
{
this.text.setText( text );
}
/* (non-Javadoc)
* #see com.google.gwt.safehtml.client.HasSafeHtml#setHTML(com.google.gwt.safehtml.shared.SafeHtml)
*/
#Override
public void setHTML( SafeHtml html )
{
setHTML( html.asString() );
}
}
Extends the DialogBox to use the new ButtonCaption available
class CaptionCloseableDialogBox extends DialogBox
{
public CaptionCloseableDialogBox()
{
super( new ButtonCaption( "dialog box title" ) );
setAutoHideEnabled( false );
ButtonCaption ref = (ButtonCaption) this.getCaption();
PushButton closeButton = ref.getCloseButton();
// apply button face here closeButton;
closeButton.addClickHandler( /* attach any click handler here like close this dialog */ );
}
}
Hope it helps any.
Check out the active project:
http://code.google.com/p/gwt-mosaic/
Their noble goal is, as mentioned on their page:
The goal is to provide a complete widget set by keeping the API as close as possible to the GWT's standard widgets API.
Have been trapped in the GXT vortex. Not at all a fan of how they require users to use entirely different API for listeners, etc. On their part this makes sense. After all, GXT is just a port of their existing javascript libraries. But I've been looking for this MOSAIC project for too long...
Just using GWT and no external libraries you can intercept the click events on the caption element and perform a hit test to see if the x,y mouse coord is within the bounds of the anchor element (or other element your using as a ClickHandler).
// Create anchor we want to accept click events
final Anchor myAnchor = new Anchor("My Anchor");
// Add handler to anchor
myAnchor.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
Window.alert("Anchor was clicked");
}
});
// Create dialog
final DialogBox myDialog = new DialogBox();
myDialog.setText("My Dialog");
// Get caption element
final HTML caption = ((HTML)myDialog.getCaption());
// Add anchor to caption
caption.getElement().appendChild(myAnchor.getElement());
// Add click handler to caption
caption.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// Get x,y caption click relative to the anchor
final int x = event.getRelativeX(myAnchor.getElement());
final int y = event.getRelativeY(myAnchor.getElement());
// Check click was within bounds of anchor
if(x >= 0 && y >= 0 &&
x <= myAnchor.getOffsetWidth() &&
y <= myAnchor.getOffsetHeight()) {
// Raise event on anchor
myAnchor.fireEvent(event);
}
}
});
// Show the dialog
myDialog.show();
I realize this is ridiculously old, but you can just use absolute positioning with top and right of 0 to get a widget in the upper right. The dialog box is itself absolutely positioned, so the positioning of your widget will be against it.
This works if you just wan't a simple solution for the question asked:
Image button = new Image("images/cancel.png");
button.addClickHandler(new ClickHandler(){
public void onClick(ClickEvent event) {
hide();
}
});
button.setStyleName("dialog-close");
HorizontalPanel header = new HorizontalPanel();
header.add(new HTML("Example Tool"));
header.add(button);
setHTML(header.getElement().getInnerHTML());
You can find the closeable dialogbox in google code under the project synthfuljava.
It is actually called scrollable dialog box with a close X button at the caption.
The following blog explains the impediments that had to be overcome in order for thecaption X button to be able to listen to the click event to let it work:
http://h2g2java.blessedgeek.com/2009/07/gwt-useable-closeable-scrollable.html
I think the ButtonCaption of cavila is the best solution, but there is a bug in the implementation of the caption. The call of one of the overidden methods causes a infinitive loop because the method calls itself recursively.
To prevent this you you can call the method on the InlineLabel text instead:
#Override
public HandlerRegistration addMouseDownHandler( MouseDownHandler handler ) {
return text.addMouseDownHandler( handler );
}
The GWT dialog box's top level DIV has absolute positioning, so you can do the same with your close button. This allows you to put it in the body of the dialog as far as the DOM is concerned, but make it physically appear in the caption.
In my example below, I place it in the exact upper right of the dialog, and center it on the caption using padding.
<ui:style>
.close {
position: absolute;
top: 0;
right: 0;
padding: 3px 3px 1px 3px !important;
border-radius: 4px;
margin: 5px;
}
</ui:style>
<g:PushButton ui:field="closeButton" addStyleNames="{style.close}">
<g:upFace image='{closeIcon}'/>
<g:downFace image='{closeIcon}'/>
<g:upHoveringFace image='{closeIcon}'/>
<g:downHoveringFace image='{closeIcon}'/>
<g:upDisabledFace image='{closeIcon}'/>
<g:downDisabledFace image='{closeIcon}'/>
</g:PushButton>

Layout problems in FieldEditorPreferencePage

I have following problems with layout settings in the FieldEditorPreferencePage.
My code is something like this:
public void createFieldEditors () {
Group pv = new group(getfieldEditorParent(), SWT.SHADOW_OUT);
Group of = new group(getfieldEditorParent(), SWT.SHADOW_OUT);
pv.setText(“pv”);
of.setText(“of”);
GridLayout layout = new GridLayout(2,false);
pv.setLayout(layout);
of.setLayout(layout);
addField(new StringFieldEditor(“PreferenceStore name”,“Text:”, pv);
addField(new StringFieldEditor(“PreferenceStore name”,“Text:”, pv);
addField(new StringFieldEditor(“PreferenceStore name”,“Text:”, of);
addField(new StringFieldEditor(“PreferenceStore name”,“Text:”, of);
and so on.
}
The problem is that it does not work with GridLayout.
The StringFieldEditors are not parallel. The number of columns is always 1. Also when I try to change the size of StringFieldEditors in the groups, it doesn’t work too.
Anybody have any ideas?
Thanks.
The problem is that when you are using FieldEditorPreferencePage, you can use only FieldEditor subclasses as components. Here's a snippet from a documentation:
FieldEditorPreferencePage implements a
page that uses these field editors to
display and store the preference
values on the page. Instead of
creating SWT controls to fill its
contents, a FieldEditorPreferencePage
subclass creates field editors to
display the contents. All of the
fields on the page must be implemented
as field editors.
That means you have two options how to achieve what you want:
Implement your own subclass of FieldEditor, which would represent the Group widget.
Do not extend FieldEditorPreferencePage, but only a PreferencePage instead. Then you have to implement createContents method instead of createFieldEditors. You will also have to manage loading and saving of the properties.
I think the second way might be easier if you want to provide some complex layout. You may find some information more here
Another (easy) workaround:
You can also create new Composites to create more columns. The problem is that these FieldEditors communicate with their parent and mess up your layout. So, by creating an "empty" composite, they can communicate as much as they want :)
someGroup = new Group(..., SWT.NONE);
someGroup .setLayout(new GridLayout(16, false));
Composite myC1= new Composite(someGroup,SWT.NONE);
addField(new BooleanFieldEditor(...,C1);
Composite myC2= new Composite(someGroup,SWT.NONE);
addField(new BooleanFieldEditor(...,C2);
Two things to understand about FieldEditorPreferencePage (with GRID style):
The layout of field editor parents is always set to GridLayout even for "custom" components like Groups;
The number of columns in layout is adjusted according to the maximum number of components in any of the field editors (which is 2 in case of StringFieldEditor).
In the above example, the layout data of Groups should take this into account:
GridDataFactory.defaultsFor(pv).grab(true, false).span(2, 1).applyTo(pv);
GridDataFactory.defaultsFor(of).grab(true, false).span(2, 1).applyTo(of);
I implementer the Group-FieldEditor which can contain other FieldEditors and layout them as a Group.
import java.util.Collection;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
/**
* Class is intended to create a Group Widgets, inside of the {#link FieldEditorPreferencePage}
* objects.
* This class should be used as following:
*
* use the {#link #getFieldEditorParent()} to as a parent, while creating new Field Editors.
* use {#link #setFieldEditors(Collection)} to add the collection of FieldEditors to the
* {#link GroupFieldEditor}.
*
*
* #author alf
*
*/
public class GroupFieldEditor extends FieldEditor {
private String name;
private Collection members;
private int numcolumns;
private Group group;
private Composite parent;
/**
* The gap outside, between the group-frame and the widgets around the group
*/
private static final int GROUP_PADDING = 5; // px
/**
* The gap inside, between the group-frame and the content
*/
private static final int GROUP_VERTICAL_MARGIN = 5; // px
/**
* The inside-distance creates a new boolean field editor
*/
protected GroupFieldEditor() {
}
/**
* Creates a Group of {#link FieldEditor} objects
*
* #param name
* - name
* #param fieldEditorParent
* - parent
*/
public GroupFieldEditor(String name, Composite fieldEditorParent) {
this.name = name;
// the parent is a Composite, which is contained inside of the preference page. Initially it
// does not have any layout.
this.parent = fieldEditorParent;
FillLayout fillLayout = new FillLayout();
fillLayout.marginHeight = GROUP_VERTICAL_MARGIN;
this.parent.setLayout(fillLayout);
this.group = new Group(parent, SWT.DEFAULT);
this.group.setText(this.name);
}
/**
* The parent for all the FieldEditors inside of this Group.
*
* #return - the parent
*/
public Composite getFieldEditorParent() {
return group;
}
/**
* Sets the FieldeditorChildren for this {#link GroupFieldEditor}
*
* #param membersParam
*/
public void setFieldEditors(Collection membersParam) {
this.members = membersParam;
doFillIntoGrid(getFieldEditorParent(), numcolumns);
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
#Override
protected void adjustForNumColumns(int numColumns) {
this.numcolumns = numColumns;
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
#Override
protected void doFillIntoGrid(Composite parentParam, int numColumns) {
GridLayout gridLayout = new GridLayout();
gridLayout.marginLeft = GROUP_PADDING;
gridLayout.marginRight = GROUP_PADDING;
gridLayout.marginTop = GROUP_PADDING;
gridLayout.marginBottom = GROUP_PADDING;
this.group.setLayout(gridLayout);
this.parent.layout();
this.parent.redraw();
if (members != null) {
for (FieldEditor editor : members) {
editor.fillIntoGrid(getFieldEditorParent(), 1);
}
}
}
/*
* (non-Javadoc) Method declared on FieldEditor. Loads the value from the
* preference store and sets it to the check box.
*/
#Override
protected void doLoad() {
if (members != null) {
for (FieldEditor editor : members) {
editor.load();
}
}
}
/*
* (non-Javadoc) Method declared on FieldEditor. Loads the default value
* from the preference store and sets it to the check box.
*/
#Override
protected void doLoadDefault() {
if (members != null) {
for (FieldEditor editor : members) {
editor.loadDefault();
}
}
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
#Override
protected void doStore() {
if (members != null) {
for (FieldEditor editor : members) {
editor.store();
}
}
}
#Override
public void store() {
super.store();
doStore();
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
#Override
public int getNumberOfControls() {
return 1;
}
/*
* (non-Javadoc) Method declared on FieldEditor.
*/
#Override
public void setFocus() {
if (members != null && !members.isEmpty()) {
members.iterator().next().setFocus();
}
}
/*
* #see FieldEditor.setEnabled
*/
#Override
public void setEnabled(boolean enabled, Composite parentParam) {
if (members != null) {
for (FieldEditor editor : members) {
editor.setEnabled(enabled, parentParam);
}
}
}
#Override
public void setPreferenceStore(IPreferenceStore store) {
super.setPreferenceStore(store);
if (members != null) {
for (FieldEditor editor : members) {
editor.setPreferenceStore(store);
}
}
}
}
Yet another workaround:
Use Labels to separate groups of fields.
The following creates a vertical line separator and puts text directly beneath it:
new Label(getFieldEditorParent(), SWT.SEPARATOR | SWT.HORIZONTAL)
.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
new Label(getFieldEditorParent(), SWT.NONE).setText("My Group Title");