Display custom color dialog directly- JavaFX ColorPicker - javafx-8

I need to show a "continuous" color palette for color selection inside a ContextMenu. Similar to CustomColorDialog that pops up on ColorPicker.
Is there a different class for this purpose or is it possible to work around by extending ColorPicker and showing directly CustomColorDialog instead of first showing ColorPicker.
TIA

For starters, com.sun.javafx.scene.control.skin.CustomColorDialog is private API, and it's not advisable to use it, as it may change in the future without notice.
Besides, it is a Dialog, what means you can't embed it into a ContextMenu, it has its own window and it's modal.
This is a short example of using this (very big, not customizable) dialog in your application, without using a ColorPicker.
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("Open Custom Color Dialog");
btn.setOnAction(e -> {
CustomColorDialog dialog = new CustomColorDialog(primaryStage.getOwner());
dialog.show();
});
Scene scene = new Scene(new StackPane(btn), 300, 250);
primaryStage.setTitle("CustomColorDialog");
primaryStage.setScene(scene);
primaryStage.show();
}
You'll get the dialog, but you won't get any possibility to send a custom color or retrieve the selected color, since properties like customColorProperty() are only accesible within the com.sun.javafx.scene.control.skin package.
So we need another way to implement our custom color selector. If you have a look at the source code of CustomColorDialog you'll see that it's relatively a simple control, and most important, almost based on public API: panes, regions and color.
Trying to put all in a ContextMenu could be overkilling, so I've come up with this basic example, where I'll just use the left part of the dialog, displaying the central bar on top. Most of the code is from the class. The CSS styling was also taken from modena.css (under custom-color-dialog CSS selector), but was customized as some of the nodes were rotated 90º.
This is a short version of CustomColorDialog class:
public class MyCustomColorPicker extends VBox {
private final ObjectProperty<Color> currentColorProperty =
new SimpleObjectProperty<>(Color.WHITE);
private final ObjectProperty<Color> customColorProperty =
new SimpleObjectProperty<>(Color.TRANSPARENT);
private Pane colorRect;
private final Pane colorBar;
private final Pane colorRectOverlayOne;
private final Pane colorRectOverlayTwo;
private Region colorRectIndicator;
private final Region colorBarIndicator;
private Pane newColorRect;
private DoubleProperty hue = new SimpleDoubleProperty(-1);
private DoubleProperty sat = new SimpleDoubleProperty(-1);
private DoubleProperty bright = new SimpleDoubleProperty(-1);
private DoubleProperty alpha = new SimpleDoubleProperty(100) {
#Override protected void invalidated() {
setCustomColor(new Color(getCustomColor().getRed(), getCustomColor().getGreen(),
getCustomColor().getBlue(), clamp(alpha.get() / 100)));
}
};
public MyCustomColorPicker() {
getStyleClass().add("my-custom-color");
VBox box = new VBox();
box.getStyleClass().add("color-rect-pane");
customColorProperty().addListener((ov, t, t1) -> colorChanged());
colorRectIndicator = new Region();
colorRectIndicator.setId("color-rect-indicator");
colorRectIndicator.setManaged(false);
colorRectIndicator.setMouseTransparent(true);
colorRectIndicator.setCache(true);
final Pane colorRectOpacityContainer = new StackPane();
colorRect = new StackPane();
colorRect.getStyleClass().addAll("color-rect", "transparent-pattern");
Pane colorRectHue = new Pane();
colorRectHue.backgroundProperty().bind(new ObjectBinding<Background>() {
{
bind(hue);
}
#Override protected Background computeValue() {
return new Background(new BackgroundFill(
Color.hsb(hue.getValue(), 1.0, 1.0),
CornerRadii.EMPTY, Insets.EMPTY));
}
});
colorRectOverlayOne = new Pane();
colorRectOverlayOne.getStyleClass().add("color-rect");
colorRectOverlayOne.setBackground(new Background(new BackgroundFill(
new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(255, 255, 255, 1)),
new Stop(1, Color.rgb(255, 255, 255, 0))),
CornerRadii.EMPTY, Insets.EMPTY)));
EventHandler<MouseEvent> rectMouseHandler = event -> {
final double x = event.getX();
final double y = event.getY();
sat.set(clamp(x / colorRect.getWidth()) * 100);
bright.set(100 - (clamp(y / colorRect.getHeight()) * 100));
updateHSBColor();
};
colorRectOverlayTwo = new Pane();
colorRectOverlayTwo.getStyleClass().addAll("color-rect");
colorRectOverlayTwo.setBackground(new Background(new BackgroundFill(
new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(0, 0, 0, 0)), new Stop(1, Color.rgb(0, 0, 0, 1))),
CornerRadii.EMPTY, Insets.EMPTY)));
colorRectOverlayTwo.setOnMouseDragged(rectMouseHandler);
colorRectOverlayTwo.setOnMousePressed(rectMouseHandler);
Pane colorRectBlackBorder = new Pane();
colorRectBlackBorder.setMouseTransparent(true);
colorRectBlackBorder.getStyleClass().addAll("color-rect", "color-rect-border");
colorBar = new Pane();
colorBar.getStyleClass().add("color-bar");
colorBar.setBackground(new Background(new BackgroundFill(createHueGradient(),
CornerRadii.EMPTY, Insets.EMPTY)));
colorBarIndicator = new Region();
colorBarIndicator.setId("color-bar-indicator");
colorBarIndicator.setMouseTransparent(true);
colorBarIndicator.setCache(true);
colorRectIndicator.layoutXProperty().bind(
sat.divide(100).multiply(colorRect.widthProperty()));
colorRectIndicator.layoutYProperty().bind(
Bindings.subtract(1, bright.divide(100)).multiply(colorRect.heightProperty()));
colorBarIndicator.layoutXProperty().bind(
hue.divide(360).multiply(colorBar.widthProperty()));
colorRectOpacityContainer.opacityProperty().bind(alpha.divide(100));
EventHandler<MouseEvent> barMouseHandler = event -> {
final double x = event.getX();
hue.set(clamp(x / colorRect.getWidth()) * 360);
updateHSBColor();
};
colorBar.setOnMouseDragged(barMouseHandler);
colorBar.setOnMousePressed(barMouseHandler);
newColorRect = new Pane();
newColorRect.getStyleClass().add("color-new-rect");
newColorRect.setId("new-color");
newColorRect.backgroundProperty().bind(new ObjectBinding<Background>() {
{
bind(customColorProperty);
}
#Override protected Background computeValue() {
return new Background(new BackgroundFill(customColorProperty.get(), CornerRadii.EMPTY, Insets.EMPTY));
}
});
colorBar.getChildren().setAll(colorBarIndicator);
colorRectOpacityContainer.getChildren().setAll(colorRectHue, colorRectOverlayOne, colorRectOverlayTwo);
colorRect.getChildren().setAll(colorRectOpacityContainer, colorRectBlackBorder, colorRectIndicator);
VBox.setVgrow(colorRect, Priority.SOMETIMES);
box.getChildren().addAll(colorBar, colorRect, newColorRect);
getChildren().add(box);
if (currentColorProperty.get() == null) {
currentColorProperty.set(Color.TRANSPARENT);
}
updateValues();
}
private void updateValues() {
hue.set(getCurrentColor().getHue());
sat.set(getCurrentColor().getSaturation()*100);
bright.set(getCurrentColor().getBrightness()*100);
alpha.set(getCurrentColor().getOpacity()*100);
setCustomColor(Color.hsb(hue.get(), clamp(sat.get() / 100),
clamp(bright.get() / 100), clamp(alpha.get()/100)));
}
private void colorChanged() {
hue.set(getCustomColor().getHue());
sat.set(getCustomColor().getSaturation() * 100);
bright.set(getCustomColor().getBrightness() * 100);
}
private void updateHSBColor() {
Color newColor = Color.hsb(hue.get(), clamp(sat.get() / 100),
clamp(bright.get() / 100), clamp(alpha.get() / 100));
setCustomColor(newColor);
}
#Override
protected void layoutChildren() {
super.layoutChildren();
colorRectIndicator.autosize();
}
static double clamp(double value) {
return value < 0 ? 0 : value > 1 ? 1 : value;
}
private static LinearGradient createHueGradient() {
double offset;
Stop[] stops = new Stop[255];
for (int x = 0; x < 255; x++) {
offset = (double)((1.0 / 255) * x);
int h = (int)((x / 255.0) * 360);
stops[x] = new Stop(offset, Color.hsb(h, 1.0, 1.0));
}
return new LinearGradient(0f, 0f, 1f, 0f, true, CycleMethod.NO_CYCLE, stops);
}
public void setCurrentColor(Color currentColor) {
this.currentColorProperty.set(currentColor);
updateValues();
}
Color getCurrentColor() {
return currentColorProperty.get();
}
final ObjectProperty<Color> customColorProperty() {
return customColorProperty;
}
void setCustomColor(Color color) {
customColorProperty.set(color);
}
Color getCustomColor() {
return customColorProperty.get();
}
}
This is the color.css file:
.context-menu{
-fx-background-color: derive(#ececec,26.4%);
}
.menu-item:focused {
-fx-background-color: transparent;
}
/* CUSTOM COLOR */
.my-custom-color {
-fx-background-color: derive(#ececec,26.4%);
-fx-padding: 1.25em;
-fx-spacing: 1.25em;
-fx-min-width: 20em;
-fx-pref-width: 20em;
-fx-max-width: 20em;
}
.my-custom-color:focused,
.my-custom-color:selected {
-fx-background-color: transparent;
}
.my-custom-color > .color-rect-pane {
-fx-spacing: 0.75em;
-fx-pref-height: 16.666667em;
-fx-alignment: top-left;
-fx-fill-height: true;
}
.my-custom-color .color-rect-pane .color-rect {
-fx-min-width: 16.666667em;
-fx-min-height: 16.666667em;
}
.my-custom-color .color-rect-pane .color-rect-border {
-fx-border-color: derive(#ececec, -20%);
}
.my-custom-color > .color-rect-pane #color-rect-indicator {
-fx-background-color: null;
-fx-border-color: white;
-fx-border-radius: 0.4166667em;
-fx-translate-x: -0.4166667em;
-fx-translate-y: -0.4166667em;
-fx-pref-width: 0.833333em;
-fx-pref-height: 0.833333em;
-fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
}
.my-custom-color > .color-rect-pane > .color-bar {
-fx-min-height: 1.666667em;
-fx-min-width: 16.666667em;
-fx-max-height: 1.666667em;
-fx-border-color: derive(#ececec, -20%);
}
.my-custom-color > .color-rect-pane > .color-bar > #color-bar-indicator {
-fx-border-radius: 0.333333em;
-fx-border-color: white;
-fx-effect: dropshadow(three-pass-box, black, 2, 0.0, 0, 1);
-fx-pref-height: 2em;
-fx-pref-width: 0.833333em;
-fx-translate-y: -0.1666667em;
-fx-translate-x: -0.4166667em;
}
.my-custom-color .transparent-pattern {
-fx-background-image: url("pattern-transparent.png");
-fx-background-repeat: repeat;
-fx-background-size: auto;
}
.my-custom-color .color-new-rect {
-fx-min-width: 10.666667em;
-fx-min-height: 1.75em;
-fx-pref-width: 10.666667em;
-fx-pref-height: 1.75em;
-fx-border-color: derive(#ececec, -20%);
}
The image can be found here.
And finally, our application class.
public class CustomColorContextMenu extends Application {
private final ObjectProperty<Color> sceneColorProperty =
new SimpleObjectProperty<>(Color.WHITE);
#Override
public void start(Stage primaryStage) {
Rectangle rect = new Rectangle(400,400);
rect.fillProperty().bind(sceneColorProperty);
Scene scene = new Scene(new StackPane(rect), 400, 400);
scene.getStylesheets().add(getClass().getResource("color.css").toExternalForm());
scene.setOnMouseClicked(e->{
if(e.getButton().equals(MouseButton.SECONDARY)){
MyCustomColorPicker myCustomColorPicker = new MyCustomColorPicker();
myCustomColorPicker.setCurrentColor(sceneColorProperty.get());
CustomMenuItem itemColor = new CustomMenuItem(myCustomColorPicker);
itemColor.setHideOnClick(false);
sceneColorProperty.bind(myCustomColorPicker.customColorProperty());
ContextMenu contextMenu = new ContextMenu(itemColor);
contextMenu.setOnHiding(t->sceneColorProperty.unbind());
contextMenu.show(scene.getWindow(),e.getScreenX(),e.getScreenY());
}
});
primaryStage.setTitle("Custom Color Selector");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Note the use of CustomMenuItem to allow clicking on the color selectors without closing the context menu. To close it just click anywhere outside the popup window.
This is how it looks like:
Based on this custom dialog, you can improve it and add the functionality you may need.

Here is how I use com.sun.javafx.scene.control.skin.CustomColorDialog:
public Color showColorDialog(String title, Color initialColor) {
CountDownLatch countDownLatch = new CountDownLatch(1);
ObjectHolder<Color> selectedColorHolder = new ObjectHolder<>();
Platform.runLater(new Runnable() {
#Override
public void run() {
try {
final CustomColorDialog customColorDialog = new CustomColorDialog(getWindow());
customColorDialog.setCurrentColor(initialColor);
// remove save button
VBox controllBox = (VBox) customColorDialog.getChildren().get(1);
HBox buttonBox = (HBox) controllBox.getChildren().get(2);
buttonBox.getChildren().remove(0);
Runnable saveUseRunnable = new Runnable() {
#Override
public void run() {
try {
Field customColorPropertyField = CustomColorDialog.class
.getDeclaredField("customColorProperty"); //$NON-NLS-1$
customColorPropertyField.setAccessible(true);
#SuppressWarnings("unchecked")
ObjectProperty<Color> customColorPropertyValue = (ObjectProperty<Color>) customColorPropertyField
.get(customColorDialog);
selectedColorHolder.setObject(customColorPropertyValue.getValue());
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
LOG.error(e, e);
}
}
};
customColorDialog.setOnUse(saveUseRunnable);
customColorDialog.setOnHidden(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
countDownLatch.countDown();
}
});
Field dialogField = CustomColorDialog.class.getDeclaredField("dialog"); //$NON-NLS-1$
dialogField.setAccessible(true);
Stage dialog = (Stage) dialogField.get(customColorDialog);
dialog.setTitle(title);
customColorDialog.show();
dialog.centerOnScreen();
} catch (Exception e) {
LOG.error(e, e);
countDownLatch.countDown();
}
}
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
LOG.error(e, e);
}
return selectedColorHolder.getObject();
}

The showColorDialog() method of #Bosko Popovic didn't work for me. When I called it from the JavaFX thread (for example, on response to a button-click) it blocks and freezes the application. I still think there is merit in his approach, so here is a slightly modified version:
public static Optional<Color> showColorDialog(Window owner, String title, Optional<Color> initialColor) {
AtomicReference<Color> selectedColor = new AtomicReference<>();
// Create custom-color-dialog.
CustomColorDialog customColorDialog = new CustomColorDialog(owner);
Stage dialog = customColorDialog.getDialog();
// Initialize current-color-property with supplied initial color.
initialColor.ifPresent(customColorDialog::setCurrentColor);
// Hide the Use-button.
customColorDialog.setShowUseBtn(false);
// Change the Save-button text to 'OK'.
customColorDialog.setSaveBtnToOk();
// When clicking save, we store the selected color.
customColorDialog.setOnSave(() -> selectedColor.set(customColorDialog.getCustomColor()));
// Exit the nested-event-loop when the dialog is hidden.
customColorDialog.setOnHidden(event -> {
Toolkit.getToolkit().exitNestedEventLoop(dialog, null);
});
// Show the dialog.
dialog.setTitle(title);
// Call the custom-color-dialog's show() method so that the color-pane
// is initialized with the correct color.
customColorDialog.show();
// Need to request focus as dialog can be stuck behind popup-menus.
dialog.requestFocus();
// Center the dialog or else it will show up to the right-hand side
// of the screen.
dialog.centerOnScreen();
// Enter nested-event-loop to simulate a showAndWait(). This will
// basically cause the dialog to block input from the rest of the
// window until the dialog is closed.
Toolkit.getToolkit().enterNestedEventLoop(dialog);
return Optional.ofNullable(selectedColor.get());
}
The dialog field no longer has to be retrieved via reflection. You can get it directly by calling customColorDialog.getDialog(). You also don't need to get the color from the customColorProperty field via reflection as you can directly get it by calling customColorDialog.getCustomColor(). The nested-event-loop is needed to simulate a showAndWait() call to prevent input to the background Window when the dialog is shown.
You can store this method in a utility class, and when the day comes where the API is deprecated (or changed) as #José Pereda mentions, you can then implement a custom color dialog by making use of his example code.

Related

How to change or override the tooltip in JavaFX ColorPicker

I am using JavaFX ColorPicker in my application. As per my requirements, I have mapped the default colors on the color picker to a number. I want this number to be displayed as tooltip on hover over the color instead of hex value of the color. How can I achieve this?
//Part of Code
public void handleNodes(Circle circularNode) {
final Delta offset = new Delta();
circularNode.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
((Circle)(event.getSource())).setCursor(Cursor.HAND);
}
});
circularNode.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton().equals(MouseButton.SECONDARY)) {
System.out.println("Right click");
Circle parent = ((Circle)(event.getSource()));
final ContextMenu contextMenu = new ContextMenu();
MenuItem editLabel = new MenuItem("Edit Label");
editLabel.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Edit Label");
final ColorPicker colorPicker = new ColorPicker();
colorPicker.setStyle("-fx-border-radius: 10 10 10 10;"
+ "-fx-background-radius: 10 10 10 10;");
colorPicker.setValue((Color) parent.getFill());
colorPicker.showingProperty().addListener((obs,b,b1)->{
if(b1){
PopupWindow popupWindow = getPopupWindow();
javafx.scene.Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(), new Tooltip("Custom tip for "+c.toString()));
});
}
});
panelMain.getChildren().add(colorPicker);
}
});
This is really a hacky answer.
The first problem: you have to find the popup node on the scene once it shows up. But you won't... since its not in the same window!
Having a deep look at how ScenicView does it, the trick is getting the list of windows at that moment, but using a deprectated method:
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation") final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
return (PopupWindow)window;
}
}
return null;
}
Once you have the popup window, we can now check for all the Rectangle nodes using lookupAll and the CSS selector color-rect, to get their color, and install the tooltip over its parent container:
#Override
public void start(Stage primaryStage) {
ColorPicker picker = new ColorPicker();
StackPane root = new StackPane(picker);
Scene scene = new Scene(root, 500, 400);
primaryStage.setScene(scene);
primaryStage.show();
picker.showingProperty().addListener((obs,b,b1)->{
if(b1){
PopupWindow popupWindow = getPopupWindow();
Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(), new Tooltip("Custom tip for "+c.toString()));
});
}
});
}
This is what it looks like:
Based on the code posted by the OP after my first answer, and due to the substancial changes in the problem addressed, I'm adding a new answer that covers both situations:
The ColorPicker is embedded in the main scene, as a regular node
The ColorPicker is embedded in a ContextMenu
In the second situation, the proposed solution for the first one is no longer valid, since the window found will be the one with the context menu.
A task is required to keep looking for windows until the one with the ComboBoxPopupControl is found.
This is a full runnable example:
public class ColorPickerFinder extends Application {
ExecutorService findWindowExecutor = createExecutor("FindWindow");
#Override
public void start(Stage primaryStage) {
AnchorPane panCircles = new AnchorPane();
Scene scene = new Scene(panCircles, 400, 400);
final Random random = new Random();
IntStream.range(0,5).boxed().forEach(i->{
final Circle circle= new Circle(20+random.nextInt(80),
Color.rgb(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
circle.setTranslateX(100+random.nextInt(200));
circle.setTranslateY(100+random.nextInt(200));
panCircles.getChildren().add(circle);
});
panCircles.setPrefSize(400, 400);
ColorPicker colorPicker = new ColorPicker();
panCircles.getChildren().add(colorPicker);
primaryStage.setScene(scene);
primaryStage.show();
// We add listeners AFTER showing the stage, as we are looking for nodes
// by css selectors, these will be available only after the stage is shown
colorPicker.showingProperty().addListener((obs,b,b1)->{
if(b1){
// No need for task in this case
getPopupWindow();
}
});
panCircles.getChildren().stream()
.filter(c->c instanceof Circle)
.map(c->(Circle)c)
.forEach(circle->{
circle.setOnMouseClicked(e->{
if(e.getButton().equals(MouseButton.SECONDARY)){
// We need a task, since the first window found is the ContextMenu one
findWindowExecutor.execute(new WindowTask());
final ColorPicker picker = new ColorPicker();
picker.setStyle("-fx-border-radius: 10 10 10 10;"
+ "-fx-background-radius: 10 10 10 10;");
picker.setValue((Color)(circle.getFill()));
picker.valueProperty().addListener((obs,c0,c1)->circle.setFill(c1));
final ContextMenu contextMenu = new ContextMenu();
MenuItem editLabel = new MenuItem();
contextMenu.getItems().add(editLabel);
editLabel.setGraphic(picker);
contextMenu.show(panCircles,e.getScreenX(),e.getScreenY());
}
});
});
}
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
if(window.getScene()!=null && window.getScene().getRoot()!=null){
Parent root = window.getScene().getRoot();
if(root.getChildrenUnmodifiable().size()>0){
Node popup = root.getChildrenUnmodifiable().get(0);
if(popup.lookup(".combo-box-popup")!=null){
// only process ComboBoxPopupControl
Platform.runLater(()->{
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(),
new Tooltip("Custom tip for "+c.toString()));
});
});
return (PopupWindow)window;
}
}
}
return null;
}
}
return null;
}
private class WindowTask extends Task<Void> {
#Override
protected Void call() throws Exception {
boolean found=false;
while(!found){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
found=(getPopupWindow()!=null);
}
return null;
}
}
private ExecutorService createExecutor(final String name) {
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName(name);
t.setDaemon(true);
return t;
};
return Executors.newSingleThreadExecutor(factory);
}
public static void main(String[] args) {
launch(args);
}
}
This will be the result after right clicking on a circle, and clicking on the color picker:

JFace SourceViewer - AnnotationPainter for widgets

I want to add some SWT controls into SourceViewer.
Currently i am using AnnotainerPainter. It looks fine, except one thing and that is i don't know how to dispose the controls when the annotation is removed, because the AnnotationerPainter doesn't provide any info to
ITextStyleStrategy instance about what annotation it is processing (in that case i could just add the SWT control to it and later remove it when the annotation is going to be removed from AnnotationModel).
painter.addTextStyleStrategy("CONTROL", new AnnotationPainter.ITextStyleStrategy() {
private SourceViewer fViewer = ClassSourceViewer.this;
#Override
public void applyTextStyle(StyleRange styleRange, Color annotationColor) {
// TODO Auto-generated method stub
System.out.println(styleRange.start);
System.out.println(styleRange.length);
final Button button = new Button(fViewer.getTextWidget(), SWT.PUSH);
button.setFont(new Font(Display.getCurrent(), "Tahoma", 7, SWT.NORMAL));
button.setText("jmp to 24");
button.setBackground(fViewer.getTextWidget().getBackground());
button.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
button.dispose();
((AnnotationModel) fViewer.getAnnotationModel()).removeAllAnnotations();
}
});
button.pack();
button.setCursor(new Cursor(Display.getCurrent(), SWT.CURSOR_HAND));
Rectangle rect = button.getBounds();
int ascent = 2*rect.height/3;
int descent = rect.height - ascent;
button.setLocation(fViewer.getTextWidget().getLocationAtOffset(styleRange.start));
styleRange.metrics = new GlyphMetrics(ascent, descent,rect.width);
//styleRange.foreground = new Color(Display.getCurrent(), 0, 0, 0);
//styleRange.
//styleRange.background = new Color(Display.getCurrent(), 255, 255, 0);
}
});

Netbeans ExplorerTopComponent Node losing focus when showing dialog

I have a netbeans RCP application that shows a set of nodes in the explorertopcomponent. When selected, I display details on the editortopcomponent and it works well. When I show a dialog using JOptionPage on the editor, the selected node in the tree is deselected, and eventually my editortopcomponent also loses the selected node details. Is there a way to save the selected node in the tree from being deselected if a dialog opens?
Thanks.
it is simple.
In your explorertopcomponent you have LookupListner, that "is waiting" on event "someYourNodeClass" (for example Album)appears in the lookup. You must removeLookupListener, when your explorertopcomponent is not visible or yust do nothing.
/**
* your explorertopcomponent
*/
#ConvertAsProperties(
dtd = "-//com.galileo.netbeans.module//Y//EN",
autostore = false)
#TopComponent.Description(
preferredID = "YTopComponent",
//iconBase="SET/PATH/TO/ICON/HERE",
persistenceType = TopComponent.PERSISTENCE_ALWAYS)
#TopComponent.Registration(mode = "explorer", openAtStartup = false)
#ActionID(category = "Window", id = "com.galileo.netbeans.module.YTopComponent")
#ActionReference(path = "Menu/Window" /*, position = 333 */)
#TopComponent.OpenActionRegistration(
displayName = "#CTL_YAction",
preferredID = "YTopComponent")
#Messages({
"CTL_YAction=Y",
"CTL_YTopComponent=Y Window",
"HINT_YTopComponent=This is a Y window"
})
public final class YTopComponent extends TopComponent implements LookupListener {
private Lookup.Result<Album> result;
public YTopComponent() {
initComponents();
setName(Bundle.CTL_YTopComponent());
setToolTipText(Bundle.HINT_YTopComponent());
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
}// </editor-fold>
// Variables declaration - do not modify
// End of variables declaration
#Override
public void componentOpened() {
result = Utilities.actionsGlobalContext().lookupResult(Album.class);
result.addLookupListener(this);
}
#Override
public void componentClosed() {
result.removeLookupListener(this);
}
void writeProperties(java.util.Properties p) {
// better to version settings since initial version as advocated at
// http://wiki.apidesign.org/wiki/PropertyFiles
p.setProperty("version", "1.0");
// TODO store your settings
}
void readProperties(java.util.Properties p) {
String version = p.getProperty("version");
// TODO read your settings according to their version
}
public void resultChanged(LookupEvent le) {
Collection<? extends Album> allInstances = result.allInstances();
TopComponent findTopComponent = WindowManager.getDefault().findTopComponent("YourNodeExplorerWindow");
if (findTopComponent == null) {
return;
}
if (!findTopComponent.isShowing()) {
return;
}
if (!allInstances.isEmpty()) {
showDetail(allInstances.iterator().next());
}
}
}
Jirka

GWT GUI Design - Layout Panels

i have planned to design my application with following areas:
Toolbar Area (fix size)
Workspace Header (for some information or filter criteria's, no fix size)
Workspace Area (should use all available screen space)
Statusbar Area (fix size)
Please see also the attached screenshot.
To use a datagrid which automatically resizes with the screensize, I have read that the best way
is to use the DockLayoutPanel.
I have also read following article:
https://developers.google.com/web-toolkit/doc/latest/DevGuideUiPanels
In this article it's also recommended to use Layout Panels(not only LayoutPanel.class but all Layout Panels) for better standard mode support.
Therefore I have build a very easy example to test the Layout Panels.
Here the simple screenshot example how I tried to design the application:
--- css file --
.test {
margin: 4px;
padding: 20px;
background-color: Lime;
}
package com.test.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.cellview.client.SimplePager;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.view.client.ListDataProvider;
public class GwtTestDockFilled implements EntryPoint {
private SimpleLayoutPanel slpToolbarPlaceholder;
private SimpleLayoutPanel slpWorkspacePlaceholder;
private SimpleLayoutPanel slpWorkspaceHeaderPlaceholder;
private SimpleLayoutPanel slpStatusbarPlaceholder;
private LayoutPanel layoutPanel;
private FlowPanel flowPanel;
private Button btnBack;
private Button btnNew;
private Label lblName;
private TextBox txtName;
private DisclosurePanel disclosurePanel;
private LayoutPanel layoutPanel_1;
private Label lblStreet;
private TextBox txtStreet;
private DataGrid<Person> dataGrid;
private TextColumn<Person> colName;
private TextColumn<Person> colStreet;
private DockLayoutPanel dockLayoutPanel_1;
private LayoutPanel layoutPanel_2;
private SimplePager simplePager;
private ListDataProvider<Person> dataProvider = new ListDataProvider<GwtTestDockFilled.Person>();
private static final String[] NAMES = {
"Mary", "Patricia", "Linda", "Barbara", "Elizabeth", "Jennifer", "Maria",
"Susan", "Margaret", "Dorothy", "Lisa", "Nancy", "Karen", "Betty",
"Helen", "Sandra", "Donna", "Carol", "Ruth", "Sharon", "Michelle",
"Laura", "Sarah", "Kimberly", "Deborah", "Jessica", "Shirley", "Cynthia",
"Angela", "Melissa", "Brenda", "Amy", "Anna", "Rebecca", "Virginia",
"Kathleen", "Pamela", "Martha", "Debra", "Amanda", "Stephanie", "Carolyn",
"Christine", "Marie", "Janet", "Catherine", "Frances", "Ann", "Joyce",
"Diane", "Alice", "Julie", "Heather", "Teresa", "Doris", "Gloria",
"Evelyn", "Jean", "Cheryl", "Mildred", "Katherine", "Joan", "Ashley",
"Judith", "Rose", "Janice", "Kelly", "Nicole", "Judy", "Christina",
"Kathy", "Theresa", "Beverly", "Denise", "Tammy", "Irene", "Jane", "Lori"};
/**
* This is the entry point method.
*/
public void onModuleLoad() {
RootLayoutPanel rootLayoutPanel = RootLayoutPanel.get();
DockLayoutPanel dockLayoutPanel = new DockLayoutPanel(Unit.PX);
rootLayoutPanel.add(dockLayoutPanel);
dockLayoutPanel.addNorth(getSlpToolbarPlaceholder(), 60.0);
dockLayoutPanel.addNorth(getSimpleLayoutPanel_2(), 60.0);
dockLayoutPanel.addSouth(getSimpleLayoutPanel_3(), 60.0);
dockLayoutPanel.add(getSlpWorkspacePlaceholder());
fillDataGrid();
}
private void fillDataGrid(){
for(int i = 0; i < NAMES.length; i++){
Person p = new Person();
p.setName(NAMES[i]);
p.setStreet("Spring Road");
dataProvider.getList().add(p);
}
}
private SimpleLayoutPanel getSlpToolbarPlaceholder() {
if (slpToolbarPlaceholder == null) {
slpToolbarPlaceholder = new SimpleLayoutPanel();
slpToolbarPlaceholder.setWidget(getFlowPanel());
}
return slpToolbarPlaceholder;
}
private SimpleLayoutPanel getSlpWorkspacePlaceholder() {
if (slpWorkspacePlaceholder == null) {
slpWorkspacePlaceholder = new SimpleLayoutPanel();
slpWorkspacePlaceholder.setWidget(getDockLayoutPanel_1());
}
return slpWorkspacePlaceholder;
}
private SimpleLayoutPanel getSimpleLayoutPanel_2() {
if (slpWorkspaceHeaderPlaceholder == null) {
slpWorkspaceHeaderPlaceholder = new SimpleLayoutPanel();
slpWorkspaceHeaderPlaceholder.setStyleName("test");
slpWorkspaceHeaderPlaceholder.setWidget(getLayoutPanel());
}
return slpWorkspaceHeaderPlaceholder;
}
private SimpleLayoutPanel getSimpleLayoutPanel_3() {
if (slpStatusbarPlaceholder == null) {
slpStatusbarPlaceholder = new SimpleLayoutPanel();
}
return slpStatusbarPlaceholder;
}
private LayoutPanel getLayoutPanel() {
if (layoutPanel == null) {
layoutPanel = new LayoutPanel();
layoutPanel.add(getLblName());
layoutPanel.setWidgetLeftWidth(getLblName(), 0.0, Unit.PX, 56.0, Unit.PX);
layoutPanel.setWidgetTopHeight(getLblName(), 0.0, Unit.PX, 16.0, Unit.PX);
layoutPanel.add(getTxtName());
layoutPanel.setWidgetLeftWidth(getTxtName(), 62.0, Unit.PX, 165.0, Unit.PX);
layoutPanel.setWidgetTopHeight(getTxtName(), 0.0, Unit.PX, 25.0, Unit.PX);
layoutPanel.add(getDisclosurePanel());
layoutPanel.setWidgetLeftWidth(getDisclosurePanel(), 0.0, Unit.PX, 250.0, Unit.PX);
layoutPanel.setWidgetTopHeight(getDisclosurePanel(), 31.0, Unit.PX, 200.0, Unit.PX);
}
return layoutPanel;
}
private FlowPanel getFlowPanel() {
if (flowPanel == null) {
flowPanel = new FlowPanel();
flowPanel.add(getBtnBack());
flowPanel.add(getBtnNew());
}
return flowPanel;
}
private Button getBtnBack() {
if (btnBack == null) {
btnBack = new Button("New button");
btnBack.setText("Back");
btnBack.setSize("50px", "50px");
}
return btnBack;
}
private Button getBtnNew() {
if (btnNew == null) {
btnNew = new Button("New button");
btnNew.setSize("50px", "50px");
btnNew.setText("New");
}
return btnNew;
}
private Label getLblName() {
if (lblName == null) {
lblName = new Label("Name");
}
return lblName;
}
private TextBox getTxtName() {
if (txtName == null) {
txtName = new TextBox();
}
return txtName;
}
private DisclosurePanel getDisclosurePanel() {
if (disclosurePanel == null) {
disclosurePanel = new DisclosurePanel("Additional Details", false);
disclosurePanel.setAnimationEnabled(true);
disclosurePanel.setContent(getLayoutPanel_1());
}
return disclosurePanel;
}
private LayoutPanel getLayoutPanel_1() {
if (layoutPanel_1 == null) {
layoutPanel_1 = new LayoutPanel();
layoutPanel_1.setSize("5cm", "60px");
layoutPanel_1.add(getTxtStreet());
layoutPanel_1.setWidgetLeftWidth(getTxtStreet(), 60.0, Unit.PX, 91.0, Unit.PX);
layoutPanel_1.setWidgetTopHeight(getTxtStreet(), 0.0, Unit.PX, 32.0, Unit.PX);
layoutPanel_1.add(getLblStreet());
layoutPanel_1.setWidgetLeftWidth(getLblStreet(), 0.0, Unit.PX, 56.0, Unit.PX);
layoutPanel_1.setWidgetTopHeight(getLblStreet(), 0.0, Unit.PX, 22.0, Unit.PX);
}
return layoutPanel_1;
}
private Label getLblStreet() {
if (lblStreet == null) {
lblStreet = new Label("Street");
}
return lblStreet;
}
private TextBox getTxtStreet() {
if (txtStreet == null) {
txtStreet = new TextBox();
}
return txtStreet;
}
private DataGrid<Person> getDataGrid() {
if (dataGrid == null) {
dataGrid = new DataGrid<Person>();
dataProvider.addDataDisplay(dataGrid);
dataGrid.addColumn(getColName(), "Name");
dataGrid.addColumn(getColStreet(), "Street");
}
return dataGrid;
}
private TextColumn<Person> getColName() {
if (colName == null) {
colName = new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getName();
}
};
}
return colName;
}
private TextColumn<Person> getColStreet() {
if (colStreet == null) {
colStreet = new TextColumn<Person>() {
#Override
public String getValue(Person object) {
return object.getStreet();
}
};
}
return colStreet;
}
private DockLayoutPanel getDockLayoutPanel_1() {
if (dockLayoutPanel_1 == null) {
dockLayoutPanel_1 = new DockLayoutPanel(Unit.PX);
dockLayoutPanel_1.addSouth(getLayoutPanel_2(), 40.0);
dockLayoutPanel_1.add(getDataGrid());
}
return dockLayoutPanel_1;
}
private LayoutPanel getLayoutPanel_2() {
if (layoutPanel_2 == null) {
layoutPanel_2 = new LayoutPanel();
layoutPanel_2.add(getSimplePager());
layoutPanel_2.setWidgetLeftWidth(getSimplePager(), 50.0, Unit.PCT, 50.0, Unit.PCT);
}
return layoutPanel_2;
}
private SimplePager getSimplePager() {
if (simplePager == null) {
simplePager = new SimplePager();
simplePager.setDisplay(getDataGrid());
}
return simplePager;
}
public class Person{
private String name;
private String street;
public Person(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
}
In this example I have some problems:
The css padding is not working for SimpleLayoutPanel. Which class should I use instead?
I want to use the css padding in the empty skeleton class because if I replace the widgets for
this place holder, it should automatically use the css of the parent (slpWorkspaceHeaderPlaceholder).
I don't want to set the padding for every child of slpWorkspaceHeaderPlaceholder.
The Disclosure Panel for "Additional Details" is not working. The reason is because the DockLayoutPanel
has a fix size for North. I tested a solution that I just change the North size if I open the Disclosure Pane but
the animation is not working.
Is this the right track to design my application or should I use different Panels?
Should I try to use only Layout Panels (better standard-mode support) or are there special cases for other Panels?
Try to use as few types of widgets as possible to minimize your compiled code size, speed up compilation, and improve performance. Your design needs only 3 types of panels (in addition to RootLayoutPanel). You definitely DO NOT need to use a LayoutPanel for your toolbar or workspace header.
Something like this:
FlowPanel toolbar = new FlowPanel();
FlowPanel center = new FlowPanel();
DisclosurePanel workspace = new DisclosurePanel();
workspace.addCloseHandler(new CloseHandler<DisclosurePanel>() {
#Override
public void onClose(CloseEvent<DisclosurePanel> event) {
resize();
}
});
workspace.addOpenHandler(new OpenHandler<DisclosurePanel>() {
#Override
public void onOpen(OpenEvent<DisclosurePanel> event) {
resize();
}
});
Window.addResizeHandler(new ResizeHandler() {
#Override
public void onResize(ResizeEvent event) {
resize();
}
});
DataGrid<Person> dataGrid = new DataGrid<Person>();
FlowPanel statusBar = new FlowPanel();
center.add(workspace);
center.add(dataGrid);
LayoutPanel myPage = new LayoutPanel();
myPage.add(toolbar);
myPage.add(center);
myPage.add(statusBar);
myPage.setWidgetTopHeight(toolbar, 0, Unit.PX, 60, Unit.PX);
myPage.setWidgetTopBottom(center, 60, Unit.PX, 60, Unit.PX);
myPage.setWidgetBottomHeight(statusBar, 0, Unit.PX, 60, Unit.PX);
// add myPage to RootLayoutPanel
resize();
Create resize() method:
private void resize() {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
dataGrid.setHeight(center.getOffsetHeight() - workspace.getOffsetHeight() + "px");
}
});
}
Add buttons directly to toolbar widget with the following style:
.myToolbarButton {
width: 50px;
height: 50px;
float: left;
margin: 5px 10px 5px 0;
}
If you style your toolbar (background color), add the following to its style:
overflow: hidden;
HTML has terrible support for "let this component take up as much space as it needs, and then let this component take up the rest of the space." As far as I know, the best way to do what you want in GWT would be to use all LayoutPanels, override onResize, and then do the mathematical layout calculations yourself:
int heightOfEntireCenter = centerPanel.getClientHeight();
int heightOfToolbar = toolBar.getClientHeight();
workspace.setHeight(heightOfEntireCenter - heightOfToolbar);
You'll have to do a ton of finagling, hunting for mysterious layout errors, be annoyed by lag when sizes change, etc. HTML is just bad at this.
If you can fix the size of the toolbar, you'll have a much easier time. I don't even mean that the toolbar can't change size - I just mean you'll have to calculate that and set it. Then the workspace can fill the rest of the space much more naturally. LayoutPanels are the correct choice here, too.

GWT: In a TabLayoutPanel, how do I make tabs wrap if there are too many?

I'm using GWT 2.4. I have a TabLayoutPanel to which I add tabs. Each tab contains a ScrollPanel. My question is, how do I make the tabs in the tab bar wrap to the next line if the width of the tab bar exceeds the visible width?
Thanks, - Dave
GWT's TabLayoutPanel intentionally never wraps tabs. See lines 246-248 in TabLayoutPanel.java - (line 217 defines private static final int BIG_ENOUGH_TO_NOT_WRAP = 16384). You might be able to override this, but as #milan says, it's probably not good design.
Having multiple lines is, indeed, not recommended...
However, to be able to navigate left/right on a single tab bar with many tabs, you can use this recipe:
http://devnotesblog.wordpress.com/2010/06/17/scrollable-gwt-tablayoutpanel/
And an updated implementation that doesn't use the deprecated DeferredCommand:
package whatever.you.want;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.TabLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A {#link TabLayoutPanel} that shows scroll buttons if necessary
*/
public class ScrolledTabLayoutPanel extends TabLayoutPanel {
private static final int IMAGE_PADDING_PIXELS = 4;
private LayoutPanel panel;
private FlowPanel tabBar;
private Image scrollLeftButton;
private Image scrollRightButton;
private HandlerRegistration windowResizeHandler;
private ImageResource leftArrowImage;
private ImageResource rightArrowImage;
public ScrolledTabLayoutPanel(double barHeight, Unit barUnit,
ImageResource leftArrowImage, ImageResource rightArrowImage) {
super(barHeight, barUnit);
this.leftArrowImage = leftArrowImage;
this.rightArrowImage = rightArrowImage;
// The main widget wrapped by this composite, which is a LayoutPanel with the tab bar & the tab content
panel = (LayoutPanel) getWidget();
// Find the tab bar, which is the first flow panel in the LayoutPanel
for (int i = 0; i < panel.getWidgetCount(); ++i) {
Widget widget = panel.getWidget(i);
if (widget instanceof FlowPanel) {
tabBar = (FlowPanel) widget;
break; // tab bar found
}
}
initScrollButtons();
}
#Override
public void add(Widget child, Widget tab) {
super.add(child, tab);
checkIfScrollButtonsNecessary();
}
#Override
public boolean remove(Widget w) {
boolean b = super.remove(w);
checkIfScrollButtonsNecessary();
return b;
}
#Override
protected void onLoad() {
super.onLoad();
if (windowResizeHandler == null) {
windowResizeHandler = Window.addResizeHandler(new ResizeHandler() {
#Override
public void onResize(ResizeEvent event) {
checkIfScrollButtonsNecessary();
}
});
}
}
#Override
protected void onUnload() {
super.onUnload();
if (windowResizeHandler != null) {
windowResizeHandler.removeHandler();
windowResizeHandler = null;
}
}
private ClickHandler createScrollClickHandler(final int diff) {
return new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
Widget lastTab = getLastTab();
if (lastTab == null)
return;
int newLeft = parsePosition(tabBar.getElement().getStyle().getLeft()) + diff;
int rightOfLastTab = getRightOfWidget(lastTab);
// Prevent scrolling the last tab too far away form the right border,
// or the first tab further than the left border position
if (newLeft <= 0 && (getTabBarWidth() - newLeft < (rightOfLastTab + 20))) {
scrollTo(newLeft);
}
}
};
}
/** Create and attach the scroll button images with a click handler */
private void initScrollButtons() {
scrollLeftButton = new Image(leftArrowImage);
int leftImageWidth = scrollLeftButton.getWidth();
panel.insert(scrollLeftButton, 0);
panel.setWidgetLeftWidth(scrollLeftButton, 0, Unit.PX, leftImageWidth, Unit.PX);
panel.setWidgetTopHeight(scrollLeftButton, 0, Unit.PX, scrollLeftButton.getWidth(), Unit.PX);
scrollLeftButton.addClickHandler(createScrollClickHandler(+20));
scrollLeftButton.setVisible(false);
scrollRightButton = new Image(rightArrowImage);
panel.insert(scrollRightButton, 0);
panel.setWidgetLeftWidth(scrollRightButton, leftImageWidth + IMAGE_PADDING_PIXELS, Unit.PX, scrollRightButton.getWidth(), Unit.PX);
panel.setWidgetTopHeight(scrollRightButton, 0, Unit.PX, scrollRightButton.getHeight(), Unit.PX);
scrollRightButton.addClickHandler(createScrollClickHandler(-20));
scrollRightButton.setVisible(false);
}
private void checkIfScrollButtonsNecessary() {
// Defer size calculations until sizes are available, when calculating immediately after
// add(), all size methods return zero
Scheduler.get().scheduleDeferred( new Scheduler.ScheduledCommand() {
#Override
public void execute() {
boolean isScrolling = isScrollingNecessary();
// When the scroll buttons are being hidden, reset the scroll position to zero to
// make sure no tabs are still out of sight
if (scrollRightButton.isVisible() && !isScrolling) {
resetScrollPosition();
}
scrollRightButton.setVisible(isScrolling);
scrollLeftButton.setVisible(isScrolling);
}
}
);
}
private void resetScrollPosition() {
scrollTo(0);
}
private void scrollTo(int pos) {
tabBar.getElement().getStyle().setLeft(pos, Unit.PX);
}
private boolean isScrollingNecessary() {
Widget lastTab = getLastTab();
if (lastTab == null)
return false;
return getRightOfWidget(lastTab) > getTabBarWidth();
}
private int getRightOfWidget(Widget widget) {
return widget.getElement().getOffsetLeft() + widget.getElement().getOffsetWidth();
}
private int getTabBarWidth() {
return tabBar.getElement().getParentElement().getClientWidth();
}
private Widget getLastTab() {
if (tabBar.getWidgetCount() == 0)
return null;
return tabBar.getWidget(tabBar.getWidgetCount() - 1);
}
private static int parsePosition(String positionString) {
int position;
try {
for (int i = 0; i < positionString.length(); i++) {
char c = positionString.charAt(i);
if (c != '-' && !(c >= '0' && c <= '9')) {
positionString = positionString.substring(0, i);
}
}
position = Integer.parseInt(positionString);
} catch (NumberFormatException ex) {
position = 0;
}
return position;
}
}