Samuel Sjöberg's weblog

Skip to navigation

Show and hide scrollbars on mouseenter and mouseleave

I'm experimenting with scrollbars and how to make them look better with a design I'm working on. I really don't like messing too much with the user's settings but this time the aesthetics call for it.

After trying out a couple of plugins that really didn't do anything for me I decided to switch approach. What if we simply hide the scrollbar when the user doesn't focus on the content? I'm not sure how users will react but since it can be done, here it is:

// Trick to only show the scrollbar when focused.
$(document).ready(function() {
    $(".sneaky-scrollbar").mouseleave(function() {
        var $this = $(this);
        var padding = parseInt($this.css("padding-right"));
        $this.data("scrollTop", $this.scrollTop());
        $this.css({
            "overflow": "hidden",
            "padding-right": padding + $.getScrollbarWidth(),
            "width": $this.width() - $.getScrollbarWidth()
        });
    }).mouseenter(function() {
        var $this = $(this);
        var padding = parseInt($this.css("padding-right"));
        $this.css({
            "overflow": "auto",
            "padding-right": padding - $.getScrollbarWidth(),
            "width": $this.width() + $.getScrollbarWidth()
        });

        // Reset scrollbar before setting position again,
        // otherwise it won't update its position correctly.
        $this.scrollTop(0).scrollTop($this.data("scrollTop"));
    }).mouseleave();
});

This script depends on the $.getScrollbarWidth() plugin by Brandon Aaron. You'll need to include the linked snippet too.

Just add the sneaky-scrollbar class to the element that should hide its scrollbar. I'm assuming it has a fixed height and overflow:auto to begin with.

The code is pretty straight-forward, the only detail worth mentioning is that the scrollbar will have the wrong position once it shows again if scrolled down a bit when hidden. To fix this, I'm storing the scroll position and restore it when showing the scrollbar again. Before restorting it, I'm setting it to zero to ensure that the UI will render the update.

Turning of comments

Today I decided to remove the comments form that's been part of this blog since the beginning. The reason for this is that I write too infrequently to manage the blog and respond to comments. As comments are so unusual months can pass before I notice them. And no, this homegrown blog does not post email notifiers.

So for now comments are off. If I were to write something worth commenting, email will continue to be the best and fasted way to get in touch.

Autocompletion follow up

As pointed out in the comments for the Autocompletion in Swing post, there was a problem when providing default values that had a match returned by the CompletionService.

To reproduce the problem, one had to add the following (in bold) to the provided sample code:

input.setDocument(autoCompleteDocument);

// After setting the document, add a default value
input.setText("Chloe");

The result was an empty text box. If the value instead was Chlo the value in the text box was set to e.

These problems occurred because JTextComponent.setText(String) delegates to Document.insertString where autocompletion happens. The autocompletion code did not handle scenarios when there wasn't any existing text value. The solve the problem, it was necessary to check that some text already existed before applying the completion. In code (changes in bold):

/** {@inheritDoc} */
@Override
public void insertString(int offs, String str, AttributeSet a)
        throws BadLocationException {
    if (str == null || str.length() == 0) {
        return;
    }

    String text = getText(0, offs); // Current text.
    String completion = complete(text + str);
    int length = offs + str.length();
    if (completion != null && text.length() > 0) {
        str = completion.substring(length - 1);
        super.insertString(offs, str, a);
        documentOwner.select(length, getLength());
    } else {
        super.insertString(offs, str, a);
    }
}

I have updated the binary and source files linked in the original post.

Multiline labels in Swing with drop shadow

Continuing the Swing trail, I'm presenting my solution for multiline text labels in Swing. I wrote this during this spring and had simply forgotten about it.

The problem with JLabel is the way in which it supports multiple lines (with wrapping) and centered text. You need to use HTML to activate this functionality. Enabling HTML is pretty simple:

JLabel label = new JLabel(
        "<html>Text that'll wrap if necessary");

To center the text, you'll add the <center> tag to your string. Not pretty, but it gets the job done.

This approach has two drawbacks. First, it will bloat your code or resource strings since you'll need to add the HTML tags somewhere. Second, you'll loose control of how text is being drawn once HTML mode is set. When you use HTML in a Swing component, it will be rendered as a View (see javax.swing.plaf.basic.BasicHTML for implementation details).

It's all about text effects

The motivation behind implementing multiline support for labels is:

To be honest, my main motivation was that I needed drop shadow on the text in labels, like on the desktop in OS X and Windows. I tried two solutions, the obvious one with a offscreen image that you apply a gaussian filter on and one that simply painted the text in various offset positions and alpha blends until it looked kind of like a drop shadow.

I'd read about this second approach in the book Filthy Rich Clients where an example produces a text glow effect in this way. After playing around with this approach I decided to go for it. The results looked better than with a gausiann blur and it felt like a bad idea to have an offscreen image for each drop shadow label in the GUI.

I first implemented the solution as an UI delegate that painted my custom text effect. Happy to see it work, I quickly realized I wanted this effect in combination with multiline support.

Introducing the general purpose MultiLineLabelUI

A couple (okay, many) hours later, I'd written a general purpose MultiLineLabelUI that enabled any JLabel to support multiline text with line wrapping and preserving hard line breaks. On top of that, I'd made the MultiLineShadowUI, enabling text drop shadow in any JLabel.

To enable support for mulitple lines, simply do this:

JLabel label = new JLabel("Text that'll wrap if necessary");
label.setUI(MultiLineLabelUI.labelUI);

To use the drop shadow, instead to this:

label.setUI(MultiLineShadowUI.labelUI);

Details, details, details...

The multiline labels will only wrap the text when needed. It can be useful to know that the preferred height reported by the UI delegate will be the height required to render lines without any additional wrapping (i.e., hard wraps, inserted by the user, is accounted for). It is not until the label is painted, and the actual width budget is known, that the line wrapping can be correctly computed. The wrapped lines are stored as a client property on the label to avoid unnecessary calculations.

The label listens to dimension changes and recalculate line breaks as the component is resized. Resizing the window on OS X will for example rearrange the line wraps (note: this will not work on windows since component bounds don't seem to be updated correctly when the window is resized).

Improving alignment in the JLabel

To enable better support for text alignment in the JLabel I've created the MultiLineLabel class. It's using the MultiLineLabelUI by default and adds the properties verticalTextAlignment and horizontalTextAlignment.

This makes it possible (and really easy) to have a multiline label that's for example centered at the bottom of the component.

MultiLineLabel label = new MultiLineLabel(
        "Text that'll wrap if necessary");
label.setHorizontalTextAlignment(JLabel.CENTER);
label.setVerticalTextAlignment(JLabel.BOTTOM);

Demo and source

Feel free to try out the Java Web Start demo. It requires Java 1.5+.

Here's the binary and source distribution.

The code is released under the MIT-license, so you're basically free to use it in any ways you find useful.

Autocompletion in Swing

In a small application I developed not long ago I wanted to use autocompletion directly inside a text field. The completion was used in a multiline JTextArea and therefore I didn't want any drop-down list displaying the suggestions (this is the approach taken by most existing solutions). Instead I wanted the suggestion to appear as a selected text range when a unique suggestion was found.

Selected auto completion

The picture above illustrates the behavior I was looking for. As the user starts to type, additional (completed) characters should be selected. If the user type another character, the suggested completion will be removed and the completion evaluation cycle executes again with the newly added character.

I also decided that erasing a character should not trigger a new completion lookup. Only inserts should result in completions. This behavior is consistent with for example the URL address field in Safari.

A generic solution

My goal was to build something generic that can be reused among all JTextComponents. To achieve this, I decided to make an autocompleting Document model. The Document is the model in a JTextComponent, containing the characters.

Let's walk through the provided sample to see how the solution works (source code is attached below).

// Create the completion service.
NameService nameService = new NameService();

First thing we need to do is to create the CompletionService. This service will be used by our AutoCompleteDocument to lookup completions for the already typed characters.

In the sample, we'll use a simple service that offers autocompletion of popular baby names.

// Create the input field.
JTextField input = new JTextField();

// Create the auto completing document model with a
// reference to the service and the input field.
Document autoCompleteDocument = new AutoCompleteDocument(
        nameService, input);

The next step is to create a text field. After that, we create our AutoCompleteDocument and supply it with a reference to the nameService and the text field itself.

The last step required is to set the AutoCompleteDocument on the text field:

input.setDocument(autoCompleteDocument);

That's it. Your done!

But wait, this is not entirely true. You'll also need to provide an implementation of the CompletionService. This is the interface to implement:

public interface CompletionService<T> {

    /**
     * Autocomplete the passed string. The method will return the matching
     * object when one single object matches the search criteria. As long as
     * multiple objects stored in the service matches, the method will return
     * <code>null</code>.
     *
     * @param startsWith
     *            prefix string
     * @return the matching object or <code>null</code> if multiple matches are
     *         found.
     */
    T autoComplete(String startsWith);
}

My idea is that the CompletionService interface is implemented as part of the lookup services that e.g. hit the database to look up things. Other possibilities is to build a wrapper service that is populated with a list of possible completions.

The intention is to have a flexible way of providing autocompletion that is properly decoupled from the Swing implementation details.

Demo and source

Feel free to try out the Java Web Start demo. It requires Java 1.5+. Note that the sample is case-sensitive when performing autocompletion.

Here's the binary and source distribution.

I'm releasing this to the public domain, without any warranty or support. If you find this useful, I'd gladly hear about it though!

Escape jQuery selectors

I've started to play with jQuery and needed an easy way to escape my selectors in situations when they're out of my control. A function like this will get the job done.

function escapeExpression(str) {
    return str.replace(/([#;&,\.\+\*\~':"\!\^$\[\]\(\)=>\|])/g, "\\$1");
}

Swing 1.1.1

I'm stuck with Swing 1.1.1, trying to create a filthy rich client at work. The reason for being stuck with an obsolete Swing release is that we're developing on CrE-ME, a Java ME CDC implementation for Windows CE.

Swing 1.1.1 is missing a lot of features you take for granted when you've been working with recent Swing releases and want to create visual effects. When I started to create the client UI I intended to use gradients and shadows because they're pretty. But wait, let's list some of the things that's missing in Swing 1.1.1

AlphaComposite
Bye bye drop shadow and transparency.
GradientPaint
It's true, no gradients... not even a Paint interface.
AffineTransform
No scale operations and no rotation on the graphics canvas. Too bad.

The only thing left to do is faking as much as possible. I'm creating faux shadows, rendering solid shadows with colors carefully matched to the background. Gradients can be achieved with a tiled background image slice.