Aug 02

I intended to write about the Java Swing layout manager that I now use as my next post; however I got sidetracked on a small experiment that I wanted to post about first.

Almost 5 years ago to the day, I had posted on my previous blog about how to create a Cocoa based calculator using the now defunct Cocoa-Java bridge. At that time, I was working on a commercial tool called XMLFace that allowed you to define your GUI (view) using XML, write your controller code in Java and then run your application (assuming you used standard controls) using either Swing or SWT. The Cocoa/Java calculator example was my first experiments into researching the viability of adding Cocoa bindings to XMLFace. Luckily, due to the lifespan and robustness (or lack thereof) of the Cocoa/Java bridge, I didn't spend much more time on it.

Recently, I've been intrigued with the idea that I could use Ruby to write cross platform applications instead of Java; where the controller logic is in Ruby, and the view, while implemented in Ruby, is platform specific so that the application feels (or actually is) native. Also, this would give me an opportunity to learn a new language after working almost exclusively with Java and C# in the recent years.

I decided last week, waiting in an airport for a delayed flight to arrive, to try to implement that Calculator example that I had written using the Cocoa/Java bridge using MacRuby. It took me the duration of the flight, and a few hours at home afterwards to get it up and running (mostly due to my lack of Ruby experience and due to an initial lack of understand of how MacRuby wraps the Cocoa framework); however I did get it working rather painlessly. Here is the obligitory screenshot:

Screenshot of calculator implemented using MacRuby

If your curious, you can download the source code to the original Java source code here: Calculator.java.

The source code to the ruby version is below; however if you want to download it, you can download it here: Calculator.rb.

Note: I've only run this on the version of MacRuby available in their SVN trunk. I'm not sure if it works with the downloadable version (0.2) available on the website.

  1. # This is an example of how to use MacRuby to programatically
  2. # build a Cocoa application without using InterfaceBuilder.
  3. #
  4. # Author::    Jon Lipsky (jon.lipsky [at] elevenworks.com)
  5. # Copyright:: Copyright (c) 2008, Jon Lipsky
  6. # License::   Apache Software License
  7.  
  8. framework 'Cocoa'
  9.  
  10. class Calculator
  11.  
  12.   # Define the constants for the operations
  13.   NONE = -1
  14.   ADDITION = 0
  15.   SUBTRACTION = 1
  16.   MULTIPLICATION = 2
  17.   DIVISION = 3
  18.  
  19.   # Define the constants for the window and buttons sizes
  20.   WINDOW_WIDTH = 220
  21.   WINDOW_HEIGHT = 280
  22.   BUTTON_COLUMNS = 4
  23.   BUTTON_ROWS = 6
  24.   BUTTON_SPACING = 2
  25.   WINDOW_MARGIN = 8
  26.   BUTTON_WIDTH = (WINDOW_WIDTH - (WINDOW_MARGIN * 2) - ((BUTTON_COLUMNS + 1) * BUTTON_SPACING)) / BUTTON_COLUMNS
  27.   BUTTON_HEIGHT = (WINDOW_HEIGHT - (WINDOW_MARGIN * 2) - ((BUTTON_ROWS + 1) * BUTTON_SPACING)) / BUTTON_ROWS
  28.  
  29.   # Define other constants
  30.   MAX_PRECISION = 10
  31.  
  32.   def initialize   
  33.     @editing = false
  34.     @result = 0.0
  35.     @entered_number = 0.0
  36.     @fractional_digits = 0
  37.     @operation = 0
  38.     @decimal_separator = false   
  39.    
  40.     @application = NSApplication.sharedApplication()
  41.     self.setup
  42.     @application.run
  43.   end
  44.  
  45.   def setup 
  46.     # Create the text field to display the current value
  47.     x = BUTTON_SPACING + WINDOW_MARGIN
  48.     y = BUTTON_SPACING * 6 + (BUTTON_HEIGHT * 5) + WINDOW_MARGIN + BUTTON_SPACING * 2
  49.  
  50.     width = BUTTON_WIDTH * 4 + (BUTTON_SPACING * 3)
  51.     height = BUTTON_HEIGHT - (BUTTON_SPACING * 2)
  52.    
  53.     @display = NSTextField.alloc.initWithFrame [x,y,width,height]
  54.     @display.setEditable(false)
  55.     @display.setBezeled(true)
  56.     @display.setBezelStyle(NSBezelBorder)
  57.     @display.setDrawsBackground(true)
  58.     @display.setAlignment(NSRightTextAlignment)
  59.  
  60.     # Create the buttons
  61.     buttons = Array.new
  62.  
  63.     buttons <<create_digit_button(0, 0, 0)
  64.     buttons <<create_button(2, 0, 1, 1, "handle_decimal:", ".", ".", -1)
  65.     buttons <<create_button(3, 0, 1, 2, "handle_equals:", "=", "=", -1)
  66.  
  67.     buttons <<create_digit_button(0, 1, 1)
  68.     buttons <<create_digit_button(1, 1, 2)
  69.     buttons <<create_digit_button(2, 1, 3)
  70.  
  71.     buttons <<create_digit_button(0, 2, 4)
  72.     buttons <<create_digit_button(1, 2, 5)
  73.     buttons <<create_digit_button(2, 2, 6)
  74.     buttons <<create_button(3, 2, 1, 1, "handle_operation:", "+", "+", ADDITION)
  75.  
  76.     buttons <<create_digit_button(0, 3, 7)
  77.     buttons <<create_digit_button(1, 3, 8)
  78.     buttons <<create_digit_button(2, 3, 9)
  79.     buttons <<create_button(3, 3, 1, 1, "handle_operation:", "-", "-", SUBTRACTION)
  80.  
  81.     buttons <<create_button(0, 4, 1, 1, "handle_clear:", "CL", "c", -1)
  82.     buttons <<create_button(1, 4, 1, 1, "handle_square_root:", "SQR", "s", -1)
  83.     buttons <<create_button(2, 4, 1, 1, "handle_operation:", "/", "/", DIVISION)
  84.     buttons <<create_button(3, 4, 1, 1, "handle_operation:", "*", "*", MULTIPLICATION)
  85.  
  86.     # Create the window
  87.     win = NSWindow.alloc.initWithContentRect [100,100,WINDOW_WIDTH,WINDOW_HEIGHT], :styleMask => (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask), :backing => NSBackingStoreBuffered, :defer=> false
  88.     win.setTitle("Calculator")
  89.     win.setDelegate(self)
  90.  
  91.     # Add the controls to the window
  92.     buttons.each do |button|
  93.       win.contentView.addSubview(button)
  94.     end   
  95.     win.contentView.addSubview(@display)
  96.    
  97.     win.center
  98.     win.makeKeyAndOrderFront(win)
  99.   end
  100.  
  101.   # Create a digit button for the calculator panel
  102.   #
  103.   # aCellX - The X position of the button in the grid
  104.   # aCellY - The Y position of the button in the grid
  105.   def create_digit_button(aCellX, aCellY, aDigit)
  106.     create_button(aCellX, aCellY, aDigit==0?2:1, 1, "handle_digit:", aDigit.to_s, aDigit.to_s, aDigit)
  107.   end
  108.  
  109.   # Create a a NSButton positioned in the grid.  The grid starts in the
  110.   # lower left and extends right and up.  (0,0) is in the lower left
  111.   # hand corner.
  112.   #
  113.   # aCellX          - The X position of the button in the grid
  114.   # aCellY          - The Y position of the button in the grid
  115.   # aColumns        - The number of columns the button should span
  116.   # aRows           - The number of rows the button should span
  117.   # aMethod         - The method name to call when the action occurs
  118.   # aTitle          - The title of the button
  119.   # aKeyEquivalent  - The key equivalent of the button.
  120.   # aTag            - The tag of the button
  121.   def create_button(aCellX, aCellY, aColumns, aRows, aMethod, aTitle, aKeyEquivalent, aTag)
  122.  
  123.     x = (BUTTON_SPACING * (aCellX + 1)) + (BUTTON_WIDTH * aCellX) + WINDOW_MARGIN
  124.     y = (BUTTON_SPACING * (aCellY + 1)) + (BUTTON_HEIGHT * aCellY) + WINDOW_MARGIN
  125.  
  126.     button_width = BUTTON_WIDTH * aColumns + (BUTTON_SPACING * (aColumns - 1))
  127.     button_height = BUTTON_HEIGHT * aRows + (BUTTON_SPACING * (aRows - 1))
  128.    
  129.     vButton = NSButton.alloc.initWithFrame [x, y, button_width, button_height]
  130.    
  131.     vButton.setTarget(self)
  132.     vButton.setAction(aMethod)
  133.     vButton.setTitle(aTitle)
  134.     vButton.setKeyEquivalent(aKeyEquivalent)
  135.     vButton.setTag(aTag)
  136.     vButton.setButtonType(NSMomentaryPushInButton)
  137.     vButton.setImagePosition(NSNoImage)
  138.     vButton.setBezelStyle(NSRegularSquareBezelStyle)
  139.  
  140.     return vButton
  141.   end
  142.    
  143.   def windowWillClose(a_notification)
  144.     Process.exit
  145.   end
  146.  
  147.   # Handle that the decimal button was pressed
  148.   #
  149.   # @param aButton  The button that was pressed 
  150.   def handle_decimal(sender)
  151.     if !@editing   
  152.       @entered_number = 0
  153.       @decimal_separator = false
  154.       @fractional_digits = 0
  155.       @editing = true
  156.     end
  157.     if !@decimal_separator   
  158.       @decimal_separator = true
  159.       update_display(@fractional_digits)
  160.     end
  161.   end
  162.  
  163.   def handle_equals(sender)
  164.     case @operation   
  165.       when NONE
  166.         @result = @entered_number
  167.         @entered_number = 0
  168.         @decimal_separator = false
  169.         @fractional_digits = 0
  170.         return
  171.       when ADDITION
  172.         @result = @result + @entered_number
  173.       when SUBTRACTION
  174.         @result = @result - @entered_number       
  175.       when MULTIPLICATION
  176.         @result = @result * @entered_number       
  177.       when DIVISION
  178.         if @entered_number == 0       
  179.           report_error("Error")               
  180.         else
  181.           @result = @result / @entered_number
  182.         end
  183.     end
  184.  
  185.     @entered_number = @result
  186.     update_display(MAX_PRECISION)
  187.  
  188.     @operation = NONE
  189.     @editing = false
  190.   end
  191.  
  192.   # Handle that an operation button was pressed
  193.   #
  194.   # sender  The button that was pressed
  195.   def handle_operation(sender)
  196.     if (@operation == NONE)
  197.       @result = @entered_number
  198.       @entered_number = 0
  199.       @decimal_separator = false
  200.       @fractional_digits = 0
  201.       @operation = sender.tag
  202.     else
  203.       handle_equals(nil)
  204.       handle_operation(sender)
  205.     end   
  206.   end
  207.  
  208.   #Handle that the square root button was pressed
  209.   #
  210.   #sender  The button that was pressed 
  211.   def handle_square_root(sender)
  212.     if (@operation == NONE)
  213.       @result = Math.sqrt(@entered_number)
  214.       @entered_number = @result
  215.      
  216.       @editing = true
  217.       update_display(MAX_PRECISION)
  218.       @editing = false
  219.       @operation = NONE   
  220.     else   
  221.       handle_equals(nil)
  222.       handle_square_root(sender)
  223.     end 
  224.   end
  225.  
  226.   # Handle that a digit button was pressed
  227.   #
  228.   # sender  The button that was pressed
  229.   def handle_digit(sender)
  230.     digit = sender.tag.to_i
  231.     puts("digit pressed: #{digit}")
  232.    
  233.     if !@editing
  234.       @entered_number = 0
  235.       @decimal_separator = false
  236.       @fractional_digits = 0
  237.       @editing = true
  238.     end  
  239.  
  240.     if @decimal_separator
  241.       @entered_number = @entered_number + digit * (0.1**(1 + @fractional_digits))
  242.       @fractional_digits+=1
  243.     else       
  244.       @entered_number = @entered_number * 10 + digit
  245.  
  246.       # Check overflow
  247.       if (@entered_number> 10**15)
  248.         report_error("Overflow Error")
  249.         return
  250.       end
  251.     end
  252.  
  253.     update_display(@fractional_digits)   
  254.   end
  255.  
  256.   # Handle that the clear button was pressed
  257.   #
  258.   # sender  The button that was pressed
  259.   def handle_clear(sender) 
  260.    
  261.     @result = 0
  262.     @enteredNumber = 0
  263.     @operation = NONE
  264.     @fractionalDigits = 0
  265.     @decimalSeparator = false
  266.     @editing = false
  267.  
  268.     update_display(@fractional_digits)
  269.   end 
  270.  
  271.   #Update the number displayed in the text field
  272.   def update_display(a_fractional_digits)
  273.     if !@editing
  274.       value=""
  275.     elsif  (@decimal_separator) || (a_fractional_digits>0)
  276.       if a_fractional_digits == 0
  277.         value = "#{@entered_number}."
  278.       else
  279.         value = @entered_number.to_s
  280.       end                         
  281.     else
  282.       value = @entered_number.to_i.to_s
  283.     end
  284.  
  285.     puts("display=#{value}")
  286.     @display.setStringValue(value)
  287.   end
  288.  
  289.   # Report an error to the user
  290.   #
  291.   # a_message  The message to display in the text input field
  292.   def report_error(a_message)
  293.     @display.setStringValue(a_message)
  294.   end
  295. end
  296.  
  297. ## Create a new calculator when this ruby script is run
  298. Calculator.new

Since this process was rather painless, I'm thinking about attempting to port an existing Java Swing application that I wrote for my personal use over to Cocoa + MacRuby. In the end, I think the hardest part will be giving up the nice code completion and language support I get from IntelliJ when doing Java development. For this experiment I used Netbeans as my Ruby editor; however I might give a few others a try. (I did try XCode shortly, but discarded it since I didn't want/need to use Interface Builder.)

I'm sure I'll have more to post on this subject as time goes on.

Jul 23

This post is a precursor to to my next blog entry, which will be about the custom layout manager that I now use for most of my Swing layout needs.

Almost 6 years ago, James Elliott wrote an article about a layout manager that he had written called RelativeLayout. At that time, as I do now, I created all of my Swing GUI's by hand without the use of a designer, so I was intrigued by RelativeLayout since I wasn't happy with the standard Java layout managers.

RelativeLayout (as published by James in that article) allowed you to to either programatically, or via XML, define how the position of each control related to the position of another control or it's container. You essentially were defining constraints like "the left edge of component1 is 5 pixels to the right edge of component2". Here is an example of what one of the XML files might look like:

  1. <?xml version="1.0"?>
  2. <!DOCTYPE  constraint-set
  3.     PUBLIC "-//Brunch Boy Design//RelativeLayout Constraint Set DTD 1.0//EN"
  4.     "http://dtd.brunchboy.com/RelativeLayout/constraint-set.dtd">
  5. <constraint -set>
  6.     <constrain name="title">
  7.         <top>
  8.             <toattribute reference="_container" attribute="top" offset="10"/>
  9.         </top>
  10.         <horizontalcenter>
  11.             <toattribute reference="_container" attribute="horizontalCenter"/>
  12.         </horizontalcenter>
  13.     </constrain>
  14.     <constrain name="version">
  15.         <top>
  16.             <toattribute reference="title" attribute="bottom" offset="8"/>
  17.         </top>
  18.         <left>
  19.             <toattribute reference="_container" attribute="left" offset="5"/>
  20.         </left>
  21.     </constrain>
  22.     <constrain name="date">
  23.         <top>
  24.             <toattribute reference="version" attribute="top"/>
  25.         </top>
  26.         <right>
  27.             <toattribute reference="_container" attribute="right" offset="-5"/>
  28.         </right>
  29.     </constrain>
  30.     <constrain name="details">
  31.         <left>
  32.             <toattribute reference="version" attribute="left"/>
  33.         </left>
  34.         <right>
  35.             <toattribute reference="date" attribute="right"/>
  36.         </right>
  37.         <top>
  38.             <toattribute reference="version" attribute="bottom" offset="4"/>
  39.         </top>
  40.         <bottom>
  41.             <toattribute reference="ok" attribute="top" offset="-4"/>
  42.         </bottom>
  43.     </constrain>
  44.     <constrain name="ok">
  45.         <horizontalcenter>
  46.             <toaxis reference="_container" axis="horizontal" fraction="0.5"/>
  47.         </horizontalcenter>
  48.         <bottom>
  49.             <toattribute reference="_container" attribute="bottom" offset="-10"/>
  50.         </bottom>
  51.     </constrain>
  52. </constraint>

Note: WordPress keeps mangling the XML snippet above, so there is actually a few inaccuracies in case you wanted to copy and paste this to try with RelativeLayout.

What drew me to RelativeLayout is that my Java code would no longer be littered with layout code and I could externalize and manage layout separately from the code. (Note: I also tend to externalize my form definition to XML or YAML as well so that my Java code contains controller code only.)

I immediately started to use RelativeLayout, and it's XML constraint files, on several projects; however I started modifying it to meet my needs soon after beginning with it . One of my first additions was to port it to work with SWT, which I was working with in parallel. (Note: I posted the source code to the Swing/SWT version on the predecessor blog to this blog, so that version might be floating around in the wild somewhere.) The second addition I made was to replace the XML based constraint files with a simpler, more readable, less verbose, text file (properties file) format.

Here is the XML constraints file above in my text format:

  1. title.top=_container.top+10
  2. title.horizontalCenter=_container.horizontalCenter
  3.  
  4. version.top=title.bottom+8
  5. version.left=_container.left+5
  6.  
  7. date.top=version.top
  8. date.right-_container.right-5
  9.  
  10. details.left=version.left
  11. details.right=date.right
  12. details.top=version.bottom+4
  13. details.bottom=ok.top-4
  14.  
  15. ok.horizontalCenter=_container.horizontal%.5
  16. ok.bottom=_container.bottom-10

Over the years, I made other additions to RelativeLayout, such as adding support for baselines, as the features of Swing have evolved. I have/had been pretty successful in using RelativeLayout for the majority of my layout needs; however there have been times when it didn't meet my needs, or where I had to manage different constraint files for the same panel/form. I had to manage different constraint files when I either wanted to tailor the layout for a specific platform (mac vs. windows vs. linux) to follow GUI design guidlines, or because of the localization of the application (localization into German was the specific culprit here because of word length).

As you may have figured out, I no longer use RelativeLayout itself; however it's concept is the basis for the new layout manager that I use. In fact, the textual constraint files can be used with my newer layout manager without modification, though there are additional things that you can do that don't make the constraint files backward compatible.

I plan to post about the new layout manager later this week as time allows.

Jul 22

Wow, it's been over two years since I last posted. Once you start getting bombarded with comment spam, it becomes more of a chore to post than fun.

I have a few things I want to write about (Some UI rants, and some new Swing code to share) so I thought I'd spend time cleaning things up:

... upgrade WordPress (check)
... delete all 473,262 comment spams (check)
... create new template since my WordPress template had been hacked (check)
... install and activate plugin(s) to help with comment spam (check)
... start posting content again (soon)

Mar 26

Well, it only took a month and a half longer than I thought, but I've finally had the time to post the source code and an example of how to use my iTunes-like tree table control. If you are already using the JXTreeTable from the swingx project, then this should be a drop in replacement.

Here is a screenshot of the sample application:

TreeTable Sample Application

It demonstrates how to use JXTreeTable to create a treetable displaying the component hiearchy of a running Swing application. It should be a good starting point for anyone wanting to use JXTreeTable, as well as how to use my look and feel with it. Here are the download links:

Note: As usual the source code for this is released under the Apache license.

Feb 02

I'm working on cleaning up the code a bit for the new components and UI delegates, so I thought I would post a screenshot of one of my personal applications that uses them. (It's a small tool to manage metadata about Java classes).

In the screenshot below, you can see an example of the new iTunes-like JXTreeTable implementation, as well as an MacOSX-like toolbar UI delegate.

The UI delegate for the toolbar was simple to create wheras customizing the look and feel of JXTreeTable was a little trickier. I didn't want to rewrite JXTreeTable, only subclass it, which made creating this control a little harder than normal. I think I tried 3 or 4 approaches before I finally found one that worked.

Screenshot of treetable and toolbar controls

Jan 30

Wow! It's been around 5 months since I lasted posted!

Mostly, I've been so busy with work that I haven't had time for my pet projects. However, I've been able to do a little GUI work the past few days, so I'll be adding some new posts here shortly. I've created a version of the "swingx" JXTreeTable control that looks like an iTunes, as well as a new JToolbar delegate. Both should make it up on the blog quickly.

I've also decided to share the source code of a custom control that will show people what's involved in something more complex that the standard buttons, toolbars, etc... It's going to take a while to package it up with examples and documenation, so it may be a week or so before that becomes available.

Look for some posts later this week. Also, I've been thinking of packaging up all of my custom controls into a real deliverable... Anyone interested in something like that?

Sep 19

Instead of just posting my source code that shows how to simulate the iTunes user interface, I decided to write a few more of my "tutorial" style posts about how I simulated the look. The first thing that I'll demonstrate is how to create the "info panel" that appears at the top of the iTunes window:

The information panel from iTunes 5

In order to implement this, I decided to implement a JPanel UI delegate as opposed to subclassing JPanel iteself. The first thing we obviously need to do is create a new java class that extends BasicPanelUI.

  1. public class TigerInfoPanelUI extends BasicPanelUI
  2. {
  3.  
  4. }

Next, the default behavior for a JPanel is to have it be opaque, which means we wouldn't be able to see any panels drawn behind it (such as a brushed metal or gradient panel). In order to ensure any JPanel using ourUI delegate would not be opaque we need to override the installDefaults method to have it set the panel as not opaque:

  1. protected void installDefaults(JPanel p)
  2. {
  3.     p.setOpaque(false);
  4. }

Now that we have that small detail out of the way, we can begin the implement the code to paint the panel. We do this by overriding the paint method. The first thing we will need to do is take the insets into consideration and calculate the x, y, width and height of the panel. Additionally, since this panel has rounded corners, we need to consider the size of the corner arc:

  1. public void paint(Graphics g, JComponent c)
  2. {
  3.     Graphics2D g2d = (Graphics2D)g;
  4.     g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  5.  
  6.     Insets vInsets = c.getInsets();
  7.  
  8.     int w = c.getWidth() - (vInsets.left + vInsets.right);
  9.     int h = c.getHeight() - (vInsets.top + vInsets.bottom);
  10.    
  11.     int x = vInsets.left;
  12.     int y = vInsets.top;
  13.     int arc = 8;
  14. }

Now that we know the position and size of the panel, we can begin to draw it. In order to do this, we are going to create a rounded rectangle clipping area, and then use fillRect to draw the two different background colors.

  1. public void paint(Graphics g, JComponent c)
  2. {
  3.   // Code from above...
  4.  
  5.     Shape vButtonShape = new RoundRectangle2D.Double((double) x, (double) y, (double) w, (double) h, (double)arc, (double)arc);
  6.     Shape vOldClip = g.getClip();
  7.  
  8.     g2d.setClip(vButtonShape);
  9.     g2d.setColor(backgroundColor1);
  10.     g2d.fillRect(x,y,w,h/2);
  11.     g2d.setColor(backgroundColor2);
  12.     g2d.fillRect(x,y+h/2,w,h/2);
  13. }

At this point, we have enough implemented where we can write and run a test application to view the panel. In my test application, I added a gradient panel behind the info panel to simulate how it would look in an application that looked like iTunes. Here is what our panel looks like:

Screenshot after the first step is completed

Now that the background is done, we need to draw the border and the shadows of the panel. The first thing will do is reset the clipping region to it's original value, then we will create a gradient paint and rounded rectangle border:

  1. public void paint(Graphics g, JComponent c)
  2. {
  3.   // Code from above...
  4.  
  5.     g2d.setClip(vOldClip);   
  6.     GradientPaint vPaint = new GradientPaint(x,y,borderColor,x,y+h,borderHighlight);
  7.     g2d.setPaint(vPaint);
  8.     g2d.drawRoundRect(x,y,w,h,arc,arc);
  9. }

If we run our test application now, this is what we will see:

Screenshot after the second step is completed

Obviously, with just that border, the panel doesn't look very good, so we need to draw a few more highlights. The next thing we will do, is set a new clipping area so we don't draw a shadow at the bottom, and then we will draw a shadow with a transparent version of the border color:

  1. public void paint(Graphics g, JComponent c)
  2. {
  3.   // Code from above...
  4.  
  5.     g2d.clipRect(x,y,w+1,h-arc/4);
  6.     g2d.setColor(borderColorAlpha1);
  7.     g2d.drawRoundRect(x,y+1,w,h-1,arc,arc-1);
  8. }

If we run our test application now, this is what we will see:

Screenshot after the third step is completed

This looks alot closer to the desired look we are trying to get, but if you look at the original version in iTunes, there is a slight shadow at the bottom of the panel. In order to create this, we will use an even more transparent version of the border color than we used before, and then draw another rounded rectangle:

  1. public void paint(Graphics g, JComponent c)
  2. {
  3.   // Code from above...
  4.  
  5.         g2d.setClip(vOldClip);
  6.         g2d.setColor(borderColorAlpha2);
  7.         g2d.drawRoundRect(x+1,y+2,w-2,h-3,arc,arc-2);
  8. }

If we run our test application now, this is what we will see:

Screenshot after the last step is completed

As you see, this is pretty close to the panel that is used in iTunes, and really wasn't too hard to create.

As with all of my examples, I'm releasing the source code under the Apache license so that you can use this in your own applications. Here are the links to download the source code:

Sep 09

I was browsing through JavaBlogs yesterday and I stumbled upon this post from Adam Walker where he was adding a Tiger/iTunes 5 look to his application. After seeing this I was inspired (and had some free time for once) to try to update my iTunes in Swing demo to look like the newly released iTunes 5.

I have a little more work to do, but here is what I have so far:

I'll clean up the code, add copyright notices and get it posted this weekend for whoever's curious. In the process, I've also fixed a few bugs and made a few improvements to some of the UI delegates, so when I post the source code, it'll include the previous (brushed-metal) example as well.

Aug 09

I want to apologize for the delay in getting this posted as I had planned on posting this a few weeks ago. Unfortunately, I got swamped with real work. :-(

You can download the source code for the sample here. I've included an ANT build script to compile and run the application. Simply execute the "run" target to compile and start it.

Let me know if there are any questions!

Note: You will need Java 5 to run and compile this example.

Jul 19

Last night I had a chance to (finally) implement 2 of 3 missing UI items from my Swing based iTunes clone. I implemented a UI delegate for the buttons at the bottom of the window, as well as implementing the delegate for the volume slider. I still haven't implemented anything for the scroll bars, and I have a laundry list of things that I'd like to improve, but I now think it is "good enough" to show.

I took the time to create a WebStartable version of the example in case anyone wants to see what it looks like on their machine. You'll need to have Java 5 installed in order to run it. Here is the link:

If you don't want to run the example to see what it looks like, here is the most recent screenshot:

As I haven't had time to write tutorials on how to create something like this as fast as I would have liked, I am thinking about releasing the source code and then explaining it later. Any thoughts?