The Source for Java Technology Collaboration
User: Password:



Kirill Grouchnikov's Blog

October 2005 Archives


"Totally splendid variable-renaming facility" in Visual Studio 2005

Posted by kirillcool on October 27, 2005 at 06:59 AM | Permalink | Comments (12)

You will chuckle, you will grin and sometimes you will want to laugh. Read this talk delivered by Charles Petzold, the author of "Programming Windows" book (and 12 other Windows books). Charles is one of the gurus of Windows GUI programming on C / C++ and lately .Net. After having programmed for about 5 years (three of them exclusive) with Visual Studio 6, i agree with everything he says on wizards and generated code in VS. Of course, the same has been said repeatedly about GUI builders (long gone and emerging) in Java IDEs.

However, the really funny stuff can only be seen by us, Java developers that have come to expect everything (and then some) from our IDEs. Let's see.

Quote: IntelliSense is a technology that is inevitable. If Microsoft hadn’t done it, somebody else would have. Hmmm, are we talking about Ctrl+Space that has been like, forever, in Java IDEs?

Quote: Visual Studio 2005 has a totally splendid variable-renaming facility. You know how sometimes you really want to rename a variable to be more in tune with its actual function in the program but you’re afraid to because of possible side effects of search-and-replace? Well, this new variable-renaming avoids all that, and it will also tell you if you’re renaming something to an existing name. I hope people take advantage of this to rename their controls to something other than the Visual Studio defaults. Hmmm, guess now Microsoft will patent it and make JetBrains guys pay them. Brrrrrr.

Quote: Along with Visual Studio is also the .NET Framework 2.0, with some significant enhancements to Windows Forms, including a very strong commitment to "dynamic layout" (sometimes also known as "automatic layout"). Dynamic layout is also a significant part of the design philosophy of Avalon.... The FlowLayoutPanel and the TableLayoutPanel together with the SplitContainer and the Dock property provide a full array of tools for dynamic layout. Hmmmm, significant indeed, only to be eclipsed by FlowLayout / GridBagLayout present in Java since, like, forever?

Addition - as pointed out in the comments, Microsoft has added this feature in Visual Basic 5 that came out in February 1997. JBuilder 2 that came out in March 1998 was the first (as far as i remember) Java IDE to feature the CodeInsight (as opposed to IntelliSense). The next in line must have been Visual Cafe 3 that came out in October 1998 with Code Helper. Altough obviously both were released after Visual Basic 5, i think that the origins of IntelliSense come from UNIX editors such as vi and xemacs (remember the Alt+/?)

Spicing up your JTabbedPane

Posted by kirillcool on October 27, 2005 at 02:20 AM | Permalink | Comments (4)

My previous entry showed the way to provide visual indication of frames with changed content. Based on the suggestions from the comments, this behaviour has been also added to JTabbedPane.

Application should set SubstanceLookAndFeel.WINDOW_MODIFIED client property on the tabbed pane's Component (if it's JComponent). The value that corresponds to unsaved state is Boolean.TRUE. See 67-second AVI movie (1.2 MB) illustrating the technique.

Pulsating loop on JTabbedPane - red theme (animation start):

Pulsating loop on JTabbedPane - orange theme (midway through animation):

Pulsating loop on JTabbedPane - yellow theme (animation flex point):

In addition, the tabs can now have close buttons (like in NetBeans or SWT). Application should set SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY client property on either the tabbed pane's Component (if it's JComponent), JTabbedPane (for all tabs of that pane) or the UIManager (for all tabbed panes). The value that corresponds to tab with close button is Boolean.TRUE. In addition, there's rollover effect on enabled tabs (like on any other control such as buttons or scroll bars). See 45-second AVI movie (810 KB) illustrating the technique.

Close button on active tab in JTabbedPane - mouse not over close button (includes mouse pointer):

Close button on active tab in JTabbedPane - mouse over close button (includes mouse pointer):

Close button on inactive enabled tab in JTabbedPane - mouse not over tab (includes mouse pointer):

Close button on inactive enabled tab in JTabbedPane - mouse over tab but not over close button (includes mouse pointer):

Close button on inactive enabled tab in JTabbedPane - mouse over tab and over close button (includes mouse pointer):

This property can be set in three ways:
  • On JTabbedPane tab component itself (if it's JComponent). If the property is specified here and it's either Boolean.TRUE or Boolean.FALSE, its value is taken.
  • On JTabbedPane itself. If the property is specified here and it's either Boolean.TRUE or Boolean.FALSE, its value is taken and is relevant for all tabs in this JTabbedPane that weren't found in the previous step.
  • As global setting in UIManager. If the property is specified here and it's either Boolean.TRUE or Boolean.FALSE, its value is taken and is relevant for all tabs in all JTabbedPanes that weren't found in the previous two steps.
Note that the default behaviour (if you do not specify this property anywhere) is not to use close buttons at all - in order to not interfere with Java 6.0 tabbed components. For example:
  JTabbedPane pane = new JTabbedPane();
  JPanel component = new JPanel();
  component.putClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY,
	Boolean.TRUE);
  pane.addTab(component);
  JPanel component2 = new JPanel();
  // Will not have close button unless the tabbed pane itself specifies 
  // Boolean.TRUE as value for its client property or UIManager.put is 
  // called on this property with Boolean.TRUE

  pane.addTab(component2);
Once again - the behaviour is not disruptive. If you don't specify this property anywhere, you will not have close buttons on your tabs. This will allow Java 6.0 applications that set custom tab header components to continue working correctly.

As always, you can run a Web Start demo or download the latest Substance 2.1 development drop from here.

Providing visual indication of changed contents in Swing frames

Posted by kirillcool on October 20, 2005 at 02:18 AM | Permalink | Comments (10)

Yesterday i have stumbled upon this entry from Apple developer zone. It shows how to indicate that a document window (frame in Swing application) has changed contents. The indication is similar to that of regular Mac application (dark dot in the close button). The way to accomplish this is to put a windowModified client property with Boolean.TRUE value on either the JRootPane or on the JInternalFrame.

This, of course, is Mac-specific, and does not have Swing support on other platforms. That is, until now (kind of). The latest drop of Substance version 2.1 provides just that - a pulsating close button of JFrame, JInternalFrame and JDesktopIcon when the above property is set to Boolean.TRUE. You can view it in action in this movie (31-second, 526KB, originally WMV format, but should play as AVI too). In addition, you can run the Web Start demo to see it in action. Go to "Desktop" tab, click on "add" button and click on "Mark changed" in the new internal frame. Here are few screenshots from the movie:

Pulsating loop on JInternalFrame - red theme:

Pulsating loop on JInternalFrame - yellow theme:

Pulsating loop on JInternalFrame - tooltip on close button in unsaved state:

Pulsating loop on JDesktopIcon:

Pulsating loop on JFrame:



Note that the close button is painted in red and pulsates every 5 seconds, changing its theme from red to yellow and back to red in two seconds.

Who put a rhino in my NetBeans?

Posted by kirillcool on October 17, 2005 at 03:36 AM | Permalink | Comments (11)

What would you say if you opened the Options dialog in your application and it looked like this (click to see larger version):



I've blogged about adding watermark image before, but pay attention to the buttons. They are shaped like little rhinos. Don't like rhinos (or Beyonce)? No biggie, just change a few VM flags and here you go (click for larger view):



And now to the reason why would you want to do such a thing. It didn't appear to me until after i have started to think about pluggable button shapers that i realized a whole market that Java is missing - non-male non-geek non-blind-Dvorak-typing one. What about applications that are mainly targetting female / child audience. Wouldn't it be nice to make an application that has dolphin-shaped buttons when the application is a marine encyclopedia?

Swing already has support for different button shapes - you need to implement your own ButtonUI delegate and override a couple of functions (notably the one that paints the background, the one that paints the border and the one that tests the mouse hit). So, the button shaper plugin for Substance look-and-feel provides exactly that - a collection of button shapers. The currently available shapers are:



The implementation is quite simple - each button shaper has an associated contour (implemented by GeneralPath). When a button needs to be drawn, the contour is stretched to accomodate the text. Note that the contour maintains the original proportion (unless the application explicitly sets the dimension, like in NetBeans first screenshot). In addition, each button has a shine spot that follows the outline of the button itself (more on this later):



The button contour is created, edited and saved in the shape editor (click here to launch Web Start version). Working with editor (on new shape) involves the following steps:
  • Create an optional image (to simplify the process of creating the contour):
  • Load the image in the editor:
  • Edit major points (each segment will be represented with quadTo of GeneralPath:
  • You can unselect Show image check box to view major points only:
  • Edit minor points (each segment will be represented with quadTo of GeneralPath:
  • You can unselect Show image check box to view minor points only:
  • Click Save contour and choose the location to save to
The contour is saved as binary file (all the coordinates + the ratio of the original image) and loaded at runtime.

The most challenging part of creating arbitrarily-shaped buttons was creating a (fake) 3D shine-spot on the top half of the image. Here is how it can be accomplished using a few tricks of Java2D:

The easy part is the main background and the contour itself. Just use Graphics2D.setClip() and Graphics2D.draw(Shape) (GeneralPath is a Shape):



Now, we create a temporary image and draw the top half of the contour unto it in thick black stroke. Note that the image has margins on all sides so the the contour will fit in (this will be needed in the next step):
    
   int shineHeight = (int) (height / 1.8);
   int kernelSize = (int) Math.min(12, Math.pow(Math
         .min(width, height), 0.8) / 4);
   if (kernelSize == 0)
      kernelSize = 1;
   BufferedImage ghostContour = getBlankImage(width + 2 * kernelSize,
         height + 2 * kernelSize);
   Graphics2D ghostGraphics = (Graphics2D) ghostContour.getGraphics()
         .create();
   ghostGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
         RenderingHints.VALUE_ANTIALIAS_ON);

   ghostGraphics.setStroke(new BasicStroke(2 * kernelSize));
   ghostGraphics.setColor(Color.black);
   ghostGraphics.translate(kernelSize, kernelSize);
   ghostGraphics.draw(contour);
The result is:



Now, we convolve the contour image with a simple filter, creating a blurred version (this is way we needed the extra margins in the previous step, otherwise the blurred contour becomes too transparent):
    
   int kernelMatrixSize = (2 * kernelSize + 1) * (2 * kernelSize + 1);
   float[] kernelData = new float[kernelMatrixSize];
   for (int i = 0; i < kernelMatrixSize; i++)
      kernelData[i] = 1.0f / kernelMatrixSize;
   Kernel kernel = new Kernel(2 * kernelSize, 2 * kernelSize,
         kernelData);
   ConvolveOp convolve = new ConvolveOp(kernel);
   BufferedImage blurredGhostContour = getBlankImage(width + 2
         * kernelSize, height + 2 * kernelSize);
   convolve.filter(ghostContour, blurredGhostContour);
   Graphics2D blurredGraphics = (Graphics2D) blurredGhostContour
         .getGraphics();
The result is:



Now, we use the AlphaComposite.DstOut to fill the reverse of the blurred contour. The fill is opaque on top and translucent on bottom:
    
   BufferedImage reverseGhostContour = getBlankImage(width + 2
         * kernelSize, height + 2 * kernelSize);
   Graphics2D reverseGraphics = (Graphics2D) reverseGhostContour
         .getGraphics();
   Color bottomShineColorTransp = new Color(bottomShineColor.getRed(),
         bottomShineColor.getGreen(), bottomShineColor.getBlue(), 32);
   GradientPaint gradientShine = new GradientPaint(0, kernelSize,
         topShineColor, 0, kernelSize + shineHeight,
         bottomShineColorTransp);
   reverseGraphics.setPaint(gradientShine);
   reverseGraphics.fillRect(0, kernelSize, width + 2 * kernelSize,
         kernelSize + shineHeight);
   reverseGraphics.setComposite(AlphaComposite.DstOut);
   reverseGraphics.drawImage(blurredGhostContour, 0, 0, null);

   graphics.drawImage(reverseGhostContour, 0, 0, width - 1,
         shineHeight, kernelSize, kernelSize,
         kernelSize + width - 1, kernelSize + shineHeight, null);
The result is:



This is almost what we wanted. However, the shine spot is a little bit too blurry, making the button look fuzzy. We need to draw the blurred contour once again in the main gradient of the button background, using AlphaComposite.DstIn:
    
   BufferedImage overGhostContour = getBlankImage(width + 2
         * kernelSize, height + 2 * kernelSize);
   Graphics2D overGraphics = (Graphics2D) overGhostContour
         .getGraphics();
   overGraphics.setPaint(new GradientPaint(0, kernelSize,
         topFillColor, 0, kernelSize + height / 2, midFillColor));
   overGraphics.fillRect(kernelSize, kernelSize, kernelSize + width,
         kernelSize + shineHeight);
   overGraphics.setComposite(AlphaComposite.DstIn);
   overGraphics.drawImage(blurredGhostContour, 0, 0, null);

   graphics.drawImage(overGhostContour, 0, 0, width - 1, shineHeight,
         kernelSize, kernelSize, kernelSize + width - 1, kernelSize
         + shineHeight, null);
The result is:





Developing Java project - a team effort

Posted by kirillcool on October 14, 2005 at 02:04 PM | Permalink | Comments (8)

I was watching one of the Monday night NFL games that i have on tape. It was a Packers vs. Bears from three years ago. Bears had a lousy night with three interceptions (one returned for TD), three sacks on defense, fumbled kick return, missed onside kick and a variety of assorted mishaps. They lost 21-34 and went on to have a 4-12 season. One phrase by Madden got stuck with me (guess i missed it back then). One of the defensive stars of the league is Brian Urlacher who's been with Bears for the last six seasons. Madden called him "the perfect middle line-backer and the perfect football player".

The track record for Bears over these six seasons (Urlacher has missed only 7 games due to injury over these seasons) has been far from perfect though - only one winning season (13-3 in 2001) and 22-46 in other five seasons (including 1-3 during the current one). The reason is obvious - it takes 53 players (11 at any given moment, unless you want to get a "12 men on field" penalty) to play the game. Have one star and ten dead-beats - the star isn't going make a difference.

Over the same six years, four different teams have won the Superbowl, with three victories going to the Patriots. In all cases except the Ravens (and the SB score is a little misleading), the winning teams had a perfect combination on all teams, offensive, defensive and special (2000 Ravens defense gave up an amazing 9.4 points per game, so there was no need for real offense).

Every year, we (the lowly developers) are attacked with a variety of new solutions that promise to cut development cost, time and productivity in half (in worst case). Last year's award undoubtedly goes to AOP (thankfully we got over it before it hit the mainstream). This year, it's AJAX, SOA and (sort of) Web 2.0. This entry caught my eye recently. It outlines an interesting cross-section of technologies that should be mastered by an AJAX developer. And that is only for the GUI interaction with the server. What about the persistence layer, internationalization support, deployment and installation, data structures, flow engine, interoperability with legacy systems? Regard any one of those as minor issue and it will bite you. Hard.

The religious wars over "which IDE is best" kind of ignore a simple problem - if you chose a poor solution for clustering / high availability, the best IDE will get you to the dead end twice as fast. You will just have a little more time to understand that you will not make the deadline. It takes a team of programmers to do the job, but it also takes a team of solutions for a team of problems. Pick the current shining star and forget all the rest? See you on your next project.

Aligning menu items in Swing applications

Posted by kirillcool on October 10, 2005 at 02:40 PM | Permalink | Comments (3)

Does the following look familiar (under default Ocean theme in Metal LAF)?


The menu doesn't look good, with jagged items all over the place. The common solution in this scenario is to use a transparent icon on those menu items that don't have one. There are a few problems with this approach:
  • What happens if you add another menu item with slightly wider icon? You will have to adjust all other icons accordingly.
  • What happens with JCheckBoxMenuItems and JRadioButtonMenuItems? Once you have those, it's almost impossible to get it done correctly, especially if they have icons themselves.
  • There is no JMenu constructor that gets an icon. You'll have to call setIcon().
  • The last, but most certainly the most annoying - why should you even bother to do this?
The answer to the last question lies in the BasicMenuItemUI class that does the actual layout and rendering - it's looking at one menu item at a time. It's not even aware that the specific menu item is a part of visual group, so it happily hacks (and quite ugly) at the corresponding menu item, and the result is far from perfect:

(the above is Windows LAF from JDK).

To the rescue come custom look-and-feels. JGoodies Looks comes equiped with custom menu item renderer. It keeps track of the maximum text offset on the menu item parent (JPopupMenu in our case), and once the rendering should be done, the offset is taken from the parent (client property). The result is much more satisfying (see this issue and Karsten's comment below regarding the layout JCheckBoxMenuItems and JRadioButtonMenuItems with icons - the check marks are drawn underneath the icons):


Substance LAF takes a different approach, inheriting from BasicMenuItemUI and overriding the function that computes the text offset. It does the same as JGoodies, but for every menu item, it goes over all entries in the parent component (JPopupMenu) and computes the maximal text offset. This approach is more time consuming but also more dynamic. The result is:


This also works on "real" application, such as NetBeans:
Before

After




Ribbon and smart resizing

Posted by kirillcool on October 07, 2005 at 02:42 AM | Permalink | Comments (6)

One of my previous entries was written fresh on the heels of unveiling of the Office 12 UI. A lot of stuff was left unfinished, some of it due to lack of time, and most of it due to lack of information on how this UI really works. So, after reading plethora of information on Jensen's blog, the Java implementation of Ribbon has moved to the second gear.

The following points were missing in the initial implementation (see Jensen's blog for more details):
  • Concept of gallery.
  • Gallery button (the one with the downward-pointing arrow).
  • In-ribbon gallery (the one with the scroll arrows on the right-hand side and the downward-pointing arrow).
  • Smart layout of the ribbon components on resize.
Let's look at the following screenshot (WebStart link at the end of this entry):



Every button that has a downward-pointing double arrow is a gallery button. In Office 12, clicking on such button will show a drop-down gallery of styles / choices / etc (like a combobox). Buttons with no arrows are simple buttons.

In "Quick Styles" band, you can see an in-ribbon gallery. The UI designers may decide that some galleries are much more important than others. Such galleries can be put directly in the ribbon, with scroll buttons that scroll through the gallery icons, and downward-pointing arrow that shows the entire gallery as popup.

In addition, note that gallery buttons come in three flavours:
  • Large icon with two-lined caption beneath (see Paste in Clipboard and Find in Find).
  • Small icon with one-lined caption on the right (see Paragraph's and Find buttons).
  • Small icon with no caption (see Clipboard and Quick Styles buttons).
UI designers assign importance to each button. When there's enough space for everything (see screenshots below), everything is shown in big icons with captions beneath. As more elements are put into the ribbon (or the main window is resized and shrinked), the buttons that were marked as less important are requested (by the layout manager) to switch state and take less space. This is a very important concept of smart resizing that is configurable and automatic.

The implementation of smart resizing is quite simple (although with a lot of technical details). First, there is a concept of collapse. Collapse is a request for some ribbon band to layout its components (gallery buttons, in-ribbon galleries and regular components) based on degree of collapsing. There are seven collapse degrees:
  • NONE - everything takes as much space as it wants.
  • LOW_TO_MID - components with low priority are requested to go to MID state (small icon with one-line label on the right-hand side).
  • MID_TO_MID - components with low and mid priority are requested to go to MID state.
  • LOW_TO_LOW - components with low priority are requested to go to LOW state (small icon with no label).
  • MID_TO_LOW - components with low and mid priority are requested to go to LOW state (small icon with no label).
  • HIGH_TO_MID - components with high priority are requested to go to MID state.
  • HIGH_TO_LOW - all components are requested to go to LOW state.
For each state, the layout manager needs to know how to compute the preferred size. First, all HIGHs are laid out, the all the MIDs in groups of three (taking extra LOWs to complete the last threesome), and then all the LOWs in groups of three.

There are two kinds of layout managers, one for the ribbon and one for each ribbon band. When the layoutContainer() function of ribbon layout manager is called, it does the following:
  • Requests each ribbon band layout manager to compute the preferred size for each collapse kind.
  • Computes total preferred width (of all ribbon bands) for each collapse kind.
  • Chooses the best possible collapse kind that doesn't result in width that overflows the width available for the ribbon.
  • Distributes the leftover space between all ribbon bands.
  • Requests each ribbon band layout manager to layout its components based on the chosen collapse kind
Let's see the example (you can run the Web Start version and play with the resize). In the first screenshot, there's enough place for all the buttons (dark icons are for high priority, bright icons are for medium priority and very bright icons are for low priority):


As we start making the window smaller, the band with low priority buttons gets less space. Note that they go to MEDIUM state and not LOW:


Making the window smaller, the bands with medium (and low) priority buttons get less space. Note that both medium and low priority buttons go to MEDIUM state and not LOW (since there's enough space left):


Making the window smaller, the band with low priority buttons gets even less space. Note that now they go to LOW state:


Making the window smaller, the bands with medium and low priority buttons get less space, making all these buttons go to LOW state:


Making the window smaller, there isn't enough space for the high priority buttons. However, note that after requesting these buttons to go to MEDIUM state, there's enough space left to distribute between the bands to allow the leftmost band to show its buttons in MEDIUM state (as opposed to LOW state before):


Making the window smaller, there isn't enough space for the high or medium priority buttons:


Eventually the window gets so small that all buttons go to LOW state:


Note that here there's need for dynamically-resizing icons as the buttons change their icons on the fly.

In the next installment i'll talk about drop-down galleries and various layouts. In the meantime, you are welcome to Flamingo homepage to see the code and download the binaries.

Substance 2.0 official release

Posted by kirillcool on October 04, 2005 at 01:17 AM | Permalink | Comments (0)

Substance look-and-feel has reached the 2.0 release, with a lot of new features and a lot of bugs fixed.

The brief overview of new features:
  • 13 new themes, including 6 bright, 4 cold and 3 dark themes:
  • Nine watermarks:

    including image-based watermarks:
  • Color Chooser from Quaqua look-and-feel
  • Striped renderers for lists, tables and combo boxes:
  • Desktop / internal frames enhancements (preview window on minimized frames, translucent desktop icons etc.):
  • Integrated menu search panel mentioned by Joerg:
  • Ribbon component
  • Theme-based tree rendering:
The full list of new features is available, along with the Web Start demo.



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds